change how colors are stored (color and appearance settings currently not working properly)

with settings upgrade from previous version
version code increase to make sure colors are upgraded
This commit is contained in:
Helium314 2025-02-10 18:39:16 +01:00
parent 6dad54b83d
commit 5300c4d930
9 changed files with 290 additions and 223 deletions

View file

@ -13,7 +13,7 @@ android {
applicationId = "helium314.keyboard"
minSdk = 21
targetSdk = 34
versionCode = 2301
versionCode = 2302
versionName = "2.3"
ndk {
abiFilters.clear()

View file

@ -9,17 +9,23 @@ import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Build
import android.os.Build.VERSION_CODES
import android.util.TypedValue
import android.view.ContextThemeWrapper
import androidx.core.content.ContextCompat
import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.AllColors
import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.common.Colors
import helium314.keyboard.latin.common.DefaultColors
import helium314.keyboard.latin.common.DynamicColors
import helium314.keyboard.latin.common.readAllColorsMap
import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings
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.json.Json
import java.util.EnumMap
class KeyboardTheme // Note: The themeId should be aligned with "themeId" attribute of Keyboard style in values/themes-<style>.xml.
private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
@ -31,20 +37,18 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
}
companion object {
// old themes
// old themes, now called styles
const val STYLE_MATERIAL = "Material"
const val STYLE_HOLO = "Holo"
const val STYLE_ROUNDED = "Rounded"
// new themes using the custom colors
// new themes that are just colors
const val THEME_LIGHT = "light"
const val THEME_HOLO_WHITE = "holo_white"
const val THEME_DARK = "dark"
const val THEME_DARKER = "darker"
const val THEME_BLACK = "black"
const val THEME_DYNAMIC = "dynamic"
const val THEME_USER = "user"
const val THEME_USER_NIGHT = "user_night"
const val THEME_BLUE_GRAY = "blue_gray"
const val THEME_BROWN = "brown"
const val THEME_CHOCOLATE = "chocolate"
@ -55,15 +59,19 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
const val THEME_PINK = "pink"
const val THEME_SAND = "sand"
const val THEME_VIOLETTE = "violette"
val COLORS = listOfNotNull(
THEME_LIGHT, if (Build.VERSION.SDK_INT < VERSION_CODES.S) null else THEME_DYNAMIC, THEME_HOLO_WHITE, THEME_DARK,
THEME_DARKER, THEME_BLACK, THEME_BLUE_GRAY, THEME_BROWN, THEME_CHOCOLATE, THEME_CLOUDY, THEME_FOREST,
THEME_INDIGO, THEME_PINK, THEME_OCEAN, THEME_SAND, THEME_VIOLETTE, THEME_USER
)
val COLORS_DARK = listOfNotNull(
THEME_HOLO_WHITE, THEME_DARK, if (Build.VERSION.SDK_INT < VERSION_CODES.S) null else THEME_DYNAMIC,
THEME_DARKER, THEME_BLACK, THEME_CHOCOLATE, THEME_CLOUDY, THEME_FOREST, THEME_OCEAN, THEME_VIOLETTE, THEME_USER_NIGHT
)
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
val STYLES = arrayOf(STYLE_MATERIAL, STYLE_HOLO, STYLE_ROUNDED)
// These should be aligned with Keyboard.themeId and Keyboard.Case.keyboardTheme
@ -83,6 +91,18 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
KeyboardTheme(THEME_ID_ROUNDED_BASE_BORDER, R.style.KeyboardTheme_Rounded_Base_Border)
)
// named colors, with names from old settings
const val COLOR_ACCENT = "accent"
const val COLOR_GESTURE = "gesture"
const val COLOR_SUGGESTION_TEXT = "suggestion_text"
const val COLOR_TEXT = "text"
const val COLOR_HINT_TEXT = "hint_text"
const val COLOR_KEYS = "keys"
const val COLOR_FUNCTIONAL_KEYS = "functional_keys"
const val COLOR_SPACEBAR = "spacebar"
const val COLOR_SPACEBAR_TEXT = "spacebar_text"
const val COLOR_BACKGROUND = "background"
@JvmStatic
fun getKeyboardTheme(context: Context): KeyboardTheme {
val prefs = context.prefs()
@ -101,46 +121,12 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
}
@JvmStatic
fun getThemeColors(themeColors: String, themeStyle: String, context: Context, prefs: SharedPreferences, isNight: Boolean): Colors {
fun getThemeColors(themeName: String, themeStyle: String, context: Context, prefs: SharedPreferences, isNight: Boolean): Colors {
val hasBorders = prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, Defaults.PREF_THEME_KEY_BORDERS)
val backgroundImage = Settings.readUserBackgroundImage(context, isNight)
return when (themeColors) {
THEME_USER -> if (prefs.getInt(Settings.getColorPref(Settings.PREF_SHOW_MORE_COLORS, isNight), 0) == 2)
AllColors(readAllColorsMap(prefs, false), themeStyle, hasBorders, backgroundImage)
else DefaultColors(
themeStyle,
hasBorders,
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, false),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, false),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, false),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, false),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, false),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, false),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, false),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SUGGESTION_TEXT_SUFFIX, false),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX, false),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_GESTURE_SUFFIX, false),
keyboardBackground = backgroundImage
)
THEME_USER_NIGHT -> if (prefs.getInt(Settings.getColorPref(Settings.PREF_SHOW_MORE_COLORS, isNight), 0) == 2)
AllColors(readAllColorsMap(prefs, true), themeStyle, hasBorders, backgroundImage)
else DefaultColors(
themeStyle,
hasBorders,
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, true),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, true),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, true),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, true),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, true),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, true),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, true),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SUGGESTION_TEXT_SUFFIX, true),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX, true),
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_GESTURE_SUFFIX, true),
keyboardBackground = backgroundImage
)
return when (themeName) {
THEME_DYNAMIC -> {
if (Build.VERSION.SDK_INT >= VERSION_CODES.S) DynamicColors(context, themeStyle, hasBorders, backgroundImage)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) DynamicColors(context, themeStyle, hasBorders, backgroundImage)
else getThemeColors(THEME_LIGHT, themeStyle, context, prefs, isNight)
}
THEME_DARK -> DefaultColors(
@ -315,18 +301,128 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
Color.WHITE,
keyboardBackground = backgroundImage
)
else /* THEME_LIGHT */ -> DefaultColors(
themeStyle,
hasBorders,
ContextCompat.getColor(context, R.color.gesture_trail_color_lxx_light),
ContextCompat.getColor(context, R.color.keyboard_background_lxx_light_border),
ContextCompat.getColor(context, R.color.key_background_normal_lxx_light_border),
ContextCompat.getColor(context, R.color.key_background_functional_lxx_light_border),
ContextCompat.getColor(context, R.color.key_background_normal_lxx_light_border),
ContextCompat.getColor(context, R.color.key_text_color_lxx_light),
ContextCompat.getColor(context, R.color.key_hint_letter_color_lxx_light),
keyboardBackground = backgroundImage
else -> { // user-defined theme
if (readUserMoreColors(prefs, themeName) == 2)
AllColors(readUserAllColors(prefs, themeName), themeStyle, hasBorders, backgroundImage)
else {
val colors = readUserColors(prefs, themeName)
DefaultColors(
themeStyle,
hasBorders,
determineUserColor(colors, context, COLOR_ACCENT, false),
determineUserColor(colors, context, COLOR_BACKGROUND, false),
determineUserColor(colors, context, COLOR_KEYS, false),
determineUserColor(colors, context, COLOR_FUNCTIONAL_KEYS, false),
determineUserColor(colors, context, COLOR_SPACEBAR, false),
determineUserColor(colors, context, COLOR_TEXT, false),
determineUserColor(colors, context, COLOR_HINT_TEXT, false),
determineUserColor(colors, context, COLOR_SUGGESTION_TEXT, false),
determineUserColor(colors, context, COLOR_SPACEBAR_TEXT, false),
determineUserColor(colors, context, COLOR_GESTURE, false),
backgroundImage,
)
}
}
}
}
fun writeUserColors(prefs: SharedPreferences, themeName: String, colors: Map<String, Pair<Int?, Boolean>>) {
val key = Settings.PREF_USER_COLORS_PREFIX + themeName
val value = Json.encodeToString(colors.filterValues { it.first != null })
prefs.edit().putString(key, value).apply()
}
fun readUserColors(prefs: SharedPreferences, themeName: String): Map<String, Pair<Int, Boolean>> {
val key = Settings.PREF_USER_COLORS_PREFIX + themeName
return Json.decodeFromString(prefs.getString(key, Defaults.PREF_USER_COLORS)!!)
}
fun writeUserMoreColors(prefs: SharedPreferences, themeName: String, value: Int) {
val key = Settings.PREF_USER_MORE_COLORS_PREFIX + themeName
prefs.edit().putInt(key, value).apply()
}
fun readUserMoreColors(prefs: SharedPreferences, themeName: String): Int {
val key = Settings.PREF_USER_MORE_COLORS_PREFIX + themeName
return prefs.getInt(key, Defaults.PREF_USER_MORE_COLORS)
}
fun writeUserAllColors(prefs: SharedPreferences, themeName: String, colorMap: EnumMap<ColorType, Int>) {
val key = Settings.PREF_USER_ALL_COLORS_PREFIX + themeName
prefs.edit().putString(key, colorMap.map { "${it.key},${it.value}" }.joinToString(";")).apply()
}
fun readUserAllColors(prefs: SharedPreferences, themeName: String): EnumMap<ColorType, Int> {
val key = Settings.PREF_USER_ALL_COLORS_PREFIX + themeName
val colorsString = prefs.getString(key, Defaults.PREF_USER_ALL_COLORS)!!
val colorMap = EnumMap<ColorType, Int>(ColorType::class.java)
colorsString.split(";").forEach {
val ct = try {
ColorType.valueOf(it.substringBefore(",").uppercase())
} catch (_: Exception) { // todo: which one?
return@forEach
}
val i = it.substringAfter(",").toIntOrNull() ?: return@forEach
colorMap[ct] = i
}
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)
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 {
when (colorName) {
COLOR_ACCENT -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
// try determining accent color on Android 10 & 11, accent is not available in resources
val wrapper: Context = ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault)
val value = TypedValue()
if (wrapper.theme.resolveAttribute(android.R.attr.colorAccent, value, true)) return value.data
}
return ContextCompat.getColor(Settings.getDayNightContext(context, isNight), R.color.accent)
}
COLOR_GESTURE -> return determineUserColor(colors, context, COLOR_ACCENT, isNight)
COLOR_SUGGESTION_TEXT ->
return determineUserColor(colors, context, COLOR_TEXT, isNight)
COLOR_TEXT -> {
// base it on background color, and not key, because it's also used for suggestions
val background = determineUserColor(colors, context, COLOR_BACKGROUND, isNight)
return if (isBrightColor(background)) {
// but if key borders are enabled, we still want reasonable contrast
if (!context.prefs().getBoolean(Settings.PREF_THEME_KEY_BORDERS, Defaults.PREF_THEME_KEY_BORDERS)
|| isGoodContrast(Color.BLACK, determineUserColor(colors, context, COLOR_KEYS, isNight))
) Color.BLACK
else Color.GRAY
} else Color.WHITE
}
COLOR_HINT_TEXT -> {
return if (isBrightColor(determineUserColor(colors, context, COLOR_KEYS, isNight))) Color.DKGRAY
else determineUserColor(colors, context, COLOR_TEXT, isNight)
}
COLOR_KEYS ->
return brightenOrDarken(determineUserColor(colors, context, COLOR_BACKGROUND, isNight), isNight)
COLOR_FUNCTIONAL_KEYS ->
return brightenOrDarken(determineUserColor(colors, context, COLOR_KEYS, isNight), true)
COLOR_SPACEBAR -> return determineUserColor(colors, context, COLOR_KEYS, isNight)
COLOR_SPACEBAR_TEXT -> {
val spacebar = determineUserColor(colors, context, COLOR_SPACEBAR, isNight)
val hintText = determineUserColor(colors, context, COLOR_HINT_TEXT, isNight)
if (isGoodContrast(hintText, spacebar)) return hintText and -0x7f000001 // add some transparency
val text = determineUserColor(colors, context, COLOR_TEXT, isNight)
if (isGoodContrast(text, spacebar)) return text and -0x7f000001
return if (isBrightColor(spacebar)) Color.BLACK and -0x7f000001
else Color.WHITE and -0x7f000001
}
COLOR_BACKGROUND -> return ContextCompat.getColor(
Settings.getDayNightContext(context, isNight),
R.color.keyboard_background
)
else -> return ContextCompat.getColor(Settings.getDayNightContext(context, isNight), R.color.keyboard_background)
}
}
}

