add a separate fragment for setting user-defined colors

issues:
when using day/night mode, active keyboard is shown in preview, not the acually modified varian
reloading / updating the preview is either slow or will not show the keyboard again
This commit is contained in:
Helium314 2023-09-12 14:21:40 +02:00
parent d33650586b
commit b18b7dc820
20 changed files with 383 additions and 206 deletions

View file

@ -141,23 +141,22 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
return KEYBOARD_THEMES[DEFAULT_THEME_ID];
}
// todo (later): material you, system accent, ...
public static Colors getThemeColors(final String themeColors, final String themeStyle, final Context context, final SharedPreferences prefs) {
final boolean hasBorders = prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false);
switch (themeColors) {
case THEME_USER:
final int accent = prefs.getInt(Settings.PREF_THEME_USER_COLOR_ACCENT, Color.BLUE);
final int keyBgColor = prefs.getInt(Settings.PREF_THEME_USER_COLOR_KEYS, Color.LTGRAY);
final int keyTextColor = prefs.getInt(Settings.PREF_THEME_USER_COLOR_TEXT, Color.WHITE);
final int hintTextColor = prefs.getInt(Settings.PREF_THEME_USER_COLOR_HINT_TEXT, Color.WHITE);
final int background = prefs.getInt(Settings.PREF_THEME_USER_COLOR_BACKGROUND, Color.DKGRAY);
final int accent = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, false);
final int keyBgColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, false);
final int keyTextColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, false);
final int hintTextColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, false);
final int background = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, false);
return new Colors(themeStyle, hasBorders, accent, background, keyBgColor, ColorUtilKt.brightenOrDarken(keyBgColor, true), keyBgColor, keyTextColor, hintTextColor);
case THEME_USER_NIGHT:
final int accent2 = prefs.getInt(Settings.PREF_THEME_USER_DARK_COLOR_ACCENT, Color.BLUE);
final int keyBgColor2 = prefs.getInt(Settings.PREF_THEME_USER_DARK_COLOR_KEYS, Color.LTGRAY);
final int keyTextColor2 = prefs.getInt(Settings.PREF_THEME_USER_DARK_COLOR_TEXT, Color.WHITE);
final int hintTextColor2 = prefs.getInt(Settings.PREF_THEME_USER_DARK_COLOR_HINT_TEXT, Color.WHITE);
final int background2 = prefs.getInt(Settings.PREF_THEME_USER_DARK_COLOR_BACKGROUND, Color.DKGRAY);
final int accent2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, true);
final int keyBgColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, true);
final int keyTextColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, true);
final int hintTextColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, true);
final int background2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, true);
return new Colors(themeStyle, hasBorders, accent2, background2, keyBgColor2, ColorUtilKt.brightenOrDarken(keyBgColor2, true), keyBgColor2, keyTextColor2, hintTextColor2);
case THEME_DARK:
return new Colors(

View file

@ -2,10 +2,8 @@ package org.dslul.openboard.inputmethod.latin.settings
import android.content.SharedPreferences
import android.content.res.Configuration
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.TwoStatePreference
@ -137,65 +135,7 @@ class AppearanceSettingsFragment : SubScreenFragment() {
}
themeVariantNightPref?.isVisible = dayNightPref?.isChecked == true
userColorsPref.isVisible = themeVariantPref.value == KeyboardTheme.THEME_USER
userColorsPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
adjustColors(false)
true
}
userColorsPrefNight?.isVisible = dayNightPref?.isChecked == true && themeVariantNightPref?.value == KeyboardTheme.THEME_USER_NIGHT
userColorsPrefNight?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
adjustColors(true)
true
}
}
// todo: improve color selection, should at very least show a preview of the color
// but maybe a separate fragment would be better
// idea:
// left: which color (background, key, text,...)
// right: color preview (always the correct one, even if determined automatically)
// maybe copy parts from simple keyboard, see e.g. screenshot 4 in https://github.com/SimpleMobileTools/Simple-Keyboard/tree/main/fastlane/metadata/android/en-US/images/phoneScreenshots
// below (for some colors, with indent):
// enable user-defining (most colors, but definitely not background)
// use system accent (for accent and text colors)
// on click: color selector
// maybe copy parts from simple keyboard, see e.g. screenshot 4 in https://github.com/SimpleMobileTools/Simple-Keyboard/tree/main/fastlane/metadata/android/en-US/images/phoneScreenshots
// but full range would be preferable
// use some color picker library? would likely allow nicer tuning
private fun adjustColors(dark: Boolean) {
val items = listOf(
R.string.select_color_background,
R.string.select_color_key_background,
R.string.select_color_key,
R.string.select_color_key_hint,
R.string.select_color_accent,
).map { requireContext().getString(it) }
AlertDialog.Builder(requireContext())
.setPositiveButton(android.R.string.ok, null)
.setTitle(R.string.select_color_to_adjust)
.setItems(items.toTypedArray()) { _, i ->
val (pref, default) =
if (dark)
when (i) {
0 -> Settings.PREF_THEME_USER_DARK_COLOR_BACKGROUND to Color.DKGRAY
1 -> Settings.PREF_THEME_USER_DARK_COLOR_KEYS to Color.LTGRAY
2 -> Settings.PREF_THEME_USER_DARK_COLOR_TEXT to Color.WHITE
3 -> Settings.PREF_THEME_USER_DARK_COLOR_HINT_TEXT to Color.WHITE
4 -> Settings.PREF_THEME_USER_DARK_COLOR_ACCENT to Color.BLUE
else -> return@setItems
}
else
when (i) {
0 -> Settings.PREF_THEME_USER_COLOR_BACKGROUND to Color.DKGRAY
1 -> Settings.PREF_THEME_USER_COLOR_KEYS to Color.LTGRAY
2 -> Settings.PREF_THEME_USER_COLOR_TEXT to Color.WHITE
3 -> Settings.PREF_THEME_USER_COLOR_HINT_TEXT to Color.WHITE
4 -> Settings.PREF_THEME_USER_COLOR_ACCENT to Color.BLUE
else -> return@setItems
}
val d = ColorPickerDialog(requireContext(), items[i], sharedPreferences, pref, default)
d.show()
}
.show()
}
private fun setupKeyboardHeight(prefKey: String, defaultValue: Float) {

View file

@ -1,113 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// adapted from https://github.com/rkkr/simple-keyboard/blob/master/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/settings/ColorDialogPreference.java
package org.dslul.openboard.inputmethod.latin.settings;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import org.dslul.openboard.inputmethod.latin.R;
public class ColorPickerDialog extends AlertDialog implements SeekBar.OnSeekBarChangeListener {
protected ColorPickerDialog(final Context context, final String title, final SharedPreferences prefs,
final String colorPref, final int defaultColor) {
super(context);
setTitle(title);
View view = getLayoutInflater().inflate(R.layout.color_dialog, null);
mSeekBarRed = (SeekBar)view.findViewById(R.id.seek_bar_dialog_bar_red);
mSeekBarRed.setMax(255);
mSeekBarRed.setOnSeekBarChangeListener(this);
mSeekBarRed.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
mSeekBarRed.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
mSeekBarGreen = (SeekBar)view.findViewById(R.id.seek_bar_dialog_bar_green);
mSeekBarGreen.setMax(255);
mSeekBarGreen.setOnSeekBarChangeListener(this);
mSeekBarGreen.getThumb().setColorFilter(Color.GREEN, PorterDuff.Mode.SRC_IN);
mSeekBarGreen.getProgressDrawable().setColorFilter(Color.GREEN, PorterDuff.Mode.SRC_IN);
mSeekBarBlue = (SeekBar)view.findViewById(R.id.seek_bar_dialog_bar_blue);
mSeekBarBlue.setMax(255);
mSeekBarBlue.setOnSeekBarChangeListener(this);
mSeekBarBlue.getThumb().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
mSeekBarBlue.getProgressDrawable().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
mValueView = (TextView)view.findViewById(R.id.seek_bar_dialog_value);
setView(view);
// init with correct values
// using onShowListener?
setOnShowListener(dialogInterface -> {
int color = prefs.getInt(colorPref, defaultColor);
mSeekBarRed.setProgress(Color.red(color));
mSeekBarGreen.setProgress(Color.green(color));
mSeekBarBlue.setProgress(Color.blue(color));
setHeaderText(color);
});
// set on ok and on cancel listeners
setButton(BUTTON_NEGATIVE, context.getText(android.R.string.cancel), (dialogInterface, i) -> dismiss());
setButton(BUTTON_POSITIVE, context.getText(android.R.string.ok), (dialogInterface, i) -> {
final int value = Color.rgb(
mSeekBarRed.getProgress(),
mSeekBarGreen.getProgress(),
mSeekBarBlue.getProgress());
prefs.edit().putInt(colorPref, value).apply();
dismiss();
});
}
private final TextView mValueView;
private final SeekBar mSeekBarRed;
private final SeekBar mSeekBarGreen;
private final SeekBar mSeekBarBlue;
@Override
public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {
int color = Color.rgb(
mSeekBarRed.getProgress(),
mSeekBarGreen.getProgress(),
mSeekBarBlue.getProgress());
setHeaderText(color);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
private void setHeaderText(int color) {
mValueView.setText(getValueText(color));
boolean bright = Color.red(color) + Color.green(color) + Color.blue(color) > 128 * 3;
mValueView.setTextColor(bright ? Color.BLACK : Color.WHITE);
mValueView.setBackgroundColor(color);
}
private String getValueText(final int value) {
String temp = Integer.toHexString(value);
for (; temp.length() < 8; temp = "0" + temp);
return temp.substring(2).toUpperCase();
}
}

View file

@ -0,0 +1,169 @@
package org.dslul.openboard.inputmethod.latin.settings
import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.widget.CompoundButton
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.view.forEachIndexed
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment
import com.skydoves.colorpickerview.ColorPickerDialog
import com.skydoves.colorpickerview.flag.BubbleFlag
import com.skydoves.colorpickerview.flag.FlagMode
import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener
import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher
import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.RichInputMethodManager
import org.dslul.openboard.inputmethod.latin.databinding.ColorSettingBinding
import org.dslul.openboard.inputmethod.latin.databinding.ColorSettingsBinding
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils
import org.dslul.openboard.inputmethod.latin.utils.ExecutorUtils
import org.dslul.openboard.inputmethod.latin.utils.ResourceUtils
open class ColorsSettingsFragment : Fragment(R.layout.color_settings) {
private val binding by viewBinding(ColorSettingsBinding::bind)
open val isNight = false
open val titleResId = R.string.select_user_colors
private val prefs by lazy { DeviceProtectedUtils.getSharedPreferences(requireContext()) }
private val colorPrefs = listOf(
Settings.PREF_COLOR_BACKGROUND_SUFFIX,
Settings.PREF_COLOR_KEYS_SUFFIX,
Settings.PREF_COLOR_TEXT_SUFFIX,
Settings.PREF_COLOR_HINT_TEXT_SUFFIX,
Settings.PREF_COLOR_ACCENT_SUFFIX,
)
override fun onResume() {
super.onResume()
val activity: Activity? = activity
if (activity is AppCompatActivity) {
val actionBar = activity.supportActionBar ?: return
actionBar.setTitle(titleResId)
}
if (isNight != ResourceUtils.isNight(requireContext().resources))
// reload to get the right configuration
// todo: this does not work, keyboard also reloading with some other context
reloadKeyboard(false)
}
override fun onPause() {
super.onPause()
if (isNight != ResourceUtils.isNight(requireContext().resources))
// reload again so the correct configuration is applied
// todo: this does not work, keyboard also reloading with some other context
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val colorPrefNames = listOf(
R.string.select_color_background,
R.string.select_color_key_background,
R.string.select_color_key,
R.string.select_color_key_hint,
R.string.select_color_accent,
).map { requireContext().getString(it) }
val prefPrefix = if (isNight) Settings.PREF_THEME_USER_COLOR_NIGHT_PREFIX else Settings.PREF_THEME_USER_COLOR_PREFIX
colorPrefs.forEachIndexed { index, colorPref ->
val csb = ColorSettingBinding.inflate(layoutInflater, binding.colorSettingsContainer, true)
csb.colorSwitch.isChecked = !prefs.getBoolean(prefPrefix + colorPref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, true)
csb.colorPreview.setColorFilter(Settings.readUserColor(prefs, requireContext(), colorPrefs[index], isNight))
csb.colorText.text = colorPrefNames[index]
if (!csb.colorSwitch.isChecked) {
csb.colorSummary.setText(R.string.auto_user_color)
}
val switchListener = CompoundButton.OnCheckedChangeListener { _, b ->
val hidden = RichInputMethodManager.getInstance().inputMethodManager.hideSoftInputFromWindow(binding.dummyText.windowToken, 0)
prefs.edit { putBoolean(prefPrefix + colorPref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, !b) }
if (b) csb.colorSummary.text = ""
else csb.colorSummary.setText(R.string.auto_user_color)
reloadKeyboard(hidden)
updateColorPreviews()
}
csb.colorSwitch.setOnCheckedChangeListener(switchListener)
val clickListener = View.OnClickListener {
val hidden = RichInputMethodManager.getInstance().inputMethodManager.hideSoftInputFromWindow(binding.dummyText.windowToken, 0)
val b = ColorPickerDialog.Builder(requireContext())
.setTitle(colorPrefNames[index])
// todo: later it should be activated, but currently setting alpha leads to glitches,
// e.g. when setting alpha on key text it's not applied for key icons, but for emojis
.attachAlphaSlideBar(false)
.setPositiveButton(android.R.string.ok, ColorEnvelopeListener { envelope, _ ->
prefs.edit { putInt(prefPrefix + colorPrefs[index], envelope.color) }
if (!csb.colorSwitch.isChecked) {
prefs.edit { putBoolean(prefPrefix + colorPref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, false) }
csb.colorSwitch.setOnCheckedChangeListener(null)
csb.colorSwitch.isChecked = true
csb.colorSummary.text = ""
csb.colorSwitch.setOnCheckedChangeListener(switchListener)
reloadKeyboard(hidden)
updateColorPreviews()
return@ColorEnvelopeListener
}
reloadKeyboard(hidden)
updateColorPreviews()
})
.setNegativeButton(android.R.string.cancel) { _, _ ->
if (hidden)
RichInputMethodManager.getInstance().inputMethodManager.showSoftInput(binding.dummyText, 0)
}
val initialColor = if (prefs.contains(prefPrefix + colorPref))
prefs.getInt(prefPrefix + colorPref, Color.GRAY)
else
Settings.readUserColor(prefs, requireContext(), colorPrefs[index], isNight)
b.colorPickerView.setInitialColor(initialColor)
b.colorPickerView.setPadding(15)
// set better color drawable? neither the white circle nor the plus is nice
b.colorPickerView.setSelectorDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_plus))
b.colorPickerView.flagView = BubbleFlag(requireContext()).apply { flagMode = FlagMode.ALWAYS }
b.show()
}
csb.colorTextContainer.setOnClickListener(clickListener)
csb.colorPreview.setOnClickListener(clickListener)
}
}
private fun updateColorPreviews() {
binding.colorSettingsContainer.forEachIndexed { index, view ->
val color = Settings.readUserColor(prefs, requireContext(), colorPrefs[index], isNight)
view.findViewById<ImageView>(R.id.color_preview)?.setColorFilter(color)
}
}
private fun reloadKeyboard(show: Boolean) {
// todo: any way to make some kind of "light update" to keyboard?
// only reloading main keyboard view is necessary...
// or get an actual (live) preview instead of the full keyboard?
// or accelerate keyboard inflate, a big here issue is emojiCategory creating many keyboards
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
// todo: this does not work, keyboard also reloading with some other context
// KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(Settings.getDayNightContext(requireContext(), isNight))
if (!show) return
Thread.sleep(100) // some pause is necessary to avoid visual glitches
RichInputMethodManager.getInstance().inputMethodManager.showSoftInput(binding.dummyText, 0)
return
// for some reason showing again does not work when running with executor
// but when running without it's noticeably slow, and sometimes produces glitches
// todo: decider whether to just hide, or have some slowdown and show again
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
if (!show) return@execute
Thread.sleep(100)
RichInputMethodManager.getInstance().inputMethodManager.showSoftInput(binding.dummyText, 0)
}
}
}
class ColorsNightSettingsFragment : ColorsSettingsFragment() {
override val isNight = true
override val titleResId = R.string.select_user_colors_night
}

