upgrade user theme selection, now multiple themes are possible

currently missing: old "show more/all colors", and import/export
This commit is contained in:
Helium314 2025-02-11 17:40:53 +01:00
parent 40433bd8d2
commit 63bda02cc4
13 changed files with 430 additions and 109 deletions

View file

@ -14,7 +14,7 @@ android {
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 2302 versionCode = 2302
versionName = "2.3" versionName = "2.3+dev1"
ndk { ndk {
abiFilters.clear() abiFilters.clear()
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")) abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))

View file

@ -24,6 +24,7 @@ import helium314.keyboard.latin.utils.brightenOrDarken
import helium314.keyboard.latin.utils.isBrightColor import helium314.keyboard.latin.utils.isBrightColor
import helium314.keyboard.latin.utils.isGoodContrast import helium314.keyboard.latin.utils.isGoodContrast
import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.prefs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.util.EnumMap import java.util.EnumMap
@ -59,19 +60,23 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
const val THEME_PINK = "pink" const val THEME_PINK = "pink"
const val THEME_SAND = "sand" const val THEME_SAND = "sand"
const val THEME_VIOLETTE = "violette" const val THEME_VIOLETTE = "violette"
fun getAvailableColors(prefs: SharedPreferences, isNight: Boolean) = listOfNotNull( fun getAvailableDefaultColors(prefs: SharedPreferences, isNight: Boolean) = listOfNotNull(
if (!isNight) THEME_LIGHT else null, THEME_DARK, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) THEME_DYNAMIC else null, if (!isNight) THEME_LIGHT else null, THEME_DARK,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) THEME_DYNAMIC else null,
if (prefs.getString(Settings.PREF_THEME_STYLE, Defaults.PREF_THEME_STYLE) == STYLE_HOLO) THEME_HOLO_WHITE else null, if (prefs.getString(Settings.PREF_THEME_STYLE, Defaults.PREF_THEME_STYLE) == STYLE_HOLO) THEME_HOLO_WHITE else null,
THEME_DARKER, THEME_BLACK, if (!isNight) THEME_BLUE_GRAY else null, if (!isNight) THEME_BROWN else null, THEME_DARKER,
THEME_CHOCOLATE, THEME_CLOUDY, THEME_FOREST, if (!isNight) THEME_INDIGO else null, if (!isNight) THEME_PINK else null, THEME_BLACK,
THEME_OCEAN, if (!isNight) THEME_SAND else null, THEME_VIOLETTE if (!isNight) THEME_BLUE_GRAY else null,
) + prefs.all.keys.mapNotNull { if (!isNight) THEME_BROWN else null,
when { THEME_CHOCOLATE,
it.startsWith(Settings.PREF_USER_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_COLORS_PREFIX) THEME_CLOUDY,
it.startsWith(Settings.PREF_USER_ALL_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_ALL_COLORS_PREFIX) THEME_FOREST,
else -> null if (!isNight) THEME_INDIGO else null,
} if (!isNight) THEME_PINK else null,
}.toSortedSet() // we don't want duplicates, and we want a consistent order THEME_OCEAN,
if (!isNight) THEME_SAND else null,
THEME_VIOLETTE
)
val STYLES = arrayOf(STYLE_MATERIAL, STYLE_HOLO, STYLE_ROUNDED) val STYLES = arrayOf(STYLE_MATERIAL, STYLE_HOLO, STYLE_ROUNDED)
// These should be aligned with Keyboard.themeId and Keyboard.Case.keyboardTheme // These should be aligned with Keyboard.themeId and Keyboard.Case.keyboardTheme
@ -326,13 +331,13 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
} }
} }
fun writeUserColors(prefs: SharedPreferences, themeName: String, colors: Map<String, Pair<Int?, Boolean>>) { fun writeUserColors(prefs: SharedPreferences, themeName: String, colors: List<ColorSetting>) {
val key = Settings.PREF_USER_COLORS_PREFIX + themeName val key = Settings.PREF_USER_COLORS_PREFIX + themeName
val value = Json.encodeToString(colors.filterValues { it.first != null }) val value = Json.encodeToString(colors.filter { it.color != null || it.auto == false })
prefs.edit().putString(key, value).apply() prefs.edit().putString(key, value).apply()
} }
fun readUserColors(prefs: SharedPreferences, themeName: String): Map<String, Pair<Int, Boolean>> { fun readUserColors(prefs: SharedPreferences, themeName: String): List<ColorSetting> {
val key = Settings.PREF_USER_COLORS_PREFIX + themeName val key = Settings.PREF_USER_COLORS_PREFIX + themeName
return Json.decodeFromString(prefs.getString(key, Defaults.PREF_USER_COLORS)!!) return Json.decodeFromString(prefs.getString(key, Defaults.PREF_USER_COLORS)!!)
} }
@ -368,14 +373,16 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
return colorMap return colorMap
} }
fun determineUserColor(colors: Map<String, Pair<Int?, Boolean>>, context: Context, colorName: String, isNight: Boolean): Int { fun determineUserColor(colors: List<ColorSetting>, context: Context, colorName: String, isNight: Boolean): Int {
val (color, auto) = colors[colorName] ?: (null to true) val c = colors.firstOrNull { it.name == colorName }
val color = c?.color
val auto = c?.auto ?: true
return if (auto || color == null) return if (auto || color == null)
determineAutoColor(colors, colorName, isNight, context) determineAutoColor(colors, colorName, isNight, context)
else color else color
} }
private fun determineAutoColor(colors: Map<String, Pair<Int?, Boolean>>, colorName: String, isNight: Boolean, context: Context): Int { private fun determineAutoColor(colors: List<ColorSetting>, colorName: String, isNight: Boolean, context: Context): Int {
when (colorName) { when (colorName) {
COLOR_ACCENT -> { COLOR_ACCENT -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
@ -427,3 +434,8 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
} }
} }
} }
@Serializable
data class ColorSetting(val name: String, val auto: Boolean?, val color: Int?) {
var displayName = name
}

