diff --git a/app/src/main/java/helium314/keyboard/settings/AllPrefs.kt b/app/src/main/java/helium314/keyboard/settings/AllPrefs.kt index a82f270c8..5e41f00eb 100644 --- a/app/src/main/java/helium314/keyboard/settings/AllPrefs.kt +++ b/app/src/main/java/helium314/keyboard/settings/AllPrefs.kt @@ -8,6 +8,7 @@ import androidx.annotation.StringRes import androidx.compose.runtime.Composable import helium314.keyboard.settings.screens.createAboutPrefs import helium314.keyboard.settings.screens.createCorrectionPrefs +import helium314.keyboard.settings.screens.createPreferencesPrefs class AllPrefs(context: Context) { private val list = createPrefDefs(context) @@ -49,7 +50,8 @@ class PrefDef( } } -private fun createPrefDefs(context: Context) = createAboutPrefs(context) + createCorrectionPrefs(context) +private fun createPrefDefs(context: Context) = createAboutPrefs(context) + + createCorrectionPrefs(context) + createPreferencesPrefs(context) // todo: move somewhere else fun Context.getActivity(): ComponentActivity? { diff --git a/app/src/main/java/helium314/keyboard/settings/Preference.kt b/app/src/main/java/helium314/keyboard/settings/Preference.kt index 933b2f75c..ed4b7c4b0 100644 --- a/app/src/main/java/helium314/keyboard/settings/Preference.kt +++ b/app/src/main/java/helium314/keyboard/settings/Preference.kt @@ -22,6 +22,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +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.platform.LocalContext @@ -33,6 +37,7 @@ import androidx.compose.ui.unit.dp import helium314.keyboard.latin.R import helium314.keyboard.latin.utils.DeviceProtectedUtils import helium314.keyboard.latin.utils.Log +import helium314.keyboard.settings.dialogs.SliderDialog // taken from StreetComplete (and a bit SCEE) @@ -183,6 +188,51 @@ fun SwitchPreference( ) } +@Composable +/** Slider preference for Int or Float (weird casting stuff, but should be fine) */ +fun SliderPreference( + name: String, + modifier: Modifier = Modifier, + pref: String, + description: @Composable (T) -> String, + default: T, + range: ClosedFloatingPointRange, + onValueChanged: (Float) -> Unit = { }, +) { + val ctx = LocalContext.current + val prefs = DeviceProtectedUtils.getSharedPreferences(ctx) + val b = (ctx.getActivity() as? SettingsActivity2)?.prefChanged?.collectAsState() + if (b?.value ?: 0 < 0) + Log.v("irrelevant", "stupid way to trigger recomposition on preference change") + val initialValue = if (default is Int) prefs.getInt(pref, default) + else if (default is Float) prefs.getFloat(pref, default) + else throw IllegalArgumentException("only float and int are supported") + + var showDialog by remember { mutableStateOf(false) } + Preference( + name = name, + onClick = { showDialog = true }, + modifier = modifier, + description = description(initialValue as T) + ) + if (showDialog) + SliderDialog( + onDismissRequest = { showDialog = false }, + onDone = { + if (default is Int) prefs.edit().putInt(pref, it.toInt()).apply() + else prefs.edit().putFloat(pref, it).apply() + }, + initialValue = initialValue.toFloat(), + range = range, + positionString = { + description((if (default is Int) it.toInt() else it) as T) + }, + onValueChanged = onValueChanged, + showDefault = true, + onDefault = { prefs.edit().remove(pref).apply() } + ) +} + @Preview @Composable private fun PreferencePreview() { @@ -196,6 +246,13 @@ private fun PreferencePreview() { onClick = {}, icon = R.drawable.ic_settings_about_foreground ) + SliderPreference( + name = "SliderPreference", + pref = "", + default = 1, + description = { it.toString() }, + range = -5f..5f + ) Preference( name = "Preference with icon and description", description = "some text", diff --git a/app/src/main/java/helium314/keyboard/settings/SettingsActivity.kt b/app/src/main/java/helium314/keyboard/settings/SettingsActivity.kt index ac51fdaac..84f298d10 100644 --- a/app/src/main/java/helium314/keyboard/settings/SettingsActivity.kt +++ b/app/src/main/java/helium314/keyboard/settings/SettingsActivity.kt @@ -14,7 +14,8 @@ import helium314.keyboard.latin.utils.DeviceProtectedUtils import kotlinx.coroutines.flow.MutableStateFlow // todo -// more pref screens, seekBarPref, reorderDialog, and other super-custom things +// add reorder / enable dialog (maybe not as dialog?) +// more pref screens, and other super-custom things // consider IME insets when searching // improve performance when loading screens with many settings (lazyColumn?) // consider that stuff in composables can get called quite often on any changes -> use remember for things that are slow (maybe add test logging) @@ -44,6 +45,7 @@ import kotlinx.coroutines.flow.MutableStateFlow // -> users confused, but probably better than the 2 above // maybe later +// weird problem with app sometimes closing on back, but that's related to "old" settings (don't care if all are removed) // bottom text field (though we have the search now anyway) // remove navHost? but probably too useful to have... // lazyColumn for prefs (or just in category?) diff --git a/app/src/main/java/helium314/keyboard/settings/SettingsNavHost.kt b/app/src/main/java/helium314/keyboard/settings/SettingsNavHost.kt index 15adbc55c..9a584c2fc 100644 --- a/app/src/main/java/helium314/keyboard/settings/SettingsNavHost.kt +++ b/app/src/main/java/helium314/keyboard/settings/SettingsNavHost.kt @@ -12,6 +12,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import helium314.keyboard.settings.screens.AboutScreen import helium314.keyboard.settings.screens.MainSettingsScreen +import helium314.keyboard.settings.screens.PreferencesScreen import helium314.keyboard.settings.screens.TextCorrectionScreen import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -46,6 +47,7 @@ fun SettingsNavHost( MainSettingsScreen( onClickAbout = { navController.navigate(SettingsDestination.About) }, onClickTextCorrection = { navController.navigate(SettingsDestination.TextCorrection) }, + onClickPreferences = { navController.navigate(SettingsDestination.Preferences) }, onClickBack = ::goBack, ) } @@ -59,6 +61,11 @@ fun SettingsNavHost( onClickBack = ::goBack ) } + composable(SettingsDestination.Preferences) { + PreferencesScreen ( + onClickBack = ::goBack + ) + } } } @@ -66,6 +73,7 @@ object SettingsDestination { const val Settings = "settings" const val About = "about" const val TextCorrection = "text_correction" + const val Preferences = "preferences" val navTarget = MutableStateFlow(Settings) private val navScope = CoroutineScope(Dispatchers.Default) diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/SliderDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/SliderDialog.kt new file mode 100644 index 000000000..e8faa60d7 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/settings/dialogs/SliderDialog.kt @@ -0,0 +1,107 @@ +package helium314.keyboard.settings.dialogs + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.window.DialogProperties +import helium314.keyboard.latin.R + +@Composable +fun SliderDialog( + onDismissRequest: () -> Unit, + onDone: (Float) -> Unit, + initialValue: Float, + range: ClosedFloatingPointRange, + modifier: Modifier = Modifier, + showDefault: Boolean = false, + onDefault: () -> Unit? = { }, + onValueChanged: (Float) -> Unit = { }, + title: (@Composable () -> Unit)? = null, + intermediateSteps: Int? = null, + positionString: (@Composable (Float) -> String) = { it.toString() }, + shape: Shape = MaterialTheme.shapes.medium, + backgroundColor: Color = MaterialTheme.colorScheme.surface, + contentColor: Color = contentColorFor(backgroundColor), + properties: DialogProperties = DialogProperties() +) { + var sliderPosition by remember { mutableFloatStateOf(initialValue) } + + AlertDialog( + onDismissRequest = onDismissRequest, + confirmButton = { + TextButton( + onClick = { onDismissRequest(); onDone(sliderPosition) }, + ) { Text(stringResource(android.R.string.ok)) } + }, + modifier = modifier, + dismissButton = { + Row { + if (showDefault) + TextButton(onClick = { onDismissRequest(); onDefault() }) + { Text(stringResource(R.string.button_default)) } + TextButton(onClick = onDismissRequest) + { Text(stringResource(android.R.string.cancel)) } + } + }, + title = title, + text = { + CompositionLocalProvider( + LocalTextStyle provides MaterialTheme.typography.bodyLarge + ) { + Column { + if (intermediateSteps == null) + Slider( + value = sliderPosition, + onValueChange = { sliderPosition = it }, + onValueChangeFinished = { onValueChanged(sliderPosition) }, + valueRange = range, + ) + else + Slider( + value = sliderPosition, + onValueChange = { sliderPosition = it }, + onValueChangeFinished = { onValueChanged(sliderPosition) }, + valueRange = range, + steps = intermediateSteps + ) + Text(positionString(sliderPosition)) + } + } + }, + shape = shape, + containerColor = backgroundColor, + textContentColor = contentColor, + properties = properties, + ) +} + +@Preview +@Composable +private fun PreviewSliderDialog() { + SliderDialog( + onDismissRequest = { }, + onDone = { }, + initialValue = 100f, + range = 0f..500f, + title = { Text("move it") }, + showDefault = true + ) +} diff --git a/app/src/main/java/helium314/keyboard/settings/screens/MainSettingsScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/MainSettingsScreen.kt index f395b202c..fca15b450 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/MainSettingsScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/MainSettingsScreen.kt @@ -35,6 +35,7 @@ import helium314.keyboard.settings.getActivity fun MainSettingsScreen( onClickAbout: () -> Unit, onClickTextCorrection: () -> Unit, + onClickPreferences: () -> Unit, onClickBack: () -> Unit, ) { val ctx = LocalContext.current @@ -42,6 +43,17 @@ fun MainSettingsScreen( onClickBack = onClickBack, title = stringResource(R.string.ime_settings), ) { + Preference( + name = stringResource(R.string.settings_screen_preferences), + onClick = onClickPreferences, + icon = R.drawable.ic_settings_preferences_foreground + ) { + Icon( + painter = painterResource(R.drawable.ic_arrow_left), + modifier = Modifier.scale(-1f, 1f), + contentDescription = null + ) + } Preference( name = stringResource(R.string.settings_screen_correction), onClick = onClickTextCorrection, @@ -117,7 +129,7 @@ fun Activity.switchTo(fragment: androidx.fragment.app.Fragment) { private fun PreviewScreen() { Theme(true) { Surface { - MainSettingsScreen({}, {}, {}) + MainSettingsScreen({}, {}, {}, {}) } } } diff --git a/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt new file mode 100644 index 000000000..767b95f85 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt @@ -0,0 +1,92 @@ +package helium314.keyboard.settings.screens + +import android.content.Context +import android.media.AudioManager +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import helium314.keyboard.latin.AudioAndHapticFeedbackManager +import helium314.keyboard.latin.R +import helium314.keyboard.latin.settings.Settings +import helium314.keyboard.latin.utils.DeviceProtectedUtils +import helium314.keyboard.settings.AllPrefs +import helium314.keyboard.settings.PrefDef +import helium314.keyboard.settings.Preference +import helium314.keyboard.settings.SearchPrefScreen +import helium314.keyboard.settings.SettingsActivity2 +import helium314.keyboard.settings.SliderPreference +import helium314.keyboard.settings.Theme +import helium314.keyboard.settings.dialogs.SliderDialog + +@Composable +fun PreferencesScreen( + onClickBack: () -> Unit, +) { + SearchPrefScreen( + onClickBack = onClickBack, + title = stringResource(R.string.settings_screen_preferences), + ) { + SettingsActivity2.allPrefs.map[Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME]!!.Preference() + SettingsActivity2.allPrefs.map[Settings.PREF_VIBRATION_DURATION_SETTINGS]!!.Preference() + SettingsActivity2.allPrefs.map[Settings.PREF_KEYPRESS_SOUND_VOLUME]!!.Preference() + } +} + +fun createPreferencesPrefs(context: Context) = listOf( + PrefDef(context, Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME, R.string.clipboard_history_retention_time) { def -> + SliderPreference( + name = def.title, + pref = def.key, + default = 10, + description = { + if (it < 0) stringResource(R.string.settings_no_limit) + else stringResource(R.string.abbreviation_unit_minutes, it.toString()) + }, + range = -1f..120f, + ) + }, + PrefDef(context, Settings.PREF_VIBRATION_DURATION_SETTINGS, R.string.prefs_keypress_vibration_duration_settings) { def -> + SliderPreference( + name = def.title, + pref = def.key, + default = -1, + description = { + if (it < 0) stringResource(R.string.settings_system_default) + else stringResource(R.string.abbreviation_unit_milliseconds, it.toString()) + }, + range = -1f..100f, + onValueChanged = { AudioAndHapticFeedbackManager.getInstance().vibrate(it.toLong()) } + ) + }, + PrefDef(context, Settings.PREF_KEYPRESS_SOUND_VOLUME, R.string.prefs_keypress_sound_volume_settings) { def -> + val audioManager = LocalContext.current.getSystemService(Context.AUDIO_SERVICE) as AudioManager + SliderPreference( + name = def.title, + pref = def.key, + default = -0.01f, + description = { + if (it < 0) stringResource(R.string.settings_system_default) + else (it * 100).toInt().toString() + }, + range = -0.01f..1f, + onValueChanged = { audioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, it) } + ) + }, +) + +@Preview +@Composable +private fun Preview() { + SettingsActivity2.allPrefs = AllPrefs(LocalContext.current) + Theme(true) { + Surface { + PreferencesScreen { } + } + } +}