Skip to content

Commit

Permalink
feat: notify on preference changes
Browse files Browse the repository at this point in the history
  • Loading branch information
WhiredPlanck committed Jan 29, 2025
1 parent cf80466 commit dec905d
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 51 deletions.
17 changes: 15 additions & 2 deletions app/src/main/java/com/osfans/trime/data/prefs/AppPrefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}

/**
Expand Down
94 changes: 58 additions & 36 deletions app/src/main/java/com/osfans/trime/data/prefs/PreferenceDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -14,52 +15,45 @@ open class PreferenceDelegate<T : Any>(
val key: String,
val defaultValue: T,
) : ReadWriteProperty<Any?, T> {
@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<String>)
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())
}
}
}

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<T : Any> {
fun serialize(t: T): String
Expand All @@ -73,24 +67,52 @@ open class PreferenceDelegate<T : Any>(
defaultValue: T,
private val serializer: Serializer<T>,
) : PreferenceDelegate<T>(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<in T : Any> {
fun onChange(
key: String,
value: T,
)
}

private lateinit var listeners: MutableSet<OnChangeListener<T>>

/**
* **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<T>) {
if (!::listeners.isInitialized) {
listeners = WeakHashSet()
}
listeners.add(listener)
}

fun unregisterOnChangeListener(listener: OnChangeListener<T>) {
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) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, PreferenceDelegate<*>> = mutableMapOf()
Expand All @@ -20,6 +21,26 @@ abstract class PreferenceDelegateProvider {
open fun createUi(screen: PreferenceScreen) {
}

fun interface OnChangeListener {
fun onChange(key: String)
}

private val onChangeListeners = WeakHashSet<OnChangeListener>()

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)
}
Expand Down
16 changes: 7 additions & 9 deletions app/src/main/java/com/osfans/trime/ime/bar/QuickBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -98,6 +100,25 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
var lastCommittedText: CharSequence = ""
private set

private val recreateInputViewPrefs: Array<PreferenceDelegate<*>> =
arrayOf(
prefs.keyboard.showSchemaSwitches,
prefs.keyboard.showArrowInSwitches,
prefs.keyboard.hideQuickBar,
)

@Keep
private val recreateInputViewListener =
PreferenceDelegate.OnChangeListener<Any> { _, _ ->
replaceInputView(ThemeManager.activeTheme)
}

@Keep
private val recreateCandidatesViewListener =
PreferenceDelegateProvider.OnChangeListener {
replaceCandidateView(ThemeManager.activeTheme)
}

@Keep
private val onThemeChangeListener =
ThemeManager.OnThemeChangeListener {
Expand All @@ -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(
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,5 @@ SPDX-License-Identifier: GPL-3.0-or-later
<string name="candidates_window_position">候选词窗口位置</string>
<string name="deploy_failure">部署失败</string>
<string name="view_deploy_failure_log">部署失败。点此查看错误日志。</string>
<string name="hide_quick_bar_when_always_show">始终显示候选词窗口时隐藏快捷栏</string>
<string name="hide_quick_bar">隐藏快捷栏</string>
</resources>
2 changes: 1 addition & 1 deletion app/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,5 @@ SPDX-License-Identifier: GPL-3.0-or-later
<string name="candidates_window_position">候選詞視窗位置</string>
<string name="deploy_failure">部署失敗</string>
<string name="view_deploy_failure_log">部署失敗。點此檢視錯誤日誌。</string>
<string name="hide_quick_bar_when_always_show">始終顯示候選詞視窗時隱藏快捷欄</string>
<string name="hide_quick_bar">隱藏快捷欄</string>
</resources>
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,5 @@ SPDX-License-Identifier: GPL-3.0-or-later
<string name="candidates_window_position">Candidates window position</string>
<string name="deploy_failure">Deploy failure</string>
<string name="view_deploy_failure_log">Deploy failure. Click here to view error log.</string>
<string name="hide_quick_bar_when_always_show">Hide quick bar when always show candidates window</string>
<string name="hide_quick_bar">Hide quick bar</string>
</resources>
5 changes: 5 additions & 0 deletions app/src/main/res/xml/keyboard_preference.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="hide_quick_bar"
android:title="@string/hide_quick_bar"
app:iconSpaceReserved="false" />
</PreferenceCategory>

<PreferenceCategory
Expand Down

0 comments on commit dec905d

Please sign in to comment.