View file

@ -4,6 +4,7 @@ package helium314.keyboard.latin
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import androidx.core.content.edit import androidx.core.content.edit
import helium314.keyboard.keyboard.ColorSetting
import helium314.keyboard.keyboard.KeyboardTheme import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.common.ColorType import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.common.LocaleUtils.constructLocale import helium314.keyboard.latin.common.LocaleUtils.constructLocale
@ -167,14 +168,14 @@ fun checkVersionUpgrade(context: Context) {
} }
// day colors // day colors
val themeNameDay = context.getString(R.string.theme_name_user) val themeNameDay = context.getString(R.string.theme_name_user)
val colorsDay = colorPrefsAndResIds.associate { val colorsDay = colorPrefsAndResIds.map {
val pref = "theme_color_" + it.first val pref = "theme_color_" + it.first
val color = if (prefs.contains(pref)) prefs.getInt(pref, 0) else null val color = if (prefs.contains(pref)) prefs.getInt(pref, 0) else null
val result = it.first to (color to prefs.getBoolean(pref + "_auto", true)) val result = ColorSetting(it.first, prefs.getBoolean(pref + "_auto", true), color)
prefs.edit().remove(pref).remove(pref + "_auto").apply() prefs.edit().remove(pref).remove(pref + "_auto").apply()
result result
} }
if (colorsDay.any { it.value.first != null }) { if (colorsDay.any { it.color != null }) {
KeyboardTheme.writeUserColors(prefs, themeNameDay, colorsDay) KeyboardTheme.writeUserColors(prefs, themeNameDay, colorsDay)
} }
val moreColorsDay = prefs.getInt("theme_color_show_more_colors", 0) val moreColorsDay = prefs.getInt("theme_color_show_more_colors", 0)
@ -190,14 +191,14 @@ fun checkVersionUpgrade(context: Context) {
// same for night colors // same for night colors
val themeNameNight = context.getString(R.string.theme_name_user_night) val themeNameNight = context.getString(R.string.theme_name_user_night)
val colorsNight = colorPrefsAndResIds.associate { val colorsNight = colorPrefsAndResIds.map {
val pref = "theme_dark_color_" + it.first val pref = "theme_dark_color_" + it.first
val color = if (prefs.contains(pref)) prefs.getInt(pref, 0) else null val color = if (prefs.contains(pref)) prefs.getInt(pref, 0) else null
val result = it.first to (color to prefs.getBoolean(pref + "_auto", true)) val result = ColorSetting(it.first, prefs.getBoolean(pref + "_auto", true), color)
prefs.edit().remove(pref).remove(pref + "_auto").apply() prefs.edit().remove(pref).remove(pref + "_auto").apply()
result result
} }
if (colorsNight.any { it.value.first != null }) { if (colorsNight.any { it.color!= null }) {
KeyboardTheme.writeUserColors(prefs, themeNameNight, colorsNight) KeyboardTheme.writeUserColors(prefs, themeNameNight, colorsNight)
} }
val moreColorsNight = prefs.getInt("theme_dark_color_show_more_colors", 0) val moreColorsNight = prefs.getInt("theme_dark_color_show_more_colors", 0)

View file

@ -143,7 +143,7 @@ object Defaults {
const val PREF_SHOW_SUGGESTION_INFOS = false const val PREF_SHOW_SUGGESTION_INFOS = false
const val PREF_FORCE_NON_DISTINCT_MULTITOUCH = false const val PREF_FORCE_NON_DISTINCT_MULTITOUCH = false
const val PREF_SLIDING_KEY_INPUT_PREVIEW = true const val PREF_SLIDING_KEY_INPUT_PREVIEW = true
const val PREF_USER_COLORS = "{}" const val PREF_USER_COLORS = "[]"
const val PREF_USER_MORE_COLORS = 0 const val PREF_USER_MORE_COLORS = 0
const val PREF_USER_ALL_COLORS = "" const val PREF_USER_ALL_COLORS = ""
} }

