diff --git a/samples/android/src/main/AndroidManifest.xml b/samples/android/src/main/AndroidManifest.xml index 96782b71..dfa169a5 100644 --- a/samples/android/src/main/AndroidManifest.xml +++ b/samples/android/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ + diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/SampleActivity.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/SampleActivity.kt index 8d9be558..25e5f342 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/SampleActivity.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/SampleActivity.kt @@ -32,6 +32,7 @@ import cafe.adriel.voyager.sample.rxJavaIntegration.RxJavaIntegrationActivity import cafe.adriel.voyager.sample.screenModel.ScreenModelActivity import cafe.adriel.voyager.sample.stateStack.StateStackActivity import cafe.adriel.voyager.sample.tabNavigation.TabNavigationActivity +import cafe.adriel.voyager.sample.transition.TransitionActivity class SampleActivity : ComponentActivity() { @@ -58,6 +59,7 @@ class SampleActivity : ComponentActivity() { StartSampleButton("Tab Navigation") StartSampleButton("BottomSheet Navigation") StartSampleButton("Nested Navigation") + StartSampleButton("Transition") StartSampleButton("Android ViewModel") StartSampleButton("ScreenModel") StartSampleButton("Koin Integration") diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/FadeScreen.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/FadeScreen.kt new file mode 100644 index 00000000..3c16b45e --- /dev/null +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/FadeScreen.kt @@ -0,0 +1,28 @@ +package cafe.adriel.voyager.sample.transition + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.core.screen.uniqueScreenKey + +data object FadeScreen : Screen { + override val key = uniqueScreenKey + + @Composable + override fun Content() { + Box(modifier = Modifier.fillMaxSize()) { + Text( + text = "Fade Screen", + modifier = Modifier.align(alignment = Alignment.Center), + color = Color.Red, + fontSize = 30.sp + ) + } + } +} diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/ScaleScreen.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/ScaleScreen.kt new file mode 100644 index 00000000..e5fabe0b --- /dev/null +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/ScaleScreen.kt @@ -0,0 +1,28 @@ +package cafe.adriel.voyager.sample.transition + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.core.screen.uniqueScreenKey + +data object ScaleScreen : Screen { + override val key = uniqueScreenKey + + @Composable + override fun Content() { + Box(modifier = Modifier.fillMaxSize()) { + Text( + text = "Scale Screen", + modifier = Modifier.align(alignment = Alignment.Center), + color = Color.Red, + fontSize = 30.sp + ) + } + } +} diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/SelfResetNavigator.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/SelfResetNavigator.kt new file mode 100644 index 00000000..f618811e --- /dev/null +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/SelfResetNavigator.kt @@ -0,0 +1,122 @@ +package cafe.adriel.voyager.sample.transition + +import android.util.Log +import androidx.compose.runtime.saveable.SaveableStateHolder +import cafe.adriel.voyager.core.annotation.InternalVoyagerApi +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.core.stack.Stack +import cafe.adriel.voyager.core.stack.toMutableStateStack +import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.navigator.NavigatorCreator +import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +fun SelfResetNavigatorCreator(resetAfter: Long): NavigatorCreator = { + screenCollection: List, + s: String, + saveableStateHolder: SaveableStateHolder, + navigatorDisposeBehavior: NavigatorDisposeBehavior, + navigator: Navigator?, -> + SelfResetNavigator(resetAfter, screenCollection, s, saveableStateHolder, navigatorDisposeBehavior, navigator) +} + +private class SelfResetNavigator @InternalVoyagerApi constructor( + val resetAfter: Long, + screens: List, + key: String, + stateHolder: SaveableStateHolder, + disposeBehavior: NavigatorDisposeBehavior, + parent: Navigator? = null, + private val stack: Stack = screens.toMutableStateStack(minSize = 1) +) : Navigator(screens, key, stateHolder, disposeBehavior, parent, stack) { + + private val timeStamp = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + + private val scope = CoroutineScope(Job() + Dispatchers.Default) + + init { + Log.d("@@@", "Init") + observeTimeStamp() + } + + private fun observeTimeStamp() { + scope.launch { + timeStamp.collectLatest { + Log.d("@@@", "Before delay") + delay(resetAfter) + Log.d("@@@", "After delay") + if (canPop) { + Log.d("@@@", "Reset") + popAll() + } + } + } + } + + override fun push(item: Screen) { + timeStamp.tryEmit(Unit) + stack.push(item) + } + + override fun push(items: List) { + timeStamp.tryEmit(Unit) + stack.push(items) + } + + override fun replace(item: Screen) { + timeStamp.tryEmit(Unit) + stack.replace(item) + } + + override fun replaceAll(item: Screen) { + timeStamp.tryEmit(Unit) + stack.replaceAll(item) + } + + override fun replaceAll(items: List) { + timeStamp.tryEmit(Unit) + stack.replaceAll(items) + } + + override fun pop(): Boolean { + if (canPop) { + timeStamp.tryEmit(Unit) + } + return stack.pop() + } + + override fun popAll() { + timeStamp.tryEmit(Unit) + stack.popAll() + } + + override fun popUntil(predicate: (Screen) -> Boolean): Boolean { + timeStamp.tryEmit(Unit) + return stack.popUntil(predicate) + } + + override fun plusAssign(item: Screen) { + timeStamp.tryEmit(Unit) + stack.plusAssign(item) + } + + override fun plusAssign(items: List) { + timeStamp.tryEmit(Unit) + stack.plusAssign(items) + } + + override fun clearEvent() { + timeStamp.tryEmit(Unit) + stack.clearEvent() + } +} diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/ShrinkScreen.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/ShrinkScreen.kt new file mode 100644 index 00000000..4e069268 --- /dev/null +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/ShrinkScreen.kt @@ -0,0 +1,28 @@ +package cafe.adriel.voyager.sample.transition + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.core.screen.uniqueScreenKey + +data object ShrinkScreen : Screen { + override val key = uniqueScreenKey + + @Composable + override fun Content() { + Box(modifier = Modifier.fillMaxSize()) { + Text( + text = "Shrink Screen", + modifier = Modifier.align(alignment = Alignment.Center), + color = Color.Red, + fontSize = 30.sp + ) + } + } +} diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/TransitionActivity.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/TransitionActivity.kt new file mode 100644 index 00000000..22c7ea02 --- /dev/null +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/TransitionActivity.kt @@ -0,0 +1,175 @@ +package cafe.adriel.voyager.sample.transition + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.ContentTransform +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.keyframes +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.DefaultNavigatorCreator +import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.navigator.isPopLastEvent +import cafe.adriel.voyager.navigator.isPushLastEvent +import cafe.adriel.voyager.navigator.summonNavigatorCreator +import cafe.adriel.voyager.transitions.ScreenTransition +import cafe.adriel.voyager.transitions.ScreenTransitionContent + +class TransitionActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + summonNavigatorCreator = SelfResetNavigatorCreator(5000L) + Navigator(TransitionScreen) { + TransitionDemo(it) + } + } + } + + override fun onDestroy() { + super.onDestroy() + summonNavigatorCreator = DefaultNavigatorCreator() + } +} + +@Composable +fun TransitionDemo( + navigator: Navigator, + modifier: Modifier = Modifier, + content: ScreenTransitionContent = { it.Content() } +) { + val transition: AnimatedContentTransitionScope.() -> ContentTransform = { + // Define any StackEvent you want transition to be + val isPush = navigator.isPushLastEvent() + val isPop = navigator.isPopLastEvent() + // Define any Screen you want transition must be from + val invoker = this.initialState + val target = this.targetState + val isInvokerTransitionScreen = invoker == TransitionScreen + val isInvokerFadeScreen = invoker == FadeScreen + val isInvokerShrinkScreen = invoker == ShrinkScreen + val isInvokerScaleScreen = invoker == ScaleScreen + // Define any Screen you want transition must be to + val isTargetTransitionScreen = target == TransitionScreen + val isTargetFadeScreen = target == FadeScreen + val isTargetShrinkScreen = target == ShrinkScreen + val isTargetScaleScreen = target == ScaleScreen + + val tweenOffset: FiniteAnimationSpec = tween( + durationMillis = 2000, + delayMillis = 100, + easing = LinearEasing + ) + val tweenSize: FiniteAnimationSpec = tween( + durationMillis = 2000, + delayMillis = 100, + easing = LinearEasing + ) + + val sizeDefault = ({ size: Int -> size }) + val sizeMinus = ({ size: Int -> -size }) + val (initialOffset, targetOffset) = when { + isPush && isInvokerTransitionScreen -> { + if (isTargetFadeScreen || isTargetShrinkScreen) sizeMinus to sizeDefault + else sizeDefault to sizeMinus + } + isPop && isInvokerFadeScreen && isTargetTransitionScreen -> sizeDefault to sizeMinus + else -> sizeDefault to sizeMinus + } + + val fadeInFrames = keyframes { + durationMillis = 2000 + 0.1f at 0 with LinearEasing + 0.2f at 1800 with LinearEasing + 1.0f at 2000 with LinearEasing + } + val fadeOutFrames = keyframes { + durationMillis = 2000 + 0.9f at 0 with LinearEasing + 0.8f at 100 with LinearEasing + 0.7f at 200 with LinearEasing + 0.6f at 300 with LinearEasing + 0.5f at 400 with LinearEasing + 0.4f at 500 with LinearEasing + 0.3f at 600 with LinearEasing + 0.2f at 1000 with LinearEasing + 0.1f at 1500 with LinearEasing + 0.0f at 2000 with LinearEasing + } + + val scaleInFrames = keyframes { + durationMillis = 2000 + 0.1f at 0 with LinearEasing + 0.3f at 1500 with LinearEasing + 1.0f at 2000 with LinearEasing + } + val scaleOutFrames = keyframes { + durationMillis = 2000 + 0.9f at 0 with LinearEasing + 0.7f at 500 with LinearEasing + 0.3f at 700 with LinearEasing + 0.0f at 2000 with LinearEasing + } + + when { + // Define any transition you want based on the StackEvent, invoker and target + isPush && isInvokerTransitionScreen && isTargetFadeScreen || + isPop && isInvokerFadeScreen && isTargetTransitionScreen -> { + val enter = slideInHorizontally(tweenOffset, initialOffset) + fadeIn(fadeInFrames) + val exit = slideOutHorizontally(tweenOffset, targetOffset) + fadeOut(fadeOutFrames) + enter togetherWith exit + } + isPush && isInvokerTransitionScreen && isTargetShrinkScreen || + isPop && isInvokerShrinkScreen && isTargetTransitionScreen -> { + val enter = slideInVertically(tweenOffset, initialOffset) + val exit = shrinkVertically(animationSpec = tweenSize, shrinkTowards = Alignment.Top) + enter togetherWith exit + } + isPush && isInvokerTransitionScreen && isTargetScaleScreen -> { + val enter = slideInVertically(tweenOffset, initialOffset) + fadeIn(fadeInFrames) + scaleIn(scaleInFrames) + val exit = slideOutVertically(tweenOffset, targetOffset) + fadeOut(fadeOutFrames) + scaleOut(scaleOutFrames) + enter togetherWith exit + } + isPop && isInvokerScaleScreen && isTargetTransitionScreen -> { + val enter = slideInHorizontally(tweenOffset, initialOffset) + fadeIn(fadeInFrames) + scaleIn(scaleInFrames) + val exit = slideOutHorizontally(tweenOffset, targetOffset) + fadeOut(fadeOutFrames) + scaleOut(scaleOutFrames) + enter togetherWith exit + } + else -> { + val animationSpec: FiniteAnimationSpec = tween( + durationMillis = 500, + delayMillis = 100, + easing = LinearEasing + ) + slideInHorizontally(animationSpec, initialOffset) togetherWith + slideOutHorizontally(animationSpec, targetOffset) + } + } + } + ScreenTransition( + navigator = navigator, + transition = transition, + modifier = modifier, + content = content, + ) +} diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/TransitionScreen.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/TransitionScreen.kt new file mode 100644 index 00000000..919cc78d --- /dev/null +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/transition/TransitionScreen.kt @@ -0,0 +1,59 @@ +package cafe.adriel.voyager.sample.transition + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.core.screen.uniqueScreenKey +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow + +data object TransitionScreen : Screen { + + override val key = uniqueScreenKey + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxSize() + ) { + PushButton(text = "Push fade left\nPop fade right") { + navigator.push(FadeScreen) + } + Spacer(modifier = Modifier.height(50.dp)) + PushButton(text = "Push shrink top\nPop shrink bottom") { + navigator.push(ShrinkScreen) + } + Spacer(modifier = Modifier.height(50.dp)) + PushButton(text = "Push fade scale bottom\nPop scale right") { + navigator.push(ScaleScreen) + } + } + } +} + +@Composable +private fun PushButton( + text: String, + onClick: () -> Unit +) { + Button( + onClick = onClick, + modifier = Modifier.sizeIn(minWidth = 200.dp, minHeight = 70.dp) + ) { + Text(text = text) + } +} diff --git a/voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.android.kt b/voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.android.kt index 4c2f7b18..8b0e15ca 100644 --- a/voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.android.kt +++ b/voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.android.kt @@ -8,7 +8,9 @@ import cafe.adriel.voyager.core.screen.Screen * Navigator Saver that forces all Screens be [Parcelable], if not, it will throw a exception while trying to save * the navigator state. */ -public fun parcelableNavigatorSaver(): NavigatorSaver = +public fun parcelableNavigatorSaver( + navigatorCreator: NavigatorCreator = summonNavigatorCreator +): NavigatorSaver = NavigatorSaver { _, key, stateHolder, disposeBehavior, parent -> listSaver( save = { navigator -> @@ -28,7 +30,7 @@ public fun parcelableNavigatorSaver(): NavigatorSaver = screenAsParcelables }, - restore = { items -> Navigator(items as List, key, stateHolder, disposeBehavior, parent) } + restore = { items -> navigatorCreator(items as List, key, stateHolder, disposeBehavior, parent) } ) } diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/DefaultNavigator.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/DefaultNavigator.kt new file mode 100644 index 00000000..efc69af8 --- /dev/null +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/DefaultNavigator.kt @@ -0,0 +1,16 @@ +package cafe.adriel.voyager.navigator + +import androidx.compose.runtime.saveable.SaveableStateHolder +import cafe.adriel.voyager.core.annotation.InternalVoyagerApi +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.core.stack.Stack +import cafe.adriel.voyager.core.stack.toMutableStateStack + +public class DefaultNavigator @InternalVoyagerApi constructor( + screens: List, + key: String, + stateHolder: SaveableStateHolder, + disposeBehavior: NavigatorDisposeBehavior, + parent: Navigator? = null, + stack: Stack = screens.toMutableStateStack(minSize = 1) +) : Navigator(screens, key, stateHolder, disposeBehavior, parent, stack) diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/Navigator.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/Navigator.kt index 0aa0e187..36286380 100644 --- a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/Navigator.kt +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/Navigator.kt @@ -19,6 +19,7 @@ import cafe.adriel.voyager.core.lifecycle.getNavigatorScreenLifecycleProvider import cafe.adriel.voyager.core.lifecycle.rememberScreenLifecycleOwner import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.stack.Stack +import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.core.stack.toMutableStateStack import cafe.adriel.voyager.navigator.internal.ChildrenNavigationDisposableEffect import cafe.adriel.voyager.navigator.internal.LocalNavigatorStateHolder @@ -102,13 +103,14 @@ public fun Navigator( } } -public class Navigator @InternalVoyagerApi constructor( +public abstract class Navigator @InternalVoyagerApi constructor( screens: List, @InternalVoyagerApi public val key: String, private val stateHolder: SaveableStateHolder, public val disposeBehavior: NavigatorDisposeBehavior, - public val parent: Navigator? = null -) : Stack by screens.toMutableStateStack(minSize = 1) { + public val parent: Navigator? = null, + private val stack: Stack = screens.toMutableStateStack(minSize = 1) +) : Stack by stack { public val level: Int = parent?.level?.inc() ?: 0 @@ -190,3 +192,8 @@ public data class NavigatorDisposeBehavior( public fun compositionUniqueId(): String = currentCompositeKeyHash.toString(MaxSupportedRadix) private val MaxSupportedRadix = 36 + +public fun Navigator?.isPushLastEvent(): Boolean = this?.lastEvent == StackEvent.Push +public fun Navigator?.isPopLastEvent(): Boolean = this?.lastEvent == StackEvent.Pop +public fun Navigator?.isReplaceLastEvent(): Boolean = this?.lastEvent == StackEvent.Replace +public fun Navigator?.isIdleLastEvent(): Boolean = this?.lastEvent == StackEvent.Idle diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/NavigatorCreator.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/NavigatorCreator.kt new file mode 100644 index 00000000..5712b1c3 --- /dev/null +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/NavigatorCreator.kt @@ -0,0 +1,24 @@ +package cafe.adriel.voyager.navigator + +import androidx.compose.runtime.saveable.SaveableStateHolder +import cafe.adriel.voyager.core.screen.Screen + +public typealias NavigatorCreator = ( + screens: List, + key: String, + stateHolder: SaveableStateHolder, + disposeBehavior: NavigatorDisposeBehavior, + parent: Navigator? +) -> Navigator + +// Choose your Navigator +public var summonNavigatorCreator: NavigatorCreator = DefaultNavigatorCreator() + +public fun DefaultNavigatorCreator(): NavigatorCreator = { + screenCollection: List, + s: String, + saveableStateHolder: SaveableStateHolder, + navigatorDisposeBehavior: NavigatorDisposeBehavior, + navigator: Navigator?, -> + DefaultNavigator(screenCollection, s, saveableStateHolder, navigatorDisposeBehavior, navigator) +} diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.kt index 86c1e41c..496fbd9e 100644 --- a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.kt +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.kt @@ -33,10 +33,12 @@ public fun interface NavigatorSaver { * * If you want to use only Parcelable and want a NavigatorSaver that forces the use Parcelable, you can use [parcelableNavigatorSaver]. */ -public fun defaultNavigatorSaver(): NavigatorSaver = +public fun defaultNavigatorSaver( + navigatorCreator: NavigatorCreator = summonNavigatorCreator +): NavigatorSaver = NavigatorSaver { _, key, stateHolder, disposeBehavior, parent -> listSaver( save = { navigator -> navigator.items }, - restore = { items -> Navigator(items, key, stateHolder, disposeBehavior, parent) } + restore = { items -> navigatorCreator(items, key, stateHolder, disposeBehavior, parent) } ) } diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorSaverInternal.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorSaverInternal.kt index 4411f0d2..293f11cc 100644 --- a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorSaverInternal.kt +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorSaverInternal.kt @@ -9,7 +9,9 @@ import androidx.compose.runtime.staticCompositionLocalOf import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigatorSaver import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.navigator.NavigatorCreator import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior +import cafe.adriel.voyager.navigator.summonNavigatorCreator internal val LocalNavigatorStateHolder: ProvidableCompositionLocal = staticCompositionLocalOf { error("LocalNavigatorStateHolder not initialized") } @@ -19,7 +21,8 @@ internal fun rememberNavigator( screens: List, key: String, disposeBehavior: NavigatorDisposeBehavior, - parent: Navigator? + parent: Navigator?, + navigatorCreator: NavigatorCreator = summonNavigatorCreator ): Navigator { val stateHolder = LocalNavigatorStateHolder.current val navigatorSaver = LocalNavigatorSaver.current @@ -28,6 +31,6 @@ internal fun rememberNavigator( } return rememberSaveable(saver = saver, key = key) { - Navigator(screens, key, stateHolder, disposeBehavior, parent) + navigatorCreator(screens, key, stateHolder, disposeBehavior, parent) } }