Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +19 −37 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ package com.android.compose.animation.scene import com.android.compose.animation.scene.content.state.TransitionState import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job Loading Loading @@ -63,16 +62,8 @@ internal fun CoroutineScope.animateToScene( if (transitionState.toScene == target) { // The user is currently swiping to [target] but didn't release their pointer yet: // animate the progress to `1`. check(transitionState.fromScene == transitionState.currentScene) val progress = transitionState.progress if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) { // The transition is already finished (progress ~= 1): no need to animate. We // finish the current transition early to make sure that the current state // change is committed. layoutState.finishTransition(transitionState, target) null } else { // The transition is in progress: start the canned animation at the same // progress as it was in. animateToScene( Loading @@ -82,19 +73,11 @@ internal fun CoroutineScope.animateToScene( isInitiatedByUserInput, replacedTransition = transitionState, ) } } else if (transitionState.fromScene == target) { // There is a transition from [target] to another scene: simply animate the same // transition progress to `0`. check(transitionState.toScene == transitionState.currentScene) val progress = transitionState.progress if (progress.absoluteValue < ProgressVisibilityThreshold) { // The transition is at progress ~= 0: no need to animate.We finish the current // transition early to make sure that the current state change is committed. layoutState.finishTransition(transitionState, target) null } else { animateToScene( layoutState, target, Loading @@ -103,7 +86,6 @@ internal fun CoroutineScope.animateToScene( reversed = true, replacedTransition = transitionState, ) } } else { // Generic interruption; the current transition is neither from or to [target]. val interruptionResult = Loading Loading @@ -185,7 +167,7 @@ private fun CoroutineScope.animateToScene( oneOffAnimation = oneOffAnimation, targetProgress = targetProgress, startTransition = { layoutState.startTransition(transition, chain) }, finishTransition = { layoutState.finishTransition(transition, targetScene) }, finishTransition = { layoutState.finishTransition(transition) }, ) return transition Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +2 −1 Original line number Diff line number Diff line Loading @@ -777,7 +777,8 @@ private class SwipeTransition( fun snapToScene(scene: SceneKey) { cancelOffsetAnimation() layoutState.finishTransition(this, idleScene = scene) check(currentScene == scene) layoutState.finishTransition(this) } override fun finish(): Job { Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt +1 −1 Original line number Diff line number Diff line Loading @@ -129,7 +129,7 @@ private class PredictiveBackTransition( try { animatable.animateTo(targetProgress) } finally { state.finishTransition(this@PredictiveBackTransition, scene) state.finishTransition(this@PredictiveBackTransition) } } .also { animationJob = it } Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +40 −72 Original line number Diff line number Diff line Loading @@ -167,8 +167,6 @@ internal class MutableSceneTransitionLayoutStateImpl( override val transitionState: TransitionState get() = transitionStates.last() private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() override val currentTransitions: List<TransitionState.Transition> get() { if (transitionStates.last() is TransitionState.Idle) { Loading @@ -180,12 +178,8 @@ internal class MutableSceneTransitionLayoutStateImpl( } } /** * The mapping of transitions that are finished, i.e. for which [finishTransition] was called, * to their idle scene. */ @VisibleForTesting internal val finishedTransitions = mutableMapOf<TransitionState.Transition, SceneKey>() /** The transitions that are finished, i.e. for which [finishTransition] was called. */ @VisibleForTesting internal val finishedTransitions = mutableSetOf<TransitionState.Transition>() internal fun checkThread() { val current = Thread.currentThread() Loading Loading @@ -261,7 +255,7 @@ internal class MutableSceneTransitionLayoutStateImpl( } // Handle transition links. cancelActiveTransitionLinks() currentTransition?.let { cancelActiveTransitionLinks(it) } setupTransitionLinks(transition) if (!enableInterruptions) { Loading Loading @@ -289,8 +283,7 @@ internal class MutableSceneTransitionLayoutStateImpl( // Force finish all transitions. while (currentTransitions.isNotEmpty()) { val transition = transitionStates[0] as TransitionState.Transition finishTransition(transition, transition.currentScene) finishTransition(transitionStates[0] as TransitionState.Transition) } // We finished all transitions, so we are now idle. We remove this state so that Loading Loading @@ -328,18 +321,17 @@ internal class MutableSceneTransitionLayoutStateImpl( ) } private fun cancelActiveTransitionLinks() { for ((link, linkedTransition) in activeTransitionLinks) { link.target.finishTransition(linkedTransition, linkedTransition.currentScene) private fun cancelActiveTransitionLinks(transition: TransitionState.Transition) { transition.activeTransitionLinks.forEach { (link, linkedTransition) -> link.target.finishTransition(linkedTransition) } activeTransitionLinks.clear() transition.activeTransitionLinks.clear() } private fun setupTransitionLinks(transitionState: TransitionState) { if (transitionState !is TransitionState.Transition) return private fun setupTransitionLinks(transition: TransitionState.Transition) { stateLinks.fastForEach { stateLink -> val matchingLinks = stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) } stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) } if (matchingLinks.isEmpty()) return@fastForEach if (matchingLinks.size > 1) error("More than one link matched.") Loading @@ -350,31 +342,27 @@ internal class MutableSceneTransitionLayoutStateImpl( val linkedTransition = LinkedTransition( originalTransition = transitionState, originalTransition = transition, fromScene = targetCurrentScene, toScene = matchingLink.targetTo, key = matchingLink.targetTransitionKey, ) stateLink.target.startTransition(linkedTransition) activeTransitionLinks[stateLink] = linkedTransition transition.activeTransitionLinks[stateLink] = linkedTransition } } /** * Notify that [transition] was finished and that we should settle to [idleScene]. This will do * nothing if [transition] was interrupted since it was started. * Notify that [transition] was finished and that it settled to its * [currentScene][TransitionState.currentScene]. This will do nothing if [transition] was * interrupted since it was started. */ internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) { internal fun finishTransition(transition: TransitionState.Transition) { checkThread() val existingIdleScene = finishedTransitions[transition] if (existingIdleScene != null) { if (finishedTransitions.contains(transition)) { // This transition was already finished. check(idleScene == existingIdleScene) { "Transition $transition was finished multiple times with different " + "idleScene ($existingIdleScene != $idleScene)" } return } Loading @@ -386,15 +374,15 @@ internal class MutableSceneTransitionLayoutStateImpl( check(transitionStates.fastAll { it is TransitionState.Transition }) // Mark this transition as finished and save the scene it is settling at. finishedTransitions[transition] = idleScene // Mark this transition as finished. finishedTransitions.add(transition) // Finish all linked transitions. finishActiveTransitionLinks(idleScene) finishActiveTransitionLinks(transition) // Keep a reference to the idle scene of the last removed transition, in case we remove all // transitions and should settle to Idle. var lastRemovedIdleScene: SceneKey? = null // Keep a reference to the last transition, in case we remove all transitions and should // settle to Idle. val lastTransition = transitionStates.last() // Remove all first n finished transitions. var i = 0 Loading @@ -407,14 +395,14 @@ internal class MutableSceneTransitionLayoutStateImpl( } // Remove the transition from the set of finished transitions. lastRemovedIdleScene = finishedTransitions.remove(t) finishedTransitions.remove(t) i++ } // If all transitions are finished, we are idle. if (i == nStates) { check(finishedTransitions.isEmpty()) this.transitionStates = listOf(TransitionState.Idle(checkNotNull(lastRemovedIdleScene))) this.transitionStates = listOf(TransitionState.Idle(lastTransition.currentScene)) } else if (i > 0) { this.transitionStates = transitionStates.subList(fromIndex = i, toIndex = nStates) } Loading @@ -426,28 +414,18 @@ internal class MutableSceneTransitionLayoutStateImpl( // Force finish all transitions. while (currentTransitions.isNotEmpty()) { val transition = transitionStates[0] as TransitionState.Transition finishTransition(transition, transition.currentScene) finishTransition(transition) } check(transitionStates.size == 1) transitionStates = listOf(TransitionState.Idle(scene)) } private fun finishActiveTransitionLinks(idleScene: SceneKey) { val previousTransition = this.transitionState as? TransitionState.Transition ?: return for ((link, linkedTransition) in activeTransitionLinks) { if (previousTransition.fromScene == idleScene) { // The transition ended by arriving at the fromScene, move link to Idle(fromScene). link.target.finishTransition(linkedTransition, linkedTransition.fromScene) } else if (previousTransition.toScene == idleScene) { // The transition ended by arriving at the toScene, move link to Idle(toScene). link.target.finishTransition(linkedTransition, linkedTransition.toScene) } else { // The transition was interrupted by something else, we reset to initial state. link.target.finishTransition(linkedTransition, linkedTransition.fromScene) } private fun finishActiveTransitionLinks(transition: TransitionState.Transition) { for ((link, linkedTransition) in transition.activeTransitionLinks) { link.target.finishTransition(linkedTransition) } activeTransitionLinks.clear() transition.activeTransitionLinks.clear() } /** Loading @@ -465,31 +443,21 @@ internal class MutableSceneTransitionLayoutStateImpl( fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold fun finishAllTransitions(lastTransitionIdleScene: SceneKey) { fun finishAllTransitions() { // Force finish all transitions. while (currentTransitions.isNotEmpty()) { val transition = transitionStates[0] as TransitionState.Transition val idleScene = if (transitionStates.size == 1) { lastTransitionIdleScene } else { transition.currentScene } finishTransition(transition, idleScene) finishTransition(transitionStates[0] as TransitionState.Transition) } } return when { isProgressCloseTo(0f) -> { finishAllTransitions(transition.fromScene) val shouldSnap = (isProgressCloseTo(0f) && transition.currentScene == transition.fromScene) || (isProgressCloseTo(1f) && transition.currentScene == transition.toScene) return if (shouldSnap) { finishAllTransitions() true } isProgressCloseTo(1f) -> { finishAllTransitions(transition.toScene) true } else -> false } else { false } } } Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt +6 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ import com.android.compose.animation.scene.SceneTransitionLayoutImpl import com.android.compose.animation.scene.TransformationSpec import com.android.compose.animation.scene.TransformationSpecImpl import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.transition.link.LinkedTransition import com.android.compose.animation.scene.transition.link.StateLink import kotlinx.coroutines.Job import kotlinx.coroutines.launch Loading Loading @@ -110,6 +112,9 @@ sealed interface ContentState<out T : ContentKey> { */ private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null /** The map of active links that connects this transition to other transitions. */ internal val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() init { check(fromContent != toContent) check( Loading @@ -131,7 +136,7 @@ sealed interface ContentState<out T : ContentKey> { * animation is complete or cancel it to snap the animation. Calling [finish] multiple * times will return the same [Job]. */ abstract fun finish(): Job internal abstract fun finish(): Job /** * Whether we are transitioning. If [from] or [to] is empty, we will also check that they Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +19 −37 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ package com.android.compose.animation.scene import com.android.compose.animation.scene.content.state.TransitionState import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job Loading Loading @@ -63,16 +62,8 @@ internal fun CoroutineScope.animateToScene( if (transitionState.toScene == target) { // The user is currently swiping to [target] but didn't release their pointer yet: // animate the progress to `1`. check(transitionState.fromScene == transitionState.currentScene) val progress = transitionState.progress if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) { // The transition is already finished (progress ~= 1): no need to animate. We // finish the current transition early to make sure that the current state // change is committed. layoutState.finishTransition(transitionState, target) null } else { // The transition is in progress: start the canned animation at the same // progress as it was in. animateToScene( Loading @@ -82,19 +73,11 @@ internal fun CoroutineScope.animateToScene( isInitiatedByUserInput, replacedTransition = transitionState, ) } } else if (transitionState.fromScene == target) { // There is a transition from [target] to another scene: simply animate the same // transition progress to `0`. check(transitionState.toScene == transitionState.currentScene) val progress = transitionState.progress if (progress.absoluteValue < ProgressVisibilityThreshold) { // The transition is at progress ~= 0: no need to animate.We finish the current // transition early to make sure that the current state change is committed. layoutState.finishTransition(transitionState, target) null } else { animateToScene( layoutState, target, Loading @@ -103,7 +86,6 @@ internal fun CoroutineScope.animateToScene( reversed = true, replacedTransition = transitionState, ) } } else { // Generic interruption; the current transition is neither from or to [target]. val interruptionResult = Loading Loading @@ -185,7 +167,7 @@ private fun CoroutineScope.animateToScene( oneOffAnimation = oneOffAnimation, targetProgress = targetProgress, startTransition = { layoutState.startTransition(transition, chain) }, finishTransition = { layoutState.finishTransition(transition, targetScene) }, finishTransition = { layoutState.finishTransition(transition) }, ) return transition Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +2 −1 Original line number Diff line number Diff line Loading @@ -777,7 +777,8 @@ private class SwipeTransition( fun snapToScene(scene: SceneKey) { cancelOffsetAnimation() layoutState.finishTransition(this, idleScene = scene) check(currentScene == scene) layoutState.finishTransition(this) } override fun finish(): Job { Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt +1 −1 Original line number Diff line number Diff line Loading @@ -129,7 +129,7 @@ private class PredictiveBackTransition( try { animatable.animateTo(targetProgress) } finally { state.finishTransition(this@PredictiveBackTransition, scene) state.finishTransition(this@PredictiveBackTransition) } } .also { animationJob = it } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +40 −72 Original line number Diff line number Diff line Loading @@ -167,8 +167,6 @@ internal class MutableSceneTransitionLayoutStateImpl( override val transitionState: TransitionState get() = transitionStates.last() private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() override val currentTransitions: List<TransitionState.Transition> get() { if (transitionStates.last() is TransitionState.Idle) { Loading @@ -180,12 +178,8 @@ internal class MutableSceneTransitionLayoutStateImpl( } } /** * The mapping of transitions that are finished, i.e. for which [finishTransition] was called, * to their idle scene. */ @VisibleForTesting internal val finishedTransitions = mutableMapOf<TransitionState.Transition, SceneKey>() /** The transitions that are finished, i.e. for which [finishTransition] was called. */ @VisibleForTesting internal val finishedTransitions = mutableSetOf<TransitionState.Transition>() internal fun checkThread() { val current = Thread.currentThread() Loading Loading @@ -261,7 +255,7 @@ internal class MutableSceneTransitionLayoutStateImpl( } // Handle transition links. cancelActiveTransitionLinks() currentTransition?.let { cancelActiveTransitionLinks(it) } setupTransitionLinks(transition) if (!enableInterruptions) { Loading Loading @@ -289,8 +283,7 @@ internal class MutableSceneTransitionLayoutStateImpl( // Force finish all transitions. while (currentTransitions.isNotEmpty()) { val transition = transitionStates[0] as TransitionState.Transition finishTransition(transition, transition.currentScene) finishTransition(transitionStates[0] as TransitionState.Transition) } // We finished all transitions, so we are now idle. We remove this state so that Loading Loading @@ -328,18 +321,17 @@ internal class MutableSceneTransitionLayoutStateImpl( ) } private fun cancelActiveTransitionLinks() { for ((link, linkedTransition) in activeTransitionLinks) { link.target.finishTransition(linkedTransition, linkedTransition.currentScene) private fun cancelActiveTransitionLinks(transition: TransitionState.Transition) { transition.activeTransitionLinks.forEach { (link, linkedTransition) -> link.target.finishTransition(linkedTransition) } activeTransitionLinks.clear() transition.activeTransitionLinks.clear() } private fun setupTransitionLinks(transitionState: TransitionState) { if (transitionState !is TransitionState.Transition) return private fun setupTransitionLinks(transition: TransitionState.Transition) { stateLinks.fastForEach { stateLink -> val matchingLinks = stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) } stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) } if (matchingLinks.isEmpty()) return@fastForEach if (matchingLinks.size > 1) error("More than one link matched.") Loading @@ -350,31 +342,27 @@ internal class MutableSceneTransitionLayoutStateImpl( val linkedTransition = LinkedTransition( originalTransition = transitionState, originalTransition = transition, fromScene = targetCurrentScene, toScene = matchingLink.targetTo, key = matchingLink.targetTransitionKey, ) stateLink.target.startTransition(linkedTransition) activeTransitionLinks[stateLink] = linkedTransition transition.activeTransitionLinks[stateLink] = linkedTransition } } /** * Notify that [transition] was finished and that we should settle to [idleScene]. This will do * nothing if [transition] was interrupted since it was started. * Notify that [transition] was finished and that it settled to its * [currentScene][TransitionState.currentScene]. This will do nothing if [transition] was * interrupted since it was started. */ internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) { internal fun finishTransition(transition: TransitionState.Transition) { checkThread() val existingIdleScene = finishedTransitions[transition] if (existingIdleScene != null) { if (finishedTransitions.contains(transition)) { // This transition was already finished. check(idleScene == existingIdleScene) { "Transition $transition was finished multiple times with different " + "idleScene ($existingIdleScene != $idleScene)" } return } Loading @@ -386,15 +374,15 @@ internal class MutableSceneTransitionLayoutStateImpl( check(transitionStates.fastAll { it is TransitionState.Transition }) // Mark this transition as finished and save the scene it is settling at. finishedTransitions[transition] = idleScene // Mark this transition as finished. finishedTransitions.add(transition) // Finish all linked transitions. finishActiveTransitionLinks(idleScene) finishActiveTransitionLinks(transition) // Keep a reference to the idle scene of the last removed transition, in case we remove all // transitions and should settle to Idle. var lastRemovedIdleScene: SceneKey? = null // Keep a reference to the last transition, in case we remove all transitions and should // settle to Idle. val lastTransition = transitionStates.last() // Remove all first n finished transitions. var i = 0 Loading @@ -407,14 +395,14 @@ internal class MutableSceneTransitionLayoutStateImpl( } // Remove the transition from the set of finished transitions. lastRemovedIdleScene = finishedTransitions.remove(t) finishedTransitions.remove(t) i++ } // If all transitions are finished, we are idle. if (i == nStates) { check(finishedTransitions.isEmpty()) this.transitionStates = listOf(TransitionState.Idle(checkNotNull(lastRemovedIdleScene))) this.transitionStates = listOf(TransitionState.Idle(lastTransition.currentScene)) } else if (i > 0) { this.transitionStates = transitionStates.subList(fromIndex = i, toIndex = nStates) } Loading @@ -426,28 +414,18 @@ internal class MutableSceneTransitionLayoutStateImpl( // Force finish all transitions. while (currentTransitions.isNotEmpty()) { val transition = transitionStates[0] as TransitionState.Transition finishTransition(transition, transition.currentScene) finishTransition(transition) } check(transitionStates.size == 1) transitionStates = listOf(TransitionState.Idle(scene)) } private fun finishActiveTransitionLinks(idleScene: SceneKey) { val previousTransition = this.transitionState as? TransitionState.Transition ?: return for ((link, linkedTransition) in activeTransitionLinks) { if (previousTransition.fromScene == idleScene) { // The transition ended by arriving at the fromScene, move link to Idle(fromScene). link.target.finishTransition(linkedTransition, linkedTransition.fromScene) } else if (previousTransition.toScene == idleScene) { // The transition ended by arriving at the toScene, move link to Idle(toScene). link.target.finishTransition(linkedTransition, linkedTransition.toScene) } else { // The transition was interrupted by something else, we reset to initial state. link.target.finishTransition(linkedTransition, linkedTransition.fromScene) } private fun finishActiveTransitionLinks(transition: TransitionState.Transition) { for ((link, linkedTransition) in transition.activeTransitionLinks) { link.target.finishTransition(linkedTransition) } activeTransitionLinks.clear() transition.activeTransitionLinks.clear() } /** Loading @@ -465,31 +443,21 @@ internal class MutableSceneTransitionLayoutStateImpl( fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold fun finishAllTransitions(lastTransitionIdleScene: SceneKey) { fun finishAllTransitions() { // Force finish all transitions. while (currentTransitions.isNotEmpty()) { val transition = transitionStates[0] as TransitionState.Transition val idleScene = if (transitionStates.size == 1) { lastTransitionIdleScene } else { transition.currentScene } finishTransition(transition, idleScene) finishTransition(transitionStates[0] as TransitionState.Transition) } } return when { isProgressCloseTo(0f) -> { finishAllTransitions(transition.fromScene) val shouldSnap = (isProgressCloseTo(0f) && transition.currentScene == transition.fromScene) || (isProgressCloseTo(1f) && transition.currentScene == transition.toScene) return if (shouldSnap) { finishAllTransitions() true } isProgressCloseTo(1f) -> { finishAllTransitions(transition.toScene) true } else -> false } else { false } } } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt +6 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ import com.android.compose.animation.scene.SceneTransitionLayoutImpl import com.android.compose.animation.scene.TransformationSpec import com.android.compose.animation.scene.TransformationSpecImpl import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.transition.link.LinkedTransition import com.android.compose.animation.scene.transition.link.StateLink import kotlinx.coroutines.Job import kotlinx.coroutines.launch Loading Loading @@ -110,6 +112,9 @@ sealed interface ContentState<out T : ContentKey> { */ private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null /** The map of active links that connects this transition to other transitions. */ internal val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() init { check(fromContent != toContent) check( Loading @@ -131,7 +136,7 @@ sealed interface ContentState<out T : ContentKey> { * animation is complete or cancel it to snap the animation. Calling [finish] multiple * times will return the same [Job]. */ abstract fun finish(): Job internal abstract fun finish(): Job /** * Whether we are transitioning. If [from] or [to] is empty, we will also check that they Loading