View file

@ -13,6 +13,7 @@ import androidx.navigation.compose.rememberNavController
import helium314.keyboard.settings.screens.AboutScreen import helium314.keyboard.settings.screens.AboutScreen
import helium314.keyboard.settings.screens.AdvancedSettingsScreen import helium314.keyboard.settings.screens.AdvancedSettingsScreen
import helium314.keyboard.settings.screens.AppearanceScreen import helium314.keyboard.settings.screens.AppearanceScreen
import helium314.keyboard.settings.screens.ColorsScreen
import helium314.keyboard.settings.screens.DebugScreen import helium314.keyboard.settings.screens.DebugScreen
import helium314.keyboard.settings.screens.GestureTypingScreen import helium314.keyboard.settings.screens.GestureTypingScreen
import helium314.keyboard.settings.screens.MainSettingsScreen import helium314.keyboard.settings.screens.MainSettingsScreen
@ -95,16 +96,10 @@ fun SettingsNavHost(
// ) // )
} }
composable(SettingsDestination.Colors) { composable(SettingsDestination.Colors) {
// ColorsScreen( ColorsScreen(isNight = false, onClickBack = ::goBack)
// night = false,
// onClickBack = ::goBack
// )
} }
composable(SettingsDestination.ColorsNight) { composable(SettingsDestination.ColorsNight) {
// ColorsScreen( ColorsScreen(isNight = true, onClickBack = ::goBack)
// night = true,
// onClickBack = ::goBack
// )
} }
} }
} }

View file