View file

@ -4,10 +4,13 @@ package helium314.keyboard.latin
import android.app.Application
import android.content.Context
import androidx.core.content.edit
import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX
import helium314.keyboard.latin.settings.colorPrefsAndResIds
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.latin.utils.ToolbarKey
@ -19,6 +22,7 @@ import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.latin.utils.protectedPrefs
import helium314.keyboard.latin.utils.upgradeToolbarPrefs
import java.io.File
import java.util.EnumMap
class App : Application() {
override fun onCreate() {
@ -97,13 +101,13 @@ fun checkVersionUpgrade(context: Context) {
prefs.edit { putBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, true) }
}
if (oldVersion <= 2100) {
if (prefs.contains(Settings.PREF_SHOW_MORE_COLORS)) {
val moreColors = prefs.getInt(Settings.PREF_SHOW_MORE_COLORS, 0)
if (prefs.contains("show_more_colors")) {
val moreColors = prefs.getInt("show_more_colors", 0)
prefs.edit {
putInt(Settings.getColorPref(Settings.PREF_SHOW_MORE_COLORS, false), moreColors)
putInt("theme_color_show_more_colors", moreColors)
if (prefs.getBoolean(Settings.PREF_THEME_DAY_NIGHT, false))
putInt(Settings.getColorPref(Settings.PREF_SHOW_MORE_COLORS, true), moreColors)
remove(Settings.PREF_SHOW_MORE_COLORS)
putInt("theme_dark_color_show_more_colors", moreColors)
remove("show_more_colors")
}
}
}
@ -144,6 +148,69 @@ fun checkVersionUpgrade(context: Context) {
it.renameTo(newFile)
}
}
if (oldVersion <= 2301) {
// upgrade and remove old color prefs
fun readAllColorsMap(isNight: Boolean): EnumMap<ColorType, Int> {
val prefPrefix = if (isNight) "theme_dark_color_" else "theme_color_"
val colorsString = prefs.getString(prefPrefix + "all_colors", "") ?: ""
val colorMap = EnumMap<ColorType, Int>(ColorType::class.java)
colorsString.split(";").forEach {
val ct = try {
ColorType.valueOf(it.substringBefore(",").uppercase())
} catch (_: Exception) {
return@forEach
}
val i = it.substringAfter(",").toIntOrNull() ?: return@forEach
colorMap[ct] = i
}
return colorMap
}
// day colors
val themeNameDay = context.getString(R.string.theme_name_user)
val colorsDay = colorPrefsAndResIds.associate {
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))
prefs.edit().remove(pref).remove(pref + "_auto").apply()
result
}
if (colorsDay.any { it.value.first != null }) {
KeyboardTheme.writeUserColors(prefs, themeNameDay, colorsDay)
}
val moreColorsDay = prefs.getInt("theme_color_show_more_colors", 0)
prefs.edit().remove("theme_color_show_more_colors").apply()
KeyboardTheme.writeUserMoreColors(prefs, themeNameDay, moreColorsDay)
if (prefs.contains("theme_color_all_colors")) {
val allColorsDay = readAllColorsMap(false)
prefs.edit().remove("theme_color_all_colors").apply()
KeyboardTheme.writeUserAllColors(prefs, themeNameDay, allColorsDay)
}
if (prefs.getString(Settings.PREF_THEME_COLORS, Defaults.PREF_THEME_COLORS) == "user")
prefs.edit().putString(Settings.PREF_THEME_COLORS, themeNameDay).apply()
// same for night colors
val themeNameNight = context.getString(R.string.theme_name_user_night)
val colorsNight = colorPrefsAndResIds.associate {
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))
prefs.edit().remove(pref).remove(pref + "_auto").apply()
result
}
if (colorsNight.any { it.value.first != null }) {
KeyboardTheme.writeUserColors(prefs, themeNameNight, colorsNight)
}
val moreColorsNight = prefs.getInt("theme_dark_color_show_more_colors", 0)
prefs.edit().remove("theme_dark_color_show_more_colors").apply()
KeyboardTheme.writeUserMoreColors(prefs, themeNameNight, moreColorsNight)
if (prefs.contains("theme_dark_color_all_colors")) {
val allColorsNight = readAllColorsMap(false)
prefs.edit().remove("theme_dark_color_all_colors").apply()
KeyboardTheme.writeUserAllColors(prefs, themeNameNight, allColorsNight)
}
if (prefs.getString(Settings.PREF_THEME_COLORS_NIGHT, Defaults.PREF_THEME_COLORS_NIGHT) == "user_night")
prefs.edit().putString(Settings.PREF_THEME_COLORS_NIGHT, themeNameNight).apply()
}
upgradeToolbarPrefs(prefs)
onCustomLayoutFileListChanged() // just to be sure
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }

