From 3b039d8dfd5fdbb107c211ba9fef2c64b0553c1b Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 31 Jan 2026 20:03:11 +0300 Subject: [PATCH] feat: initial settings implementation --- .../dev/mi6e4ka/openstore/MainActivity.kt | 4 + .../ui/screen/search/SearchScreen.kt | 15 +- .../ui/screen/settings/SettingsScreen.kt | 281 ++++++++++++++++++ .../ui/screen/settings/SettingsViewModel.kt | 19 ++ .../main/res/drawable/ic_settings_24px.xml | 10 + app/src/main/res/values-en/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsScreen.kt create mode 100644 app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsViewModel.kt create mode 100644 app/src/main/res/drawable/ic_settings_24px.xml diff --git a/app/src/main/java/dev/mi6e4ka/openstore/MainActivity.kt b/app/src/main/java/dev/mi6e4ka/openstore/MainActivity.kt index acdeeeb..540f382 100644 --- a/app/src/main/java/dev/mi6e4ka/openstore/MainActivity.kt +++ b/app/src/main/java/dev/mi6e4ka/openstore/MainActivity.kt @@ -27,6 +27,7 @@ import dev.mi6e4ka.openstore.ui.screen.details.DetailsScreen import dev.mi6e4ka.openstore.ui.screen.results.ResultsScreen import dev.mi6e4ka.openstore.ui.screen.reviews.ReviewsScreen import dev.mi6e4ka.openstore.ui.screen.search.SearchScreen +import dev.mi6e4ka.openstore.ui.screen.settings.SettingsScreen import dev.mi6e4ka.openstore.ui.theme.AppTheme class MainActivity : ComponentActivity() { @@ -106,6 +107,9 @@ class MainActivity : ComponentActivity() { val packageName = it.arguments?.getString("packageName") ?: "" ReviewsScreen(packageName = packageName, navController = navController) } + composable("settings") { + SettingsScreen(navController = navController) + } } } } diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/search/SearchScreen.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/search/SearchScreen.kt index 33bfcd7..58381ad 100644 --- a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/search/SearchScreen.kt +++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/search/SearchScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -22,11 +23,15 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -54,6 +59,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import dev.mi6e4ka.openstore.BuildConfig import dev.mi6e4ka.openstore.R +import dev.mi6e4ka.openstore.ui.screen.results.Platform import java.net.URLEncoder @OptIn(ExperimentalMaterial3Api::class) @@ -79,7 +85,14 @@ fun SearchScreen(navController: NavController = rememberNavController()) { Scaffold( modifier = Modifier.fillMaxSize(), - topBar = { TopAppBar(title = {Text(stringResource(R.string.app_name))}) }, + topBar = { TopAppBar( + title = {Text(stringResource(R.string.app_name))}, + actions = { + IconButton({navController.navigate("settings")}) { + Icon(painterResource(R.drawable.ic_settings_24px), "settings") + } + } + ) }, ) { innerPadding -> Column( modifier = Modifier diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsScreen.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsScreen.kt new file mode 100644 index 0000000..e4f7730 --- /dev/null +++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsScreen.kt @@ -0,0 +1,281 @@ +package dev.mi6e4ka.openstore.ui.screen.settings + +import android.content.Context +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Checkbox +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import dev.mi6e4ka.openstore.R +import androidx.core.content.edit + +data class SettingsSection( + val id: String, + val title: String, + val description: String?, + val icon: Int, + val type: ParamType, + val defaultVal: String, + val select: List? = null +) + +data class SettingsSectionSelect( + val title: String, + val value: String +) + +enum class ParamType { + SWITCH, + SELECT, + MULTI_SELECT +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen( + navController: NavController +) { + val sections = listOf( + SettingsSection( + id = "installer_type", + title = "Установщик", + description = "Если есть проблемы при установке приложений", + icon = R.drawable.apk_install_24px, + type = ParamType.SELECT, + defaultVal = "session", + select = listOf( + SettingsSectionSelect( + title = "Session", + value = "session" + ), + SettingsSectionSelect( + title = "Intent", + value = "intent" + ) + ) + ), + SettingsSection( + id = "updates_sources", + title = "Проверка обновлений", + description = "Для приложений из каких источников проверять обновления", + icon = R.drawable.ic_update_24px, + type = ParamType.MULTI_SELECT, + defaultVal = "", + select = listOf( + SettingsSectionSelect( + title = "OpenStore", + value = "dev.mi6e4ka.dev" + ), + SettingsSectionSelect( + title = "RuStore", + value = "ru.vk.store" + ), + SettingsSectionSelect( + title = "Google Play", + value = "com.android.vending" + ) + ) + ), + SettingsSection( + id = "updates_check_all", + title = "Проверять обновления для всех не системных приложений", + description = "RuStore получает полный список установленных приложений, не безопасно", + icon = R.drawable.ic_arrow_circle_up_24px, + type = ParamType.SWITCH, + defaultVal = "" + ) + ) + + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.settings_title)) }, + navigationIcon = { + IconButton( + {navController.popBackStack()} + ) { + Icon(painterResource(R.drawable.ic_arrow_back_24px), "back") + } + } + ) + } + ) { + innerPadding -> + LazyColumn( + modifier = Modifier.fillMaxSize().padding(innerPadding) + ) { + items(sections) { item -> + SettingsCard( + item = item + ) + } + } + } +} + +@Composable +fun SettingsCard( + item: SettingsSection +) { + + val context = LocalContext.current + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + var showDialog by remember { mutableStateOf(false) } + + var booleanSelect by remember { mutableStateOf( + if (item.type == ParamType.SWITCH) prefs.getBoolean(item.id, false) else false + )} + + ElevatedCard ( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 4.dp), + ) { + Row( + Modifier + .clickable{ + if (item.type != ParamType.SWITCH) { + showDialog = true + } else { + prefs.edit { putBoolean(item.id, !booleanSelect) } + booleanSelect = !booleanSelect + } + } + .padding(15.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(15.dp) + ) { + Icon( + painterResource(item.icon), + "icon", + modifier = Modifier.size(32.dp) + ) + Column( + modifier = Modifier.weight(1f) + ) { + Text(item.title, fontWeight = FontWeight.Medium) + Text(item.description?:"", color = Color.Gray, fontWeight = FontWeight.Normal) + } + if (item.type == ParamType.SWITCH) { + + Switch( + booleanSelect, + { + v -> prefs.edit { putBoolean(item.id, v) }; booleanSelect = v + } + ) + } + } + if (showDialog) { + val selected = remember { mutableStateListOf() } + if (item.type == ParamType.MULTI_SELECT) { + selected.apply { addAll(prefs.getString(item.id, item.defaultVal)!!.split(";").mapNotNull { + v -> + item.select?.firstOrNull { it.value == v } + }) + } + } + var selectedVal by remember { mutableStateOf(prefs.getString(item.id, item.defaultVal)) } + AlertDialog( + onDismissRequest = { showDialog = false }, + + title = { + Text(item.title) + }, + + text = { + LazyColumn { + items(item.select?:listOf()) { entry -> + Row( + Modifier + .fillMaxWidth() + .clickable { + if (item.type == ParamType.MULTI_SELECT) { + if (entry in selected) { + selected.remove(entry) + } else { + selected.add(entry) + } + } else { + selectedVal = entry.value + } + } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (item.type == ParamType.SELECT) { + RadioButton( + selected = selectedVal == entry.value, + onClick = { selectedVal = entry.value } + ) + } else { + Checkbox( + checked = entry in selected, + onCheckedChange = { + if (it) selected.add(entry) + else selected.remove(entry) + } + ) + } + + Text( + entry.title, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + } + }, + + confirmButton = { + TextButton(onClick = { + if (item.type == ParamType.MULTI_SELECT) { + prefs.edit { putString(item.id, selected.joinToString(";") { it.value }) } + + } else { + prefs.edit { putString(item.id, selectedVal) } + } + showDialog = false + }) { + Text("Сохранить") + } + } + ) + } + } + + +} diff --git a/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsViewModel.kt b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsViewModel.kt new file mode 100644 index 0000000..31109b9 --- /dev/null +++ b/app/src/main/java/dev/mi6e4ka/openstore/ui/screen/settings/SettingsViewModel.kt @@ -0,0 +1,19 @@ +package dev.mi6e4ka.openstore.ui.screen.settings + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class DetailsViewModel() : ViewModel() { + +} + +class SettingsViewModelFactory() : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(DetailsViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return DetailsViewModel() as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} diff --git a/app/src/main/res/drawable/ic_settings_24px.xml b/app/src/main/res/drawable/ic_settings_24px.xml new file mode 100644 index 0000000..4bcd4aa --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 52864a8..c6e30c9 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -33,4 +33,5 @@ Negative Download has started Nothing found + Settings \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 777ee96..9b34320 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -34,4 +34,5 @@ Негативные Загрузка началась Ничего не найдено + Настройки \ No newline at end of file