Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +17 −3 Original line number Diff line number Diff line Loading @@ -48,8 +48,15 @@ internal fun CoroutineScope.animateToScene( } return when (transitionState) { is TransitionState.Idle -> animate(layoutState, target, transitionKey, isInitiatedByUserInput = false) is TransitionState.Idle -> { animate( layoutState, target, transitionKey, isInitiatedByUserInput = false, replacedTransition = null, ) } is TransitionState.Transition -> { val isInitiatedByUserInput = transitionState.isInitiatedByUserInput Loading Loading @@ -79,6 +86,7 @@ internal fun CoroutineScope.animateToScene( isInitiatedByUserInput, initialProgress = progress, initialVelocity = transitionState.progressVelocity, replacedTransition = transitionState, ) } } else if (transitionState.fromScene == target) { Loading @@ -101,6 +109,7 @@ internal fun CoroutineScope.animateToScene( initialProgress = progress, initialVelocity = transitionState.progressVelocity, reversed = true, replacedTransition = transitionState, ) } } else { Loading Loading @@ -137,6 +146,7 @@ internal fun CoroutineScope.animateToScene( isInitiatedByUserInput, fromScene = animateFrom, chain = chain, replacedTransition = null, ) } } Loading @@ -148,6 +158,7 @@ private fun CoroutineScope.animate( targetScene: SceneKey, transitionKey: TransitionKey?, isInitiatedByUserInput: Boolean, replacedTransition: TransitionState.Transition?, initialProgress: Float = 0f, initialVelocity: Float = 0f, reversed: Boolean = false, Loading @@ -164,6 +175,7 @@ private fun CoroutineScope.animate( currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, isUserInputOngoing = false, replacedTransition = replacedTransition, ) } else { OneOffTransition( Loading @@ -173,6 +185,7 @@ private fun CoroutineScope.animate( currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, isUserInputOngoing = false, replacedTransition = replacedTransition, ) } Loading Loading @@ -214,7 +227,8 @@ private class OneOffTransition( override val currentScene: SceneKey, override val isInitiatedByUserInput: Boolean, override val isUserInputOngoing: Boolean, ) : TransitionState.Transition(fromScene, toScene) { replacedTransition: TransitionState.Transition?, ) : TransitionState.Transition(fromScene, toScene, replacedTransition) { /** * The animatable used to animate this transition. * Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +6 −3 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job import kotlinx.coroutines.launch interface DraggableHandler { internal interface DraggableHandler { /** * Start a drag in the given [startedPosition], with the given [overSlop] and number of * [pointersDown]. Loading @@ -51,7 +51,7 @@ interface DraggableHandler { * The [DragController] provides control over the transition between two scenes through the [onDrag] * and [onStop] methods. */ interface DragController { internal interface DragController { /** Drag the current scene by [delta] pixels. */ fun onDrag(delta: Float) Loading Loading @@ -537,6 +537,7 @@ private fun SwipeTransition( orientation = orientation, isUpOrLeft = isUpOrLeft, requiresFullDistanceSwipe = result.requiresFullDistanceSwipe, replacedTransition = null, ) } Loading @@ -553,6 +554,7 @@ private fun SwipeTransition(old: SwipeTransition): SwipeTransition { isUpOrLeft = old.isUpOrLeft, lastDistance = old.lastDistance, requiresFullDistanceSwipe = old.requiresFullDistanceSwipe, replacedTransition = old, ) .apply { _currentScene = old._currentScene Loading @@ -571,9 +573,10 @@ private class SwipeTransition( override val orientation: Orientation, override val isUpOrLeft: Boolean, val requiresFullDistanceSwipe: Boolean, replacedTransition: SwipeTransition?, var lastDistance: Float = DistanceUnspecified, ) : TransitionState.Transition(_fromScene.key, _toScene.key), TransitionState.Transition(_fromScene.key, _toScene.key, replacedTransition), TransitionState.HasOverscrollProperties { var _currentScene by mutableStateOf(_fromScene) override val currentScene: SceneKey Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +4 −0 Original line number Diff line number Diff line Loading @@ -495,6 +495,10 @@ private fun prepareInterruption( transition: TransitionState.Transition, previousTransition: TransitionState.Transition, ) { if (transition.replacedTransition == previousTransition) { return } val sceneStates = element.sceneStates fun updatedSceneState(key: SceneKey): Element.SceneState? { return sceneStates[key]?.also { it.selfUpdateValuesBeforeInterruption() } Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +16 −0 Original line number Diff line number Diff line Loading @@ -224,6 +224,9 @@ sealed interface TransitionState { /** The scene this transition is going to. Can't be the same as fromScene */ val toScene: SceneKey, /** The transition that `this` transition is replacing, if any. */ internal val replacedTransition: Transition? = null, ) : TransitionState { /** * The key of this transition. This should usually be null, but it can be specified to use a Loading Loading @@ -279,6 +282,11 @@ sealed interface TransitionState { init { check(fromScene != toScene) check( replacedTransition == null || (replacedTransition.fromScene == fromScene && replacedTransition.toScene == toScene) ) } /** Loading Loading @@ -321,6 +329,10 @@ sealed interface TransitionState { return 0f } if (replacedTransition != null) { return replacedTransition.interruptionProgress(layoutImpl) } fun create(): Animatable<Float, AnimationVector1D> { val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold) layoutImpl.coroutineScope.launch { Loading Loading @@ -521,6 +533,10 @@ internal abstract class BaseSceneTransitionLayoutState( check(transitionStates.size == 1) check(transitionStates[0] is TransitionState.Idle) transitionStates = listOf(transition) } else if (currentState == transition.replacedTransition) { // Replace the transition. transitionStates = transitionStates.subList(0, transitionStates.lastIndex) + transition } else { // Append the new transition. transitionStates = transitionStates + transition Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +13 −0 Original line number Diff line number Diff line Loading @@ -1233,4 +1233,17 @@ class DraggableHandlerTest { advanceUntilIdle() assertIdle(SceneB) } @Test fun interceptingTransitionReplacesCurrentTransition() = runGestureTest { val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.5f)) val transition = assertThat(layoutState.transitionState).isTransition() controller.onDragStopped(velocity = 0f) // Intercept the transition. onDragStartedImmediately() val newTransition = assertThat(layoutState.transitionState).isTransition() assertThat(newTransition).isNotSameInstanceAs(transition) assertThat(newTransition.replacedTransition).isSameInstanceAs(transition) } } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +17 −3 Original line number Diff line number Diff line Loading @@ -48,8 +48,15 @@ internal fun CoroutineScope.animateToScene( } return when (transitionState) { is TransitionState.Idle -> animate(layoutState, target, transitionKey, isInitiatedByUserInput = false) is TransitionState.Idle -> { animate( layoutState, target, transitionKey, isInitiatedByUserInput = false, replacedTransition = null, ) } is TransitionState.Transition -> { val isInitiatedByUserInput = transitionState.isInitiatedByUserInput Loading Loading @@ -79,6 +86,7 @@ internal fun CoroutineScope.animateToScene( isInitiatedByUserInput, initialProgress = progress, initialVelocity = transitionState.progressVelocity, replacedTransition = transitionState, ) } } else if (transitionState.fromScene == target) { Loading @@ -101,6 +109,7 @@ internal fun CoroutineScope.animateToScene( initialProgress = progress, initialVelocity = transitionState.progressVelocity, reversed = true, replacedTransition = transitionState, ) } } else { Loading Loading @@ -137,6 +146,7 @@ internal fun CoroutineScope.animateToScene( isInitiatedByUserInput, fromScene = animateFrom, chain = chain, replacedTransition = null, ) } } Loading @@ -148,6 +158,7 @@ private fun CoroutineScope.animate( targetScene: SceneKey, transitionKey: TransitionKey?, isInitiatedByUserInput: Boolean, replacedTransition: TransitionState.Transition?, initialProgress: Float = 0f, initialVelocity: Float = 0f, reversed: Boolean = false, Loading @@ -164,6 +175,7 @@ private fun CoroutineScope.animate( currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, isUserInputOngoing = false, replacedTransition = replacedTransition, ) } else { OneOffTransition( Loading @@ -173,6 +185,7 @@ private fun CoroutineScope.animate( currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, isUserInputOngoing = false, replacedTransition = replacedTransition, ) } Loading Loading @@ -214,7 +227,8 @@ private class OneOffTransition( override val currentScene: SceneKey, override val isInitiatedByUserInput: Boolean, override val isUserInputOngoing: Boolean, ) : TransitionState.Transition(fromScene, toScene) { replacedTransition: TransitionState.Transition?, ) : TransitionState.Transition(fromScene, toScene, replacedTransition) { /** * The animatable used to animate this transition. * Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +6 −3 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job import kotlinx.coroutines.launch interface DraggableHandler { internal interface DraggableHandler { /** * Start a drag in the given [startedPosition], with the given [overSlop] and number of * [pointersDown]. Loading @@ -51,7 +51,7 @@ interface DraggableHandler { * The [DragController] provides control over the transition between two scenes through the [onDrag] * and [onStop] methods. */ interface DragController { internal interface DragController { /** Drag the current scene by [delta] pixels. */ fun onDrag(delta: Float) Loading Loading @@ -537,6 +537,7 @@ private fun SwipeTransition( orientation = orientation, isUpOrLeft = isUpOrLeft, requiresFullDistanceSwipe = result.requiresFullDistanceSwipe, replacedTransition = null, ) } Loading @@ -553,6 +554,7 @@ private fun SwipeTransition(old: SwipeTransition): SwipeTransition { isUpOrLeft = old.isUpOrLeft, lastDistance = old.lastDistance, requiresFullDistanceSwipe = old.requiresFullDistanceSwipe, replacedTransition = old, ) .apply { _currentScene = old._currentScene Loading @@ -571,9 +573,10 @@ private class SwipeTransition( override val orientation: Orientation, override val isUpOrLeft: Boolean, val requiresFullDistanceSwipe: Boolean, replacedTransition: SwipeTransition?, var lastDistance: Float = DistanceUnspecified, ) : TransitionState.Transition(_fromScene.key, _toScene.key), TransitionState.Transition(_fromScene.key, _toScene.key, replacedTransition), TransitionState.HasOverscrollProperties { var _currentScene by mutableStateOf(_fromScene) override val currentScene: SceneKey Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +4 −0 Original line number Diff line number Diff line Loading @@ -495,6 +495,10 @@ private fun prepareInterruption( transition: TransitionState.Transition, previousTransition: TransitionState.Transition, ) { if (transition.replacedTransition == previousTransition) { return } val sceneStates = element.sceneStates fun updatedSceneState(key: SceneKey): Element.SceneState? { return sceneStates[key]?.also { it.selfUpdateValuesBeforeInterruption() } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +16 −0 Original line number Diff line number Diff line Loading @@ -224,6 +224,9 @@ sealed interface TransitionState { /** The scene this transition is going to. Can't be the same as fromScene */ val toScene: SceneKey, /** The transition that `this` transition is replacing, if any. */ internal val replacedTransition: Transition? = null, ) : TransitionState { /** * The key of this transition. This should usually be null, but it can be specified to use a Loading Loading @@ -279,6 +282,11 @@ sealed interface TransitionState { init { check(fromScene != toScene) check( replacedTransition == null || (replacedTransition.fromScene == fromScene && replacedTransition.toScene == toScene) ) } /** Loading Loading @@ -321,6 +329,10 @@ sealed interface TransitionState { return 0f } if (replacedTransition != null) { return replacedTransition.interruptionProgress(layoutImpl) } fun create(): Animatable<Float, AnimationVector1D> { val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold) layoutImpl.coroutineScope.launch { Loading Loading @@ -521,6 +533,10 @@ internal abstract class BaseSceneTransitionLayoutState( check(transitionStates.size == 1) check(transitionStates[0] is TransitionState.Idle) transitionStates = listOf(transition) } else if (currentState == transition.replacedTransition) { // Replace the transition. transitionStates = transitionStates.subList(0, transitionStates.lastIndex) + transition } else { // Append the new transition. transitionStates = transitionStates + transition Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +13 −0 Original line number Diff line number Diff line Loading @@ -1233,4 +1233,17 @@ class DraggableHandlerTest { advanceUntilIdle() assertIdle(SceneB) } @Test fun interceptingTransitionReplacesCurrentTransition() = runGestureTest { val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.5f)) val transition = assertThat(layoutState.transitionState).isTransition() controller.onDragStopped(velocity = 0f) // Intercept the transition. onDragStartedImmediately() val newTransition = assertThat(layoutState.transitionState).isTransition() assertThat(newTransition).isNotSameInstanceAs(transition) assertThat(newTransition.replacedTransition).isSameInstanceAs(transition) } }