Skip to content

Commit e2f1c73

Browse files
committed
fix: smooth drawer animation and respect OS animation preferences (#10480)
The sidebar animation when switching between the account list and folder list was too fast/jarring, and played even when the user disabled animations OS-wide (Developer Options > animation scale = 0). - Increase animation duration from 200ms to 300ms with FastOutSlowInEasing - Read OS-wide ANIMATOR_DURATION_SCALE to detect disabled animations - Use snap() (instant transition) when OS animations are disabled in all drawer animations: account selector slide, account switch, and expand icon rotation
1 parent 3e0c7bc commit e2f1c73

File tree

4 files changed

+55
-13
lines changed

4 files changed

+55
-13
lines changed

feature/navigation/drawer/dropdown/src/debug/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/account/AccountViewPreview.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ internal fun AccountViewPreview() {
1414
onClick = {},
1515
onAvatarClick = {},
1616
showAccountSelection = true,
17+
isShowAnimations = true,
1718
)
1819
}
1920
}
@@ -27,6 +28,7 @@ internal fun AccountViewWithoutAccountPreview() {
2728
onClick = {},
2829
onAvatarClick = {},
2930
showAccountSelection = false,
31+
isShowAnimations = true,
3032
)
3133
}
3234
}

feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerContent.kt

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package net.thunderbird.feature.navigation.drawer.dropdown.ui
22

33
import androidx.compose.animation.AnimatedContent
4+
import androidx.compose.animation.AnimatedContentTransitionScope
5+
import androidx.compose.animation.ContentTransform
6+
import androidx.compose.animation.core.FastOutSlowInEasing
7+
import androidx.compose.animation.core.snap
48
import androidx.compose.animation.core.tween
59
import androidx.compose.animation.slideInVertically
610
import androidx.compose.animation.slideOutVertically
@@ -14,7 +18,9 @@ import androidx.compose.foundation.layout.width
1418
import androidx.compose.foundation.layout.windowInsetsPadding
1519
import androidx.compose.runtime.Composable
1620
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.platform.LocalContext
1722
import androidx.compose.ui.unit.IntOffset
23+
import android.provider.Settings
1824
import app.k9mail.core.ui.compose.designsystem.atom.DividerHorizontal
1925
import app.k9mail.core.ui.compose.designsystem.atom.Surface
2026
import app.k9mail.core.ui.compose.theme2.MainTheme
@@ -31,7 +37,34 @@ import net.thunderbird.feature.navigation.drawer.dropdown.ui.folder.FolderList
3137
import net.thunderbird.feature.navigation.drawer.dropdown.ui.setting.AccountSettingList
3238
import net.thunderbird.feature.navigation.drawer.dropdown.ui.setting.FolderSettingList
3339

34-
private const val ANIMATION_DURATION_MS = 200
40+
private const val ANIMATION_DURATION_MS = 300
41+
42+
@Composable
43+
private fun areSystemAnimationsEnabled(): Boolean {
44+
return Settings.Global.getFloat(
45+
LocalContext.current.contentResolver,
46+
Settings.Global.ANIMATOR_DURATION_SCALE,
47+
1f,
48+
) != 0f
49+
}
50+
51+
private fun accountSelectorTransitionSpec(
52+
areAnimationsEnabled: Boolean,
53+
): AnimatedContentTransitionScope<Boolean>.() -> ContentTransform = {
54+
if (areAnimationsEnabled) {
55+
val animationSpec = tween<IntOffset>(durationMillis = ANIMATION_DURATION_MS, easing = FastOutSlowInEasing)
56+
if (targetState) {
57+
slideInVertically(animationSpec = animationSpec) { -it } togetherWith
58+
slideOutVertically(animationSpec = animationSpec) { it }
59+
} else {
60+
slideInVertically(animationSpec = animationSpec) { it } togetherWith
61+
slideOutVertically(animationSpec = animationSpec) { -it }
62+
}
63+
} else {
64+
slideInVertically(animationSpec = snap()) { 0 } togetherWith
65+
slideOutVertically(animationSpec = snap()) { 0 }
66+
}
67+
}
3568