@ -3,10 +3,10 @@ package helium314.keyboard.settings.dialogs
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
@ -17,21 +17,19 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.skydoves.colorpicker.compose.AlphaSlider import com.github.skydoves.colorpicker.compose.AlphaSlider
import com.github.skydoves.colorpicker.compose.BrightnessSlider import com.github.skydoves.colorpicker.compose.BrightnessSlider
import com.github.skydoves.colorpicker.compose.ColorEnvelope
import com.github.skydoves.colorpicker.compose.HsvColorPicker import com.github.skydoves.colorpicker.compose.HsvColorPicker
import com.github.skydoves.colorpicker.compose.rememberColorPickerController import com.github.skydoves.colorpicker.compose.rememberColorPickerController
// todo:
// setting from text doesn't work
// weird effect on start, did this start with the top row showing colors?
// text field doesn't look nice
// for initial color picks performance is not good
@Composable @Composable
fun ColorPickerDialog( fun ColorPickerDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
@ -40,8 +38,17 @@ fun ColorPickerDialog(
onConfirmed: (Int) -> Unit, onConfirmed: (Int) -> Unit,
) { ) {
val controller = rememberColorPickerController() val controller = rememberColorPickerController()
val wheelPaint = Paint().apply {
alpha = 0.5f
style = PaintingStyle.Stroke
strokeWidth = 5f
color = Color.White
}
controller.wheelPaint = wheelPaint
val barHeight = 35.dp val barHeight = 35.dp
var value by remember { mutableStateOf(TextFieldValue(initialColor.toString(16))) } val initialString = initialColor.toUInt().toString(16)
var textValue by remember { mutableStateOf(TextFieldValue(initialString, TextRange(initialString.length))) }
var currentColor by remember { mutableStateOf(Color(initialColor)) }
ThreeButtonAlertDialog( ThreeButtonAlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onConfirmed = { onConfirmed(controller.selectedColor.value.toArgb()) }, onConfirmed = { onConfirmed(controller.selectedColor.value.toArgb()) },
@ -56,7 +63,7 @@ fun ColorPickerDialog(
.height(barHeight)) .height(barHeight))
{ } { }
Surface( Surface(
color = controller.selectedColor.value, color = currentColor,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
.padding(end = 10.dp) .padding(end = 10.dp)
.height(barHeight)) .height(barHeight))
@ -65,13 +72,15 @@ fun ColorPickerDialog(
HsvColorPicker( HsvColorPicker(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight(0.6f) .height(300.dp)
.padding(10.dp), .padding(10.dp),
controller = controller, controller = controller,
onColorChanged = { colorEnvelope: ColorEnvelope -> onColorChanged = {
value = TextFieldValue(colorEnvelope.hexCode) if (it.fromUser)
textValue = TextFieldValue(it.hexCode, selection = TextRange(it.hexCode.length))
currentColor = it.color
}, },
initialColor = Color(initialColor) initialColor = Color(initialColor),
) )
AlphaSlider( AlphaSlider(
modifier = Modifier modifier = Modifier
@ -79,6 +88,8 @@ fun ColorPickerDialog(
.padding(10.dp) .padding(10.dp)
.height(barHeight), .height(barHeight),
controller = controller, controller = controller,
initialColor = Color(initialColor),
wheelPaint = wheelPaint
) )
BrightnessSlider( BrightnessSlider(
modifier = Modifier modifier = Modifier
@ -86,13 +97,18 @@ fun ColorPickerDialog(
.padding(10.dp) .padding(10.dp)
.height(barHeight), .height(barHeight),
controller = controller, controller = controller,
initialColor = Color(initialColor),
wheelPaint = wheelPaint
) )
TextField( TextField(
value = value, value = textValue,
// KeyboardType.Password is a crappy way of avoiding suggestions... is there really no way in compose?
keyboardOptions = KeyboardOptions(autoCorrectEnabled = false, keyboardType = KeyboardType.Password),
onValueChange = { onValueChange = {
val androidColor = kotlin.runCatching { android.graphics.Color.parseColor("#$it") }.getOrNull() textValue = it
val androidColor = kotlin.runCatching { android.graphics.Color.parseColor("#${it.text}") }.getOrNull()
if (androidColor != null) if (androidColor != null)
controller.selectByColor(Color(androidColor), true) controller.selectByColor(Color(androidColor), false)
} }
) )
} }
@ -103,5 +119,5 @@ fun ColorPickerDialog(
@Preview @Preview
@Composable @Composable
private fun Preview() { private fun Preview() {
ColorPickerDialog({}, android.graphics.Color.MAGENTA, "color name", {}) ColorPickerDialog({}, -0x0f4488aa, "color name", {})
} }

View file

@ -0,0 +1,185 @@
package helium314.keyboard.settings.dialogs
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
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
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.R
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.getStringResourceOrName
import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.Setting
import helium314.keyboard.settings.SettingsDestination
import helium314.keyboard.settings.keyboardNeedsReload
// specialized variant of ListPickerDialog
@Composable
fun ColorThemePickerDialog(
onDismissRequest: () -> Unit,
setting: Setting,
isNight: Boolean,
default: String
) {
val ctx = LocalContext.current
val prefs = ctx.prefs()
val defaultColors = KeyboardTheme.getAvailableDefaultColors(prefs, false)
// prefs.all is null in preview only
val userColors = (prefs.all ?: mapOf(Settings.PREF_USER_COLORS_PREFIX + "usercolor" to "") ).keys.mapNotNull {
when {
it.startsWith(Settings.PREF_USER_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_COLORS_PREFIX)
it.startsWith(Settings.PREF_USER_ALL_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_ALL_COLORS_PREFIX)
else -> null
}
}.toSortedSet() // we don't want duplicates, and we want a consistent order
val selectedColor = prefs.getString(setting.key, default)!!
if (selectedColor !in defaultColors)
userColors.add(selectedColor) // there are cases where we have no settings for a user theme
val colors = defaultColors + userColors + ""
val state = rememberLazyListState()
LaunchedEffect(selectedColor) {
val index = colors.indexOf(selectedColor)
if (index != -1) state.scrollToItem(index, -state.layoutInfo.viewportSize.height / 3)
}
ThreeButtonAlertDialog(
onDismissRequest = onDismissRequest,
cancelButtonText = stringResource(R.string.dialog_close),
onConfirmed = { },
confirmButtonText = null,
// neutralButtonText = stringResource(R.string.load),
onNeutral = {
// todo: launcher to select file
// when importing make sure name is not in use
},
title = { Text(setting.title) },
text = {
CompositionLocalProvider(
LocalTextStyle provides MaterialTheme.typography.bodyLarge
) {
LazyColumn(state = state) {
items(colors) { item ->
if (item == "") {
var textValue by remember { mutableStateOf(TextFieldValue()) }
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(painterResource(R.drawable.ic_plus), stringResource(R.string.add)) // todo: should it be a button?
TextField(
value = textValue,
onValueChange = { textValue = it },
modifier = Modifier.weight(1f),
singleLine = true
)
IconButton(
enabled = textValue.text.isNotEmpty() && textValue.text !in userColors,
onClick = {
onDismissRequest()
prefs.edit().putString(setting.key, textValue.text).apply()
if (isNight) SettingsDestination.navigateTo(SettingsDestination.ColorsNight)
else SettingsDestination.navigateTo(SettingsDestination.Colors)
keyboardNeedsReload = true
}
) { Icon(painterResource(R.drawable.ic_edit), null) }
}
} else {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable {
onDismissRequest()
prefs.edit().putString(setting.key, item).apply()
keyboardNeedsReload = true
}
.padding(start = 6.dp)
.heightIn(min = 40.dp)
) {
RadioButton(
selected = selectedColor == item,
onClick = {
onDismissRequest()
prefs.edit().putString(setting.key, item).apply()
keyboardNeedsReload = true
}
)
Text(
text = item.getStringResourceOrName("theme_name_", ctx),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f),
)
if (item in userColors) {
var showDialog by remember { mutableStateOf(false) }
IconButton(
onClick = { showDialog = true }
) { Icon(painterResource(R.drawable.ic_bin), null) }
IconButton(
onClick = {
onDismissRequest()
prefs.edit().putString(setting.key, item).apply()
if (isNight) SettingsDestination.navigateTo(SettingsDestination.ColorsNight)
else SettingsDestination.navigateTo(SettingsDestination.Colors)
keyboardNeedsReload = true
}
) { Icon(painterResource(R.drawable.ic_edit), null) }
if (showDialog)
ConfirmationDialog(
onDismissRequest = { showDialog = false },
text = { Text(stringResource(R.string.delete_confirmation, item)) },
onConfirmed = {
onDismissRequest()
prefs.edit().remove(Settings.PREF_USER_COLORS_PREFIX + item)
.remove(Settings.PREF_USER_ALL_COLORS_PREFIX + item)
.remove(Settings.PREF_USER_MORE_COLORS_PREFIX + item).apply()
if (item == selectedColor)
prefs.edit().remove(setting.key).apply()
keyboardNeedsReload = true
}
)
}
}
}
}
}
}
},
)
}
@Preview
@Composable
private fun PreviewListPickerDialog() {
ColorThemePickerDialog(
onDismissRequest = {},
setting = Setting(LocalContext.current, "", R.string.settings) {},
default = "dark",
isNight = true
)
}

View file

@ -86,7 +86,6 @@ fun <T: Any> ListPickerDialog(
) )
Text( Text(
text = getItemName(item), text = getItemName(item),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
} }

