Loading packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt +6 −2 Original line number Diff line number Diff line Loading @@ -107,6 +107,9 @@ private fun CoroutineScope.animate( reversed: Boolean = false, ) { val fromScene = layoutImpl.state.transitionState.currentScene val isUserInput = (layoutImpl.state.transitionState as? TransitionState.Transition)?.isUserInputDriven ?: false val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec val visibilityThreshold = Loading @@ -116,9 +119,9 @@ private fun CoroutineScope.animate( val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { OneOffTransition(target, fromScene, currentScene = target, animatable) OneOffTransition(target, fromScene, currentScene = target, isUserInput, animatable) } else { OneOffTransition(fromScene, target, currentScene = target, animatable) OneOffTransition(fromScene, target, currentScene = target, isUserInput, animatable) } // Change the current layout state to use this new transition. Loading @@ -139,6 +142,7 @@ private class OneOffTransition( override val fromScene: SceneKey, override val toScene: SceneKey, override val currentScene: SceneKey, override val isUserInputDriven: Boolean, private val animatable: Animatable<Float, AnimationVector1D>, ) : TransitionState.Transition { override val progress: Float Loading packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt +12 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,17 @@ sealed class ObservableTransitionState { val fromScene: SceneKey, val toScene: SceneKey, val progress: Flow<Float>, /** * Whether the transition was originally triggered by user input rather than being * programmatic. If this value is initially true, it will remain true until the transition * fully completes, even if the user input that triggered the transition has ended. Any * sub-transitions launched by this one will inherit this value. For example, if the user * drags a pointer but does not exceed the threshold required to transition to another * scene, this value will remain true after the pointer is no longer touching the screen and * will be true in any transition created to animate back to the original position. */ val isUserInputDriven: Boolean, ) : ObservableTransitionState() } Loading @@ -62,6 +73,7 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans fromScene = state.fromScene, toScene = state.toScene, progress = snapshotFlow { state.progress }, isUserInputDriven = state.isUserInputDriven, ) } } Loading packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +3 −0 Original line number Diff line number Diff line Loading @@ -68,5 +68,8 @@ sealed interface TransitionState { * when flinging quickly during a swipe gesture. */ val progress: Float /** Whether the transition was triggered by user input rather than being programmatic. */ val isUserInputDriven: Boolean } } packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt +2 −0 Original line number Diff line number Diff line Loading @@ -137,6 +137,8 @@ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition return offset / distance } override val isUserInputDriven = true /** The current offset caused by the drag gesture. */ var dragOffset by mutableFloatStateOf(0f) Loading packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +4 −0 Original line number Diff line number Diff line Loading @@ -122,6 +122,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating back to A (currentScene = SceneA) given // that 55dp < positional threshold. Loading @@ -133,6 +134,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene A. rule.waitForIdle() Loading @@ -154,6 +156,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating to C (currentScene = SceneC) given // that 56dp >= positional threshold. Loading @@ -165,6 +168,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() Loading Loading
packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt +6 −2 Original line number Diff line number Diff line Loading @@ -107,6 +107,9 @@ private fun CoroutineScope.animate( reversed: Boolean = false, ) { val fromScene = layoutImpl.state.transitionState.currentScene val isUserInput = (layoutImpl.state.transitionState as? TransitionState.Transition)?.isUserInputDriven ?: false val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec val visibilityThreshold = Loading @@ -116,9 +119,9 @@ private fun CoroutineScope.animate( val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { OneOffTransition(target, fromScene, currentScene = target, animatable) OneOffTransition(target, fromScene, currentScene = target, isUserInput, animatable) } else { OneOffTransition(fromScene, target, currentScene = target, animatable) OneOffTransition(fromScene, target, currentScene = target, isUserInput, animatable) } // Change the current layout state to use this new transition. Loading @@ -139,6 +142,7 @@ private class OneOffTransition( override val fromScene: SceneKey, override val toScene: SceneKey, override val currentScene: SceneKey, override val isUserInputDriven: Boolean, private val animatable: Animatable<Float, AnimationVector1D>, ) : TransitionState.Transition { override val progress: Float Loading
packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt +12 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,17 @@ sealed class ObservableTransitionState { val fromScene: SceneKey, val toScene: SceneKey, val progress: Flow<Float>, /** * Whether the transition was originally triggered by user input rather than being * programmatic. If this value is initially true, it will remain true until the transition * fully completes, even if the user input that triggered the transition has ended. Any * sub-transitions launched by this one will inherit this value. For example, if the user * drags a pointer but does not exceed the threshold required to transition to another * scene, this value will remain true after the pointer is no longer touching the screen and * will be true in any transition created to animate back to the original position. */ val isUserInputDriven: Boolean, ) : ObservableTransitionState() } Loading @@ -62,6 +73,7 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans fromScene = state.fromScene, toScene = state.toScene, progress = snapshotFlow { state.progress }, isUserInputDriven = state.isUserInputDriven, ) } } Loading
packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +3 −0 Original line number Diff line number Diff line Loading @@ -68,5 +68,8 @@ sealed interface TransitionState { * when flinging quickly during a swipe gesture. */ val progress: Float /** Whether the transition was triggered by user input rather than being programmatic. */ val isUserInputDriven: Boolean } }
packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt +2 −0 Original line number Diff line number Diff line Loading @@ -137,6 +137,8 @@ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition return offset / distance } override val isUserInputDriven = true /** The current offset caused by the drag gesture. */ var dragOffset by mutableFloatStateOf(0f) Loading
packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +4 −0 Original line number Diff line number Diff line Loading @@ -122,6 +122,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating back to A (currentScene = SceneA) given // that 55dp < positional threshold. Loading @@ -133,6 +134,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene A. rule.waitForIdle() Loading @@ -154,6 +156,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating to C (currentScene = SceneC) given // that 56dp >= positional threshold. Loading @@ -165,6 +168,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() Loading