View file

@ -3,7 +3,6 @@
package helium314.keyboard.latin.common
import android.content.Context
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.content.res.TypedArray
@ -19,7 +18,6 @@ import android.widget.ImageView
import androidx.annotation.ColorInt
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.graphics.ColorUtils
@ -29,7 +27,6 @@ import helium314.keyboard.keyboard.KeyboardTheme.Companion.STYLE_HOLO
import helium314.keyboard.keyboard.KeyboardTheme.Companion.STYLE_MATERIAL
import helium314.keyboard.latin.common.ColorType.*
import helium314.keyboard.latin.R
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.adjustLuminosityAndKeepAlpha
import helium314.keyboard.latin.utils.brighten
import helium314.keyboard.latin.utils.brightenOrDarken
@ -600,30 +597,6 @@ class AllColors(private val colorMap: EnumMap<ColorType, Int>, override val them
private fun getColorFilter(color: ColorType) = colorFilters.getOrPut(color) { colorFilter(get(color)) }
}
fun readAllColorsMap(prefs: SharedPreferences, isNight: Boolean): EnumMap<ColorType, Int> {
val prefPrefix = if (isNight) Settings.PREF_THEME_USER_COLOR_NIGHT_PREFIX else Settings.PREF_THEME_USER_COLOR_PREFIX
val colorsString = prefs.getString(prefPrefix + Settings.PREF_ALL_COLORS_SUFFIX, "") ?: ""
val colorMap = EnumMap<ColorType, Int>(ColorType::class.java)
colorsString.split(";").forEach {
val ct = try {
ColorType.valueOf(it.substringBefore(",").uppercase())
} catch (_: Exception) { // todo: which one?
return@forEach
}
val i = it.substringAfter(",").toIntOrNull() ?: return@forEach
colorMap[ct] = i
}
return colorMap
}
fun writeAllColorsMap(colorMap: EnumMap<ColorType, Int>, prefs: SharedPreferences, isNight: Boolean) {
val prefPrefix = if (isNight) Settings.PREF_THEME_USER_COLOR_NIGHT_PREFIX else Settings.PREF_THEME_USER_COLOR_PREFIX
prefs.edit { putString(
prefPrefix + Settings.PREF_ALL_COLORS_SUFFIX,
colorMap.map { "${it.key},${it.value}" }.joinToString(";")
) }
}
private fun colorFilter(color: Int, mode: BlendModeCompat = BlendModeCompat.MODULATE): ColorFilter {
// using !! for the color filter because null is only returned for unsupported blend modes, which are not used
return BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, mode)!!

