add sliderPreference and a partial PreferencesScreen

This commit is contained in:
Helium314 2025-01-26 21:16:26 +01:00
parent e7c2301643
commit 3563ded922
7 changed files with 283 additions and 3 deletions

View file

@ -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? {

View file

@ -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 <T: Number> SliderPreference(
name: String,
modifier: Modifier = Modifier,
pref: String,
description: @Composable (T) -> String,
default: T,
range: ClosedFloatingPointRange<Float>,
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",

View file

@ -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?)

View file

@ -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)

View file

@ -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<Float>,
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
)
}

View file

@ -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({}, {}, {}, {})
}
}
}

View file

@ -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 { }
}
}
}