From 6206cfc0fed88a8b64a86ffb05268a32bf28369e Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Sat, 30 May 2026 21:09:10 +0100 Subject: [PATCH 1/4] refactor: extract AnimationPreferences to :common AnkiActivity needs to be moved to common for multimodule prep. Therefore AnimUtils needs to move Therefore `PrefsRepository(context).removeAppAnimations` This is the first step: making 'PrefsRepository' an 'AnimationPreferences' Part of 20737 Assisted-by: Claude Opus 4.8 --- .../main/java/com/ichi2/anki/settings/Prefs.kt | 5 +++-- .../common/preferences/AnimationPreferences.kt | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 common/src/main/java/com/ichi2/anki/common/preferences/AnimationPreferences.kt diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt b/AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt index f91c7b124be1..8e5adf5f6d20 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt @@ -25,6 +25,7 @@ import com.ichi2.anki.AnkiDroidApp import com.ichi2.anki.BuildConfig import com.ichi2.anki.R import com.ichi2.anki.cardviewer.TapGestureMode +import com.ichi2.anki.common.preferences.AnimationPreferences import com.ichi2.anki.common.preferences.sharedPrefs import com.ichi2.anki.common.utils.isRunningAsUnitTest import com.ichi2.anki.settings.enums.AppTheme @@ -45,7 +46,7 @@ object Prefs : PrefsRepository(AnkiDroidApp.sharedPrefs(), AnkiDroidApp.appResou open class PrefsRepository( val sharedPrefs: SharedPreferences, private val resources: Resources, -) { +) : AnimationPreferences { constructor(context: Context) : this(context.sharedPrefs(), context.resources) @VisibleForTesting @@ -381,7 +382,7 @@ open class PrefsRepository( val answerButtonsSize: Int by intPref(R.string.answer_button_size_preference, 100) val cardZoom: Int by intPref(R.string.card_zoom_preference, 100) - val removeAppAnimations by booleanPref(R.string.safe_display_key, defaultValue = false) + override val removeAppAnimations by booleanPref(R.string.safe_display_key, defaultValue = false) // **************************************** Advanced **************************************** // diff --git a/common/src/main/java/com/ichi2/anki/common/preferences/AnimationPreferences.kt b/common/src/main/java/com/ichi2/anki/common/preferences/AnimationPreferences.kt new file mode 100644 index 000000000000..aba9e0432cb0 --- /dev/null +++ b/common/src/main/java/com/ichi2/anki/common/preferences/AnimationPreferences.kt @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: Copyright (c) 2025 Brayan Oliveira + +package com.ichi2.anki.common.preferences + +/** + * Preferences controlling how the app displays animations. + */ +interface AnimationPreferences { + /** + * Whether the user has opted to remove app animations + * (the 'Safe display mode' / 'Remove animations' accessibility setting). + */ + val removeAppAnimations: Boolean +} From 5d8343b4ccf7c342f3ee2ec11b390f484bfbbd91 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Wed, 3 Jun 2026 05:24:26 +0100 Subject: [PATCH 2/4] refactor: extract Animations to :common:android It was decided that Preferences are per-profile, so this preference needs to be profile-aware. ---- AnkiActivity needs to be moved to common for multimodule prep. Therefore AnimUtils needs to move This is the second step: exposing 'AnimationPreferences' in a profile-safe manner and moving AnimUtils. Part of 20737 Assisted-by: Claude Opus 4.8 --- .../main/java/com/ichi2/anki/AnkiActivity.kt | 4 ++-- .../main/java/com/ichi2/anki/AnkiDroidApp.kt | 3 +++ .../AccessibilitySettingsFragment.kt | 4 ++-- .../com/ichi2/anki/preferences/Preferences.kt | 4 ++-- .../ui/windows/reviewer/AnswerFeedbackView.kt | 4 ++-- .../ichi2/anki/common/android/Animations.kt | 20 ++++++++++++++----- 6 files changed, 26 insertions(+), 13 deletions(-) rename AnkiDroid/src/main/java/com/ichi2/anki/utils/AnimUtils.kt => common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt (68%) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt index 12d067f5ee94..6eecf8a21496 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt @@ -61,6 +61,7 @@ import com.ichi2.anki.analytics.UsageAnalytics import com.ichi2.anki.android.input.ShortcutGroup import com.ichi2.anki.android.input.ShortcutGroupProvider import com.ichi2.anki.android.input.shortcut +import com.ichi2.anki.common.android.Animations import com.ichi2.anki.common.android.AnkiBroadcastReceiver import com.ichi2.anki.common.annotations.LegacyNotifications import com.ichi2.anki.common.annotations.NeedsTest @@ -88,7 +89,6 @@ import com.ichi2.anki.libanki.Collection import com.ichi2.anki.receiver.SdCardReceiver import com.ichi2.anki.settings.Prefs import com.ichi2.anki.snackbar.showSnackbar -import com.ichi2.anki.utils.AnimUtils import com.ichi2.anki.utils.ext.requireString import com.ichi2.anki.utils.ext.showDialogFragment import com.ichi2.anki.workarounds.AppLoadedFromBackupWorkaround.showedActivityFailedScreen @@ -301,7 +301,7 @@ open class AnkiActivity( * * @see .animationEnabled */ - fun animationDisabled(): Boolean = !AnimUtils.areAnimationsEnabled(this) + fun animationDisabled(): Boolean = !Animations.areAnimationsEnabled(this) /** * Whether animations should be displayed diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt index f78cc39e1e9c..3129e96e2bc1 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt @@ -24,6 +24,7 @@ import anki.collection.OpChanges import com.ichi2.anki.AnkiDroidApp.Companion.sharedPreferencesTestingOverride import com.ichi2.anki.analytics.UsageAnalytics import com.ichi2.anki.browser.SharedPreferencesLastDeckIdRepository +import com.ichi2.anki.common.android.Animations import com.ichi2.anki.common.android.ApplicationContextInitializer import com.ichi2.anki.common.android.getCurrentLocaleTag import com.ichi2.anki.common.android.withAppLocale @@ -53,6 +54,7 @@ import com.ichi2.anki.servicelayer.ThrowableFilterService import com.ichi2.anki.services.AlarmManagerService import com.ichi2.anki.services.NotificationService import com.ichi2.anki.settings.Prefs +import com.ichi2.anki.settings.PrefsRepository import com.ichi2.anki.ui.dialogs.ActivityAgnosticDialogs import com.ichi2.utils.AdaptionUtil import com.ichi2.utils.ExceptionUtil @@ -138,6 +140,7 @@ open class AnkiDroidApp : initializeAcraCrashReporter() initializeNavigator() + Animations.setPreferencesProvider { context -> PrefsRepository(context) } val logType = LogType.value when (logType) { LogType.DEBUG -> Timber.plant(DebugTree()) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AccessibilitySettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AccessibilitySettingsFragment.kt index 74a735302676..58011f0f1ad5 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AccessibilitySettingsFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AccessibilitySettingsFragment.kt @@ -18,8 +18,8 @@ package com.ichi2.anki.preferences import androidx.preference.Preference import androidx.preference.SwitchPreferenceCompat import com.ichi2.anki.R +import com.ichi2.anki.common.android.Animations import com.ichi2.anki.settings.Prefs -import com.ichi2.anki.utils.AnimUtils /** * Fragment with preferences related to notifications @@ -48,7 +48,7 @@ class AccessibilitySettingsFragment : SettingsFragment() { override fun onResume() { super.onResume() requirePreference(R.string.safe_display_key).isEnabled = - AnimUtils.areSystemAnimationsEnabled(requireContext()) + Animations.areSystemAnimationsEnabled(requireContext()) } companion object { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/Preferences.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/Preferences.kt index 47c1a6484f57..6bff6691e57b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/Preferences.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/Preferences.kt @@ -41,12 +41,12 @@ import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.MaterialToolbar import com.ichi2.anki.R import com.ichi2.anki.SingleFragmentActivity +import com.ichi2.anki.common.android.Animations import com.ichi2.anki.common.annotations.LegacyNotifications import com.ichi2.anki.common.utils.android.getResFromAttr import com.ichi2.anki.preferences.HeaderFragment.Companion.getHeaderKeyForFragment import com.ichi2.anki.reviewreminders.ReviewReminderScope import com.ichi2.anki.reviewreminders.ScheduleRemindersFragment -import com.ichi2.anki.utils.AnimUtils import com.ichi2.anki.utils.isWindowCompact import com.ichi2.utils.FragmentFactoryUtils import timber.log.Timber @@ -208,7 +208,7 @@ class PreferencesFragment : } private fun setFadeTransition(fragmentTransaction: FragmentTransaction) { - if (AnimUtils.areAnimationsEnabled(requireContext())) { + if (Animations.areAnimationsEnabled(requireContext())) { fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/AnswerFeedbackView.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/AnswerFeedbackView.kt index d29c5e35ca68..6157bf0d3491 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/AnswerFeedbackView.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/AnswerFeedbackView.kt @@ -23,7 +23,7 @@ import android.view.animation.Animation import android.view.animation.AnimationUtils import androidx.appcompat.widget.AppCompatImageView import com.ichi2.anki.R -import com.ichi2.anki.utils.AnimUtils +import com.ichi2.anki.common.android.Animations import com.ichi2.anki.utils.postDelayed import kotlin.time.Duration.Companion.milliseconds @@ -47,7 +47,7 @@ class AnswerFeedbackView : AppCompatImageView { fadeOutRunnable = null } - if (!AnimUtils.areAnimationsEnabled(context)) { + if (!Animations.areAnimationsEnabled(context)) { visibility = VISIBLE fadeOutRunnable = Runnable { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/AnimUtils.kt b/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt similarity index 68% rename from AnkiDroid/src/main/java/com/ichi2/anki/utils/AnimUtils.kt rename to common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt index 44302a7779da..f9108f55c665 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/utils/AnimUtils.kt +++ b/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt @@ -13,20 +13,30 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -package com.ichi2.anki.utils +package com.ichi2.anki.common.android +import android.app.Application import android.content.Context import android.provider.Settings -import com.ichi2.anki.settings.PrefsRepository +import com.ichi2.anki.common.preferences.AnimationPreferences /** * Utility class for animation-related helper functions */ -object AnimUtils { +object Animations { + /** resolved against the supplied [Context] so the result uses the active profile */ + private lateinit var preferencesProvider: (Context) -> AnimationPreferences + + /** Use during app startup to register the source of [AnimationPreferences]. */ + context(_: Application) + fun setPreferencesProvider(provider: (Context) -> AnimationPreferences) { + preferencesProvider = provider + } + /** * @return whether the animations are enabled by the system settings, * i.e. the 'Remove animations' setting is disabled. - * On most cases, using [AnimUtils.areAnimationsEnabled] is preferred + * On most cases, using [Animations.areAnimationsEnabled] is preferred * because it considers the app's own 'Remove animations' setting */ fun areSystemAnimationsEnabled(context: Context): Boolean = @@ -44,5 +54,5 @@ object AnimUtils { * @return whether animations are enabled on the system and app settings */ fun areAnimationsEnabled(context: Context): Boolean = - areSystemAnimationsEnabled(context) && !PrefsRepository(context).removeAppAnimations + areSystemAnimationsEnabled(context) && !preferencesProvider(context).removeAppAnimations } From fa247d8a45d4f6d446586f554d08752e2ad254db Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Sun, 31 May 2026 00:21:32 +0100 Subject: [PATCH 3/4] docs(common): cleanup 'Animations' This commit was deferred so the previous commit was detected as a rename Assisted-by: Claude Opus 4.8 --- .../ichi2/anki/common/android/Animations.kt | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt b/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt index f9108f55c665..991b5411fb78 100644 --- a/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt +++ b/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt @@ -1,18 +1,6 @@ -/* - * Copyright (c) 2025 Sanjay Sargam - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3 of the License, or (at your option) any later - * version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: Copyright (c) 2025 Sanjay Sargam + package com.ichi2.anki.common.android import android.app.Application @@ -21,7 +9,7 @@ import android.provider.Settings import com.ichi2.anki.common.preferences.AnimationPreferences /** - * Utility class for animation-related helper functions + * Animation related functionality for the app. */ object Animations { /** resolved against the supplied [Context] so the result uses the active profile */ From 8434e63e010a5236bedaaa034ad52ac15cbc9d43 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Sun, 31 May 2026 00:34:24 +0100 Subject: [PATCH 4/4] refactor: animationEnabled/Disabled into :common:android AnkiActivity needs to be moved to common as prep for multi-modules. This is the final step of extracting animations from AnkiActivity: moving them to extension methods on FragmentActivity. Part of 20737 Assisted-by: Claude Opus 4.8 --- .../com/ichi2/anki/AbstractFlashcardViewer.kt | 2 ++ .../main/java/com/ichi2/anki/AnkiActivity.kt | 20 +---------------- .../java/com/ichi2/anki/CardTemplateEditor.kt | 1 + .../main/java/com/ichi2/anki/DeckPicker.kt | 1 + .../anki/DeckPickerFloatingActionMenu.kt | 1 + .../ichi2/anki/NavigationDrawerActivity.kt | 1 + .../java/com/ichi2/anki/NoteEditorFragment.kt | 1 + .../com/ichi2/anki/reviewer/AnswerButtons.kt | 1 + .../anki/testutils/EmptyApplicationTest.kt | 21 ++++++++++++++++++ .../testutils/TestApplicationSingletons.kt | 19 ++++++++++++++++ .../com/ichi2/testutils/EmptyApplication.kt | 22 ++++++------------- common/android/build.gradle.kts | 1 + .../ichi2/anki/common/android/Animations.kt | 19 ++++++++++++++++ 13 files changed, 76 insertions(+), 34 deletions(-) create mode 100644 AnkiDroid/src/test/java/com/ichi2/anki/testutils/EmptyApplicationTest.kt create mode 100644 AnkiDroid/src/test/java/com/ichi2/anki/testutils/TestApplicationSingletons.kt diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt index f519382db003..5fed87e4a064 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt @@ -97,6 +97,8 @@ import com.ichi2.anki.cardviewer.ViewerCommand import com.ichi2.anki.cardviewer.ViewerRefresh import com.ichi2.anki.cardviewer.handledGamepadKeyDown import com.ichi2.anki.cardviewer.handledGamepadKeyUp +import com.ichi2.anki.common.android.animationDisabled +import com.ichi2.anki.common.android.animationEnabled import com.ichi2.anki.common.annotations.NeedsTest import com.ichi2.anki.common.preferences.sharedPrefs import com.ichi2.anki.common.utils.HashUtil.hashSetInit diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt index 6eecf8a21496..c609b16e19a2 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt @@ -61,8 +61,8 @@ import com.ichi2.anki.analytics.UsageAnalytics import com.ichi2.anki.android.input.ShortcutGroup import com.ichi2.anki.android.input.ShortcutGroupProvider import com.ichi2.anki.android.input.shortcut -import com.ichi2.anki.common.android.Animations import com.ichi2.anki.common.android.AnkiBroadcastReceiver +import com.ichi2.anki.common.android.animationDisabled import com.ichi2.anki.common.annotations.LegacyNotifications import com.ichi2.anki.common.annotations.NeedsTest import com.ichi2.anki.common.crashreporting.CrashReportService @@ -294,24 +294,6 @@ open class AnkiActivity( fun colIsOpenUnsafe(): Boolean = CollectionManager.isOpenUnsafe() - /** - * Whether animations should not be displayed - * This is used to improve the UX for e-ink devices - * Can be tested via Settings - Advanced - Safe display mode - * - * @see .animationEnabled - */ - fun animationDisabled(): Boolean = !Animations.areAnimationsEnabled(this) - - /** - * Whether animations should be displayed - * This is used to improve the UX for e-ink devices - * Can be tested via Settings - Advanced - Safe display mode - * - * @see .animationDisabled - */ - fun animationEnabled(): Boolean = !animationDisabled() - override fun setContentView(view: View?) { if (animationDisabled()) { view?.clearAnimation() diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 90ccd4e49f96..79d8fa723a08 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -63,6 +63,7 @@ import com.ichi2.anki.CollectionManager.withCol import com.ichi2.anki.android.input.ShortcutGroup import com.ichi2.anki.android.input.shortcut import com.ichi2.anki.cardviewer.SingleCardSide +import com.ichi2.anki.common.android.animationDisabled import com.ichi2.anki.common.android.appContext import com.ichi2.anki.common.annotations.NeedsTest import com.ichi2.anki.common.utils.android.getColorFromAttr diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt index a9d2e382fa52..bac0b63c3b98 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt @@ -89,6 +89,7 @@ import com.ichi2.anki.android.back.exitViaDoubleTapBackCallback import com.ichi2.anki.android.input.ShortcutGroup import com.ichi2.anki.android.input.shortcut import com.ichi2.anki.android.view.locationInWindow +import com.ichi2.anki.common.android.animationDisabled import com.ichi2.anki.common.android.appContext import com.ichi2.anki.common.annotations.NeedsTest import com.ichi2.anki.common.crashreporting.CrashReportService diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.kt index 19024921a881..745577e851c9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.kt @@ -13,6 +13,7 @@ import android.widget.LinearLayout import androidx.annotation.VisibleForTesting import com.google.android.material.color.MaterialColors import com.ichi2.anki.CollectionManager.TR +import com.ichi2.anki.common.android.animationEnabled import com.ichi2.anki.databinding.ActivityHomescreenBinding import com.ichi2.anki.databinding.IncludeFloatingAddButtonBinding import com.ichi2.anki.ui.DoubleTapListener diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.kt index 3447c3db9d76..fac5f1263465 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.kt @@ -35,6 +35,7 @@ import com.google.android.material.color.MaterialColors import com.google.android.material.navigation.NavigationView import com.ichi2.anki.IntentHandler.Companion.grantedStoragePermissions import com.ichi2.anki.NoteEditorFragment.Companion.NoteEditorCaller +import com.ichi2.anki.common.android.animationEnabled import com.ichi2.anki.common.preferences.sharedPrefs import com.ichi2.anki.common.utils.android.HandlerUtils import com.ichi2.anki.dialogs.help.HelpDialog diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt index 43e9324df291..0e63aa247195 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt @@ -76,6 +76,7 @@ import com.ichi2.anki.OnContextAndLongClickListener.Companion.setOnContextAndLon import com.ichi2.anki.android.input.ShortcutGroup import com.ichi2.anki.android.input.ShortcutGroupProvider import com.ichi2.anki.android.input.shortcut +import com.ichi2.anki.common.android.animationEnabled import com.ichi2.anki.common.android.appContext import com.ichi2.anki.common.annotations.NeedsTest import com.ichi2.anki.common.crashreporting.CrashReportService diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/AnswerButtons.kt b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/AnswerButtons.kt index 2f97e611efe0..702107fc7d5d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/AnswerButtons.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/AnswerButtons.kt @@ -20,6 +20,7 @@ import android.content.Context import com.ichi2.anki.AnkiActivity import com.ichi2.anki.R import com.ichi2.anki.cardviewer.ViewerCommand +import com.ichi2.anki.common.android.animationEnabled import com.ichi2.anki.common.utils.android.getColorsFromAttrs import com.ichi2.anki.common.utils.android.getResFromAttr diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/testutils/EmptyApplicationTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/testutils/EmptyApplicationTest.kt new file mode 100644 index 000000000000..9ef389a2a35e --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/anki/testutils/EmptyApplicationTest.kt @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.ichi2.anki.testutils + +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.ichi2.anki.common.android.Animations +import com.ichi2.testutils.EmptyApplication +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +@Config(application = EmptyApplication::class) +class EmptyApplicationTest { + @Test + fun `Animations provider is registered`() { + // Must not throw UninitializedPropertyAccessException + Animations.areAnimationsEnabled(getApplicationContext()) + } +} diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/testutils/TestApplicationSingletons.kt b/AnkiDroid/src/test/java/com/ichi2/anki/testutils/TestApplicationSingletons.kt new file mode 100644 index 000000000000..27ccd8b6cafd --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/anki/testutils/TestApplicationSingletons.kt @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.ichi2.anki.testutils + +import android.app.Application +import com.ichi2.anki.common.android.Animations +import com.ichi2.anki.navigation.initializeNavigator +import com.ichi2.anki.settings.PrefsRepository +import com.ichi2.testutils.EmptyApplication + +/** + * Registers the global singletons that [com.ichi2.anki.AnkiDroidApp.onCreate] wires up, for tests + * which use [EmptyApplication] (and therefore skip `AnkiDroidApp.onCreate`). + */ +context(_: Application) +fun registerTestApplicationSingletons() { + initializeNavigator() + Animations.setPreferencesProvider { context -> PrefsRepository(context) } +} diff --git a/AnkiDroid/src/test/java/com/ichi2/testutils/EmptyApplication.kt b/AnkiDroid/src/test/java/com/ichi2/testutils/EmptyApplication.kt index 33da5415a710..778f881f1b0a 100644 --- a/AnkiDroid/src/test/java/com/ichi2/testutils/EmptyApplication.kt +++ b/AnkiDroid/src/test/java/com/ichi2/testutils/EmptyApplication.kt @@ -1,22 +1,10 @@ -/* - * Copyright (c) 2020 David Allison - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3 of the License, or (at your option) any later - * version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ +// SPDX-License-Identifier: GPL-3.0-or-later + package com.ichi2.testutils import android.app.Application import com.ichi2.anki.AnkiDroidApp +import com.ichi2.anki.testutils.registerTestApplicationSingletons /** * A performance improvement to be used to avoid the startup cost of [AnkiDroidApp]. @@ -40,5 +28,9 @@ class EmptyApplication : Application() { // reset the static state of the app AnkiDroidApp.simulateRestoreFromBackup() + + // EmptyApplication skips AnkiDroidApp.onCreate, so register the global singletons that + // activity code relies on (e.g. Animations, used by `Context.animationDisabled()`) + registerTestApplicationSingletons() } } diff --git a/common/android/build.gradle.kts b/common/android/build.gradle.kts index f148645df271..44d86d85dae5 100644 --- a/common/android/build.gradle.kts +++ b/common/android/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation(libs.androidx.annotation) implementation(libs.androidx.appcompat) implementation(libs.androidx.core.ktx) + implementation(libs.androidx.fragment.ktx) implementation(libs.androidx.preference.ktx) implementation(libs.google.material) implementation(libs.jakewharton.timber) diff --git a/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt b/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt index 991b5411fb78..855b11656d6b 100644 --- a/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt +++ b/common/android/src/main/java/com/ichi2/anki/common/android/Animations.kt @@ -6,6 +6,7 @@ package com.ichi2.anki.common.android import android.app.Application import android.content.Context import android.provider.Settings +import androidx.fragment.app.FragmentActivity import com.ichi2.anki.common.preferences.AnimationPreferences /** @@ -44,3 +45,21 @@ object Animations { fun areAnimationsEnabled(context: Context): Boolean = areSystemAnimationsEnabled(context) && !preferencesProvider(context).removeAppAnimations } + +/** + * Whether animations should not be displayed + * This is used to improve the UX for e-ink devices + * Can be tested via Settings - Advanced - Safe display mode + * + * @see animationEnabled + */ +fun FragmentActivity.animationDisabled(): Boolean = !Animations.areAnimationsEnabled(this) + +/** + * Whether animations should be displayed + * This is used to improve the UX for e-ink devices + * Can be tested via Settings - Advanced - Safe display mode + * + * @see animationDisabled + */ +fun FragmentActivity.animationEnabled(): Boolean = !animationDisabled()