View file

@ -0,0 +1,46 @@
package org.dslul.openboard.inputmethod.latin.settings
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
// taken from StreetComplete, ViewBinder.kt
inline fun <reified T : ViewBinding> Fragment.viewBinding(
noinline viewBinder: (View) -> T,
rootViewId: Int? = null
) = FragmentViewBindingPropertyDelegate(this, viewBinder, rootViewId)
class FragmentViewBindingPropertyDelegate<T : ViewBinding>(
private val fragment: Fragment,
private val viewBinder: (View) -> T,
private val rootViewId: Int? = null
) : ReadOnlyProperty<Fragment, T>, LifecycleEventObserver {
private var binding: T? = null
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
binding = null
source.lifecycle.removeObserver(this)
}
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
if (binding == null) {
val rootView = if (rootViewId != null) {
thisRef.requireView().findViewById<ViewGroup>(rootViewId)!!.getChildAt(0)
} else {
thisRef.requireView()
}
binding = viewBinder(rootView)
fragment.viewLifecycleOwner.lifecycle.addObserver(this)
}
return binding!!
}
}

View file

@ -22,12 +22,16 @@ import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import org.dslul.openboard.inputmethod.keyboard.KeyboardTheme;
import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager;
@ -37,6 +41,7 @@ import org.dslul.openboard.inputmethod.latin.common.Colors;
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
import org.dslul.openboard.inputmethod.latin.common.StringUtils;
import org.dslul.openboard.inputmethod.latin.utils.AdditionalSubtypeUtils;
import org.dslul.openboard.inputmethod.latin.utils.ColorUtilKt;
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
import org.dslul.openboard.inputmethod.latin.utils.JniUtils;
import org.dslul.openboard.inputmethod.latin.utils.ResourceUtils;
@ -66,16 +71,14 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_THEME_VARIANT_NIGHT = "theme_variant_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_TEXT = "theme_color_text";
public static final String PREF_THEME_USER_COLOR_HINT_TEXT = "theme_color_hint_text";
public static final String PREF_THEME_USER_COLOR_BACKGROUND = "theme_color_background";
public static final String PREF_THEME_USER_COLOR_KEYS = "theme_color_keys";
public static final String PREF_THEME_USER_COLOR_ACCENT = "theme_color_accent";
public static final String PREF_THEME_USER_DARK_COLOR_TEXT = "theme_dark_color_text";
public static final String PREF_THEME_USER_DARK_COLOR_HINT_TEXT = "theme_dark_color_hint_text";
public static final String PREF_THEME_USER_DARK_COLOR_BACKGROUND = "theme_dark_color_background";
public static final String PREF_THEME_USER_DARK_COLOR_KEYS = "theme_dark_color_keys";
public static final String PREF_THEME_USER_DARK_COLOR_ACCENT = "theme_dark_color_accent";
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_ACCENT_SUFFIX = "accent";
public static final String PREF_COLOR_TEXT_SUFFIX = "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_VOICE_INPUT_KEY = "pref_voice_input_key";
public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
public static final String PREF_AUTO_CORRECTION = "pref_key_auto_correction";
@ -532,9 +535,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
}
public static Colors getColorsForCurrentTheme(final Context context, final SharedPreferences prefs) {
// todo: night mode can be unspecified -> maybe need to adjust for correct behavior on some devices?
final boolean isNight = (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
final String themeColors = (isNight && prefs.getBoolean(Settings.PREF_THEME_DAY_NIGHT, context.getResources().getBoolean(R.bool.day_night_default)))
final boolean isNight = ResourceUtils.isNight(context.getResources());
final String themeColors = (isNight && prefs.getBoolean(PREF_THEME_DAY_NIGHT, context.getResources().getBoolean(R.bool.day_night_default)))
? prefs.getString(Settings.PREF_THEME_VARIANT_NIGHT, KeyboardTheme.THEME_DARKER)
: prefs.getString(Settings.PREF_THEME_VARIANT, KeyboardTheme.THEME_LIGHT);
final String themeStyle = prefs.getString(Settings.PREF_THEME_STYLE, KeyboardTheme.THEME_STYLE_MATERIAL);
@ -542,4 +544,58 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return KeyboardTheme.getThemeColors(themeColors, themeStyle, context, prefs);
}
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
// todo: test whether this actually works
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_TEXT_SUFFIX:
// base it on background color, and not key, because it's also used for suggestions
if (ColorUtilKt.isBrightColor(readUserColor(prefs, context, PREF_COLOR_BACKGROUND_SUFFIX, isNight))) return Color.BLACK;
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 Color.LTGRAY;
case PREF_COLOR_KEYS_SUFFIX:
return ColorUtilKt.brightenOrDarken(readUserColor(prefs, context, PREF_COLOR_BACKGROUND_SUFFIX, isNight), isNight);
case PREF_COLOR_BACKGROUND_SUFFIX:
default:
return ContextCompat.getColor(getDayNightContext(context, isNight), R.color.keyboard_background);
}
}
public static Context getDayNightContext(final Context context, final boolean wantNight) {
final boolean isNight = (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
if (isNight == wantNight)
return context;
final Configuration config = new Configuration(context.getResources().getConfiguration());
final int night = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
final int uiModeWithNightBitsZero = config.uiMode - night;
config.uiMode = uiModeWithNightBitsZero + (wantNight ? Configuration.UI_MODE_NIGHT_YES : Configuration.UI_MODE_NIGHT_NO);
final ContextThemeWrapper wrapper = new ContextThemeWrapper(context, R.style.platformActivityTheme);
wrapper.applyOverrideConfiguration(config);
return wrapper;
}
}

View file

@ -16,6 +16,7 @@
package org.dslul.openboard.inputmethod.latin.utils;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
@ -298,4 +299,9 @@ public final class ResourceUtils {
public static boolean isStringValue(final TypedValue v) {
return v.type == TypedValue.TYPE_STRING;
}
public static boolean isNight(final Resources res) {
// todo: night mode can be unspecified -> maybe need to adjust for correct behavior on some devices?
return (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
}
}