commit 5d4e2aacb1dfc682e8fa0f9adbd9183c7b023104
parent d3434a4be26c7b894ace3914c5aa12afd6f7e430
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Mon, 31 Jul 2023 00:53:55 +0200

ui(features): dialogs & translations

Diffstat:
Mapp/build.gradle.kts | 12++++++------
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/Dialogs.kt | 255+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/MainActivity.kt | 43+++++++++++--------------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/Navigation.kt | 3++-
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/Section.kt | 6++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/sections/FeaturesSection.kt | 32++++++++++++++++++++------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/sections/NotImplemented.kt | 5-----
Mcore/src/main/kotlin/me/rhunk/snapenhance/bridge/wrapper/TranslationWrapper.kt | 4++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/ui/menu/impl/SettingsGearInjector.kt | 5+++--
9 files changed, 186 insertions(+), 179 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts @@ -22,8 +22,7 @@ android { defaultConfig { applicationId = rootProject.ext["applicationId"].toString() minSdk = 28 - //noinspection OldTargetApi - targetSdk = 33 + targetSdk = 34 multiDexEnabled = true } @@ -37,11 +36,12 @@ android { flavorDimensions += "abi" productFlavors { + + create("armv8") { ndk { abiFilters.add("arm64-v8a") } - dimension = "abi" } @@ -81,13 +81,13 @@ android { dependencies { implementation(project(":core")) implementation(libs.androidx.material.icons.extended) - implementation(libs.androidx.navigation.compose) - implementation(libs.androidx.activity.ktx) implementation(libs.androidx.material3) implementation(libs.androidx.material) + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.navigation.compose) debugImplementation("androidx.compose.ui:ui-tooling:1.4.3") - implementation("androidx.compose.ui:ui-tooling-preview:1.4.3") + debugImplementation("androidx.compose.ui:ui-tooling-preview:1.4.3") implementation(kotlin("reflect")) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/Dialogs.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/Dialogs.kt @@ -18,159 +18,173 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.impl.ConfigIntegerValue import me.rhunk.snapenhance.config.impl.ConfigStateListValue import me.rhunk.snapenhance.config.impl.ConfigStateSelection -import me.rhunk.snapenhance.config.impl.ConfigStringValue +import me.rhunk.snapenhance.manager.data.ManagerContext -@Composable -@Preview -fun TestPreview() { - KeyboardInputDialog(config = ConfigProperty.SAVE_FOLDER) -} +class Dialogs( + private val context: ManagerContext +) { + @Composable + fun DefaultDialogCard(content: @Composable ColumnScope.() -> Unit) { + Card( + shape = MaterialTheme.shapes.medium, + modifier = Modifier + .padding(10.dp, 5.dp, 10.dp, 10.dp), + ) { + Column( + modifier = Modifier + .padding(10.dp, 10.dp, 10.dp, 10.dp) + .verticalScroll(ScrollState(0)), + ) { content() } + } + } -@Composable -fun DefaultDialogCard(content: @Composable ColumnScope.() -> Unit) { - Card( - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .padding(10.dp, 5.dp, 10.dp, 10.dp), - ) { - Column( + @Composable + fun DefaultEntryText(text: String, modifier: Modifier = Modifier) { + Text( + text = text, modifier = Modifier .padding(10.dp, 10.dp, 10.dp, 10.dp) - .verticalScroll(ScrollState(0)), - ) { content() } + .then(modifier) + ) } -} - -@Composable -fun DefaultEntryText(text: String, modifier: Modifier = Modifier) { - Text( - text = text, - modifier = Modifier - .padding(10.dp, 10.dp, 10.dp, 10.dp) - .then(modifier) - ) -} -@Composable -fun StateSelectionDialog(config: ConfigProperty) { - assert(config.valueContainer is ConfigStateSelection) - val keys = (config.valueContainer as ConfigStateSelection).keys() - val selectedValue = remember { - mutableStateOf(config.valueContainer.value()) - } - DefaultDialogCard { - keys.forEach { item -> - fun select() { - selectedValue.value = item - config.valueContainer.writeFrom(item) - } + @Composable + fun StateSelectionDialog(config: ConfigProperty) { + assert(config.valueContainer is ConfigStateSelection) + val keys = (config.valueContainer as ConfigStateSelection).keys() + val selectedValue = remember { + mutableStateOf(config.valueContainer.value()) + } + DefaultDialogCard { + keys.forEach { item -> + fun select() { + selectedValue.value = item + config.valueContainer.writeFrom(item) + } - Row( - modifier = Modifier.clickable { select() }, - verticalAlignment = Alignment.CenterVertically - ) { - DefaultEntryText( - text = item, - modifier = Modifier.weight(1f) - ) - RadioButton( - selected = selectedValue.value == item, - onClick = { select() } - ) + Row( + modifier = Modifier.clickable { select() }, + verticalAlignment = Alignment.CenterVertically + ) { + DefaultEntryText( + text = if (config.disableValueLocalization) + item + else context.translation.propertyOption(config, item), + modifier = Modifier.weight(1f) + ) + RadioButton( + selected = selectedValue.value == item, + onClick = { select() } + ) + } } } } -} -@Composable -fun KeyboardInputDialog(config: ConfigProperty, dismiss: () -> Unit = {}) { - val focusRequester = remember { FocusRequester() } + @Composable + fun KeyboardInputDialog(config: ConfigProperty, dismiss: () -> Unit = {}) { + val focusRequester = remember { FocusRequester() } + + DefaultDialogCard { + val fieldValue = remember { + mutableStateOf(config.valueContainer.read().let { + TextFieldValue( + text = it, + selection = TextRange(it.length) + ) + }) + } - DefaultDialogCard { - val fieldValue = remember { mutableStateOf(config.valueContainer.read()) } - TextField( - modifier = Modifier.fillMaxWidth().padding(all = 10.dp).focusRequester(focusRequester), - value = fieldValue.value, onValueChange = { - fieldValue.value = it - }, - keyboardOptions = when (config.valueContainer) { - is ConfigIntegerValue -> { - KeyboardOptions(keyboardType = KeyboardType.Number) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(all = 10.dp) + .onGloballyPositioned { + focusRequester.requestFocus() + } + .focusRequester(focusRequester), + value = fieldValue.value, + onValueChange = { + fieldValue.value = it + }, + keyboardOptions = when (config.valueContainer) { + is ConfigIntegerValue -> { + KeyboardOptions(keyboardType = KeyboardType.Number) + } + else -> { + KeyboardOptions(keyboardType = KeyboardType.Text) + } + }, + singleLine = true + ) + + Row( + modifier = Modifier.padding(top = 10.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Button(onClick = { dismiss() }) { + Text(text = "Cancel") } - else -> { - KeyboardOptions(keyboardType = KeyboardType.Text) + Button(onClick = { + config.valueContainer.writeFrom(fieldValue.value.text) + dismiss() + }) { + Text(text = "Ok") } - }, - singleLine = true - ) - - Row( - modifier = Modifier.padding(top = 10.dp).fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly, - ) { - Button(onClick = { dismiss() }) { - Text(text = "Cancel") - } - Button(onClick = { - config.valueContainer.writeFrom(fieldValue.value) - dismiss() - }) { - Text(text = "Ok") } } } - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } -} - -@Composable -fun StateListDialog(config: ConfigProperty) { - assert(config.valueContainer is ConfigStateListValue) - val stateList = (config.valueContainer as ConfigStateListValue).value() - DefaultDialogCard { - stateList.keys.forEach { key -> - val state = remember { - mutableStateOf(stateList[key] ?: false) - } + @Composable + fun StateListDialog(config: ConfigProperty) { + assert(config.valueContainer is ConfigStateListValue) + val stateList = (config.valueContainer as ConfigStateListValue).value() + DefaultDialogCard { + stateList.keys.forEach { key -> + val state = remember { + mutableStateOf(stateList[key] ?: false) + } - fun toggle(value: Boolean? = null) { - state.value = value ?: !state.value - stateList[key] = state.value - } + fun toggle(value: Boolean? = null) { + state.value = value ?: !state.value + stateList[key] = state.value + } - Row( - modifier = Modifier.clickable { toggle() }, - verticalAlignment = Alignment.CenterVertically - ) { - DefaultEntryText( - text = key, - modifier = Modifier - .weight(1f) - ) - Switch( - checked = state.value, - onCheckedChange = { - toggle(it) - } - ) + Row( + modifier = Modifier.clickable { toggle() }, + verticalAlignment = Alignment.CenterVertically + ) { + DefaultEntryText( + text = if (config.disableValueLocalization) + key + else context.translation.propertyOption(config, key), + modifier = Modifier + .weight(1f) + ) + Switch( + checked = state.value, + onCheckedChange = { + toggle(it) + } + ) + } } } } -}- \ No newline at end of file +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/MainActivity.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/MainActivity.kt @@ -4,22 +4,8 @@ import android.annotation.SuppressLint import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.TopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.navigation import androidx.navigation.compose.rememberNavController import me.rhunk.snapenhance.manager.data.ManagerContext @@ -27,26 +13,20 @@ class MainActivity : ComponentActivity() { @SuppressLint("UnusedMaterialScaffoldPaddingParameter") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val startDestination = intent.getStringExtra("route")?.let { EnumSection.fromRoute(it) } ?: EnumSection.HOME val managerContext = ManagerContext(this) setContent { - App(managerContext) + val navController = rememberNavController() + val navigation = Navigation(managerContext) + AppMaterialTheme { + Scaffold( + containerColor = MaterialTheme.colorScheme.background, + bottomBar = { navigation.NavBar(navController = navController) } + ) { innerPadding -> + navigation.NavigationHost(navController = navController, innerPadding = innerPadding, startDestination = startDestination) + } + } } } } - -@Composable -fun App( - context: ManagerContext -) { - val navController = rememberNavController() - val navigation = Navigation(context) - AppMaterialTheme { - Scaffold( - containerColor = MaterialTheme.colorScheme.background, - bottomBar = { navigation.NavBar(navController = navController) } - ) { innerPadding -> - navigation.NavigationHost(navController = navController, innerPadding = innerPadding) - } - } -}- \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/Navigation.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/Navigation.kt @@ -30,6 +30,7 @@ class Navigation( ) { @Composable fun NavigationHost( + startDestination: EnumSection, navController: NavHostController, innerPadding: PaddingValues ) { @@ -40,7 +41,7 @@ class Navigation( instance.navController = navController } } - NavHost(navController, startDestination = EnumSection.FEATURES.route, Modifier.padding(innerPadding)) { + NavHost(navController, startDestination = startDestination.route, Modifier.padding(innerPadding)) { sections.forEach { (section, instance) -> composable(section.route) { instance.Content() diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/Section.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/Section.kt @@ -48,6 +48,12 @@ enum class EnumSection( title = "Debug", icon = Icons.Filled.BugReport ); + + companion object { + fun fromRoute(route: String): EnumSection { + return values().first { it.route == route } + } + } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/FeaturesSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/FeaturesSection.kt @@ -4,9 +4,12 @@ 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.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape @@ -31,6 +34,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -43,15 +47,15 @@ import me.rhunk.snapenhance.config.impl.ConfigStateListValue import me.rhunk.snapenhance.config.impl.ConfigStateSelection import me.rhunk.snapenhance.config.impl.ConfigStateValue import me.rhunk.snapenhance.config.impl.ConfigStringValue -import me.rhunk.snapenhance.manager.StateListDialog +import me.rhunk.snapenhance.manager.Dialogs import me.rhunk.snapenhance.manager.Section -import me.rhunk.snapenhance.manager.StateSelectionDialog -import me.rhunk.snapenhance.manager.KeyboardInputDialog typealias ClickCallback = (Boolean) -> Unit typealias RegisterClickCallback = (ClickCallback) -> ClickCallback class FeaturesSection : Section() { + private val dialogs by lazy { Dialogs(manager) } + @Composable private fun PropertyAction(item: ConfigProperty, registerClickCallback: RegisterClickCallback) { val showDialog = remember { mutableStateOf(false) } @@ -82,12 +86,15 @@ class FeaturesSection : Section() { is ConfigStateSelection -> { registerDialogOnClickCallback() dialogComposable.value = { - StateSelectionDialog(item) + dialogs.StateSelectionDialog(item) } Text( - text = container.value().let { - it.substring(0, it.length.coerceAtMost(20)) - } + overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = Modifier.widthIn(0.dp, 120.dp), + text = if (item.disableValueLocalization) container.value() else { + manager.translation.propertyOption(item, container.value()) + }, ) } @@ -95,10 +102,10 @@ class FeaturesSection : Section() { dialogComposable.value = { when (container) { is ConfigStateListValue -> { - StateListDialog(item) + dialogs.StateListDialog(item) } is ConfigStringValue, is ConfigIntegerValue -> { - KeyboardInputDialog(item) { showDialog.value = false } + dialogs.KeyboardInputDialog(item) { showDialog.value = false } } } } @@ -106,7 +113,7 @@ class FeaturesSection : Section() { registerDialogOnClickCallback().let { { it.invoke(true) } }.also { if (container is ConfigIntegerValue) { FilledIconButton(onClick = it) { - Text(text = container.value().toString()) + Text(text = container.value().toString(), modifier = Modifier.wrapContentWidth(), overflow = TextOverflow.Ellipsis) } } else { IconButton(onClick = it) { @@ -132,7 +139,7 @@ class FeaturesSection : Section() { Row( modifier = Modifier .fillMaxSize() - .padding(all = 10.dp), + .padding(all = 4.dp), horizontalArrangement = Arrangement.SpaceBetween ) { Column( @@ -206,10 +213,11 @@ class FeaturesSection : Section() { ) LazyColumn( modifier = Modifier - .fillMaxSize(), + .fillMaxHeight(), verticalArrangement = Arrangement.Center ) { items(configItems) { item -> + if (item.shouldAppearInSettings.not()) return@items PropertyCard(item) } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/NotImplemented.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/NotImplemented.kt @@ -1,13 +1,8 @@ package me.rhunk.snapenhance.manager.sections -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview import me.rhunk.snapenhance.manager.Section class NotImplemented : Section() { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/bridge/wrapper/TranslationWrapper.kt b/core/src/main/kotlin/me/rhunk/snapenhance/bridge/wrapper/TranslationWrapper.kt @@ -88,6 +88,10 @@ class TranslationWrapper { return get("property.${property.translationKey}.description") } + fun propertyOption(property: ConfigProperty, item: String): String { + return get(property.getOptionTranslationKey(item)) + } + fun format(key: String, vararg args: Pair<String, String>): String { return args.fold(get(key)) { acc, pair -> acc.replace("{${pair.first}}", pair.second) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/ui/menu/impl/SettingsGearInjector.kt b/core/src/main/kotlin/me/rhunk/snapenhance/ui/menu/impl/SettingsGearInjector.kt @@ -48,9 +48,10 @@ class SettingsGearInjector : AbstractMenu() { setOnClickListener { val intent = Intent().apply { - setClassName(BuildConfig.APPLICATION_ID, ConfigActivity::class.java.name) + setClassName(BuildConfig.APPLICATION_ID, "me.rhunk.snapenhance.manager.MainActivity") + putExtra("route", "features") + putExtra("lspatched", File(context.cacheDir, "lspatch/origin").exists()) } - intent.putExtra("lspatched", File(context.cacheDir, "lspatch/origin").exists()) context.startActivity(intent) }