diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt index 16863f6ac7..6403bca582 100644 --- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt +++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.runtime.withFrameNanos import com.airbnb.lottie.LottieComposition import kotlinx.coroutines.CancellationException import kotlinx.coroutines.NonCancellable @@ -255,10 +256,22 @@ private class LottieAnimatableImpl : LottieAnimatable { } } - // We use withInfiniteAnimationFrameNanos because it allows tests to add a CoroutineContext - // element that will cancel infinite transitions instead of preventing composition from ever going idle. - private suspend fun doFrame(iterations: Int): Boolean = withInfiniteAnimationFrameNanos { frameNanos -> - val composition = composition ?: return@withInfiniteAnimationFrameNanos true + private suspend fun doFrame(iterations: Int): Boolean { + return if (iterations == LottieConstants.IterateForever) { + // We use withInfiniteAnimationFrameNanos because it allows tests to add a CoroutineContext + // element that will cancel infinite transitions instead of preventing composition from ever going idle. + withInfiniteAnimationFrameNanos { frameNanos -> + onFrame(iterations, frameNanos) + } + } else { + withFrameNanos { frameNanos -> + onFrame(iterations, frameNanos) + } + } + } + + private fun onFrame(iterations: Int, frameNanos: Long): Boolean { + val composition = composition ?: return true val dNanos = if (lastFrameNanos == AnimationConstants.UnspecifiedTime) 0L else (frameNanos - lastFrameNanos) lastFrameNanos = frameNanos @@ -279,7 +292,7 @@ private class LottieAnimatableImpl : LottieAnimatable { if (iteration + dIterations > iterations) { progress = endProgress iteration = iterations - return@withInfiniteAnimationFrameNanos false + return false } iteration += dIterations val progressPastEndRem = progressPastEndOfIteration - (dIterations - 1) * durationProgress @@ -289,7 +302,7 @@ private class LottieAnimatableImpl : LottieAnimatable { } } - true + return true } } diff --git a/sample-compose/src/androidTest/java/com/airbnb/lottie/samples/WalkthroughAnimationTest.kt b/sample-compose/src/androidTest/java/com/airbnb/lottie/samples/WalkthroughAnimationTest.kt new file mode 100644 index 0000000000..ec6239793f --- /dev/null +++ b/sample-compose/src/androidTest/java/com/airbnb/lottie/samples/WalkthroughAnimationTest.kt @@ -0,0 +1,46 @@ +package com.airbnb.lottie.samples + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import com.airbnb.lottie.LottieCompositionFactory +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.animateLottieCompositionAsState +import com.airbnb.lottie.sample.compose.ComposeActivity +import com.airbnb.lottie.sample.compose.R +import org.junit.Rule +import org.junit.Test + +class WalkthroughAnimationTest { + + @get:Rule + val composeTestRule = createAndroidComposeRule(ComposeActivity::class.java) + + @Test + fun testWalkthroughCompletes() { + val composition = LottieCompositionFactory.fromRawResSync(composeTestRule.activity, R.raw.walkthrough).value!! + var animationCompleted = true + + composeTestRule.setContent { + val progress by animateLottieCompositionAsState(composition, iterations = 1) + + Box( + modifier = Modifier + .fillMaxSize() + ) { + LottieAnimation( + composition, + progress, + ) + } + + if (progress == 1f) { + animationCompleted = true + } + } + + composeTestRule.mainClock.advanceTimeUntil { animationCompleted } + } +} \ No newline at end of file