diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt index 81408884..d0959a41 100644 --- a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt @@ -2,12 +2,14 @@ package cafe.adriel.voyager.navigator.internal import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import cafe.adriel.voyager.core.annotation.InternalVoyagerApi import cafe.adriel.voyager.core.lifecycle.DisposableEffectIgnoringConfiguration import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.lifecycle.NavigatorLifecycleStore -private val disposableEvents: Set = +@InternalVoyagerApi +public val disposableEvents: Set = setOf(StackEvent.Pop, StackEvent.Replace) @Composable diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt index bdf169e9..fa57aaa9 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt @@ -6,13 +6,17 @@ import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ContentTransform import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.navigator.internal.disposableEvents +import cafe.adriel.voyager.transitions.internal.rememberPrevious @ExperimentalVoyagerApi public interface ScreenTransition { @@ -84,6 +88,28 @@ public fun ScreenTransition( modifier: Modifier = Modifier, content: ScreenTransitionContent = { it.Content() } ) { + ScreenTransition( + navigator = navigator, + transition = transition, + modifier = modifier, + disposeScreenAfterTransitionEnd = false, + content = content, + ) +} + +@ExperimentalVoyagerApi +@OptIn(ExperimentalAnimationApi::class) +@Composable +public fun ScreenTransition( + navigator: Navigator, + transition: AnimatedContentTransitionScope.() -> ContentTransform, + modifier: Modifier = Modifier, + disposeScreenAfterTransitionEnd: Boolean = false, + content: ScreenTransitionContent = { it.Content() } +) { + // This can be costly because is checking every single item in the list + // we should re evaluate how validation works, maybe validating screen keys or === + val previousItems = rememberPrevious(navigator.items) AnimatedContent( targetState = navigator.lastItem, transitionSpec = { @@ -104,6 +130,21 @@ public fun ScreenTransition( }, modifier = modifier ) { screen -> + if (this.transition.targetState == this.transition.currentState) { + LaunchedEffect(Unit) { + if(disposeScreenAfterTransitionEnd) { + // if disposeSteps = true, lastEvent will be always idle + // else it will keep the event and we can dispose our self. + if (navigator.lastEvent in disposableEvents) { + val newScreenKeys = navigator.items.map { it.key } + previousItems?.filter { it.key !in newScreenKeys }?.forEach { + navigator.dispose(it) + } + navigator.clearEvent() + } + } + } + } navigator.saveableState("transition", screen) { content(screen) } diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt new file mode 100644 index 00000000..433c38e5 --- /dev/null +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt @@ -0,0 +1,38 @@ +package cafe.adriel.voyager.transitions.internal + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember + +@Composable +internal fun rememberRef(): MutableState { + // for some reason it always recreated the value with vararg keys, + // leaving out the keys as a parameter for remember for now + return remember() { + object: MutableState { + override var value: T? = null + + override fun component1(): T? = value + + override fun component2(): (T?) -> Unit = { value = it } + } + } +} + +@Composable +internal fun rememberPrevious( + current: T, + shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b }, +): T? { + val ref = rememberRef() + + // launched after render, so the current render will have the old value anyway + SideEffect { + if (shouldUpdate(ref.value, current)) { + ref.value = current + } + } + + return ref.value +}