View file

@ -168,7 +168,7 @@ class AppearanceSettingsFragment : SubScreenFragment() {
}
private fun setColorPrefs(style: String) {
colorsPref.apply {
/* colorsPref.apply {
entryValues = if (style == KeyboardTheme.STYLE_HOLO) KeyboardTheme.COLORS.toTypedArray()
else KeyboardTheme.COLORS.filterNot { it == KeyboardTheme.THEME_HOLO_WHITE }.toTypedArray()
entries = entryValues.getNamesFromResourcesIfAvailable("theme_name_")
@ -195,11 +195,11 @@ class AppearanceSettingsFragment : SubScreenFragment() {
userColorsPrefNight?.isVisible = value == KeyboardTheme.THEME_USER_NIGHT
true
}
}
}*/
}
private fun setupTheme() {
stylePref.apply {
/* stylePref.apply {
entryValues = KeyboardTheme.STYLES
entries = entryValues.getNamesFromResourcesIfAvailable("style_name_")
if (value !in entryValues)
@ -232,7 +232,7 @@ class AppearanceSettingsFragment : SubScreenFragment() {
}
colorsNightPref?.isVisible = dayNightPref?.isChecked == true
userColorsPref.isVisible = colorsPref.value == KeyboardTheme.THEME_USER
userColorsPrefNight?.isVisible = dayNightPref?.isChecked == true && colorsNightPref?.value == KeyboardTheme.THEME_USER_NIGHT
userColorsPrefNight?.isVisible = dayNightPref?.isChecked == true && colorsNightPref?.value == KeyboardTheme.THEME_USER_NIGHT*/
}
// performance is not good, but not bad enough to justify work

View file

@ -6,6 +6,7 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Bundle
import android.view.Menu
@ -30,28 +31,24 @@ import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import com.rarepebble.colorpicker.ColorPickerView
import helium314.keyboard.keyboard.KeyboardSwitcher
import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.R
import helium314.keyboard.latin.RichInputMethodManager
import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.common.default
import helium314.keyboard.latin.common.readAllColorsMap
import helium314.keyboard.latin.common.splitOnWhitespace
import helium314.keyboard.latin.common.writeAllColorsMap
import helium314.keyboard.latin.databinding.ColorSettingBinding
import helium314.keyboard.latin.databinding.ColorSettingsBinding
import helium314.keyboard.latin.utils.DeviceProtectedUtils
import helium314.keyboard.latin.utils.ExecutorUtils
import helium314.keyboard.latin.utils.ResourceUtils
import helium314.keyboard.latin.utils.infoDialog
import helium314.keyboard.latin.utils.prefs
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.EnumMap
open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvider {
/*
private val binding by viewBinding(ColorSettingsBinding::bind)
open val isNight = false
open val titleResId = R.string.select_user_colors
@ -109,7 +106,7 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
activity?.removeMenuProvider(this)
}
*/
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
menu.add(Menu.NONE, 0, Menu.NONE, R.string.main_colors)
menu.add(Menu.NONE, 1, Menu.NONE, R.string.more_colors)
@ -121,7 +118,7 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
// necessary, even though we only have a single menu item
// because the back arrow on top absurdly is implemented as a menu item
if (menuItem.itemId in 0..2) {
/* if (menuItem.itemId in 0..2) {
if (moreColors == menuItem.itemId) return true
if (moreColors == 2 || menuItem.itemId == 2) {
RichInputMethodManager.getInstance().inputMethodManager.hideSoftInputFromWindow(binding.dummyText.windowToken, 0)
@ -138,10 +135,10 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi
if (menuItem.itemId == 4) {
loadDialog()
return true
}
}*/
return false
}
/*
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
// updateColorPrefs must be called after super.onViewStateRestored because for some reason Android
@ -322,14 +319,12 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi
RichInputMethodManager.getInstance().inputMethodManager.showSoftInput(binding.dummyText, 0)
}
}
*/
companion object {
var forceOppositeTheme = false
}
/*
// ----------------- stuff for import / export ---------------------------
@Serializable
private data class SaveThoseColors(val moreColors: Int, val colors: Map<String, Pair<Int?, Boolean>>)
private fun saveDialog() {
AlertDialog.Builder(requireContext())
@ -415,7 +410,7 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi
if (moreColors == 2)
return Json.encodeToString(readAllColorsMap(prefs, isNight).map { it.key.name to it.value }.toMap())
// read the actual prefs!
val colors = colorPrefsAndNames.associate {
val colors = colorPrefsAndResIds.associate {
val pref = Settings.getColorPref(it.first, isNight)
val color = if (prefs.contains(pref)) prefs.getInt(pref, 0) else null
it.first to (color to prefs.getBoolean(pref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, true))
@ -436,10 +431,32 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi
loadColorString(it.reader().readText())
} ?: infoDialog(requireContext(), R.string.file_read_error)
}
*/
}
class ColorsNightSettingsFragment : ColorsSettingsFragment() {
override val isNight = true
override val titleResId = R.string.select_user_colors_night
// override val isNight = true
// override val titleResId = R.string.select_user_colors_night
}
@Serializable
data class SaveThoseColors(val moreColors: Int, val colors: Map<String, Pair<Int?, Boolean>>)
val colorPrefsAndResIds = listOf(
KeyboardTheme.COLOR_BACKGROUND to R.string.select_color_background,
KeyboardTheme.COLOR_KEYS to R.string.select_color_key_background,
KeyboardTheme.COLOR_FUNCTIONAL_KEYS to R.string.select_color_functional_key_background,
KeyboardTheme.COLOR_SPACEBAR to R.string.select_color_spacebar_background,
KeyboardTheme.COLOR_TEXT to R.string.select_color_key,
KeyboardTheme.COLOR_HINT_TEXT to R.string.select_color_key_hint,
KeyboardTheme.COLOR_SUGGESTION_TEXT to R.string.select_color_suggestion,
KeyboardTheme.COLOR_SPACEBAR_TEXT to R.string.select_color_spacebar_text,
KeyboardTheme.COLOR_ACCENT to R.string.select_color_accent,
KeyboardTheme.COLOR_GESTURE to R.string.select_color_gesture,
)
fun getColorPrefsToHideInitially(prefs: SharedPreferences): List<String> {
return listOf(KeyboardTheme.COLOR_SUGGESTION_TEXT, KeyboardTheme.COLOR_SPACEBAR_TEXT, KeyboardTheme.COLOR_GESTURE) +
if (prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false)) listOf(KeyboardTheme.COLOR_SPACEBAR_TEXT)
else listOf(KeyboardTheme.COLOR_FUNCTIONAL_KEYS)
}

View file

@ -143,4 +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_MORE_COLORS = 0
const val PREF_USER_ALL_COLORS = ""
}

