From 45e9770667172e0d17d43d031b3b6022f1e58660 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 7 Apr 2026 17:13:40 +0200 Subject: [PATCH 1/3] feat(settings): Animation setting inherits systemwide settings Convert the "Use gaudy visual effects" boolean checkbox to a tri-state option (On/Off/Use system default) that defaults to following Android's system animation accessibility setting. Similar to ThemeManager, introduce a new AnimationManager and funnel all access to this option through there. I'm on the fence as to whether that isn't too much overkill, IMO it would make sense to merge them both into one "settings manager". Right now there is only one place where the setting is being read, in ViewSwitcher. However, there are many other places where the setting is supposed to be respected but isn't (sidebar, messagetopview #10796) so I figured it's better to centralize the resolution outside of ViewSwitcher. --- .../core/preference/GeneralSettings.kt | 6 +++++ .../visualSettings/DisplayVisualSettings.kt | 5 ++-- ...tDisplayVisualSettingsPreferenceManager.kt | 6 ++--- core/ui/animation/manager/build.gradle.kts | 11 ++++++++ .../ui/animation/manager/AnimationManager.kt | 21 +++++++++++++++ .../GeneralSettingsDescriptions.java | 8 ++++-- .../com/fsck/k9/preferences/Settings.java | 2 +- .../GeneralSettingsUpgraderTo111.java | 25 ++++++++++++++++++ .../values/arrays_general_settings_values.xml | 6 +++++ .../k9/preferences/K9StoragePersister.java | 2 +- .../migration/StorageMigrationTo30.kt | 26 +++++++++++++++++++ .../migration/StorageMigrations.kt | 1 + legacy/ui/base/build.gradle.kts | 1 + .../java/com/fsck/k9/ui/base/KoinModule.kt | 5 ++++ .../general/GeneralSettingsDataStore.kt | 22 +++++++++++++--- .../java/com/fsck/k9/view/ViewSwitcher.java | 20 +++++++------- .../arrays_general_settings_strings.xml | 6 +++++ .../ui/legacy/src/main/res/values/strings.xml | 3 +++ .../src/main/res/xml/general_settings.xml | 7 +++-- settings.gradle.kts | 1 + 20 files changed, 159 insertions(+), 25 deletions(-) create mode 100644 core/ui/animation/manager/build.gradle.kts create mode 100644 core/ui/animation/manager/src/main/kotlin/net/thunderbird/core/ui/animation/manager/AnimationManager.kt create mode 100644 legacy/core/src/main/java/com/fsck/k9/preferences/upgrader/GeneralSettingsUpgraderTo111.java create mode 100644 legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrationTo30.kt diff --git a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettings.kt b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettings.kt index 3ae144259a7..16c50fe669a 100644 --- a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettings.kt +++ b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettings.kt @@ -81,3 +81,9 @@ enum class LockScreenNotificationVisibility { APP_NAME, NOTHING, } + +enum class AnimationPreference { + ON, + OFF, + FOLLOW_SYSTEM, +} diff --git a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/display/visualSettings/DisplayVisualSettings.kt b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/display/visualSettings/DisplayVisualSettings.kt index 67f59f444f2..c9ce03da823 100644 --- a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/display/visualSettings/DisplayVisualSettings.kt +++ b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/display/visualSettings/DisplayVisualSettings.kt @@ -1,11 +1,12 @@ package net.thunderbird.core.preference.display.visualSettings +import net.thunderbird.core.preference.AnimationPreference import net.thunderbird.core.preference.BodyContentType import net.thunderbird.core.preference.display.visualSettings.message.list.DisplayMessageListSettings const val DISPLAY_SETTINGS_DEFAULT_IS_USE_MESSAGE_VIEW_FIXED_WIDTH_FONT = false const val DISPLAY_SETTINGS_DEFAULT_IS_AUTO_FIT_WIDTH = true -const val DISPLAY_SETTINGS_DEFAULT_IS_SHOW_ANIMATION = true +val DISPLAY_SETTINGS_DEFAULT_ANIMATION_PREFERENCE = AnimationPreference.FOLLOW_SYSTEM val DISPLAY_SETTINGS_DEFAULT_BODY_CONTENT_TYPE = BodyContentType.TEXT_HTML const val DISPLAY_SETTINGS_DEFAULT_DRAWER_EXPAND_ALL_FOLDER = false const val DISPLAY_SETTINGS_DEFAULT_MESSAGE_VIEW_ARCHIVE_ACTION_VISIBLE = false @@ -15,7 +16,7 @@ const val DISPLAY_SETTINGS_DEFAULT_MESSAGE_VIEW_COPY_ACTION_VISIBLE = false const val DISPLAY_SETTINGS_DEFAULT_MESSAGE_VIEW_SPAM_ACTION_VISIBLE = false data class DisplayVisualSettings( - val isShowAnimations: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_SHOW_ANIMATION, + val animationPreference: AnimationPreference = DISPLAY_SETTINGS_DEFAULT_ANIMATION_PREFERENCE, val isUseMessageViewFixedWidthFont: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_USE_MESSAGE_VIEW_FIXED_WIDTH_FONT, val isAutoFitWidth: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_AUTO_FIT_WIDTH, val bodyContentType: BodyContentType = DISPLAY_SETTINGS_DEFAULT_BODY_CONTENT_TYPE, diff --git a/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/display/visualSettings/DefaultDisplayVisualSettingsPreferenceManager.kt b/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/display/visualSettings/DefaultDisplayVisualSettingsPreferenceManager.kt index 594129673bb..4b482bbda12 100644 --- a/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/display/visualSettings/DefaultDisplayVisualSettingsPreferenceManager.kt +++ b/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/display/visualSettings/DefaultDisplayVisualSettingsPreferenceManager.kt @@ -65,9 +65,9 @@ class DefaultDisplayVisualSettingsPreferenceManager( KEY_AUTO_FIT_WIDTH, DISPLAY_SETTINGS_DEFAULT_IS_AUTO_FIT_WIDTH, ), - isShowAnimations = storage.getBoolean( + animationPreference = storage.getEnumOrDefault( KEY_ANIMATION, - DISPLAY_SETTINGS_DEFAULT_IS_SHOW_ANIMATION, + DISPLAY_SETTINGS_DEFAULT_ANIMATION_PREFERENCE, ), bodyContentType = storage.getEnumOrDefault( KEY_MESSAGE_VIEW_BODY_CONTENT_TYPE, @@ -103,7 +103,7 @@ class DefaultDisplayVisualSettingsPreferenceManager( logger.debug(TAG) { "writeConfig() called with: config = $config" } scope.launch(ioDispatcher) { mutex.withLock { - storageEditor.putBoolean(KEY_ANIMATION, config.isShowAnimations) + storageEditor.putEnum(KEY_ANIMATION, config.animationPreference) storageEditor.putBoolean( KEY_MESSAGE_VIEW_FIXED_WIDTH_FONT, config.isUseMessageViewFixedWidthFont, diff --git a/core/ui/animation/manager/build.gradle.kts b/core/ui/animation/manager/build.gradle.kts new file mode 100644 index 00000000000..f6810eb26cc --- /dev/null +++ b/core/ui/animation/manager/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id(ThunderbirdPlugins.Library.android) +} + +android { + namespace = "net.thunderbird.core.ui.animation.manager" +} + +dependencies { + implementation(projects.core.preference.api) +} diff --git a/core/ui/animation/manager/src/main/kotlin/net/thunderbird/core/ui/animation/manager/AnimationManager.kt b/core/ui/animation/manager/src/main/kotlin/net/thunderbird/core/ui/animation/manager/AnimationManager.kt new file mode 100644 index 00000000000..b60752f8d14 --- /dev/null +++ b/core/ui/animation/manager/src/main/kotlin/net/thunderbird/core/ui/animation/manager/AnimationManager.kt @@ -0,0 +1,21 @@ +package net.thunderbird.core.ui.animation.manager + +import android.animation.ValueAnimator +import net.thunderbird.core.preference.AnimationPreference +import net.thunderbird.core.preference.display.visualSettings.DisplayVisualSettingsPreferenceManager + +interface AnimationManager { + fun shouldShowAnimations(): Boolean +} + +class DefaultAnimationManager( + private val visualSettingsPreferenceManager: DisplayVisualSettingsPreferenceManager, +) : AnimationManager { + override fun shouldShowAnimations(): Boolean { + return when (visualSettingsPreferenceManager.getConfig().animationPreference) { + AnimationPreference.ON -> true + AnimationPreference.OFF -> false + AnimationPreference.FOLLOW_SYSTEM -> ValueAnimator.areAnimatorsEnabled() + } + } +} diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java b/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java index 6de2a3f845b..f6eb86ef6e1 100644 --- a/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java +++ b/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java @@ -30,9 +30,11 @@ import com.fsck.k9.preferences.upgrader.GeneralSettingsUpgraderTo69; import com.fsck.k9.preferences.upgrader.GeneralSettingsUpgraderTo79; import com.fsck.k9.preferences.upgrader.GeneralSettingsUpgraderTo89; +import com.fsck.k9.preferences.upgrader.GeneralSettingsUpgraderTo111; import net.thunderbird.core.android.account.AccountDefaultsProvider; import net.thunderbird.core.android.account.SortType; import net.thunderbird.core.common.action.SwipeAction; +import net.thunderbird.core.preference.AnimationPreference; import net.thunderbird.core.preference.AppTheme; import net.thunderbird.core.preference.BackgroundOps; import net.thunderbird.core.preference.GeneralSettingsManager; @@ -42,6 +44,7 @@ import net.thunderbird.core.preference.SplitViewMode; import net.thunderbird.core.preference.SubTheme; import net.thunderbird.core.preference.display.coreSettings.DisplayCoreSettingsKt; +import net.thunderbird.core.preference.display.visualSettings.DisplayVisualSettingsKt; import net.thunderbird.core.preference.display.visualSettings.message.list.MessageListDateTimeFormat; import net.thunderbird.core.preference.interaction.PostRemoveNavigation; import net.thunderbird.core.preference.network.NetworkSettingsKt; @@ -57,7 +60,6 @@ import static net.thunderbird.core.preference.display.miscSettings.DisplayMiscSettingsKt.DISPLAY_SETTINGS_DEFAULT_SHOW_RECENT_CHANGES; import static net.thunderbird.core.preference.display.visualSettings.DisplayVisualSettingsKt.DISPLAY_SETTINGS_DEFAULT_IS_AUTO_FIT_WIDTH; import static net.thunderbird.core.preference.display.visualSettings.message.list.DisplayMessageListSettingsKt.MESSAGE_LIST_SETTINGS_DEFAULT_IS_CHANGE_CONTACT_NAME_COLOR; -import static net.thunderbird.core.preference.display.visualSettings.DisplayVisualSettingsKt.DISPLAY_SETTINGS_DEFAULT_IS_SHOW_ANIMATION; import static net.thunderbird.core.preference.display.visualSettings.message.list.DisplayMessageListSettingsKt.MESSAGE_LIST_SETTINGS_DEFAULT_IS_SHOW_CONTACT_NAME; import static net.thunderbird.core.preference.display.visualSettings.message.list.DisplayMessageListSettingsKt.MESSAGE_LIST_SETTINGS_DEFAULT_IS_SHOW_CONTACT_PICTURE; import static net.thunderbird.core.preference.display.visualSettings.message.list.DisplayMessageListSettingsKt.MESSAGE_LIST_SETTINGS_DEFAULT_IS_SHOW_CORRESPONDENT_NAMES; @@ -85,7 +87,8 @@ class GeneralSettingsDescriptions { */ s.put("animations", Settings.versions( - new V(1, new BooleanSetting(DISPLAY_SETTINGS_DEFAULT_IS_SHOW_ANIMATION)) + new V(1, new BooleanSetting(true)), + new V(111, new EnumSetting<>(AnimationPreference.class, AnimationPreference.FOLLOW_SYSTEM)) )); s.put("backgroundOperations", Settings.versions( new V(1, new EnumSetting<>(BackgroundOps.class, BackgroundOps.WHEN_CHECKED_AUTO_SYNC)), @@ -361,6 +364,7 @@ class GeneralSettingsDescriptions { u.put(69, new GeneralSettingsUpgraderTo69()); u.put(79, new GeneralSettingsUpgraderTo79()); u.put(89, new GeneralSettingsUpgraderTo89()); + u.put(111, new GeneralSettingsUpgraderTo111()); UPGRADERS = Collections.unmodifiableMap(u); } diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java b/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java index 3a740feea59..ade2857250d 100644 --- a/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java +++ b/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java @@ -35,7 +35,7 @@ public class Settings { * * @see SettingsExporter */ - public static final int VERSION = 110; + public static final int VERSION = 111; static Map validate(int version, Map>> settings, Map importedSettings, boolean useDefaultValues) { diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/upgrader/GeneralSettingsUpgraderTo111.java b/legacy/core/src/main/java/com/fsck/k9/preferences/upgrader/GeneralSettingsUpgraderTo111.java new file mode 100644 index 00000000000..fd0435b0bbd --- /dev/null +++ b/legacy/core/src/main/java/com/fsck/k9/preferences/upgrader/GeneralSettingsUpgraderTo111.java @@ -0,0 +1,25 @@ +package com.fsck.k9.preferences.upgrader; + + +import java.util.Map; + +import com.fsck.k9.preferences.SettingsUpgrader; +import net.thunderbird.core.preference.AnimationPreference; + + +/** + * Convert animations from boolean to {@link AnimationPreference} enum. + * + * {@code true} maps to {@link AnimationPreference#ON}, {@code false} maps to {@link AnimationPreference#OFF}. + */ +public class GeneralSettingsUpgraderTo111 implements SettingsUpgrader { + + @Override + public void upgrade(Map settings) { + Object animations = settings.get("animations"); + if (animations instanceof Boolean) { + boolean value = (Boolean) animations; + settings.put("animations", value ? AnimationPreference.ON : AnimationPreference.OFF); + } + } +} diff --git a/legacy/core/src/main/res/values/arrays_general_settings_values.xml b/legacy/core/src/main/res/values/arrays_general_settings_values.xml index 28137a771f7..09c7515dee2 100644 --- a/legacy/core/src/main/res/values/arrays_general_settings_values.xml +++ b/legacy/core/src/main/res/values/arrays_general_settings_values.xml @@ -152,6 +152,12 @@ follow_system + + ON + OFF + FOLLOW_SYSTEM + + light dark diff --git a/legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java b/legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java index ccd0bcda357..247f62fa95f 100644 --- a/legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java +++ b/legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java @@ -23,7 +23,7 @@ import net.thunderbird.core.preference.storage.StorageUpdater; public class K9StoragePersister implements StoragePersister { - private static final int DB_VERSION = 29; + private static final int DB_VERSION = 30; private static final String DB_NAME = "preferences_storage"; private final Context context; diff --git a/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrationTo30.kt b/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrationTo30.kt new file mode 100644 index 00000000000..f5982e8ec22 --- /dev/null +++ b/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrationTo30.kt @@ -0,0 +1,26 @@ +package com.fsck.k9.preferences.migration + +import android.database.sqlite.SQLiteDatabase + +private const val ANIMATION_KEY = "animations" + +/** + * Migrate the "animations" setting from boolean string to AnimationPreference enum name. + * + * - "true" -> "ON" (user explicitly enabled animations) + * - "false" -> "OFF" (user explicitly disabled animations) + * - missing -> no change (new default FOLLOW_SYSTEM will apply) + */ +class StorageMigrationTo30( + private val db: SQLiteDatabase, + private val migrationsHelper: StorageMigrationHelper, +) { + fun migrateAnimationSetting() { + val currentValue = migrationsHelper.readValue(db, ANIMATION_KEY) + when (currentValue) { + "true" -> migrationsHelper.writeValue(db, ANIMATION_KEY, "ON") + "false" -> migrationsHelper.writeValue(db, ANIMATION_KEY, "OFF") + // If missing or already migrated, do nothing + } + } +} diff --git a/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt b/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt index b47081eb249..b6dc4f600fd 100644 --- a/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt +++ b/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt @@ -36,5 +36,6 @@ internal object StorageMigrations { if (oldVersion < 27) StorageMigrationTo27(db, migrationsHelper).addAvatarMonogram() if (oldVersion < 28) StorageMigrationTo28(db, migrationsHelper).ensureAvatarSet() if (oldVersion < 29) StorageMigrationTo29(db, migrationsHelper).renameAutoSelectFolderPreference() + if (oldVersion < 30) StorageMigrationTo30(db, migrationsHelper).migrateAnimationSetting() } } diff --git a/legacy/ui/base/build.gradle.kts b/legacy/ui/base/build.gradle.kts index 607e8c9b3e4..9e0e6708ce0 100644 --- a/legacy/ui/base/build.gradle.kts +++ b/legacy/ui/base/build.gradle.kts @@ -6,6 +6,7 @@ dependencies { implementation(projects.legacy.core) api(projects.core.ui.theme.manager) + api(projects.core.ui.animation.manager) api(libs.androidx.appcompat) api(libs.androidx.activity) diff --git a/legacy/ui/base/src/main/java/com/fsck/k9/ui/base/KoinModule.kt b/legacy/ui/base/src/main/java/com/fsck/k9/ui/base/KoinModule.kt index bf5f3fff9cb..aceebcd6357 100644 --- a/legacy/ui/base/src/main/java/com/fsck/k9/ui/base/KoinModule.kt +++ b/legacy/ui/base/src/main/java/com/fsck/k9/ui/base/KoinModule.kt @@ -1,6 +1,8 @@ package com.fsck.k9.ui.base import com.fsck.k9.ui.base.locale.SystemLocaleManager +import net.thunderbird.core.ui.animation.manager.AnimationManager +import net.thunderbird.core.ui.animation.manager.DefaultAnimationManager import net.thunderbird.core.ui.theme.manager.ThemeManager import org.koin.core.qualifier.named import org.koin.dsl.bind @@ -16,6 +18,9 @@ val uiBaseModule = module { appCoroutineScope = get(named("AppCoroutineScope")), ) } bind ThemeManagerApi::class + single { + DefaultAnimationManager(visualSettingsPreferenceManager = get()) + } single { AppLanguageManager(systemLocaleManager = get(), displayCoreSettingsPreferenceManager = get()) } single { SystemLocaleManager(context = get()) } } diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt index f85704b2ee8..7254d4037a2 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt @@ -8,6 +8,7 @@ import com.fsck.k9.job.K9JobManager import com.fsck.k9.ui.base.AppLanguageManager import net.thunderbird.core.common.action.SwipeAction import net.thunderbird.core.common.action.SwipeActions +import net.thunderbird.core.preference.AnimationPreference import net.thunderbird.core.preference.AppTheme import net.thunderbird.core.preference.BackgroundOps import net.thunderbird.core.preference.BodyContentType @@ -42,7 +43,6 @@ class GeneralSettingsDataStore( val messageListSettings = visualSettings.messageListSettings return when (key) { "fixed_message_view_theme" -> coreSettings.fixedMessageViewTheme - "animations" -> visualSettings.isShowAnimations "show_unified_inbox" -> inboxSettings.isShowUnifiedInbox "show_starred_count" -> inboxSettings.isShowStarredCount "messagelist_stars" -> inboxSettings.isShowMessageListStars @@ -75,7 +75,6 @@ class GeneralSettingsDataStore( override fun putBoolean(key: String, value: Boolean) { when (key) { "fixed_message_view_theme" -> setFixedMessageViewTheme(value) - "animations" -> setIsShowAnimations(isShowAnimations = value) "drawerExpandAllFolder" -> setDrawerExpandAllFolder(drawerExpandAllFolder = value) "show_unified_inbox" -> setIsShowUnifiedInbox(value) "show_starred_count" -> setIsShowStarredCount(isShowStarredCount = value) @@ -148,6 +147,7 @@ class GeneralSettingsDataStore( return when (key) { "language" -> appLanguageManager.getAppLanguage() "theme" -> appThemeToString(coreSettings.appTheme) + "animations" -> animationPreferenceToString(visualSettings.animationPreference) "message_compose_theme" -> subThemeToString(coreSettings.messageComposeTheme) "messageViewTheme" -> subThemeToString(coreSettings.messageViewTheme) "messagelist_preview_lines" -> messageListSettings.previewLines.toString() @@ -188,6 +188,7 @@ class GeneralSettingsDataStore( } "theme" -> setTheme(value) + "animations" -> setAnimationPreference(stringToAnimationPreference(value)) "message_compose_theme" -> setMessageComposeTheme(value) "messageViewTheme" -> setMessageViewTheme(value) "messagelist_preview_lines" -> setMessageListPreviewLines(value.toInt()) @@ -428,13 +429,13 @@ class GeneralSettingsDataStore( } } - private fun setIsShowAnimations(isShowAnimations: Boolean) { + private fun setAnimationPreference(animationPreference: AnimationPreference) { skipSaveSettings = true generalSettingsManager.update { settings -> settings.copy( display = settings.display.copy( visualSettings = settings.display.visualSettings.copy( - isShowAnimations = isShowAnimations, + animationPreference = animationPreference, ), ), ) @@ -773,6 +774,19 @@ class GeneralSettingsDataStore( else -> throw AssertionError() } + private fun animationPreferenceToString(pref: AnimationPreference) = when (pref) { + AnimationPreference.ON -> "ON" + AnimationPreference.OFF -> "OFF" + AnimationPreference.FOLLOW_SYSTEM -> "FOLLOW_SYSTEM" + } + + private fun stringToAnimationPreference(value: String) = when (value) { + "ON" -> AnimationPreference.ON + "OFF" -> AnimationPreference.OFF + "FOLLOW_SYSTEM" -> AnimationPreference.FOLLOW_SYSTEM + else -> throw AssertionError() + } + private fun setBackgroundOps(value: String) { val newBackgroundOps = BackgroundOps.valueOf(value) if (newBackgroundOps != generalSettingsManager.getConfig().network.backgroundOps) { diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/view/ViewSwitcher.java b/legacy/ui/legacy/src/main/java/com/fsck/k9/view/ViewSwitcher.java index 2008a1a4389..bb72b68cc41 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/view/ViewSwitcher.java +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/view/ViewSwitcher.java @@ -1,15 +1,15 @@ package com.fsck.k9.view; -import app.k9mail.legacy.di.DI; -import net.thunderbird.core.preference.GeneralSettingsManager; - import android.content.Context; import android.util.AttributeSet; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.widget.ViewAnimator; +import app.k9mail.legacy.di.DI; +import net.thunderbird.core.ui.animation.manager.AnimationManager; + /** * A {@link ViewAnimator} that animates between two child views using different animations depending on which view is @@ -22,7 +22,7 @@ public class ViewSwitcher extends ViewAnimator implements AnimationListener { private Animation mSecondOutAnimation; private OnSwitchCompleteListener mListener; - private GeneralSettingsManager generalSettingsManager = DI.get(GeneralSettingsManager.class); + private AnimationManager animationManager = DI.get(AnimationManager.class); public ViewSwitcher(Context context) { @@ -56,7 +56,7 @@ public void showSecondView() { } private void setupAnimations(Animation in, Animation out) { - if (generalSettingsManager.getConfig().getDisplay().getVisualSettings().isShowAnimations()) { + if (animationManager.shouldShowAnimations()) { setInAnimation(in); setOutAnimation(out); out.setAnimationListener(this); @@ -67,7 +67,7 @@ private void setupAnimations(Animation in, Animation out) { } private void handleSwitchCompleteCallback() { - if (!generalSettingsManager.getConfig().getDisplay().getVisualSettings().isShowAnimations()) { + if (!animationManager.shouldShowAnimations()) { onAnimationEnd(null); } } @@ -125,12 +125,12 @@ public void onAnimationStart(Animation animation) { // unused } - public GeneralSettingsManager getGeneralSettingsManager() { - return generalSettingsManager; + public AnimationManager getAnimationManager() { + return animationManager; } - public void setGeneralSettingsManager(GeneralSettingsManager generalSettingsManager) { - this.generalSettingsManager = generalSettingsManager; + public void setAnimationManager(AnimationManager animationManager) { + this.animationManager = animationManager; } public interface OnSwitchCompleteListener { diff --git a/legacy/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml b/legacy/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml index 54e86614d0a..5aff7896b0f 100644 --- a/legacy/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml +++ b/legacy/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml @@ -95,6 +95,12 @@ @string/setting_theme_follow_system + + @string/animation_setting_on + @string/animation_setting_off + @string/animation_setting_follow_system + + @string/setting_theme_light @string/setting_theme_dark diff --git a/legacy/ui/legacy/src/main/res/values/strings.xml b/legacy/ui/legacy/src/main/res/values/strings.xml index f76a11b1b88..ba1060c5662 100644 --- a/legacy/ui/legacy/src/main/res/values/strings.xml +++ b/legacy/ui/legacy/src/main/res/values/strings.xml @@ -701,6 +701,9 @@ Animation Use gaudy visual effects + On + Off + Use system default Volume key navigation Navigate between messages using the volume keys in message view diff --git a/legacy/ui/legacy/src/main/res/xml/general_settings.xml b/legacy/ui/legacy/src/main/res/xml/general_settings.xml index 9287270ec65..54e1776ec18 100644 --- a/legacy/ui/legacy/src/main/res/xml/general_settings.xml +++ b/legacy/ui/legacy/src/main/res/xml/general_settings.xml @@ -192,9 +192,12 @@ - diff --git a/settings.gradle.kts b/settings.gradle.kts index 68d5bea77d2..f43a5e16102 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -211,6 +211,7 @@ include( ":core:ui:navigation", ":core:ui:theme:api", ":core:ui:theme:manager", + ":core:ui:animation:manager", ) include( From 1f97cf98a11cb1eeabfe5dcfff6453416adbfb3f Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 15 Apr 2026 12:26:32 +0200 Subject: [PATCH 2/3] Fix build for pre-O Android --- .../core/ui/animation/manager/AnimationManager.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/ui/animation/manager/src/main/kotlin/net/thunderbird/core/ui/animation/manager/AnimationManager.kt b/core/ui/animation/manager/src/main/kotlin/net/thunderbird/core/ui/animation/manager/AnimationManager.kt index b60752f8d14..1605adaf7f1 100644 --- a/core/ui/animation/manager/src/main/kotlin/net/thunderbird/core/ui/animation/manager/AnimationManager.kt +++ b/core/ui/animation/manager/src/main/kotlin/net/thunderbird/core/ui/animation/manager/AnimationManager.kt @@ -1,6 +1,7 @@ package net.thunderbird.core.ui.animation.manager import android.animation.ValueAnimator +import android.os.Build import net.thunderbird.core.preference.AnimationPreference import net.thunderbird.core.preference.display.visualSettings.DisplayVisualSettingsPreferenceManager @@ -15,7 +16,11 @@ class DefaultAnimationManager( return when (visualSettingsPreferenceManager.getConfig().animationPreference) { AnimationPreference.ON -> true AnimationPreference.OFF -> false - AnimationPreference.FOLLOW_SYSTEM -> ValueAnimator.areAnimatorsEnabled() + AnimationPreference.FOLLOW_SYSTEM -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ValueAnimator.areAnimatorsEnabled() + } else { + true + } } } } From 9df43695c14d6093f1f9fd2014153bf0f50195f5 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 29 Apr 2026 12:53:57 +0200 Subject: [PATCH 3/3] Address review comments --- .../GeneralSettingsUpgraderTo111Test.kt | 52 +++++++++++++ .../migration/StorageMigrationTo30Test.kt | 78 +++++++++++++++++++ .../ui/legacy/src/main/res/values/strings.xml | 1 - 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 legacy/core/src/test/java/com/fsck/k9/preferences/upgrader/GeneralSettingsUpgraderTo111Test.kt create mode 100644 legacy/storage/src/test/java/com/fsck/k9/preferences/migration/StorageMigrationTo30Test.kt diff --git a/legacy/core/src/test/java/com/fsck/k9/preferences/upgrader/GeneralSettingsUpgraderTo111Test.kt b/legacy/core/src/test/java/com/fsck/k9/preferences/upgrader/GeneralSettingsUpgraderTo111Test.kt new file mode 100644 index 00000000000..52156c4b8da --- /dev/null +++ b/legacy/core/src/test/java/com/fsck/k9/preferences/upgrader/GeneralSettingsUpgraderTo111Test.kt @@ -0,0 +1,52 @@ +package com.fsck.k9.preferences.upgrader + +import assertk.assertThat +import assertk.assertions.doesNotContainKey +import assertk.assertions.isEqualTo +import kotlin.test.Test +import net.thunderbird.core.preference.AnimationPreference + +class GeneralSettingsUpgraderTo111Test { + + private val upgrader = GeneralSettingsUpgraderTo111() + + @Test + fun `should convert animations=true to ON`() { + val settings = mutableMapOf(ANIMATIONS_KEY to true) + + upgrader.upgrade(settings) + + assertThat(settings[ANIMATIONS_KEY]).isEqualTo(AnimationPreference.ON) + } + + @Test + fun `should convert animations=false to OFF`() { + val settings = mutableMapOf(ANIMATIONS_KEY to false) + + upgrader.upgrade(settings) + + assertThat(settings[ANIMATIONS_KEY]).isEqualTo(AnimationPreference.OFF) + } + + @Test + fun `should not insert animations key when missing`() { + val settings = mutableMapOf() + + upgrader.upgrade(settings) + + assertThat(settings).doesNotContainKey(ANIMATIONS_KEY) + } + + @Test + fun `should leave existing AnimationPreference value unchanged`() { + val settings = mutableMapOf(ANIMATIONS_KEY to AnimationPreference.FOLLOW_SYSTEM) + + upgrader.upgrade(settings) + + assertThat(settings[ANIMATIONS_KEY]).isEqualTo(AnimationPreference.FOLLOW_SYSTEM) + } + + private companion object { + const val ANIMATIONS_KEY = "animations" + } +} diff --git a/legacy/storage/src/test/java/com/fsck/k9/preferences/migration/StorageMigrationTo30Test.kt b/legacy/storage/src/test/java/com/fsck/k9/preferences/migration/StorageMigrationTo30Test.kt new file mode 100644 index 00000000000..a8368c972da --- /dev/null +++ b/legacy/storage/src/test/java/com/fsck/k9/preferences/migration/StorageMigrationTo30Test.kt @@ -0,0 +1,78 @@ +package com.fsck.k9.preferences.migration + +import assertk.assertThat +import assertk.assertions.doesNotContainKey +import assertk.assertions.isEqualTo +import assertk.assertions.key +import com.fsck.k9.preferences.createPreferencesDatabase +import kotlin.test.Test +import net.thunderbird.core.logging.legacy.Log +import net.thunderbird.core.logging.testing.TestLogger +import org.junit.After +import org.junit.Before +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class StorageMigrationTo30Test { + private val database = createPreferencesDatabase() + private val migrationHelper = DefaultStorageMigrationHelper() + private val migration = StorageMigrationTo30(database, migrationHelper) + + @Before + fun setUp() { + Log.logger = TestLogger() + } + + @After + fun tearDown() { + database.close() + } + + @Test + fun `migration should convert animations=true to ON`() { + migrationHelper.insertValue(database, ANIMATIONS_KEY, "true") + + migration.migrateAnimationSetting() + + assertThat(migrationHelper.readAllValues(database)).key(ANIMATIONS_KEY).isEqualTo("ON") + } + + @Test + fun `migration should convert animations=false to OFF`() { + migrationHelper.insertValue(database, ANIMATIONS_KEY, "false") + + migration.migrateAnimationSetting() + + assertThat(migrationHelper.readAllValues(database)).key(ANIMATIONS_KEY).isEqualTo("OFF") + } + + @Test + fun `migration should not insert animations key when missing`() { + migration.migrateAnimationSetting() + + assertThat(migrationHelper.readAllValues(database)).doesNotContainKey(ANIMATIONS_KEY) + } + + @Test + fun `migration should leave already migrated ON value unchanged`() { + migrationHelper.insertValue(database, ANIMATIONS_KEY, "ON") + + migration.migrateAnimationSetting() + + assertThat(migrationHelper.readAllValues(database)).key(ANIMATIONS_KEY).isEqualTo("ON") + } + + @Test + fun `migration should leave already migrated OFF value unchanged`() { + migrationHelper.insertValue(database, ANIMATIONS_KEY, "OFF") + + migration.migrateAnimationSetting() + + assertThat(migrationHelper.readAllValues(database)).key(ANIMATIONS_KEY).isEqualTo("OFF") + } + + private companion object { + const val ANIMATIONS_KEY = "animations" + } +} diff --git a/legacy/ui/legacy/src/main/res/values/strings.xml b/legacy/ui/legacy/src/main/res/values/strings.xml index ba1060c5662..d1a54368ad2 100644 --- a/legacy/ui/legacy/src/main/res/values/strings.xml +++ b/legacy/ui/legacy/src/main/res/values/strings.xml @@ -700,7 +700,6 @@ 1000 folders Animation - Use gaudy visual effects On Off Use system default