Skip to content

Commit

Permalink
Merge pull request #797 from arkivanov/dont-animate-transition-on-bac…
Browse files Browse the repository at this point in the history
…k-start

Don't seek stack animation transition on back start when predictive b…
  • Loading branch information
arkivanov authored Oct 15, 2024
2 parents 674b14f + 2051b46 commit 53d6593
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ internal class DefaultStackAnimation<C : Any, T : Any>(
)

scope.launch {
animationHandler.start(backEvent)
animationHandler.progress(backEvent)
}
}

Expand Down Expand Up @@ -301,14 +301,6 @@ internal class DefaultStackAnimation<C : Any, T : Any>(
val exitTransitionState: SeekableTransitionState<EnterExitState> = SeekableTransitionState(EnterExitState.Visible)
val enterTransitionState: SeekableTransitionState<EnterExitState> = 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
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
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.SemanticsNodeInteraction
import kotlin.test.fail

@Composable
internal fun Transition<EnterExitState>.animateFloat(): State<Float> =
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String, Float>()

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<String, Float>()

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,
Expand Down

0 comments on commit 53d6593

Please sign in to comment.