diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt index 1ade1239..b4bed2a0 100644 --- a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt @@ -264,7 +264,7 @@ internal class DefaultStackAnimation( ) scope.launch { - animationHandler.start(backEvent) + animationHandler.progress(backEvent) } } @@ -301,14 +301,6 @@ internal class DefaultStackAnimation( val exitTransitionState: SeekableTransitionState = SeekableTransitionState(EnterExitState.Visible) val enterTransitionState: SeekableTransitionState = SeekableTransitionState(EnterExitState.PreEnter) - suspend fun start(backEvent: BackEvent) { - awaitAll( - { exitTransitionState.seekTo(fraction = backEvent.progress, targetState = EnterExitState.PostExit) }, - { enterTransitionState.seekTo(fraction = backEvent.progress, targetState = EnterExitState.Visible) }, - { animatable?.animate(backEvent) }, - ) - } - suspend fun progress(backEvent: BackEvent) { animatable?.run { animate(backEvent) diff --git a/extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt b/extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt index e22b552e..045c4ba6 100644 --- a/extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt +++ b/extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt @@ -1,5 +1,12 @@ package com.arkivanov.decompose.extensions.compose.experimental +import androidx.compose.animation.EnterExitState +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.tween +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State import androidx.compose.ui.semantics.SemanticsConfiguration import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.semantics.SemanticsProperties @@ -7,6 +14,16 @@ import androidx.compose.ui.semantics.getOrNull import androidx.compose.ui.test.SemanticsNodeInteraction import kotlin.test.fail +@Composable +internal fun Transition.animateFloat(): State = + animateFloat(transitionSpec = { tween(easing = LinearEasing) }) { state -> + when (state) { + EnterExitState.PreEnter -> 0F + EnterExitState.Visible -> 1F + EnterExitState.PostExit -> 0F + } + } + internal fun SemanticsNodeInteraction.assertTestTagToRootExists(testTag: String) { val count = collectTestTagsToRoot().filter { it == testTag }.size if (count != 1) { diff --git a/extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/PredictiveBackGestureTest.kt b/extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/PredictiveBackGestureTest.kt index aae5038d..4ca770a6 100644 --- a/extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/PredictiveBackGestureTest.kt +++ b/extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/PredictiveBackGestureTest.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import com.arkivanov.decompose.Child import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.extensions.compose.experimental.animateFloat import com.arkivanov.decompose.extensions.compose.experimental.assertTestTagToRootDoesNotExist import com.arkivanov.decompose.extensions.compose.experimental.assertTestTagToRootExists import com.arkivanov.decompose.extensions.compose.experimental.stack.dropLast @@ -408,6 +409,66 @@ class PredictiveBackGestureTest { assertFalse(values.any { it < 1F }) } + @Test + fun GIVEN_predictive_animatable_not_null_and_two_children_in_stack_WHEN_gesture_progressed_THEN_animation_transition_not_animating() { + var stack by mutableStateOf(stack("1", "2")) + + val animation = + DefaultStackAnimation( + predictiveBackAnimatable = ::TestAnimatable, + onBack = { stack = stack.dropLast() }, + ) + + val values = HashMap() + + composeRule.setContent { + animation(stack, Modifier) { + val value by transition.animateFloat() + values[it.configuration] = value + } + } + + backDispatcher.startPredictiveBack(BackEvent(progress = 0.1F)) + composeRule.waitForIdle() + backDispatcher.progressPredictiveBack(BackEvent(progress = 0.2F)) + composeRule.waitForIdle() + backDispatcher.progressPredictiveBack(BackEvent(progress = 0.3F)) + composeRule.waitForIdle() + + assertEquals(0F, values["1"]) + assertEquals(1F, values["2"]) + } + + @Test + fun GIVEN_predictive_animatable_null_and_two_children_in_stack_WHEN_gesture_progressed_THEN_animation_transition_animating() { + var stack by mutableStateOf(stack("1", "2")) + + val animation = + DefaultStackAnimation( + predictiveBackAnimatable = { null }, + onBack = { stack = stack.dropLast() }, + ) + + val values = HashMap() + + composeRule.setContent { + animation(stack, Modifier) { + val value by transition.animateFloat() + values[it.configuration] = value + } + } + + backDispatcher.startPredictiveBack(BackEvent(progress = 0.1F)) + composeRule.waitForIdle() + backDispatcher.progressPredictiveBack(BackEvent(progress = 0.2F)) + composeRule.waitForIdle() + backDispatcher.progressPredictiveBack(BackEvent(progress = 0.3F)) + composeRule.waitForIdle() + + assertEquals(0.3F, values["1"]) + assertEquals(0.7F, values["2"]) + } + private fun DefaultStackAnimation( predictiveBackAnimatable: (initialBackEvent: BackEvent) -> PredictiveBackAnimatable? = ::TestAnimatable, onBack: () -> Unit,