diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e5734a3bd..f6243f1a6 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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")) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardTheme.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardTheme.kt index a313bb2ca..bb90e5abc 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardTheme.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardTheme.kt @@ -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>) { + fun writeUserColors(prefs: SharedPreferences, themeName: String, colors: List) { 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> { + fun readUserColors(prefs: SharedPreferences, themeName: String): List { 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>, context: Context, colorName: String, isNight: Boolean): Int { - val (color, auto) = colors[colorName] ?: (null to true) + fun determineUserColor(colors: List, 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>, colorName: String, isNight: Boolean, context: Context): Int { + private fun determineAutoColor(colors: List, 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 +} diff --git a/app/src/main/java/helium314/keyboard/latin/App.kt b/app/src/main/java/helium314/keyboard/latin/App.kt index 69701828c..042df347b 100644 --- a/app/src/main/java/helium314/keyboard/latin/App.kt +++ b/app/src/main/java/helium314/keyboard/latin/App.kt @@ -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) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt index 33a474665..d9aeeb19b 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt @@ -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 = "" } diff --git a/app/src/main/java/helium314/keyboard/settings/SettingsNavHost.kt b/app/src/main/java/helium314/keyboard/settings/SettingsNavHost.kt index 527a2477d..67dc27e70 100644 --- a/app/src/main/java/helium314/keyboard/settings/SettingsNavHost.kt +++ b/app/src/main/java/helium314/keyboard/settings/SettingsNavHost.kt @@ -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) } } } diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/ColorPickerDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/ColorPickerDialog.kt index 0fe913f14..cbfef4afe 100644 --- a/app/src/main/java/helium314/keyboard/settings/dialogs/ColorPickerDialog.kt +++ b/app/src/main/java/helium314/keyboard/settings/dialogs/ColorPickerDialog.kt @@ -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", {}) } diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/ColorThemePickerDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/ColorThemePickerDialog.kt new file mode 100644 index 000000000..9bddbd2e0 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/settings/dialogs/ColorThemePickerDialog.kt @@ -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 + ) +} diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/ListPickerDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/ListPickerDialog.kt index 8472c05b3..de6fb2764 100644 --- a/app/src/main/java/helium314/keyboard/settings/dialogs/ListPickerDialog.kt +++ b/app/src/main/java/helium314/keyboard/settings/dialogs/ListPickerDialog.kt @@ -86,7 +86,6 @@ fun ListPickerDialog( ) Text( text = getItemName(item), - style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f), ) } diff --git a/app/src/main/java/helium314/keyboard/settings/screens/AppearanceScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/AppearanceScreen.kt index 7fbabf3fb..65fe9828d 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/AppearanceScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/AppearanceScreen.kt @@ -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) }, diff --git a/app/src/main/java/helium314/keyboard/settings/screens/ColorsScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/ColorsScreen.kt index 2310b90e9..87d65b756 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/ColorsScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/ColorsScreen.kt @@ -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()) } // 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 -) \ No newline at end of file +@Preview +@Composable +private fun Preview() { + Theme(true) { + Surface { + ColorsScreen(false) { } + } + } +} diff --git a/app/src/main/res/drawable/ic_user_dictionary_edit.xml b/app/src/main/res/drawable/ic_edit.xml similarity index 100% rename from app/src/main/res/drawable/ic_user_dictionary_edit.xml rename to app/src/main/res/drawable/ic_edit.xml diff --git a/app/src/main/res/layout/user_dictionary_item.xml b/app/src/main/res/layout/user_dictionary_item.xml index 1e8a8d335..c2dca562a 100644 --- a/app/src/main/res/layout/user_dictionary_item.xml +++ b/app/src/main/res/layout/user_dictionary_item.xml @@ -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"> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e1e2abc52..ee1f8b974 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -987,4 +987,6 @@ New dictionary: Customize icons Really reset all customized icons? + + Really delete %s?