Skip to content

Commit e042696

Browse files
committed
feat: Add AnimationUtils for Safe Display Mode support
1 parent d13126c commit e042696

File tree

3 files changed

+94
-33
lines changed

3 files changed

+94
-33
lines changed

AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.kt

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package com.ichi2.anki
1818
import android.animation.Animator
1919
import android.content.Context
2020
import android.content.res.ColorStateList
21-
import android.provider.Settings
2221
import android.view.KeyEvent
2322
import android.view.MotionEvent
2423
import android.view.View
@@ -27,6 +26,7 @@ import android.widget.TextView
2726
import com.google.android.material.color.MaterialColors
2827
import com.google.android.material.floatingactionbutton.FloatingActionButton
2928
import com.ichi2.anki.ui.DoubleTapListener
29+
import com.ichi2.anki.utils.AnimationUtils.areSystemAnimationsEnabled
3030
import timber.log.Timber
3131

3232
class DeckPickerFloatingActionMenu(
@@ -79,7 +79,7 @@ class DeckPickerFloatingActionMenu(
7979
/**
8080
* If system animations are true changes the FAB color otherwise it remains the same
8181
*/
82-
if (areSystemAnimationsEnabled()) {
82+
if (areSystemAnimationsEnabled(context)) {
8383
fabMain.backgroundTintList = ColorStateList.valueOf(fabPressedColor)
8484
} else {
8585
// Changes the background color of FAB
@@ -327,35 +327,6 @@ class DeckPickerFloatingActionMenu(
327327
}
328328
}
329329

330-
/**
331-
* This function returns false if any of the mentioned system animations are disabled (0f)
332-
*
333-
* ANIMATION_DURATION_SCALE - controls app switching animation speed.
334-
* TRANSITION_ANIMATION_SCALE - controls app window opening and closing animation speed
335-
* WINDOW_ANIMATION_SCALE - controls pop-up window opening and closing animation speed
336-
*/
337-
private fun areSystemAnimationsEnabled(): Boolean {
338-
val animDuration: Float =
339-
Settings.Global.getFloat(
340-
context.contentResolver,
341-
Settings.Global.ANIMATOR_DURATION_SCALE,
342-
1f,
343-
)
344-
val animTransition: Float =
345-
Settings.Global.getFloat(
346-
context.contentResolver,
347-
Settings.Global.TRANSITION_ANIMATION_SCALE,
348-
1f,
349-
)
350-
val animWindow: Float =
351-
Settings.Global.getFloat(
352-
context.contentResolver,
353-
Settings.Global.WINDOW_ANIMATION_SCALE,
354-
1f,
355-
)
356-
return animDuration != 0f && animTransition != 0f && animWindow != 0f
357-
}
358-
359330
private fun createActivationKeyListener(
360331
logMessage: String,
361332
action: () -> Unit,

AnkiDroid/src/main/java/com/ichi2/anki/preferences/Preferences.kt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import androidx.fragment.app.FragmentFactory
3232
import androidx.fragment.app.FragmentManager
3333
import androidx.fragment.app.FragmentTransaction
3434
import androidx.fragment.app.commit
35+
import androidx.lifecycle.lifecycleScope
3536
import androidx.preference.Preference
3637
import androidx.preference.PreferenceFragmentCompat
3738
import com.bytehamster.lib.preferencesearch.SearchConfiguration
@@ -46,10 +47,13 @@ import com.ichi2.anki.common.annotations.LegacyNotifications
4647
import com.ichi2.anki.preferences.HeaderFragment.Companion.getHeaderKeyForFragment
4748
import com.ichi2.anki.reviewreminders.ReviewReminderScope
4849
import com.ichi2.anki.reviewreminders.ScheduleReminders
50+
import com.ichi2.anki.utils.AnimationUtils.areSystemAnimationsEnabled
4951
import com.ichi2.anki.utils.ext.sharedPrefs
5052
import com.ichi2.anki.utils.isWindowCompact
5153
import com.ichi2.themes.Themes
5254
import com.ichi2.utils.FragmentFactoryUtils
55+
import kotlinx.coroutines.delay
56+
import kotlinx.coroutines.launch
5357
import timber.log.Timber
5458
import kotlin.reflect.KClass
5559
import kotlin.reflect.jvm.jvmName
@@ -137,10 +141,32 @@ class PreferencesFragment :
137141
addToBackStack(fragment.javaClass.name)
138142
}
139143

140-
Timber.i("Highlighting key '%s' on %s", result.key, fragment)
141-
result.highlight(fragment as PreferenceFragmentCompat)
144+
if (isTabSpecificPreference(result.resourceFile)) {
145+
(fragment as? ControlsSettingsFragment)?.lifecycleScope?.launch {
146+
if (areSystemAnimationsEnabled(requireContext())) {
147+
delay(100)
148+
fragment.selectTabForPreference(result.key)
149+
delay(150)
150+
result.highlight(fragment as PreferenceFragmentCompat)
151+
} else {
152+
// Animations disabled - do everything immediately
153+
fragment.selectTabForPreference(result.key)
154+
result.highlight(fragment as PreferenceFragmentCompat)
155+
}
156+
}
157+
} else {
158+
result.highlight(fragment as PreferenceFragmentCompat)
159+
}
142160
}
143161

162+
/**
163+
* Checks if the given resource file represents a tab-specific preference
164+
* that requires special tab navigation handling.
165+
*/
166+
private fun isTabSpecificPreference(
167+
@XmlRes file: Int,
168+
) = ControlPreferenceScreen.entries.any { it.xmlRes == file }
169+
144170
private fun setupBackCallbacks() {
145171
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, childFragmentOnBackPressedCallback)
146172
childFragmentManager.addOnBackStackChangedListener(childBackStackListener)
@@ -282,6 +308,8 @@ fun getFragmentFromXmlRes(
282308
R.xml.preferences_notifications -> NotificationsSettingsFragment()
283309
R.xml.preferences_appearance -> AppearanceSettingsFragment()
284310
R.xml.preferences_controls -> ControlsSettingsFragment()
311+
R.xml.preferences_reviewer_controls -> ControlsSettingsFragment()
312+
R.xml.preferences_previewer_controls -> ControlsSettingsFragment()
285313
R.xml.preferences_advanced -> AdvancedSettingsFragment()
286314
R.xml.preferences_accessibility -> AccessibilitySettingsFragment()
287315
R.xml.preferences_dev_options -> DevOptionsFragment()
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2025 Sanjay Sargam <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
package com.ichi2.anki.utils
17+
18+
import android.content.Context
19+
import android.provider.Settings
20+
21+
/**
22+
* Utility class for animation-related helper functions
23+
*/
24+
object AnimationUtils {
25+
/**
26+
* Checks if system animations are enabled by verifying all animation scale settings.
27+
*
28+
* This function returns false if any of the mentioned system animations are disabled (0f),
29+
* which addresses safe display mode and accessibility concerns.
30+
*
31+
* ANIMATION_DURATION_SCALE - controls app switching animation speed.
32+
* TRANSITION_ANIMATION_SCALE - controls app window opening and closing animation speed
33+
* WINDOW_ANIMATION_SCALE - controls pop-up window opening and closing animation speed
34+
*
35+
* @param context The context used to access system settings
36+
* @return true if all animation scales are non-zero, false otherwise
37+
*/
38+
fun areSystemAnimationsEnabled(context: Context): Boolean =
39+
try {
40+
val animDuration =
41+
Settings.Global.getFloat(
42+
context.contentResolver,
43+
Settings.Global.ANIMATOR_DURATION_SCALE,
44+
1f,
45+
)
46+
val animTransition =
47+
Settings.Global.getFloat(
48+
context.contentResolver,
49+
Settings.Global.TRANSITION_ANIMATION_SCALE,
50+
1f,
51+
)
52+
val animWindow =
53+
Settings.Global.getFloat(
54+
context.contentResolver,
55+
Settings.Global.WINDOW_ANIMATION_SCALE,
56+
1f,
57+
)
58+
animDuration != 0f && animTransition != 0f && animWindow != 0f
59+
} catch (e: Exception) {
60+
true // Default to animations enabled if unable to read settings
61+
}
62+
}

0 commit comments

Comments
 (0)