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
targetSdk = 34
versionCode = 2302
versionName = "2.3"
versionName = "2.3+dev1"
ndk {
abiFilters.clear()
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.isGoodContrast
import helium314.keyboard.latin.utils.prefs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
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_SAND = "sand"
const val THEME_VIOLETTE = "violette"
fun getAvailableColors(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 (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_CHOCOLATE, THEME_CLOUDY, THEME_FOREST, if (!isNight) THEME_INDIGO else null, if (!isNight) THEME_PINK else null,
THEME_OCEAN, if (!isNight) THEME_SAND else null, THEME_VIOLETTE
) + prefs.all.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
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 (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_CHOCOLATE,
THEME_CLOUDY,
THEME_FOREST,
if (!isNight) THEME_INDIGO else null,
if (!isNight) THEME_PINK else null,
THEME_OCEAN,
if (!isNight) THEME_SAND else null,
THEME_VIOLETTE
)
val STYLES = arrayOf(STYLE_MATERIAL, STYLE_HOLO, STYLE_ROUNDED)
// 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 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()
}
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
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
}
fun determineUserColor(colors: Map<String, Pair<Int?, Boolean>>, context: Context, colorName: String, isNight: Boolean): Int {
val (color, auto) = colors[colorName] ?: (null to true)
fun determineUserColor(colors: List<ColorSetting>, context: Context, colorName: String, isNight: Boolean): Int {
val c = colors.firstOrNull { it.name == colorName }
val color = c?.color
val auto = c?.auto ?: true
return if (auto || color == null)
determineAutoColor(colors, colorName, isNight, context)
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) {
COLOR_ACCENT -> {
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.content.Context
import androidx.core.content.edit
import helium314.keyboard.keyboard.ColorSetting
import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
@ -167,14 +168,14 @@ fun checkVersionUpgrade(context: Context) {
}
// day colors
val themeNameDay = context.getString(R.string.theme_name_user)
val colorsDay = colorPrefsAndResIds.associate {
val colorsDay = colorPrefsAndResIds.map {
val pref = "theme_color_" + it.first
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()
result
}
if (colorsDay.any { it.value.first != null }) {
if (colorsDay.any { it.color != null }) {
KeyboardTheme.writeUserColors(prefs, themeNameDay, colorsDay)
}
val moreColorsDay = prefs.getInt("theme_color_show_more_colors", 0)
@ -190,14 +191,14 @@ fun checkVersionUpgrade(context: Context) {
// same for night colors
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 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()
result
}
if (colorsNight.any { it.value.first != null }) {
if (colorsNight.any { it.color!= null }) {
KeyboardTheme.writeUserColors(prefs, themeNameNight, colorsNight)
}
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_FORCE_NON_DISTINCT_MULTITOUCH = false
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_ALL_COLORS = ""
}

View file

@ -13,6 +13,7 @@ import androidx.navigation.compose.rememberNavController
import helium314.keyboard.settings.screens.AboutScreen
import helium314.keyboard.settings.screens.AdvancedSettingsScreen
import helium314.keyboard.settings.screens.AppearanceScreen
import helium314.keyboard.settings.screens.ColorsScreen
import helium314.keyboard.settings.screens.DebugScreen
import helium314.keyboard.settings.screens.GestureTypingScreen
import helium314.keyboard.settings.screens.MainSettingsScreen
@ -95,16 +96,10 @@ fun SettingsNavHost(
// )
}
composable(SettingsDestination.Colors) {
// ColorsScreen(
// night = false,
// onClickBack = ::goBack
// )
ColorsScreen(isNight = false, onClickBack = ::goBack)
}
composable(SettingsDestination.ColorsNight) {
// ColorsScreen(
// night = true,
// onClickBack = ::goBack
// )
ColorsScreen(isNight = 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.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
@ -17,21 +17,19 @@ 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.Paint
import androidx.compose.ui.graphics.PaintingStyle
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.github.skydoves.colorpicker.compose.AlphaSlider
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.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
fun ColorPickerDialog(
onDismissRequest: () -> Unit,
@ -40,8 +38,17 @@ fun ColorPickerDialog(
onConfirmed: (Int) -> Unit,
) {
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
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(
onDismissRequest = onDismissRequest,
onConfirmed = { onConfirmed(controller.selectedColor.value.toArgb()) },
@ -56,7 +63,7 @@ fun ColorPickerDialog(
.height(barHeight))
{ }
Surface(
color = controller.selectedColor.value,
color = currentColor,
modifier = Modifier.fillMaxWidth()
.padding(end = 10.dp)
.height(barHeight))
@ -65,13 +72,15 @@ fun ColorPickerDialog(
HsvColorPicker(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.6f)
.height(300.dp)
.padding(10.dp),
controller = controller,
onColorChanged = { colorEnvelope: ColorEnvelope ->
value = TextFieldValue(colorEnvelope.hexCode)
onColorChanged = {
if (it.fromUser)
textValue = TextFieldValue(it.hexCode, selection = TextRange(it.hexCode.length))
currentColor = it.color
},
initialColor = Color(initialColor)
initialColor = Color(initialColor),
)
AlphaSlider(
modifier = Modifier
@ -79,6 +88,8 @@ fun ColorPickerDialog(
.padding(10.dp)
.height(barHeight),
controller = controller,
initialColor = Color(initialColor),
wheelPaint = wheelPaint
)
BrightnessSlider(
modifier = Modifier
@ -86,13 +97,18 @@ fun ColorPickerDialog(
.padding(10.dp)
.height(barHeight),
controller = controller,
initialColor = Color(initialColor),
wheelPaint = wheelPaint
)
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 = {
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)
controller.selectByColor(Color(androidColor), true)
controller.selectByColor(Color(androidColor), false)
}
)
}
@ -103,5 +119,5 @@ fun ColorPickerDialog(
@Preview
@Composable
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 = getItemName(item),
style = MaterialTheme.typography.bodyLarge,
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.KeyboardTheme
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.Settings
import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.getStringResourceOrName
import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.latin.utils.switchTo
import helium314.keyboard.settings.SettingsContainer
import helium314.keyboard.settings.preferences.ListPreference
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.SwitchPreference
import helium314.keyboard.settings.Theme
import helium314.keyboard.settings.dialogs.ColorThemePickerDialog
import helium314.keyboard.settings.dialogs.CustomizeIconsDialog
import helium314.keyboard.settings.dialogs.TextInputDialog
import helium314.keyboard.settings.keyboardNeedsReload
@ -58,14 +56,10 @@ fun AppearanceScreen(
Settings.PREF_ICON_STYLE,
Settings.PREF_CUSTOM_ICON_NAMES,
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,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
Settings.PREF_THEME_DAY_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,
SettingsWithoutKey.BACKGROUND_IMAGE,
SettingsWithoutKey.BACKGROUND_IMAGE_LANDSCAPE,
@ -139,54 +133,44 @@ fun createAppearanceSettings(context: Context) = listOf(
},
Setting(context, Settings.PREF_THEME_COLORS, R.string.theme_colors) { setting ->
val ctx = LocalContext.current
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 items = KeyboardTheme.getAvailableColors(ctx.prefs(), false).map {
it.getStringResourceOrName("theme_name_", ctx) to it
}
ListPreference(
setting,
items,
Defaults.PREF_THEME_COLORS
) { keyboardNeedsReload = true }
var showDialog by rememberSaveable { mutableStateOf(false) }
Preference(
name = setting.title,
description = prefs.getString(setting.key, Defaults.PREF_THEME_COLORS)!!.getStringResourceOrName("theme_name_", ctx),
onClick = { showDialog = true }
)
if (showDialog)
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 ->
val ctx = LocalContext.current
val b = (ctx.getActivity() as? SettingsActivity)?.prefChanged?.collectAsState()
val prefs = ctx.prefs()
if ((b?.value ?: 0) < 0)
Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
val items = KeyboardTheme.getAvailableColors(ctx.prefs(), true).map {
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
var showDialog by rememberSaveable { mutableStateOf(false) }
Preference(
name = it.title,
description = it.description,
onClick = {
ctx.getActivity()?.switchTo(ColorsSettingsFragment())
//SettingsDestination.navigateTo(SettingsDestination.Colors) todo: soon
}
name = setting.title,
description = prefs.getString(setting.key, Defaults.PREF_THEME_COLORS_NIGHT)!!.getStringResourceOrName("theme_name_", ctx),
onClick = { showDialog = true }
)
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) {
SwitchPreference(it, Defaults.PREF_THEME_KEY_BORDERS)
},

View file

@ -1,34 +1,161 @@
// SPDX-License-Identifier: GPL-3.0-only
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.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.graphics.Color
import androidx.compose.ui.platform.LocalContext
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.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.SettingsActivity
import helium314.keyboard.settings.Theme
import helium314.keyboard.settings.dialogs.ColorPickerDialog
import helium314.keyboard.settings.keyboardNeedsReload
@Composable
fun ColorsScreen(
night: Boolean,
isNight: Boolean,
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?
// todo (later): save / load / type selection here? or in ... menu as previously?
val ctx = LocalContext.current
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(
title = stringResource(if (night) R.string.select_user_colors_night else R.string.select_user_colors),
title = themeName,
onClickBack = onClickBack,
filteredItems = { search -> availableColors.filter { it.displayName.contains(search, true) } },
itemContent = { }
filteredItems = { search -> shownColors.filter {
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(
val key: String, // old, this should go away
val displayName: String,
var auto: Boolean, // not for all
var color: Int
)
@Preview
@Composable
private fun Preview() {
Theme(true) {
Surface {
ColorsScreen(false) { }
}
}
}

View file

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

View file

@ -987,4 +987,6 @@ New dictionary:
<string name="customize_icons">Customize icons</string>
<!-- Confirmation message when resetting all custom icons -->
<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>