View file

@ -12,18 +12,14 @@ import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import helium314.keyboard.keyboard.KeyboardActionListener;
import helium314.keyboard.keyboard.KeyboardTheme;
@ -34,7 +30,6 @@ import helium314.keyboard.latin.R;
import helium314.keyboard.latin.common.Colors;
import helium314.keyboard.latin.common.LocaleUtils;
import helium314.keyboard.latin.utils.AdditionalSubtypeUtils;
import helium314.keyboard.latin.utils.ColorUtilKt;
import helium314.keyboard.latin.utils.DeviceProtectedUtils;
import helium314.keyboard.latin.utils.KtxKt;
import helium314.keyboard.latin.utils.Log;
@ -67,20 +62,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_THEME_COLORS_NIGHT = "theme_colors_night";
public static final String PREF_THEME_KEY_BORDERS = "theme_key_borders";
public static final String PREF_THEME_DAY_NIGHT = "theme_auto_day_night";
public static final String PREF_THEME_USER_COLOR_PREFIX = "theme_color_";
public static final String PREF_THEME_USER_COLOR_NIGHT_PREFIX = "theme_dark_color_";
public static final String PREF_COLOR_KEYS_SUFFIX = "keys";
public static final String PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX = "functional_keys";
public static final String PREF_COLOR_SPACEBAR_SUFFIX = "spacebar";
public static final String PREF_COLOR_SPACEBAR_TEXT_SUFFIX = "spacebar_text";
public static final String PREF_COLOR_ACCENT_SUFFIX = "accent";
public static final String PREF_COLOR_GESTURE_SUFFIX = "gesture";
public static final String PREF_COLOR_TEXT_SUFFIX = "text";
public static final String PREF_COLOR_SUGGESTION_TEXT_SUFFIX = "suggestion_text";
public static final String PREF_COLOR_HINT_TEXT_SUFFIX = "hint_text";
public static final String PREF_COLOR_BACKGROUND_SUFFIX = "background";
public static final String PREF_AUTO_USER_COLOR_SUFFIX = "_auto";
public static final String PREF_ALL_COLORS_SUFFIX = "all_colors";
public static final String PREF_USER_COLORS_PREFIX = "user_colors_";
public static final String PREF_USER_ALL_COLORS_PREFIX = "user_all_colors_";
public static final String PREF_USER_MORE_COLORS_PREFIX = "user_more_colors_";
public static final String PREF_CUSTOM_ICON_NAMES = "custom_icon_names";
public static final String PREF_TOOLBAR_CUSTOM_KEY_CODES = "toolbar_custom_key_codes";
public static final String PREF_TOOLBAR_CUSTOM_LONGPRESS_CODES = "toolbar_custom_longpress_codes";
@ -190,12 +175,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_PINNED_CLIPS = "pinned_clips";
public static final String PREF_VERSION_CODE = "version_code";
public static final String PREF_SHOW_MORE_COLORS = "show_more_colors";
public static final String PREF_LIBRARY_CHECKSUM = "lib_checksum";
private static final float UNDEFINED_PREFERENCE_VALUE_FLOAT = -1.0f;
private static final int UNDEFINED_PREFERENCE_VALUE_INT = -1;
private Context mContext;
private SharedPreferences mPrefs;
private SettingsValues mSettingsValues;
@ -523,76 +504,12 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
boolean isNight = ResourceUtils.isNight(context.getResources());
if (ColorsSettingsFragment.Companion.getForceOppositeTheme()) isNight = !isNight;
else isNight = isNight && prefs.getBoolean(PREF_THEME_DAY_NIGHT, Defaults.PREF_THEME_DAY_NIGHT);
final String themeColors = (isNight)
final String themeName = (isNight)
? prefs.getString(Settings.PREF_THEME_COLORS_NIGHT, Defaults.PREF_THEME_COLORS_NIGHT)
: prefs.getString(Settings.PREF_THEME_COLORS, Defaults.PREF_THEME_COLORS);
final String themeStyle = prefs.getString(Settings.PREF_THEME_STYLE, Defaults.PREF_THEME_STYLE);
return KeyboardTheme.getThemeColors(themeColors, themeStyle, context, prefs, isNight);
}
public static int readUserColor(final SharedPreferences prefs, final Context context, final String colorName, final boolean isNight) {
final String pref = getColorPref(colorName, isNight);
if (prefs.getBoolean(pref + PREF_AUTO_USER_COLOR_SUFFIX, true)) {
return determineAutoColor(prefs, context, colorName, isNight);
}
if (prefs.contains(pref))
return prefs.getInt(pref, Color.GRAY);
else return determineAutoColor(prefs, context, colorName, isNight);
}
public static String getColorPref(final String color, final boolean isNight) {
return (isNight ? PREF_THEME_USER_COLOR_NIGHT_PREFIX : PREF_THEME_USER_COLOR_PREFIX) + color;
}
private static int determineAutoColor(final SharedPreferences prefs, final Context context, final String color, final boolean isNight) {
switch (color) {
case PREF_COLOR_ACCENT_SUFFIX:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
// try determining accent color on Android 10 & 11, accent is not available in resources
final Context wrapper = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault);
final TypedValue value = new TypedValue();
if (wrapper.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true))
return value.data;
}
return ContextCompat.getColor(getDayNightContext(context, isNight), R.color.accent);
case PREF_COLOR_GESTURE_SUFFIX:
return readUserColor(prefs, context, PREF_COLOR_ACCENT_SUFFIX, isNight);
case PREF_COLOR_SUGGESTION_TEXT_SUFFIX:
return readUserColor(prefs, context, PREF_COLOR_TEXT_SUFFIX, isNight);
case PREF_COLOR_TEXT_SUFFIX:
// base it on background color, and not key, because it's also used for suggestions
final int background = readUserColor(prefs, context, PREF_COLOR_BACKGROUND_SUFFIX, isNight);
if (ColorUtilKt.isBrightColor(background)) {
// but if key borders are enabled, we still want reasonable contrast
if (!prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, Defaults.PREF_THEME_KEY_BORDERS)
|| ColorUtilKt.isGoodContrast(Color.BLACK, readUserColor(prefs, context, PREF_COLOR_KEYS_SUFFIX, isNight)))
return Color.BLACK;
else
return Color.GRAY;
}
else return Color.WHITE;
case PREF_COLOR_HINT_TEXT_SUFFIX:
if (ColorUtilKt.isBrightColor(readUserColor(prefs, context, PREF_COLOR_KEYS_SUFFIX, isNight))) return Color.DKGRAY;
else return readUserColor(prefs, context, PREF_COLOR_TEXT_SUFFIX, isNight);
case PREF_COLOR_KEYS_SUFFIX:
return ColorUtilKt.brightenOrDarken(readUserColor(prefs, context, PREF_COLOR_BACKGROUND_SUFFIX, isNight), isNight);
case PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX:
return ColorUtilKt.brightenOrDarken(readUserColor(prefs, context, PREF_COLOR_KEYS_SUFFIX, isNight), true);
case PREF_COLOR_SPACEBAR_SUFFIX:
return readUserColor(prefs, context, PREF_COLOR_KEYS_SUFFIX, isNight);
case PREF_COLOR_SPACEBAR_TEXT_SUFFIX:
final int spacebar = readUserColor(prefs, context, PREF_COLOR_SPACEBAR_SUFFIX, isNight);
final int hintText = readUserColor(prefs, context, PREF_COLOR_HINT_TEXT_SUFFIX, isNight);
if (ColorUtilKt.isGoodContrast(hintText, spacebar)) return hintText & 0x80FFFFFF; // add some transparency
final int text = readUserColor(prefs, context, PREF_COLOR_TEXT_SUFFIX, isNight);
if (ColorUtilKt.isGoodContrast(text, spacebar)) return text & 0x80FFFFFF;
if (ColorUtilKt.isBrightColor(spacebar)) return Color.BLACK & 0x80FFFFFF;
else return Color.WHITE & 0x80FFFFFF;
case PREF_COLOR_BACKGROUND_SUFFIX:
default:
return ContextCompat.getColor(getDayNightContext(context, isNight), R.color.keyboard_background);
}
return KeyboardTheme.getThemeColors(themeName, themeStyle, context, prefs, isNight);
}
public static Context getDayNightContext(final Context context, final boolean wantNight) {

View file

@ -58,14 +58,14 @@ 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,
// 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,
// 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,
@ -142,10 +142,7 @@ fun createAppearanceSettings(context: Context) = listOf(
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 currentStyle = ctx.prefs().getString(Settings.PREF_THEME_STYLE, Defaults.PREF_THEME_STYLE)
val items = KeyboardTheme.COLORS.mapNotNull {
if (it == KeyboardTheme.THEME_HOLO_WHITE && currentStyle != KeyboardTheme.STYLE_HOLO)
return@mapNotNull null
val items = KeyboardTheme.getAvailableColors(ctx.prefs(), false).map {
it.getStringResourceOrName("theme_name_", ctx) to it
}
ListPreference(
@ -159,10 +156,7 @@ fun createAppearanceSettings(context: Context) = listOf(
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 currentStyle = ctx.prefs().getString(Settings.PREF_THEME_STYLE, Defaults.PREF_THEME_STYLE)
val items = KeyboardTheme.COLORS_DARK.mapNotNull {
if (it == KeyboardTheme.THEME_HOLO_WHITE && currentStyle == KeyboardTheme.STYLE_HOLO)
return@mapNotNull null
val items = KeyboardTheme.getAvailableColors(ctx.prefs(), true).map {
it.getStringResourceOrName("theme_name_", ctx) to it
}
ListPreference(
@ -171,14 +165,14 @@ fun createAppearanceSettings(context: Context) = listOf(
Defaults.PREF_THEME_COLORS_NIGHT
) { keyboardNeedsReload = true }
},
Setting(context, SettingsWithoutKey.ADJUST_COLORS, R.string.select_user_colors, R.string.select_user_colors_summary) {
/* Setting(context, SettingsWithoutKey.ADJUST_COLORS, R.string.select_user_colors, R.string.select_user_colors_summary) {
val ctx = LocalContext.current
Preference(
name = it.title,
description = it.description,
onClick = {
ctx.getActivity()?.switchTo(ColorsSettingsFragment())
//SettingsDestination.navigateTo(SettingsDestination.Colors) todo: later
//SettingsDestination.navigateTo(SettingsDestination.Colors) todo: soon
}
)
},
@ -189,10 +183,10 @@ fun createAppearanceSettings(context: Context) = listOf(
description = it.description,
onClick = {
ctx.getActivity()?.switchTo(ColorsNightSettingsFragment())
//SettingsDestination.navigateTo(SettingsDestination.ColorsNight) todo: later
//SettingsDestination.navigateTo(SettingsDestination.ColorsNight) todo: soon
}
)
},
},*/
Setting(context, Settings.PREF_THEME_KEY_BORDERS, R.string.key_borders) {
SwitchPreference(it, Defaults.PREF_THEME_KEY_BORDERS)
},