3204: Add an account based preference store (#3205)

* 3204: Add an account based preference store

* 3204: (related) reformat a bit, add todo

* 3204: Use the preference data store for all three account settings

* 3204: Move event handling to account settings handler

* 3204: Correct includes

* 3204: Appease linter

* 3204: Appease linter again

* 3204: Add an account based preference store

* 3204: Use the preference data store for all three account settings

* 3204: Move event handling to account settings handler

* 3204: Correct includes

* 3204: Add general "preference upgrade loop stepper"; use it for removing obsolete account settings (in shared)

* 3204: Add missing spaces

* 3204: Key is non-nullable

* 3204: Upgrade to new settings migration code

* 3204: Remove (commented) DI code
This commit is contained in:
UlrichKu 2023-02-27 14:07:28 +01:00 committed by GitHub
parent daa67632df
commit b4d10c9613
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 45 deletions

View file

@ -69,9 +69,8 @@ class TuskyApplication : Application(), HasAndroidInjector {
AppInjector.init(this) AppInjector.init(this)
// Migrate shared preference keys and defaults from version to version. The last // Migrate shared preference keys and defaults from version to version.
// version that did not have a SCHEMA_VERSION was 100, so that's the default. val oldVersion = sharedPreferences.getInt(PrefKeys.SCHEMA_VERSION, 0)
val oldVersion = sharedPreferences.getInt(PrefKeys.SCHEMA_VERSION, 100)
if (oldVersion != SCHEMA_VERSION) { if (oldVersion != SCHEMA_VERSION) {
upgradeSharedPreferences(oldVersion, SCHEMA_VERSION) upgradeSharedPreferences(oldVersion, SCHEMA_VERSION)
} }
@ -105,7 +104,13 @@ class TuskyApplication : Application(), HasAndroidInjector {
Log.d(TAG, "Upgrading shared preferences: $oldVersion -> $newVersion") Log.d(TAG, "Upgrading shared preferences: $oldVersion -> $newVersion")
val editor = sharedPreferences.edit() val editor = sharedPreferences.edit()
// Future upgrade code goes here if (oldVersion < 2023022701) {
// These preferences are (now) handled in AccountPreferenceHandler. Remove them from shared for clarity.
editor.remove(PrefKeys.ALWAYS_OPEN_SPOILER)
editor.remove(PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA)
editor.remove(PrefKeys.MEDIA_PREVIEW_ENABLED)
}
editor.putInt(PrefKeys.SCHEMA_VERSION, newVersion) editor.putInt(PrefKeys.SCHEMA_VERSION, newVersion)
editor.apply() editor.apply()

View file

@ -36,13 +36,13 @@ import com.keylesspalace.tusky.components.followedtags.FollowedTagsActivity
import com.keylesspalace.tusky.components.instancemute.InstanceListActivity import com.keylesspalace.tusky.components.instancemute.InstanceListActivity
import com.keylesspalace.tusky.components.login.LoginActivity import com.keylesspalace.tusky.components.login.LoginActivity
import com.keylesspalace.tusky.components.notifications.currentAccountNeedsMigration import com.keylesspalace.tusky.components.notifications.currentAccountNeedsMigration
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.settings.AccountPreferenceHandler
import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.settings.listPreference import com.keylesspalace.tusky.settings.listPreference
import com.keylesspalace.tusky.settings.makePreferenceScreen import com.keylesspalace.tusky.settings.makePreferenceScreen
@ -85,7 +85,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
colorInt = MaterialColors.getColor(context, R.attr.iconColor, Color.BLACK) colorInt = MaterialColors.getColor(context, R.attr.iconColor, Color.BLACK)
} }
setOnPreferenceClickListener { setOnPreferenceClickListener {
openNotificationPrefs() openNotificationSystemPrefs()
true true
} }
} }
@ -184,8 +184,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
setEntryValues(R.array.post_privacy_values) setEntryValues(R.array.post_privacy_values)
key = PrefKeys.DEFAULT_POST_PRIVACY key = PrefKeys.DEFAULT_POST_PRIVACY
setSummaryProvider { entry } setSummaryProvider { entry }
val visibility = accountManager.activeAccount?.defaultPostPrivacy val visibility = accountManager.activeAccount?.defaultPostPrivacy ?: Status.Visibility.PUBLIC
?: Status.Visibility.PUBLIC
value = visibility.serverString() value = visibility.serverString()
setIcon(getIconForVisibility(visibility)) setIcon(getIconForVisibility(visibility))
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -224,8 +223,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
setIcon(R.drawable.ic_eye_24dp) setIcon(R.drawable.ic_eye_24dp)
key = PrefKeys.DEFAULT_MEDIA_SENSITIVITY key = PrefKeys.DEFAULT_MEDIA_SENSITIVITY
isSingleLineTitle = false isSingleLineTitle = false
val sensitivity = accountManager.activeAccount?.defaultMediaSensitivity val sensitivity = accountManager.activeAccount?.defaultMediaSensitivity ?: false
?: false
setDefaultValue(sensitivity) setDefaultValue(sensitivity)
setIcon(getIconForSensitivity(sensitivity)) setIcon(getIconForSensitivity(sensitivity))
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -238,40 +236,29 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
} }
preferenceCategory(R.string.pref_title_timelines) { preferenceCategory(R.string.pref_title_timelines) {
// TODO having no activeAccount in this fragment does not really make sense, enforce it?
// All other locations here make it optional, however.
val accountPreferenceHandler = AccountPreferenceHandler(accountManager.activeAccount!!, accountManager, eventHub)
switchPreference { switchPreference {
key = PrefKeys.MEDIA_PREVIEW_ENABLED key = PrefKeys.MEDIA_PREVIEW_ENABLED
setTitle(R.string.pref_title_show_media_preview) setTitle(R.string.pref_title_show_media_preview)
isSingleLineTitle = false isSingleLineTitle = false
isChecked = accountManager.activeAccount?.mediaPreviewEnabled ?: true preferenceDataStore = accountPreferenceHandler
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.mediaPreviewEnabled = newValue as Boolean }
eventHub.dispatch(PreferenceChangedEvent(key))
true
}
} }
switchPreference { switchPreference {
key = PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA key = PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA
setTitle(R.string.pref_title_alway_show_sensitive_media) setTitle(R.string.pref_title_alway_show_sensitive_media)
isSingleLineTitle = false isSingleLineTitle = false
isChecked = accountManager.activeAccount?.alwaysShowSensitiveMedia ?: false preferenceDataStore = accountPreferenceHandler
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.alwaysShowSensitiveMedia = newValue as Boolean }
eventHub.dispatch(PreferenceChangedEvent(key))
true
}
} }
switchPreference { switchPreference {
key = PrefKeys.ALWAYS_OPEN_SPOILER key = PrefKeys.ALWAYS_OPEN_SPOILER
setTitle(R.string.pref_title_alway_open_spoiler) setTitle(R.string.pref_title_alway_open_spoiler)
isSingleLineTitle = false isSingleLineTitle = false
isChecked = accountManager.activeAccount?.alwaysOpenSpoiler ?: false preferenceDataStore = accountPreferenceHandler
setOnPreferenceChangeListener { _, newValue ->
updateAccount { it.alwaysOpenSpoiler = newValue as Boolean }
eventHub.dispatch(PreferenceChangedEvent(key))
true
}
} }
} }
@ -279,10 +266,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
preference { preference {
setTitle(R.string.pref_title_public_filter_keywords) setTitle(R.string.pref_title_public_filter_keywords)
setOnPreferenceClickListener { setOnPreferenceClickListener {
launchFilterActivity( launchFilterActivity(Filter.PUBLIC, R.string.pref_title_public_filter_keywords)
Filter.PUBLIC,
R.string.pref_title_public_filter_keywords
)
true true
} }
} }
@ -306,10 +290,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
preference { preference {
setTitle(R.string.pref_title_thread_filter_keywords) setTitle(R.string.pref_title_thread_filter_keywords)
setOnPreferenceClickListener { setOnPreferenceClickListener {
launchFilterActivity( launchFilterActivity(Filter.THREAD, R.string.pref_title_thread_filter_keywords)
Filter.THREAD,
R.string.pref_title_thread_filter_keywords
)
true true
} }
} }
@ -330,7 +311,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
requireActivity().setTitle(R.string.action_view_account_preferences) requireActivity().setTitle(R.string.action_view_account_preferences)
} }
private fun openNotificationPrefs() { private fun openNotificationSystemPrefs() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val intent = Intent() val intent = Intent()
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS" intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
@ -345,14 +326,9 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
} }
} }
private inline fun updateAccount(changer: (AccountEntity) -> Unit) {
accountManager.activeAccount?.let { account ->
changer(account)
accountManager.saveAccount(account)
}
}
private fun syncWithServer(visibility: String? = null, sensitive: Boolean? = null, language: String? = null) { private fun syncWithServer(visibility: String? = null, sensitive: Boolean? = null, language: String? = null) {
// TODO these could also be "datastore backed" preferences (a ServerPreferenceDataStore); follow-up of issue #3204
mastodonApi.accountUpdateSource(visibility, sensitive, language) mastodonApi.accountUpdateSource(visibility, sensitive, language)
.enqueue(object : Callback<Account> { .enqueue(object : Callback<Account> {
override fun onResponse(call: Call<Account>, response: Response<Account>) { override fun onResponse(call: Call<Account>, response: Response<Account>) {

View file

@ -0,0 +1,35 @@
package com.keylesspalace.tusky.settings
import androidx.preference.PreferenceDataStore
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
class AccountPreferenceHandler(
private val account: AccountEntity,
private val accountManager: AccountManager,
private val eventHub: EventHub,
) : PreferenceDataStore() {
override fun getBoolean(key: String, defValue: Boolean): Boolean {
return when (key) {
PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA -> account.alwaysShowSensitiveMedia
PrefKeys.ALWAYS_OPEN_SPOILER -> account.alwaysOpenSpoiler
PrefKeys.MEDIA_PREVIEW_ENABLED -> account.mediaPreviewEnabled
else -> defValue
}
}
override fun putBoolean(key: String, value: Boolean) {
when (key) {
PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA -> account.alwaysShowSensitiveMedia = value
PrefKeys.ALWAYS_OPEN_SPOILER -> account.alwaysOpenSpoiler = value
PrefKeys.MEDIA_PREVIEW_ENABLED -> account.mediaPreviewEnabled = value
}
accountManager.saveAccount(account)
eventHub.dispatch(PreferenceChangedEvent(key))
}
}

View file

@ -41,7 +41,7 @@ enum class AppTheme(val value: String) {
* *
* - Adding a new preference that does not change the interpretation of an existing preference * - Adding a new preference that does not change the interpretation of an existing preference
*/ */
const val SCHEMA_VERSION = 2023021501 const val SCHEMA_VERSION = 2023022701
object PrefKeys { object PrefKeys {
// Note: not all of these keys are actually used as SharedPreferences keys but we must give // Note: not all of these keys are actually used as SharedPreferences keys but we must give