View file

@ -17,15 +17,12 @@ import androidx.compose.ui.tooling.preview.Preview
import helium314.keyboard.keyboard.KeyboardSwitcher import helium314.keyboard.keyboard.KeyboardSwitcher
import helium314.keyboard.keyboard.KeyboardTheme import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.R import helium314.keyboard.latin.R
import helium314.keyboard.latin.settings.ColorsNightSettingsFragment
import helium314.keyboard.latin.settings.ColorsSettingsFragment
import helium314.keyboard.latin.settings.Defaults import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.getActivity import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.getStringResourceOrName import helium314.keyboard.latin.utils.getStringResourceOrName
import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.latin.utils.switchTo
import helium314.keyboard.settings.SettingsContainer import helium314.keyboard.settings.SettingsContainer
import helium314.keyboard.settings.preferences.ListPreference import helium314.keyboard.settings.preferences.ListPreference
import helium314.keyboard.settings.SettingsWithoutKey import helium314.keyboard.settings.SettingsWithoutKey
@ -36,6 +33,7 @@ import helium314.keyboard.settings.SettingsActivity
import helium314.keyboard.settings.preferences.SliderPreference import helium314.keyboard.settings.preferences.SliderPreference
import helium314.keyboard.settings.preferences.SwitchPreference import helium314.keyboard.settings.preferences.SwitchPreference
import helium314.keyboard.settings.Theme import helium314.keyboard.settings.Theme
import helium314.keyboard.settings.dialogs.ColorThemePickerDialog
import helium314.keyboard.settings.dialogs.CustomizeIconsDialog import helium314.keyboard.settings.dialogs.CustomizeIconsDialog
import helium314.keyboard.settings.dialogs.TextInputDialog import helium314.keyboard.settings.dialogs.TextInputDialog
import helium314.keyboard.settings.keyboardNeedsReload import helium314.keyboard.settings.keyboardNeedsReload
@ -58,14 +56,10 @@ fun AppearanceScreen(
Settings.PREF_ICON_STYLE, Settings.PREF_ICON_STYLE,
Settings.PREF_CUSTOM_ICON_NAMES, Settings.PREF_CUSTOM_ICON_NAMES,
Settings.PREF_THEME_COLORS, Settings.PREF_THEME_COLORS,
// if (prefs.getString(Settings.PREF_THEME_COLORS, Defaults.PREF_THEME_COLORS) == KeyboardTheme.THEME_USER)
// SettingsWithoutKey.ADJUST_COLORS else null, // todo: remove, this should be part of the color selection menu
Settings.PREF_THEME_KEY_BORDERS, Settings.PREF_THEME_KEY_BORDERS,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
Settings.PREF_THEME_DAY_NIGHT else null, Settings.PREF_THEME_DAY_NIGHT else null,
if (dayNightMode) Settings.PREF_THEME_COLORS_NIGHT else null, if (dayNightMode) Settings.PREF_THEME_COLORS_NIGHT else null,
// if (dayNightMode && prefs.getString(Settings.PREF_THEME_COLORS_NIGHT, Defaults.PREF_THEME_COLORS_NIGHT) == KeyboardTheme.THEME_USER_NIGHT)
// SettingsWithoutKey.ADJUST_COLORS_NIGHT else null, // todo: remove, this should be part of the color selection menu
Settings.PREF_NAVBAR_COLOR, Settings.PREF_NAVBAR_COLOR,
SettingsWithoutKey.BACKGROUND_IMAGE, SettingsWithoutKey.BACKGROUND_IMAGE,
SettingsWithoutKey.BACKGROUND_IMAGE_LANDSCAPE, SettingsWithoutKey.BACKGROUND_IMAGE_LANDSCAPE,
@ -139,54 +133,44 @@ fun createAppearanceSettings(context: Context) = listOf(
}, },
Setting(context, Settings.PREF_THEME_COLORS, R.string.theme_colors) { setting -> Setting(context, Settings.PREF_THEME_COLORS, R.string.theme_colors) { setting ->
val ctx = LocalContext.current val ctx = LocalContext.current
val prefs = ctx.prefs()
val b = (ctx.getActivity() as? SettingsActivity)?.prefChanged?.collectAsState() val b = (ctx.getActivity() as? SettingsActivity)?.prefChanged?.collectAsState()
if ((b?.value ?: 0) < 0) if ((b?.value ?: 0) < 0)
Log.v("irrelevant", "stupid way to trigger recomposition on preference change") Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
val items = KeyboardTheme.getAvailableColors(ctx.prefs(), false).map { var showDialog by rememberSaveable { mutableStateOf(false) }
it.getStringResourceOrName("theme_name_", ctx) to it Preference(
} name = setting.title,
ListPreference( description = prefs.getString(setting.key, Defaults.PREF_THEME_COLORS)!!.getStringResourceOrName("theme_name_", ctx),
setting, onClick = { showDialog = true }
items, )
Defaults.PREF_THEME_COLORS if (showDialog)
) { keyboardNeedsReload = true } ColorThemePickerDialog(
onDismissRequest = { showDialog = false },
setting = setting,
isNight = false,
default = Defaults.PREF_THEME_COLORS
)
}, },
Setting(context, Settings.PREF_THEME_COLORS_NIGHT, R.string.theme_colors_night) { setting -> Setting(context, Settings.PREF_THEME_COLORS_NIGHT, R.string.theme_colors_night) { setting ->
val ctx = LocalContext.current val ctx = LocalContext.current
val b = (ctx.getActivity() as? SettingsActivity)?.prefChanged?.collectAsState() val b = (ctx.getActivity() as? SettingsActivity)?.prefChanged?.collectAsState()
val prefs = ctx.prefs()
if ((b?.value ?: 0) < 0) if ((b?.value ?: 0) < 0)
Log.v("irrelevant", "stupid way to trigger recomposition on preference change") Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
val items = KeyboardTheme.getAvailableColors(ctx.prefs(), true).map { var showDialog by rememberSaveable { mutableStateOf(false) }
it.getStringResourceOrName("theme_name_", ctx) to it
}
ListPreference(
setting,
items,
Defaults.PREF_THEME_COLORS_NIGHT
) { keyboardNeedsReload = true }
},
/* Setting(context, SettingsWithoutKey.ADJUST_COLORS, R.string.select_user_colors, R.string.select_user_colors_summary) {
val ctx = LocalContext.current
Preference( Preference(
name = it.title, name = setting.title,
description = it.description, description = prefs.getString(setting.key, Defaults.PREF_THEME_COLORS_NIGHT)!!.getStringResourceOrName("theme_name_", ctx),
onClick = { onClick = { showDialog = true }
ctx.getActivity()?.switchTo(ColorsSettingsFragment()) )
//SettingsDestination.navigateTo(SettingsDestination.Colors) todo: soon if (showDialog)
} ColorThemePickerDialog(
onDismissRequest = { showDialog = false },
setting = setting,
isNight = true,
default = Defaults.PREF_THEME_COLORS_NIGHT
) )
}, },
Setting(context, SettingsWithoutKey.ADJUST_COLORS_NIGHT, R.string.select_user_colors_night, R.string.select_user_colors_summary) {
val ctx = LocalContext.current
Preference(
name = it.title,
description = it.description,
onClick = {
ctx.getActivity()?.switchTo(ColorsNightSettingsFragment())
//SettingsDestination.navigateTo(SettingsDestination.ColorsNight) todo: soon
}
)
},*/
Setting(context, Settings.PREF_THEME_KEY_BORDERS, R.string.key_borders) { Setting(context, Settings.PREF_THEME_KEY_BORDERS, R.string.key_borders) {
SwitchPreference(it, Defaults.PREF_THEME_KEY_BORDERS) SwitchPreference(it, Defaults.PREF_THEME_KEY_BORDERS)
}, },

View file

@ -1,34 +1,161 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
package helium314.keyboard.settings.screens package helium314.keyboard.settings.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue 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.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import helium314.keyboard.keyboard.ColorSetting
import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.R import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.common.default
import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.settings.colorPrefsAndResIds
import helium314.keyboard.latin.settings.getColorPrefsToHideInitially
import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.SearchScreen import helium314.keyboard.settings.SearchScreen
import helium314.keyboard.settings.SettingsActivity
import helium314.keyboard.settings.Theme
import helium314.keyboard.settings.dialogs.ColorPickerDialog
import helium314.keyboard.settings.keyboardNeedsReload
@Composable @Composable
fun ColorsScreen( fun ColorsScreen(
night: Boolean, isNight: Boolean,
onClickBack: () -> Unit onClickBack: () -> Unit
) { ) {
// todo:
// allow switching moreColors (three dot menu? dropdown / spinner for visibility?)
// allow save (load should be in theme selector, maybe here too)
// import/export should now also store theme name
// handle name collisions on load by simply appending a number
// allow editing theme name
// make sure import of old colors works
var availableColors by remember { mutableStateOf(emptyList<ColorSetting>()) } // todo (later): type? val ctx = LocalContext.current
// todo (later): save / load / type selection here? or in ... menu as previously? val prefs = ctx.prefs()
val b = (ctx.getActivity() as? SettingsActivity)?.prefChanged?.collectAsState()
if ((b?.value ?: 0) < 0)
Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
val themeName = if (isNight) prefs.getString(Settings.PREF_THEME_COLORS_NIGHT, Defaults.PREF_THEME_COLORS_NIGHT)!!
else prefs.getString(Settings.PREF_THEME_COLORS, Defaults.PREF_THEME_COLORS)!!
val moreColors = KeyboardTheme.readUserMoreColors(prefs, themeName)
val userColors = KeyboardTheme.readUserColors(prefs, themeName)
val shownColors = if (moreColors == 2) {
val allColors = KeyboardTheme.readUserAllColors(prefs, themeName)
ColorType.entries.map {
ColorSetting(it.name, null, allColors[it] ?: it.default())
}
} else {
val toDisplay = colorPrefsAndResIds.map { (colorName, resId) ->
val cs = userColors.firstOrNull { it.name == colorName } ?: ColorSetting(colorName, true, null)
cs.displayName = stringResource(resId)
cs
}
val colorsToHide = getColorPrefsToHideInitially(prefs)
if (moreColors == 1) toDisplay
else toDisplay.filter { it.color != null || it.name !in colorsToHide }
}
fun ColorSetting.displayColor() = if (auto == true) KeyboardTheme.determineUserColor(userColors, ctx, name, isNight)
else color ?: KeyboardTheme.determineUserColor(userColors, ctx, name, isNight)
var chosenColor: ColorSetting? by remember { mutableStateOf(null) }
SearchScreen( SearchScreen(
title = stringResource(if (night) R.string.select_user_colors_night else R.string.select_user_colors), title = themeName,
onClickBack = onClickBack, onClickBack = onClickBack,
filteredItems = { search -> availableColors.filter { it.displayName.contains(search, true) } }, filteredItems = { search -> shownColors.filter {
itemContent = { } it.displayName.split(" ", "_").any { it.startsWith(search, true) }
} },
itemContent = { colorSetting ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.clickable { chosenColor = colorSetting }
) {
Spacer(
modifier = Modifier
.background(Color(colorSetting.displayColor()), shape = CircleShape)
.size(50.dp)
) )
Column(Modifier.weight(1f).padding(horizontal = 16.dp)) {
Text(colorSetting.displayName)
if (colorSetting.auto == true)
CompositionLocalProvider(
LocalTextStyle provides MaterialTheme.typography.bodyMedium,
LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant
) {
Text(stringResource(R.string.auto_user_color))
}
}
if (colorSetting.auto != null)
Switch(colorSetting.auto, onCheckedChange = {
val oldUserColors = KeyboardTheme.readUserColors(prefs, themeName)
val newUserColors = (oldUserColors + ColorSetting(colorSetting.name, it, colorSetting.color))
.reversed().distinctBy { it.displayName }
KeyboardTheme.writeUserColors(prefs, themeName, newUserColors)
keyboardNeedsReload = true
})
}
}
)
if (chosenColor != null) {
val color = chosenColor!!
ColorPickerDialog(
onDismissRequest = { chosenColor = null },
initialColor = color.displayColor(),
title = color.displayName,
) {
if (moreColors == 2) {
val oldColors = KeyboardTheme.readUserAllColors(prefs, themeName)
oldColors[ColorType.valueOf(color.name)] = it
KeyboardTheme.writeUserAllColors(prefs, themeName, oldColors)
} else {
val oldUserColors = KeyboardTheme.readUserColors(prefs, themeName)
val newUserColors = (oldUserColors + ColorSetting(color.name, false, it))
.reversed().distinctBy { it.displayName }
KeyboardTheme.writeUserColors(prefs, themeName, newUserColors)
}
keyboardNeedsReload = true
}
}
} }
private class ColorSetting( @Preview
val key: String, // old, this should go away @Composable
val displayName: String, private fun Preview() {
var auto: Boolean, // not for all Theme(true) {
var color: Int Surface {
) ColorsScreen(false) { }
}
}
}

View file

@ -45,7 +45,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:src="@drawable/ic_user_dictionary_edit" android:src="@drawable/ic_edit"
android:contentDescription="@null"> android:contentDescription="@null">
</ImageView> </ImageView>

View file

@ -987,4 +987,6 @@ New dictionary:
<string name="customize_icons">Customize icons</string> <string name="customize_icons">Customize icons</string>
<!-- Confirmation message when resetting all custom icons --> <!-- Confirmation message when resetting all custom icons -->
<string name="customize_icons_reset_message">Really reset all customized icons?</string> <string name="customize_icons_reset_message">Really reset all customized icons?</string>
<!-- Confirmation message when deleting a something (used for custom colors) -->
<string name="delete_confirmation">Really delete %s?</string>
</resources> </resources>