From 5c9bada7aeb1d44e93c666143f8db31d626888fc Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Thu, 25 Apr 2024 01:02:40 -0400 Subject: [PATCH] Use a slider for several settings --- .../latin/uix/settings/Components.kt | 125 ++++++++++++++++++ .../uix/settings/pages/AdvancedParameters.kt | 96 +++++++------- .../latin/uix/settings/pages/Typing.kt | 21 ++- 3 files changed, 188 insertions(+), 54 deletions(-) diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt b/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt index 36992f139..11c2decd3 100644 --- a/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt +++ b/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt @@ -15,6 +15,9 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack @@ -22,24 +25,41 @@ import androidx.compose.material.icons.filled.ArrowForward import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton +import androidx.compose.material3.Slider import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController +import kotlinx.coroutines.runBlocking import org.futo.inputmethod.latin.uix.SettingsKey +import org.futo.inputmethod.latin.uix.getSetting import org.futo.inputmethod.latin.uix.theme.Typography +import kotlin.math.pow @Composable fun ScreenTitle(title: String, showBack: Boolean = false, navController: NavHostController = rememberNavController()) { @@ -243,6 +263,111 @@ fun SettingRadio( } } +@Composable +fun SettingSlider( + title: String, + setting: SettingsKey, + range: ClosedFloatingPointRange, + transform: (Float) -> T, + indicator: (T) -> String = { it.toString() }, + hardRange: ClosedFloatingPointRange = range, + power: Float = 1.0f, + subtitle: String? = null +) { + val context = LocalContext.current + + val (value, setValue) = useDataStore(key = setting.key, default = setting.default) + var virtualValue by remember { mutableFloatStateOf((runBlocking { context.getSetting(setting) }).toFloat().let { + if(it == Float.POSITIVE_INFINITY || it == Float.NEGATIVE_INFINITY) { + it + } else { + it.pow(1.0f / power) + } + }) } + var isTextFieldVisible by remember { mutableStateOf(false) } + var hasTextFieldFocusedYet by remember { mutableStateOf(false) } + var textFieldValue by remember(value) { + val s = value.toString() + mutableStateOf(TextFieldValue( + s, + selection = TextRange(0, s.length) + )) + } + + val focusRequester = remember { FocusRequester() } + + LaunchedEffect(isTextFieldVisible) { + if(isTextFieldVisible) focusRequester.requestFocus() + } + + ScreenTitle(title, showBack = false) + if(subtitle != null) { + Text(subtitle, style = Typography.bodyMedium, modifier = Modifier.padding(12.dp, 0.dp)) + } + Row(modifier = Modifier.padding(16.dp, 0.dp)) { + if (isTextFieldVisible) { + val apply = { + if(isTextFieldVisible) { + val number = textFieldValue.text.trim().toFloatOrNull() + val newValue = if (number != null) { + transform(number.coerceIn(hardRange)) + } else { + setting.default + } + + setValue(newValue) + virtualValue = newValue.toFloat().pow(1.0f / power) + + isTextFieldVisible = false + textFieldValue = TextFieldValue() + } + } + BasicTextField( + value = textFieldValue, + onValueChange = { textFieldValue = it }, + modifier = Modifier + .weight(0.33f) + .align(Alignment.CenterVertically) + .focusRequester(focusRequester) + .onFocusChanged { + if(it.isFocused) hasTextFieldFocusedYet = true + else if(!it.isFocused && hasTextFieldFocusedYet) apply() + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + keyboardActions = KeyboardActions( + onDone = { + apply() + } + ), + singleLine = true, + textStyle = Typography.labelMedium + ) + + } else { + Text( + text = indicator(value), + modifier = Modifier + .weight(0.33f) + .align(Alignment.CenterVertically) + .clickable { + hasTextFieldFocusedYet = false + isTextFieldVisible = true + }, + style = Typography.labelMedium + ) + } + Slider( + value = virtualValue, + onValueChange = { + virtualValue = it + setValue(transform(it.pow(power))) }, + valueRange = range.start.pow(1.0f / power) .. range.endInclusive.pow(1.0f / power), + enabled = !isTextFieldVisible, + modifier = Modifier.weight(1.0f) + ) + } +} + @Composable fun ScrollableList(content: @Composable () -> Unit) { val scrollState = rememberScrollState() diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/AdvancedParameters.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/AdvancedParameters.kt index 7518d64b0..866236c39 100644 --- a/java/src/org/futo/inputmethod/latin/uix/settings/pages/AdvancedParameters.kt +++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/AdvancedParameters.kt @@ -1,20 +1,13 @@ package org.futo.inputmethod.latin.uix.settings.pages -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController -import org.futo.inputmethod.latin.uix.THEME_KEY import org.futo.inputmethod.latin.uix.settings.ScreenTitle import org.futo.inputmethod.latin.uix.settings.ScrollableList -import org.futo.inputmethod.latin.uix.settings.SettingRadio +import org.futo.inputmethod.latin.uix.settings.SettingSlider import org.futo.inputmethod.latin.uix.settings.Tip -import org.futo.inputmethod.latin.uix.settings.useDataStore -import org.futo.inputmethod.latin.uix.theme.selector.ThemePicker import org.futo.inputmethod.latin.xlm.AutocorrectThresholdSetting import org.futo.inputmethod.latin.xlm.BinaryDictTransformerWeightSetting @@ -24,50 +17,55 @@ fun AdvancedParametersScreen(navController: NavHostController = rememberNavContr ScrollableList { ScreenTitle("Advanced Parameters", showBack = true, navController) - val optionsWeight = mapOf( - Float.NEGATIVE_INFINITY to "always BinaryDictionary, except if blank", - 0.0001f to "significantly favor BinaryDictionary, except if BinaryDictionary score < 0", - 0.01f to "favor BinaryDictionary", - 0.1f to "favor BinaryDictionary", - 0.2f to "favor BinaryDictionary", - 0.3f to "favor BinaryDictionary", - 0.4f to "favor BinaryDictionary", - 0.5f to "favor BinaryDictionary", - 1.0f to "normal", - 2.0f to "favor TransformerLM", - 4.0f to "significantly favor TransformerLM", - Float.POSITIVE_INFINITY to "always TransformerLM" - ) - val namesWeight = optionsWeight.map { "a = ${it.key} (${it.value})" } - SettingRadio( - title = "Weight of Transformer LM suggestions with respect to BinaryDictionary", - options = optionsWeight.keys.toList(), - optionNames = namesWeight, - setting = BinaryDictTransformerWeightSetting + Tip("Options below are experimental and may be removed or changed in the future as internal workings of the app change. Changing these values may have an adverse impact on your experience.") + + SettingSlider( + title = "Transformer LM strength", + subtitle = "Lower value will make autocorrect behave more similarly to standard AOSP keyboard, while higher value will make it more dependent on the neural network\nDefault is ${BinaryDictTransformerWeightSetting.default}", + setting = BinaryDictTransformerWeightSetting, + range = 0.0f .. 100.0f, + transform = { + if(it > 99.9f) { + Float.POSITIVE_INFINITY + } else if(it < 0.0001f) { + Float.NEGATIVE_INFINITY + } else { + it + } + }, + indicator = { + when { + it == Float.POSITIVE_INFINITY -> { + "always Transformer LM" + } + it == Float.NEGATIVE_INFINITY -> { + "always Binary Dictionary" + } + (it > 0.1f) -> { + "a = ${String.format("%.1f", it)}" + } + else -> { + "a = $it" + } + } + }, + power = 2.5f ) - - Tip("Adjust the autocorrect threshold below. A lower threshold will autocorrect more often (and miscorrect more often), while a higher threshold will autocorrect less often (and miscorrect less often)" ) - val options = mapOf( - 0.0f to "none (94.6% : 5.4%)", - 1.0f to "very low (93.4% : 4.3%)", - 2.0f to "very low (91.2% : 2.4%)", - 4.0f to "low (87.3% : 1.4%)", - 6.0f to "low (no data)", - 8.0f to "medium (82.3% : 0.9%)", - 10.0f to "medium (80.1% : 0.8%)", - 14.0f to "medium (no data)", - 18.0f to "high (74.8% : 0.5%)", - 25.0f to "high (71.6% : 0.4%)", - 50.0f to "very high (63.5% : 0.3%)", - 100.0f to "very high (54.7% : 0.2%)" - ) - val names = options.map { "T = ${it.key}" } - SettingRadio( + SettingSlider( title = "Autocorrect Threshold", - options = options.keys.toList(), - optionNames = names, - setting = AutocorrectThresholdSetting + subtitle = "A lower threshold will autocorrect more often (and miscorrect more often), while a higher threshold will autocorrect less often (and miscorrect less often).\nDefault is ${AutocorrectThresholdSetting.default}", + setting = AutocorrectThresholdSetting, + range = 0.0f .. 25.0f, + hardRange = 0.0f .. 25.0f, + transform = { + it + }, + indicator = { + "T = ${String.format("%.1f", it)}" + }, + power = 2.5f ) + } } \ No newline at end of file diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt index 972037c63..8b30f4d58 100644 --- a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt +++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt @@ -3,7 +3,6 @@ package org.futo.inputmethod.latin.uix.settings.pages import android.preference.PreferenceManager import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.booleanResource import androidx.compose.ui.res.stringResource @@ -13,7 +12,6 @@ import androidx.datastore.preferences.core.intPreferencesKey import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.futo.inputmethod.latin.R import org.futo.inputmethod.latin.settings.Settings @@ -22,11 +20,11 @@ import org.futo.inputmethod.latin.uix.SHOW_EMOJI_SUGGESTIONS import org.futo.inputmethod.latin.uix.SettingsKey import org.futo.inputmethod.latin.uix.settings.ScreenTitle import org.futo.inputmethod.latin.uix.settings.ScrollableList -import org.futo.inputmethod.latin.uix.settings.SettingRadio +import org.futo.inputmethod.latin.uix.settings.SettingSlider import org.futo.inputmethod.latin.uix.settings.SettingToggleDataStore import org.futo.inputmethod.latin.uix.settings.SettingToggleSharedPrefs import org.futo.inputmethod.latin.uix.settings.useDataStore -import org.futo.inputmethod.latin.uix.settings.useSharedPrefsBool +import kotlin.math.roundToInt val vibrationDurationSetting = SettingsKey( intPreferencesKey("vibration_duration"), @@ -90,6 +88,19 @@ fun TypingScreen(navController: NavHostController = rememberNavController()) { default = booleanResource(R.bool.config_default_key_preview_popup) ) - SettingRadio(title = "Vibration Duration", options = listOf(-1, 5, 10, 20, 40), optionNames = listOf("Default", "Low", "Medium", "High", "Higher"), setting = vibrationDurationSetting) + SettingSlider( + title = "Vibration", + setting = vibrationDurationSetting, + range = -1.0f .. 100.0f, + hardRange = -1.0f .. 2000.0f, + transform = { it.roundToInt() }, + indicator = { + if(it == -1) { + "Default" + } else { + "$it ms" + } + } + ) } } \ No newline at end of file