diff --git a/app/src/main/java/dev/ushiekane/xmanager/Application.kt b/app/src/main/java/dev/ushiekane/xmanager/Application.kt index 30565fe..57fb0a6 100644 --- a/app/src/main/java/dev/ushiekane/xmanager/Application.kt +++ b/app/src/main/java/dev/ushiekane/xmanager/Application.kt @@ -10,7 +10,7 @@ class Application : Application() { super.onCreate() startKoin { androidContext(this@Application) - modules(httpModule, viewModelModule, repositoryModule) + modules(httpModule, preferencesModule, viewModelModule, repositoryModule) } } } \ No newline at end of file diff --git a/app/src/main/java/dev/ushiekane/xmanager/MainActivity.kt b/app/src/main/java/dev/ushiekane/xmanager/MainActivity.kt index a0413c8..000da53 100644 --- a/app/src/main/java/dev/ushiekane/xmanager/MainActivity.kt +++ b/app/src/main/java/dev/ushiekane/xmanager/MainActivity.kt @@ -15,6 +15,7 @@ import com.xinto.taxi.Taxi import com.xinto.taxi.rememberBackstackNavigator import dev.ushiekane.xmanager.ui.navigation.AppDestination import dev.ushiekane.xmanager.ui.screen.HomeScreen +import dev.ushiekane.xmanager.ui.screen.SettingsScreen import dev.ushiekane.xmanager.ui.theme.XManagerTheme class MainActivity : ComponentActivity() { @@ -38,7 +39,10 @@ class MainActivity : ComponentActivity() { ) { destination -> when (destination) { is AppDestination.Home -> HomeScreen( - onClickSettings = { /* TODO */ } + onClickSettings = { navigator.push(AppDestination.Settings) } + ) + is AppDestination.Settings -> SettingsScreen( + navigator = navigator ) } } diff --git a/app/src/main/java/dev/ushiekane/xmanager/di/PreferencesModule.kt b/app/src/main/java/dev/ushiekane/xmanager/di/PreferencesModule.kt new file mode 100644 index 0000000..627094a --- /dev/null +++ b/app/src/main/java/dev/ushiekane/xmanager/di/PreferencesModule.kt @@ -0,0 +1,14 @@ +package dev.ushiekane.xmanager.di + +import android.content.Context +import dev.ushiekane.xmanager.preferences.PreferencesManager +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val preferencesModule = module { + fun providePreferences( + context: Context + ) = PreferencesManager(context.getSharedPreferences("preferences", Context.MODE_PRIVATE)) + + singleOf(::providePreferences) +} \ No newline at end of file diff --git a/app/src/main/java/dev/ushiekane/xmanager/di/ViewModelModule.kt b/app/src/main/java/dev/ushiekane/xmanager/di/ViewModelModule.kt index 7c131fe..93be760 100644 --- a/app/src/main/java/dev/ushiekane/xmanager/di/ViewModelModule.kt +++ b/app/src/main/java/dev/ushiekane/xmanager/di/ViewModelModule.kt @@ -6,4 +6,5 @@ import org.koin.dsl.module val viewModelModule = module { viewModelOf(::HomeViewModel) + viewModelOf(::SettingsViewModel) } \ No newline at end of file diff --git a/app/src/main/java/dev/ushiekane/xmanager/preferences/PreferencesManager.kt b/app/src/main/java/dev/ushiekane/xmanager/preferences/PreferencesManager.kt new file mode 100644 index 0000000..b67e218 --- /dev/null +++ b/app/src/main/java/dev/ushiekane/xmanager/preferences/PreferencesManager.kt @@ -0,0 +1,105 @@ +package dev.ushiekane.xmanager.preferences + +import android.content.SharedPreferences +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.core.content.edit +import kotlin.reflect.KProperty + +class PreferencesManager( + sharedPreferences: SharedPreferences +) : BasePreferenceManager(sharedPreferences) { + var useClone by booleanPreference("useClone", false) + var autoRefresh by booleanPreference("autoRefresh", false) + var autoInstall by booleanPreference("autoInstall", false) + var hideAds by booleanPreference("hideAds", false) +} + +@Suppress("unused") +abstract class BasePreferenceManager( + private val prefs: SharedPreferences +) { + protected fun getString(key: String, defaultValue: String?) = + prefs.getString(key, defaultValue)!! + + private fun getBoolean(key: String, defaultValue: Boolean) = prefs.getBoolean(key, defaultValue) + private fun getInt(key: String, defaultValue: Int) = prefs.getInt(key, defaultValue) + private fun getFloat(key: String, defaultValue: Float) = prefs.getFloat(key, defaultValue) + protected inline fun > getEnum(key: String, defaultValue: E) = + enumValueOf(getString(key, defaultValue.name)) + + protected fun putString(key: String, value: String?) = prefs.edit { putString(key, value) } + private fun putBoolean(key: String, value: Boolean) = prefs.edit { putBoolean(key, value) } + private fun putInt(key: String, value: Int) = prefs.edit { putInt(key, value) } + private fun putFloat(key: String, value: Float) = prefs.edit { putFloat(key, value) } + protected inline fun > putEnum(key: String, value: E) = + putString(key, value.name) + + protected class Preference( + private val key: String, + defaultValue: T, + getter: (key: String, defaultValue: T) -> T, + private val setter: (key: String, newValue: T) -> Unit + ) { + @Suppress("RedundantSetter") + var value by mutableStateOf(getter(key, defaultValue)) + private set + + operator fun getValue(thisRef: Any?, property: KProperty<*>) = value + operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) { + value = newValue + setter(key, newValue) + } + } + + protected fun stringPreference( + key: String, + defaultValue: String? + ) = Preference( + key = key, + defaultValue = defaultValue, + getter = ::getString, + setter = ::putString + ) + + protected fun booleanPreference( + key: String, + defaultValue: Boolean + ) = Preference( + key = key, + defaultValue = defaultValue, + getter = ::getBoolean, + setter = ::putBoolean + ) + + protected fun intPreference( + key: String, + defaultValue: Int + ) = Preference( + key = key, + defaultValue = defaultValue, + getter = ::getInt, + setter = ::putInt + ) + + protected fun floatPreference( + key: String, + defaultValue: Float + ) = Preference( + key = key, + defaultValue = defaultValue, + getter = ::getFloat, + setter = ::putFloat + ) + + protected inline fun > enumPreference( + key: String, + defaultValue: E + ) = Preference( + key = key, + defaultValue = defaultValue, + getter = ::getEnum, + setter = ::putEnum + ) +} \ No newline at end of file diff --git a/app/src/main/java/dev/ushiekane/xmanager/ui/component/SettingListItem.kt b/app/src/main/java/dev/ushiekane/xmanager/ui/component/SettingListItem.kt new file mode 100644 index 0000000..9b8db41 --- /dev/null +++ b/app/src/main/java/dev/ushiekane/xmanager/ui/component/SettingListItem.kt @@ -0,0 +1,62 @@ +package dev.ushiekane.xmanager.ui.component + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.ushiekane.xmanager.ui.theme.CustomFont + +@Composable +fun SettingListItem( + title: String, + subtitle: String, + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)? +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp, 8.dp, 10.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFF212121)) + ) { + Column { + Row( + modifier = Modifier.padding(10.dp, 10.dp, 10.dp, 5.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + fontFamily = CustomFont, + fontWeight = FontWeight.Bold, + color = Color(0xFF1DB954), + fontSize = 12.sp + ) + Spacer(modifier = Modifier.weight(1f)) + Switch( + modifier = Modifier.height(35.dp), + checked = checked, + onCheckedChange = onCheckedChange + ) + } + Row( + modifier = Modifier.padding(12.dp, 0.dp, 12.dp, 17.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = subtitle, + fontFamily = CustomFont, + lineHeight = 13.sp, + fontSize = 12.sp + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/ushiekane/xmanager/ui/navigation/AppDestination.kt b/app/src/main/java/dev/ushiekane/xmanager/ui/navigation/AppDestination.kt index b01074b..725b50b 100644 --- a/app/src/main/java/dev/ushiekane/xmanager/ui/navigation/AppDestination.kt +++ b/app/src/main/java/dev/ushiekane/xmanager/ui/navigation/AppDestination.kt @@ -14,4 +14,7 @@ import kotlinx.parcelize.RawValue sealed interface AppDestination : Destination { @Parcelize object Home : AppDestination + + @Parcelize + object Settings : AppDestination } \ No newline at end of file diff --git a/app/src/main/java/dev/ushiekane/xmanager/ui/screen/SettingsScreen.kt b/app/src/main/java/dev/ushiekane/xmanager/ui/screen/SettingsScreen.kt new file mode 100644 index 0000000..245a0ff --- /dev/null +++ b/app/src/main/java/dev/ushiekane/xmanager/ui/screen/SettingsScreen.kt @@ -0,0 +1,72 @@ +package dev.ushiekane.xmanager.ui.screen + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.xinto.taxi.BackstackNavigator +import dev.ushiekane.xmanager.ui.component.SettingListItem +import dev.ushiekane.xmanager.ui.navigation.AppDestination +import dev.ushiekane.xmanager.ui.viewmodel.SettingsViewModel +import org.koin.androidx.compose.getViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen( + navigator: BackstackNavigator, + viewModel: SettingsViewModel = getViewModel() +) { + val prefs = viewModel.prefs + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + TopAppBar( + title = { + Text( + text = "Settings" + ) + }, + navigationIcon = { + IconButton(onClick = { navigator.pop() }) { + Icon(Icons.Default.ArrowBack, contentDescription = null) + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + ) { + SettingListItem( + title = "CLONED VERSION (BETA)", + subtitle = "Enabling this will allow you to download and install the cloned version of the patched application.\n\nThis will also resolve most of the installation errors or problems, especially if you have a pre-installed Spotify application.", + checked = prefs.useClone, + onCheckedChange = { prefs.useClone = it } + ) + SettingListItem( + title = "LIST AUTO-REFRESH", + subtitle = "Enabling this will automatically refresh the list everytime you launch the application.\n\nYou can manually refresh the list by dragging the main screen downward.", + checked = prefs.autoRefresh, + onCheckedChange = { prefs.autoRefresh = it } + ) + SettingListItem( + title = "FORCE AUTO-INSTALL", + subtitle = "Enabling this will automatically install the patched application and update once downloaded.", + checked = prefs.autoInstall, + onCheckedChange = { prefs.autoInstall = it } + ) + SettingListItem( + title = "DISABLE REWARDED ADS", + subtitle = "We know that most of us does not like ads but in our case, this significantly help us to fund our database, hosting links, updates, more patches and daily needs.\n\nThis is the simpliest way to support us without donating or spending anything.", + checked = prefs.hideAds, + onCheckedChange = { prefs.hideAds = it } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/ushiekane/xmanager/ui/viewmodel/SettingsViewModel.kt b/app/src/main/java/dev/ushiekane/xmanager/ui/viewmodel/SettingsViewModel.kt new file mode 100644 index 0000000..350a655 --- /dev/null +++ b/app/src/main/java/dev/ushiekane/xmanager/ui/viewmodel/SettingsViewModel.kt @@ -0,0 +1,10 @@ +package dev.ushiekane.xmanager.ui.viewmodel + +import androidx.lifecycle.ViewModel +import dev.ushiekane.xmanager.preferences.PreferencesManager + +class SettingsViewModel( + val prefs: PreferencesManager +) : ViewModel() { + +} \ No newline at end of file