3669
@Composable
3770
internal fun DrawerContent(
@@ -40,6 +73,7 @@ internal fun DrawerContent(
4073
modifier: Modifier = Modifier,
4174
) {
4275
val additionalWidth = getAdditionalWidth()
76+
val areAnimationsEnabled = areSystemAnimationsEnabled()
4377

4478
Surface(
4579
modifier = modifier
@@ -59,23 +93,15 @@ internal fun DrawerContent(
5993
onClick = { onEvent(Event.OnAccountSelectorClick) },
6094
onAvatarClick = { onEvent(Event.OnAccountViewClick(selectedAccount)) },
6195
showAccountSelection = state.showAccountSelection,
96+
isShowAnimations = areAnimationsEnabled,
6297
)
6398

6499
DividerHorizontal()
65100
}
66101
AnimatedContent(
67102
targetState = state.showAccountSelection,
68103
label = "AccountSelectorVisibility",
69-
transitionSpec = {
70-
val animationSpec = tween<IntOffset>(durationMillis = ANIMATION_DURATION_MS)
71-
if (targetState) {
72-
slideInVertically(animationSpec = animationSpec) { -it } togetherWith
73-
slideOutVertically(animationSpec = animationSpec) { it }
74-
} else {
75-
slideInVertically(animationSpec = animationSpec) { it } togetherWith
76-
slideOutVertically(animationSpec = animationSpec) { -it }
77-
}
78-
},
104+
transitionSpec = accountSelectorTransitionSpec(areAnimationsEnabled),
79105
) { targetState ->
80106
if (targetState) {
81107
AccountContent(

feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/account/AccountView.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package net.thunderbird.feature.navigation.drawer.dropdown.ui.account
22

33
import androidx.compose.animation.AnimatedContent
4+
import androidx.compose.animation.core.snap
45
import androidx.compose.animation.fadeIn
56
import androidx.compose.animation.fadeOut
67
import androidx.compose.animation.slideInHorizontally
@@ -44,6 +45,7 @@ internal fun AccountView(
4445
onClick: () -> Unit,
4546
onAvatarClick: () -> Unit,
4647
showAccountSelection: Boolean,
48+
isShowAnimations: Boolean,
4749
modifier: Modifier = Modifier,
4850
) {
4951
AccountLayout(
@@ -56,11 +58,13 @@ internal fun AccountView(
5658
AccountSelectedView(
5759
account = account,
5860
onAvatarClick = onAvatarClick,
61+
isShowAnimations = isShowAnimations,
5962
)
6063
}
6164

6265
AnimatedExpandIcon(
6366
isExpanded = showAccountSelection,
67+
isShowAnimations = isShowAnimations,
6468
modifier = Modifier.padding(end = MainTheme.spacings.double),
6569
tint = MainTheme.colors.onSurfaceVariant,
6670
)
@@ -71,12 +75,18 @@ internal fun AccountView(
7175
private fun RowScope.AccountSelectedView(
7276
account: DisplayAccount,
7377
onAvatarClick: () -> Unit,
78+
isShowAnimations: Boolean,
7479
) {
7580
AnimatedContent(
7681
targetState = account,
7782
transitionSpec = {
78-
(slideInHorizontally { it } + fadeIn()) togetherWith
79-
(slideOutHorizontally { -it } + fadeOut())
83+
if (isShowAnimations) {
84+
(slideInHorizontally { it } + fadeIn()) togetherWith
85+
(slideOutHorizontally { -it } + fadeOut())
86+
} else {
87+
(slideInHorizontally(animationSpec = snap()) { 0 } + fadeIn(animationSpec = snap())) togetherWith
88+
(slideOutHorizontally(animationSpec = snap()) { 0 } + fadeOut(animationSpec = snap()))
89+
}
8090
},
8191
label = "AccountSelectedContent",
8292
contentKey = { it.id },

feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/common/AnimatedExpandIcon.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package net.thunderbird.feature.navigation.drawer.dropdown.ui.common
22

33
import androidx.compose.animation.core.animateFloatAsState
4+
import androidx.compose.animation.core.snap
5+
import androidx.compose.animation.core.spring
46
import androidx.compose.runtime.Composable
57
import androidx.compose.runtime.getValue
68
import androidx.compose.ui.Modifier
@@ -13,10 +15,12 @@ import net.thunderbird.core.ui.compose.designsystem.atom.icon.Icons
1315
internal fun AnimatedExpandIcon(
1416
isExpanded: Boolean,
1517
modifier: Modifier = Modifier,
18+
isShowAnimations: Boolean = true,
1619
tint: Color? = null,
1720
) {
1821
val rotationAngle by animateFloatAsState(
1922
targetValue = if (isExpanded) 180f else 0f,
23+
animationSpec = if (isShowAnimations) spring() else snap(),
2024
label = "rotationAngle",
2125
)
2226

0 commit comments

Comments
 (0)