From dec905d9adc135127bb8a206b53dac2c41201a30 Mon Sep 17 00:00:00 2001 From: WhiredPlanck Date: Wed, 29 Jan 2025 11:01:35 +0800 Subject: [PATCH] feat: notify on preference changes --- .../com/osfans/trime/data/prefs/AppPrefs.kt | 17 +++- .../trime/data/prefs/PreferenceDelegate.kt | 94 ++++++++++++------- .../data/prefs/PreferenceDelegateProvider.kt | 21 +++++ .../java/com/osfans/trime/ime/bar/QuickBar.kt | 16 ++-- .../trime/ime/core/TrimeInputMethodService.kt | 33 ++++++- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/xml/keyboard_preference.xml | 5 + 9 files changed, 141 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/osfans/trime/data/prefs/AppPrefs.kt b/app/src/main/java/com/osfans/trime/data/prefs/AppPrefs.kt index 3ddc175511..60cc85c492 100644 --- a/app/src/main/java/com/osfans/trime/data/prefs/AppPrefs.kt +++ b/app/src/main/java/com/osfans/trime/data/prefs/AppPrefs.kt @@ -6,6 +6,7 @@ package com.osfans.trime.data.prefs import android.content.Context import android.content.SharedPreferences +import androidx.annotation.Keep import androidx.preference.PreferenceManager import com.osfans.trime.R import com.osfans.trime.data.base.DataManager @@ -46,12 +47,24 @@ class AppPrefs( val candidates = Candidates(shared).register() + @Keep + private val onSharedPreferenceChangeListener = + SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + if (key == null) return@OnSharedPreferenceChangeListener + providers.forEach { + it.notifyChange(key) + } + } + companion object { private var defaultInstance: AppPrefs? = null fun initDefault(sharedPreferences: SharedPreferences): AppPrefs { val instance = AppPrefs(sharedPreferences) defaultInstance = instance + sharedPreferences.registerOnSharedPreferenceChangeListener( + defaultInstance().onSharedPreferenceChangeListener, + ) return instance } @@ -113,6 +126,7 @@ class AppPrefs( const val POPUP_KEY_PRESS_ENABLED = "keyboard__show_key_popup" const val SHOW_SCHEMA_SWITCHES = "show_schema_switches_in_idle" const val SHOW_ARROW_IN_SWITCHES = "show_arrow_in_switches" + const val HIDE_QUICK_BAR = "hide_quick_bar" const val LANDSCAPE_MODE = "keyboard__landscape_mode" const val SPLIT_SPACE_PERCENT = "keyboard__split_space" @@ -148,6 +162,7 @@ class AppPrefs( val popupKeyPressEnabled = bool(POPUP_KEY_PRESS_ENABLED, false) val showSchemaSwitches = bool(SHOW_SCHEMA_SWITCHES, true) val showArrowInSwitches = bool(SHOW_ARROW_IN_SWITCHES, true) + val hideQuickBar = bool(HIDE_QUICK_BAR, false) enum class LandscapeModeOption { NEVER, @@ -190,12 +205,10 @@ class AppPrefs( companion object { const val MODE = "show_candidates_window" const val POSITION = "candidates_window_position" - const val HIDE_QUICK_BAR = "hide_quick_bar" } val mode = enum(R.string.show_candidates_window, MODE, PopupCandidatesMode.DISABLED) val position = enum(R.string.candidates_window_position, POSITION, PopupPosition.BOTTOM_LEFT) - val hideQuickBar = switch(R.string.hide_quick_bar_when_always_show, HIDE_QUICK_BAR, false) } /** diff --git a/app/src/main/java/com/osfans/trime/data/prefs/PreferenceDelegate.kt b/app/src/main/java/com/osfans/trime/data/prefs/PreferenceDelegate.kt index 4ffe6f46d1..481d0d7684 100644 --- a/app/src/main/java/com/osfans/trime/data/prefs/PreferenceDelegate.kt +++ b/app/src/main/java/com/osfans/trime/data/prefs/PreferenceDelegate.kt @@ -6,6 +6,7 @@ package com.osfans.trime.data.prefs import android.content.SharedPreferences import androidx.core.content.edit +import com.osfans.trime.util.WeakHashSet import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -14,38 +15,31 @@ open class PreferenceDelegate( val key: String, val defaultValue: T, ) : ReadWriteProperty { - @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") - open fun getValue(fallbackKey: String): T { - val finalKey = key.ifEmpty { fallbackKey } - return try { + @Suppress("UNCHECKED_CAST") + open fun getValue(): T = + try { when (defaultValue) { - is Int -> sharedPreferences.getInt(finalKey, defaultValue) - is Long -> sharedPreferences.getLong(finalKey, defaultValue) - is Float -> sharedPreferences.getFloat(finalKey, defaultValue) - is Boolean -> sharedPreferences.getBoolean(finalKey, defaultValue) - is String -> sharedPreferences.getString(finalKey, defaultValue) ?: defaultValue - is Set<*> -> sharedPreferences.getStringSet(finalKey, defaultValue as? Set) + is Int -> sharedPreferences.getInt(key, defaultValue) + is Long -> sharedPreferences.getLong(key, defaultValue) + is Float -> sharedPreferences.getFloat(key, defaultValue) + is Boolean -> sharedPreferences.getBoolean(key, defaultValue) + is String -> sharedPreferences.getString(key, defaultValue) ?: defaultValue else -> null } as T } catch (e: Exception) { - setValue(fallbackKey, defaultValue) + setValue(defaultValue) defaultValue } - } - open fun setValue( - fallbackKey: String, - value: T, - ) { - val finalKey = key.ifEmpty { fallbackKey } + open fun setValue(value: T) { sharedPreferences.edit { when (value) { - is Int -> putInt(finalKey, value) - is Long -> putLong(finalKey, value) - is Float -> putFloat(finalKey, value) - is Boolean -> putBoolean(finalKey, value) - is String -> putString(finalKey, value) - is Set<*> -> putStringSet(finalKey, value.map { it.toString() }.toHashSet()) + is Int -> putInt(key, value) + is Long -> putLong(key, value) + is Float -> putFloat(key, value) + is Boolean -> putBoolean(key, value) + is String -> putString(key, value) + is Set<*> -> putStringSet(key, value.map { it.toString() }.toHashSet()) } } } @@ -53,13 +47,13 @@ open class PreferenceDelegate( override fun getValue( thisRef: Any?, property: KProperty<*>, - ): T = getValue(property.name) + ): T = getValue() override fun setValue( thisRef: Any?, property: KProperty<*>, value: T, - ) = setValue(property.name, value) + ) = setValue(value) interface Serializer { fun serialize(t: T): String @@ -73,24 +67,52 @@ open class PreferenceDelegate( defaultValue: T, private val serializer: Serializer, ) : PreferenceDelegate(sharedPreferences, key, defaultValue) { - override fun setValue( - fallbackKey: String, - value: T, - ) { - val finalKey = key.ifEmpty { fallbackKey } - sharedPreferences.edit { putString(finalKey, serializer.serialize(value)) } + override fun setValue(value: T) { + sharedPreferences.edit { putString(key, serializer.serialize(value)) } } - override fun getValue(fallbackKey: String): T { - val finalKey = key.ifEmpty { fallbackKey } - return try { - sharedPreferences.getString(finalKey, null)?.let { + override fun getValue(): T = + try { + sharedPreferences.getString(key, null)?.let { serializer.deserialize(it) } ?: defaultValue } catch (e: Exception) { - setValue(fallbackKey, defaultValue) + setValue(defaultValue) defaultValue } + } + + fun interface OnChangeListener { + fun onChange( + key: String, + value: T, + ) + } + + private lateinit var listeners: MutableSet> + + /** + * **WARN:** No anonymous listeners, please **KEEP** the reference! + * + * You may need to reference the listener once outside of it's container's constructor, + * to prevent R8 from removing the field; + * or simply mark the listener with [@Keep][androidx.annotation.Keep] . + */ + fun registerOnChangeListener(listener: OnChangeListener) { + if (!::listeners.isInitialized) { + listeners = WeakHashSet() } + listeners.add(listener) + } + + fun unregisterOnChangeListener(listener: OnChangeListener) { + if (!::listeners.isInitialized || listeners.isEmpty()) return + listeners.remove(listener) + } + + fun notifyChange() { + if (!::listeners.isInitialized || listeners.isEmpty()) return + val newValue = getValue() + listeners.forEach { it.onChange(key, newValue) } } } diff --git a/app/src/main/java/com/osfans/trime/data/prefs/PreferenceDelegateProvider.kt b/app/src/main/java/com/osfans/trime/data/prefs/PreferenceDelegateProvider.kt index ec8c949f46..afbdca4ae0 100644 --- a/app/src/main/java/com/osfans/trime/data/prefs/PreferenceDelegateProvider.kt +++ b/app/src/main/java/com/osfans/trime/data/prefs/PreferenceDelegateProvider.kt @@ -5,6 +5,7 @@ package com.osfans.trime.data.prefs import androidx.preference.PreferenceScreen +import com.osfans.trime.util.WeakHashSet abstract class PreferenceDelegateProvider { private val _preferenceDelegates: MutableMap> = mutableMapOf() @@ -20,6 +21,26 @@ abstract class PreferenceDelegateProvider { open fun createUi(screen: PreferenceScreen) { } + fun interface OnChangeListener { + fun onChange(key: String) + } + + private val onChangeListeners = WeakHashSet() + + fun registerOnChangeListener(listener: OnChangeListener) { + onChangeListeners.add(listener) + } + + fun unregisterOnChangeListener(listener: OnChangeListener) { + onChangeListeners.remove(listener) + } + + fun notifyChange(key: String) { + val preference = _preferenceDelegates[key] ?: return + onChangeListeners.forEach { it.onChange(key) } + preference.notifyChange() + } + fun PreferenceDelegateUi<*>.registerUi() { _preferenceDelegatesUi.add(this) } diff --git a/app/src/main/java/com/osfans/trime/ime/bar/QuickBar.kt b/app/src/main/java/com/osfans/trime/ime/bar/QuickBar.kt index a6bfa212c7..efdfd1ab98 100644 --- a/app/src/main/java/com/osfans/trime/ime/bar/QuickBar.kt +++ b/app/src/main/java/com/osfans/trime/ime/bar/QuickBar.kt @@ -54,7 +54,7 @@ class QuickBar( private val prefs = AppPrefs.defaultInstance() private val showSwitches by prefs.keyboard.showSchemaSwitches - private val hideQuickBar by prefs.candidates.hideQuickBar + private val hideQuickBar by prefs.keyboard.hideQuickBar val themedHeight = theme.generalStyle.candidateViewHeight + theme.generalStyle.commentHeight @@ -181,14 +181,12 @@ class QuickBar( val view by lazy { ViewAnimator(context).apply { - rime.launchOnReady { - visibility = - if (hideQuickBar && candidatesMode == PopupCandidatesMode.ALWAYS_SHOW) { - View.GONE - } else { - View.VISIBLE - } - } + visibility = + if (hideQuickBar) { + View.GONE + } else { + View.VISIBLE + } background = ColorManager.getDrawable( "candidate_background", diff --git a/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt b/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt index 96e4c11a2c..c742256954 100644 --- a/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt +++ b/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt @@ -48,6 +48,8 @@ import com.osfans.trime.daemon.RimeDaemon import com.osfans.trime.daemon.RimeSession import com.osfans.trime.data.db.DraftHelper import com.osfans.trime.data.prefs.AppPrefs +import com.osfans.trime.data.prefs.PreferenceDelegate +import com.osfans.trime.data.prefs.PreferenceDelegateProvider import com.osfans.trime.data.theme.ColorManager import com.osfans.trime.data.theme.Theme import com.osfans.trime.data.theme.ThemeManager @@ -98,6 +100,25 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { var lastCommittedText: CharSequence = "" private set + private val recreateInputViewPrefs: Array> = + arrayOf( + prefs.keyboard.showSchemaSwitches, + prefs.keyboard.showArrowInSwitches, + prefs.keyboard.hideQuickBar, + ) + + @Keep + private val recreateInputViewListener = + PreferenceDelegate.OnChangeListener { _, _ -> + replaceInputView(ThemeManager.activeTheme) + } + + @Keep + private val recreateCandidatesViewListener = + PreferenceDelegateProvider.OnChangeListener { + replaceCandidateView(ThemeManager.activeTheme) + } + @Keep private val onThemeChangeListener = ThemeManager.OnThemeChangeListener { @@ -107,7 +128,9 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { @Keep private val onColorChangeListener = ColorManager.OnColorChangeListener { - replaceInputViews(it) + ContextCompat.getMainExecutor(this).execute { + replaceInputViews(it) + } } private fun postJob( @@ -174,6 +197,10 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { handleRimeMessage(it) } } + recreateInputViewPrefs.forEach { + it.registerOnChangeListener(recreateInputViewListener) + } + prefs.candidates.registerOnChangeListener(recreateCandidatesViewListener) ThemeManager.addOnChangedListener(onThemeChangeListener) ColorManager.addOnChangedListener(onColorChangeListener) super.onCreate() @@ -311,6 +338,10 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { mIntentReceiver = null InputFeedbackManager.destroy() inputView = null + recreateInputViewPrefs.forEach { + it.unregisterOnChangeListener(recreateInputViewListener) + } + prefs.candidates.unregisterOnChangeListener(recreateCandidatesViewListener) ThemeManager.removeOnChangedListener(onThemeChangeListener) ColorManager.removeOnChangedListener(onColorChangeListener) super.onDestroy() diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 6b409a1de9..1543e29736 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -233,5 +233,5 @@ SPDX-License-Identifier: GPL-3.0-or-later 候选词窗口位置 部署失败 部署失败。点此查看错误日志。 - 始终显示候选词窗口时隐藏快捷栏 + 隐藏快捷栏 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index da5b6e117c..58138bc0be 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -233,5 +233,5 @@ SPDX-License-Identifier: GPL-3.0-or-later 候選詞視窗位置 部署失敗 部署失敗。點此檢視錯誤日誌。 - 始終顯示候選詞視窗時隱藏快捷欄 + 隱藏快捷欄 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a93c5c42d4..9806e9ba7a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -233,5 +233,5 @@ SPDX-License-Identifier: GPL-3.0-or-later Candidates window position Deploy failure Deploy failure. Click here to view error log. - Hide quick bar when always show candidates window + Hide quick bar diff --git a/app/src/main/res/xml/keyboard_preference.xml b/app/src/main/res/xml/keyboard_preference.xml index f1ecb04aa8..3da7c92a85 100644 --- a/app/src/main/res/xml/keyboard_preference.xml +++ b/app/src/main/res/xml/keyboard_preference.xml @@ -74,6 +74,11 @@ SPDX-License-Identifier: GPL-3.0-or-later android:title="@string/show_arrow_in_switches" app:dependency="show_schema_switches_in_idle" app:iconSpaceReserved="false" /> +