1package soy.iko.opencode.ui.components
3import android.provider.Settings
4import androidx.compose.animation.EnterTransition
5import androidx.compose.animation.ExitTransition
6import androidx.compose.animation.core.AnimationSpec
7import androidx.compose.animation.core.snap
8import androidx.compose.animation.core.tween
9import androidx.compose.animation.expandVertically
10import androidx.compose.animation.fadeIn
11import androidx.compose.animation.fadeOut
12import androidx.compose.animation.shrinkVertically
13import androidx.compose.runtime.Composable
14import androidx.compose.runtime.DisposableEffect
15import androidx.compose.runtime.getValue
16import androidx.compose.runtime.mutableIntStateOf
17import androidx.compose.runtime.remember
18import androidx.compose.runtime.setValue
19import androidx.compose.runtime.staticCompositionLocalOf
20import androidx.compose.ui.platform.LocalContext
21import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.LifecycleEventObserver
23import androidx.lifecycle.compose.LocalLifecycleOwner
26 * Whether the user has reduced motion enabled at the system level (animator duration scale
27 * set to 0 in Developer Options). When true, transitions should skip animations and content
28 * swaps should be instant, respecting the user's accessibility preference.
30 * Note: Compose's animation system does NOT honor [Settings.Global.ANIMATOR_DURATION_SCALE]
31 * (that setting only gates the View-system ValueAnimator/ObjectAnimator), so each Compose
32 * animation must opt in via [LocalReducedMotion].
34 * Re-reads on each ON_RESUME so toggling "Remove animations" / animator scale in Developer
35 * Options takes effect without a process restart (keying only on `context`, which is stable
36 * for the activity's lifetime, would otherwise cache the first read forever).
39fun isReducedMotion(): Boolean {
40 val context = LocalContext.current
41 var resumeTick by remember { mutableIntStateOf(0) }
42 val lifecycleOwner = LocalLifecycleOwner.current
43 DisposableEffect(lifecycleOwner) {
44 val observer = LifecycleEventObserver { _, event ->
45 if (event == Lifecycle.Event.ON_RESUME) resumeTick++
47 lifecycleOwner.lifecycle.addObserver(observer)
48 onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
50 return remember(context, resumeTick) {
52 Settings.Global.getFloat(
53 context.contentResolver,
54 Settings.Global.ANIMATOR_DURATION_SCALE,
62 * Whether reduced motion is in effect for this composition. Provided once near the app root
63 * (see [soy.iko.opencode.ui.OpencodeApp]) from [isReducedMotion] so individual animation call
64 * sites collapse their motion to instant swaps without each re-reading the system setting.
65 * Defaults to false in previews/tests (no provider).
67val LocalReducedMotion = staticCompositionLocalOf { false }
70 * Enter/exit pair for [androidx.compose.animation.AnimatedVisibility], pre-resolved to honor
71 * [LocalReducedMotion]. Under reduced motion both are [EnterTransition.None] /
72 * [ExitTransition.None] so content appears/disappears instantly; otherwise a standard
73 * expand+fade / shrink+fade. Centralizes the gate so call sites stay one-liners.
75data class VisibilityTransitions(val enter: EnterTransition, val exit: ExitTransition)
78fun rememberVisibilityTransitions(): VisibilityTransitions {
79 val reduced = LocalReducedMotion.current
80 return remember(reduced) {
82 VisibilityTransitions(EnterTransition.None, ExitTransition.None)
84 VisibilityTransitions(expandVertically() + fadeIn(), shrinkVertically() + fadeOut())
90 * A [tween] [AnimationSpec] that collapses to an instant [snap] under [LocalReducedMotion],
91 * for single-value animations (e.g. an image zoom) that want a single spec source.
94fun <T> rememberMotionTween(durationMs: Int): AnimationSpec<T> {
95 val reduced = LocalReducedMotion.current
96 return remember(reduced, durationMs) {
97 if (reduced) snap() else tween(durationMs)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list