diff --git a/integration/voyager/build.gradle.kts b/integration/voyager/build.gradle.kts index 2496e80..800041f 100644 --- a/integration/voyager/build.gradle.kts +++ b/integration/voyager/build.gradle.kts @@ -40,6 +40,12 @@ kotlin { } } + val jsMain by getting { + dependencies { + implementation(libs.serialization.json) + } + } + val jvmMain by getting { dependsOn(commonMain.get()) dependencies { @@ -74,5 +80,8 @@ kotlin { val iosArm64Main by getting { dependsOn(nativeMain) } + val iosSimulatorArm64Main by getting { + dependsOn(nativeMain) + } } } diff --git a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerNavigatorAttribute.kt b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerNavigatorAttribute.kt index 22f1e60..f31fbee 100644 --- a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerNavigatorAttribute.kt +++ b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerNavigatorAttribute.kt @@ -1,6 +1,8 @@ package dev.programadorthi.routing.voyager import cafe.adriel.voyager.navigator.Navigator +import dev.programadorthi.routing.core.Routing +import dev.programadorthi.routing.core.application import dev.programadorthi.routing.core.application.Application import dev.programadorthi.routing.core.application.ApplicationCall import io.ktor.util.AttributeKey @@ -26,3 +28,9 @@ internal var ApplicationCall.voyagerNavigator: Navigator set(value) { application.voyagerNavigator = value } + +internal var Routing.voyagerNavigator: Navigator + get() = application.voyagerNavigator + set(value) { + application.voyagerNavigator = value + } diff --git a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerResourcesBuilder.kt b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerResourcesBuilder.kt index cd23794..344da3d 100644 --- a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerResourcesBuilder.kt +++ b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerResourcesBuilder.kt @@ -5,6 +5,7 @@ import dev.programadorthi.routing.core.Route import dev.programadorthi.routing.core.RouteMethod import dev.programadorthi.routing.core.Routing import dev.programadorthi.routing.core.application.ApplicationCall +import dev.programadorthi.routing.core.asRouting import dev.programadorthi.routing.resources.handle import dev.programadorthi.routing.resources.unregisterResource import io.ktor.util.pipeline.PipelineContext @@ -16,15 +17,17 @@ import io.ktor.util.pipeline.PipelineContext * * @param body receives an instance of the typed resource [T] as the first parameter. */ -public inline fun Route.screen(noinline body: suspend PipelineContext.(T) -> Screen): Route = - handle { resource -> - screen { +public inline fun Route.screen(noinline body: suspend PipelineContext.(T) -> Screen): Route { + val routing = asRouting ?: error("Your route $this must have a parent Routing") + return handle { resource -> + screen(routing) { when (resource) { is Screen -> resource else -> body(resource) } } } +} /** * Registers a typed handler for a [Screen] defined by the [T] class. @@ -43,15 +46,17 @@ public inline fun Route.screen(): Route = screen { scree public inline fun Route.screen( method: RouteMethod, noinline body: suspend PipelineContext.(T) -> Screen, -): Route = - handle(method = method) { resource -> - screen { +): Route { + val routing = asRouting ?: error("Your route $this must have a parent Routing") + return handle(method = method) { resource -> + screen(routing) { when (resource) { is Screen -> resource else -> body(resource) } } } +} /** * Registers a typed handler for a [RouteMethod] [Screen] defined by the [T] class. diff --git a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRouting.kt b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRouting.kt index ba38ff3..e7e2b6e 100644 --- a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRouting.kt +++ b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRouting.kt @@ -6,7 +6,10 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.SideEffect import androidx.compose.runtime.currentCompositeKeyHash +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.CurrentScreen @@ -16,8 +19,12 @@ import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior import cafe.adriel.voyager.navigator.OnBackPressed import dev.programadorthi.routing.core.Route import dev.programadorthi.routing.core.Routing -import dev.programadorthi.routing.core.application +import dev.programadorthi.routing.core.application.ApplicationCall +import dev.programadorthi.routing.core.replace import dev.programadorthi.routing.core.routing +import dev.programadorthi.routing.voyager.history.VoyagerHistoryMode +import dev.programadorthi.routing.voyager.history.historyMode +import dev.programadorthi.routing.voyager.history.restoreState import io.ktor.util.logging.Logger import kotlin.coroutines.CoroutineContext @@ -28,6 +35,7 @@ public val LocalVoyagerRouting: ProvidableCompositionLocal = @Composable public fun VoyagerRouting( + historyMode: VoyagerHistoryMode = VoyagerHistoryMode.Memory, routing: Routing, initialScreen: Screen, disposeBehavior: NavigatorDisposeBehavior = NavigatorDisposeBehavior(), @@ -36,6 +44,12 @@ public fun VoyagerRouting( content: NavigatorContent = { CurrentScreen() }, ) { CompositionLocalProvider(LocalVoyagerRouting provides routing) { + var stateToRestore by remember { mutableStateOf(null) } + + routing.restoreState { state -> + stateToRestore = state + } + Navigator( screen = initialScreen, disposeBehavior = disposeBehavior, @@ -43,7 +57,18 @@ public fun VoyagerRouting( key = key, ) { navigator -> SideEffect { - routing.application.voyagerNavigator = navigator + routing.voyagerNavigator = navigator + routing.historyMode = historyMode + + if (stateToRestore != null) { + val call = stateToRestore as? ApplicationCall + val path = stateToRestore as? String ?: "" + when { + call != null -> routing.execute(call) + path.isNotBlank() -> routing.replace(path) + } + stateToRestore = null + } } content(navigator) } @@ -52,6 +77,7 @@ public fun VoyagerRouting( @Composable public fun VoyagerRouting( + historyMode: VoyagerHistoryMode = VoyagerHistoryMode.Memory, initialScreen: Screen, configuration: Route.() -> Unit, rootPath: String = "/", @@ -83,6 +109,7 @@ public fun VoyagerRouting( } VoyagerRouting( + historyMode = historyMode, routing = routing, initialScreen = initialScreen, disposeBehavior = disposeBehavior, diff --git a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRoutingBuilder.kt b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRoutingBuilder.kt index d0ab01a..b747d49 100644 --- a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRoutingBuilder.kt +++ b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRoutingBuilder.kt @@ -3,9 +3,15 @@ package dev.programadorthi.routing.voyager import cafe.adriel.voyager.core.screen.Screen import dev.programadorthi.routing.core.Route import dev.programadorthi.routing.core.RouteMethod +import dev.programadorthi.routing.core.Routing import dev.programadorthi.routing.core.application.ApplicationCall import dev.programadorthi.routing.core.application.call +import dev.programadorthi.routing.core.asRouting import dev.programadorthi.routing.core.route +import dev.programadorthi.routing.voyager.history.platformPush +import dev.programadorthi.routing.voyager.history.platformReplace +import dev.programadorthi.routing.voyager.history.platformReplaceAll +import dev.programadorthi.routing.voyager.history.shouldNeglect import io.ktor.util.pipeline.PipelineContext import io.ktor.utils.io.KtorDsl @@ -26,19 +32,27 @@ public fun Route.screen( @KtorDsl public fun Route.screen(body: suspend PipelineContext.() -> Screen) { + val routing = asRouting ?: error("Your route $this must have a parent Routing") handle { - screen { + screen(routing) { body(this) } } } -public suspend fun PipelineContext.screen(body: suspend () -> Screen) { - val navigator = call.voyagerNavigator +public suspend fun PipelineContext.screen( + routing: Routing, + body: suspend () -> Screen, +) { + if (call.shouldNeglect()) { + call.voyagerNavigator.replace(body()) + return + } + when (call.routeMethod) { - RouteMethod.Push -> navigator.push(body()) - RouteMethod.Replace -> navigator.replace(body()) - RouteMethod.ReplaceAll -> navigator.replaceAll(body()) + RouteMethod.Push -> call.platformPush(routing, body) + RouteMethod.Replace -> call.platformReplace(routing, body) + RouteMethod.ReplaceAll -> call.platformReplaceAll(routing, body) else -> error( "Voyager needs a stack route method to work. You called a screen ${call.uri} using " + diff --git a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.kt b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.kt index deff46c..7d1aa9f 100644 --- a/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.kt +++ b/integration/voyager/common/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.kt @@ -3,14 +3,22 @@ package dev.programadorthi.routing.voyager import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.Navigator import dev.programadorthi.routing.core.Routing -import dev.programadorthi.routing.core.application -public fun Routing.canPop(): Boolean = application.voyagerNavigator.canPop +internal expect fun Routing.popOnPlatform( + result: Any? = null, + fallback: () -> Unit, +) + +public expect val Routing.canPop: Boolean + +public fun Routing.canPop(): Boolean = canPop public fun Routing.pop(result: Any? = null) { - val navigator = application.voyagerNavigator - if (navigator.pop()) { - navigator.trySendPopResult(result) + popOnPlatform(result) { + val navigator = voyagerNavigator + if (navigator.pop()) { + navigator.trySendPopResult(result) + } } } @@ -18,7 +26,7 @@ public fun Routing.popUntil( result: Any? = null, predicate: (Screen) -> Boolean, ) { - val navigator = application.voyagerNavigator + val navigator = voyagerNavigator if (navigator.popUntil(predicate)) { navigator.trySendPopResult(result) } diff --git a/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryAttribute.kt b/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryAttribute.kt new file mode 100644 index 0000000..bb20fc3 --- /dev/null +++ b/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryAttribute.kt @@ -0,0 +1,21 @@ +package dev.programadorthi.routing.voyager.history + +import dev.programadorthi.routing.core.Routing +import dev.programadorthi.routing.core.application +import dev.programadorthi.routing.core.application.Application +import io.ktor.util.AttributeKey + +internal val VoyagerHistoryModeAttributeKey: AttributeKey = + AttributeKey("VoyagerHistoryModeAttributeKey") + +internal var Application.historyMode: VoyagerHistoryMode + get() = attributes[VoyagerHistoryModeAttributeKey] + set(value) { + attributes.put(VoyagerHistoryModeAttributeKey, value) + } + +internal var Routing.historyMode: VoyagerHistoryMode + get() = application.historyMode + set(value) { + application.historyMode = value + } diff --git a/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.kt b/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.kt new file mode 100644 index 0000000..5badd65 --- /dev/null +++ b/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.kt @@ -0,0 +1,26 @@ +package dev.programadorthi.routing.voyager.history + +import androidx.compose.runtime.Composable +import cafe.adriel.voyager.core.screen.Screen +import dev.programadorthi.routing.core.Routing +import dev.programadorthi.routing.core.application.ApplicationCall + +internal expect suspend fun ApplicationCall.platformPush( + routing: Routing, + body: suspend () -> Screen, +) + +internal expect suspend fun ApplicationCall.platformReplace( + routing: Routing, + body: suspend () -> Screen, +) + +internal expect suspend fun ApplicationCall.platformReplaceAll( + routing: Routing, + body: suspend () -> Screen, +) + +internal expect fun ApplicationCall.shouldNeglect(): Boolean + +@Composable +internal expect fun Routing.restoreState(onState: (Any) -> Unit) diff --git a/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryMode.kt b/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryMode.kt new file mode 100644 index 0000000..218b8a3 --- /dev/null +++ b/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryMode.kt @@ -0,0 +1,28 @@ +package dev.programadorthi.routing.voyager.history + +/** + * Options that how the web history is controlled + * + * These options affects web application only. Memory will be used by default in other targets + */ +public enum class VoyagerHistoryMode { + /** + * Hash URLs pattern. E.g: host/#/path + * Each route will have an entry on the browser history. + * To avoid browser history, set neglect = true before routing to a route + */ + Hash, + + /** + * Traditional URLs pattern. E.g: host/path + * Each route will have an entry on the browser history. + * To avoid browser history, set neglect = true before routing to a route + */ + Html5, + + /** + * No updates to URL or History stack. + * All route will be neglected. + */ + Memory, +} diff --git a/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryState.kt b/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryState.kt new file mode 100644 index 0000000..5b640ca --- /dev/null +++ b/integration/voyager/common/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryState.kt @@ -0,0 +1,35 @@ +package dev.programadorthi.routing.voyager.history + +import dev.programadorthi.routing.core.RouteMethod +import dev.programadorthi.routing.core.application.Application +import dev.programadorthi.routing.core.application.ApplicationCall +import io.ktor.http.parametersOf +import io.ktor.util.toMap +import kotlinx.serialization.Serializable + +@Serializable +internal data class VoyagerHistoryState( + val routeMethod: String, + val name: String, + val uri: String, + val parameters: Map>, +) + +internal fun VoyagerHistoryState.toCall(application: Application): ApplicationCall { + return ApplicationCall( + application = application, + name = name, + uri = uri, + routeMethod = RouteMethod.parse(routeMethod), + parameters = parametersOf(parameters), + ) +} + +internal fun ApplicationCall.toHistoryState(): VoyagerHistoryState { + return VoyagerHistoryState( + routeMethod = routeMethod.value, + name = name, + uri = uri, + parameters = parameters.toMap(), + ) +} diff --git a/integration/voyager/js/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.js.kt b/integration/voyager/js/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.js.kt new file mode 100644 index 0000000..a69a39b --- /dev/null +++ b/integration/voyager/js/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.js.kt @@ -0,0 +1,19 @@ +package dev.programadorthi.routing.voyager + +import dev.programadorthi.routing.core.Routing +import dev.programadorthi.routing.voyager.history.VoyagerHistoryMode +import dev.programadorthi.routing.voyager.history.historyMode +import dev.programadorthi.routing.voyager.history.popWindowHistory + +internal actual fun Routing.popOnPlatform( + result: Any?, + fallback: () -> Unit, +) { + when (historyMode) { + VoyagerHistoryMode.Memory -> fallback() + else -> popWindowHistory() + } +} + +public actual val Routing.canPop: Boolean + get() = historyMode != VoyagerHistoryMode.Memory || voyagerNavigator.canPop diff --git a/integration/voyager/js/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryAttribute.js.kt b/integration/voyager/js/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryAttribute.js.kt new file mode 100644 index 0000000..40726ab --- /dev/null +++ b/integration/voyager/js/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryAttribute.js.kt @@ -0,0 +1,13 @@ +package dev.programadorthi.routing.voyager.history + +import dev.programadorthi.routing.core.application.ApplicationCall +import io.ktor.util.AttributeKey + +internal val VoyagerHistoryNeglectAttributeKey: AttributeKey = + AttributeKey("VoyagerHistoryNeglectAttributeKey") + +internal var ApplicationCall.neglect: Boolean + get() = attributes.getOrNull(VoyagerHistoryNeglectAttributeKey) ?: false + set(value) { + attributes.put(VoyagerHistoryNeglectAttributeKey, value) + } diff --git a/integration/voyager/js/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.js.kt b/integration/voyager/js/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.js.kt new file mode 100644 index 0000000..69cd21a --- /dev/null +++ b/integration/voyager/js/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.js.kt @@ -0,0 +1,163 @@ +package dev.programadorthi.routing.voyager.history + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import cafe.adriel.voyager.core.screen.Screen +import dev.programadorthi.routing.core.Routing +import dev.programadorthi.routing.core.application +import dev.programadorthi.routing.core.application.ApplicationCall +import dev.programadorthi.routing.voyager.voyagerNavigator +import kotlinx.browser.window +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +private const val HASH_PREFIX = "/#" + +internal actual fun ApplicationCall.shouldNeglect(): Boolean = neglect + +internal actual suspend fun ApplicationCall.platformPush( + routing: Routing, + body: suspend () -> Screen, +) { + voyagerNavigator.push(body()) + window.history.pushState( + title = "routing", + url = uriToAddressBar(), + data = serialize(), + ) +} + +internal actual suspend fun ApplicationCall.platformReplace( + routing: Routing, + body: suspend () -> Screen, +) { + voyagerNavigator.replace(body()) + window.history.replaceState( + title = "routing", + url = uriToAddressBar(), + data = serialize(), + ) +} + +internal actual suspend fun ApplicationCall.platformReplaceAll( + routing: Routing, + body: suspend () -> Screen, +) { + voyagerNavigator.replaceAll(body()) + while (true) { + window.history.replaceState( + title = "", + url = null, + data = null, + ) + val forceBreak = + runCatching { + withTimeout(2_000) { + suspendCoroutine { continuation -> + window.onpopstate = { event -> + val state = event.state.deserialize() + continuation.resume(state == null) + } + window.history.go(-1) + } + } + }.getOrDefault(true) + if (forceBreak) { + break + } + } + + window.history.replaceState( + title = "routing", + url = uriToAddressBar(), + data = serialize(), + ) + + listenToOnPopState(routing) +} + +@Composable +internal actual fun Routing.restoreState(onState: (Any) -> Unit) { + val routing = this + + LaunchedEffect(Unit) { + window.onpageshow = { + // First time or page refresh we try continue from last state + val state = window.history.state + if (state != null) { + stateToCall(state)?.let(onState) + } else { + val hashSanitized = window.location.hash.removePrefix("#") + val destination = + when { + historyMode == VoyagerHistoryMode.Hash && hashSanitized.isNotBlank() -> hashSanitized + else -> window.location.run { pathname + search + hash } + } + onState(destination) + } + } + + listenToOnPopState(routing) + } +} + +internal fun Routing.popWindowHistory() { + val routing = this + var job: Job? = null + job = + application.launch { + window.history.replaceState( + title = "", + url = null, + data = null, + ) + try { + // Why timeout? + // Because go(-1) when there is no history don't trigger window.onpopstate + withTimeout(2_000) { + suspendCoroutine { continuation -> + listenToOnPopState(routing) { + continuation.resume(Unit) + } + window.history.go(-1) + } + } + } finally { + listenToOnPopState(routing) + job?.cancel() + } + } +} + +private fun ApplicationCall.uriToAddressBar(): String { + return when { + application.historyMode != VoyagerHistoryMode.Hash -> uri + + else -> HASH_PREFIX + uri + } +} + +private fun listenToOnPopState( + routing: Routing, + onPop: () -> Unit = {}, +) { + window.onpopstate = { event -> + routing.tryNotifyTheRoute(state = event.state) + onPop() + } +} + +private fun Routing.tryNotifyTheRoute(state: Any?) { + val call = stateToCall(state) ?: return + execute(call) +} + +private fun Routing.stateToCall(state: Any?): ApplicationCall? { + val composeHistoryState = state.deserialize() ?: return null + val call = composeHistoryState.toCall(application) + call.neglect = true + return call +} diff --git a/integration/voyager/js/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryState.js.kt b/integration/voyager/js/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryState.js.kt new file mode 100644 index 0000000..749eef5 --- /dev/null +++ b/integration/voyager/js/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryState.js.kt @@ -0,0 +1,21 @@ +package dev.programadorthi.routing.voyager.history + +import dev.programadorthi.routing.core.application.ApplicationCall +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +internal fun ApplicationCall.serialize(): String { + val state = toHistoryState() + return Json.encodeToString(state) +} + +internal fun Any?.deserialize(): VoyagerHistoryState? = + when (this) { + is String -> toState() + else -> null + } + +private fun String.toState(): VoyagerHistoryState? = + runCatching { + Json.decodeFromString(this) + }.getOrNull() diff --git a/integration/voyager/jvm/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.jvm.kt b/integration/voyager/jvm/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.jvm.kt new file mode 100644 index 0000000..154b121 --- /dev/null +++ b/integration/voyager/jvm/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.jvm.kt @@ -0,0 +1,13 @@ +package dev.programadorthi.routing.voyager + +import dev.programadorthi.routing.core.Routing + +internal actual fun Routing.popOnPlatform( + result: Any?, + fallback: () -> Unit, +) { + fallback() +} + +public actual val Routing.canPop: Boolean + get() = voyagerNavigator.canPop diff --git a/integration/voyager/jvm/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.jvm.kt b/integration/voyager/jvm/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.jvm.kt new file mode 100644 index 0000000..82ecb5d --- /dev/null +++ b/integration/voyager/jvm/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.jvm.kt @@ -0,0 +1,36 @@ +package dev.programadorthi.routing.voyager.history + +import androidx.compose.runtime.Composable +import cafe.adriel.voyager.core.screen.Screen +import dev.programadorthi.routing.core.Routing +import dev.programadorthi.routing.core.application.ApplicationCall +import dev.programadorthi.routing.voyager.voyagerNavigator + +internal actual fun ApplicationCall.shouldNeglect(): Boolean = false + +internal actual suspend fun ApplicationCall.platformPush( + routing: Routing, + body: suspend () -> Screen, +) { + voyagerNavigator.push(body()) +} + +internal actual suspend fun ApplicationCall.platformReplace( + routing: Routing, + body: suspend () -> Screen, +) { + voyagerNavigator.replace(body()) +} + +internal actual suspend fun ApplicationCall.platformReplaceAll( + routing: Routing, + body: suspend () -> Screen, +) { + voyagerNavigator.replaceAll(body()) +} + +@Composable +internal actual fun Routing.restoreState(onState: (Any) -> Unit) { + // No-op + // Voyager has its internal state restoration +} diff --git a/integration/voyager/native/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.native.kt b/integration/voyager/native/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.native.kt new file mode 100644 index 0000000..154b121 --- /dev/null +++ b/integration/voyager/native/src/dev/programadorthi/routing/voyager/VoyagerRoutingExt.native.kt @@ -0,0 +1,13 @@ +package dev.programadorthi.routing.voyager + +import dev.programadorthi.routing.core.Routing + +internal actual fun Routing.popOnPlatform( + result: Any?, + fallback: () -> Unit, +) { + fallback() +} + +public actual val Routing.canPop: Boolean + get() = voyagerNavigator.canPop diff --git a/integration/voyager/native/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.native.kt b/integration/voyager/native/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.native.kt new file mode 100644 index 0000000..82ecb5d --- /dev/null +++ b/integration/voyager/native/src/dev/programadorthi/routing/voyager/history/VoyagerHistoryExt.native.kt @@ -0,0 +1,36 @@ +package dev.programadorthi.routing.voyager.history + +import androidx.compose.runtime.Composable +import cafe.adriel.voyager.core.screen.Screen +import dev.programadorthi.routing.core.Routing +import dev.programadorthi.routing.core.application.ApplicationCall +import dev.programadorthi.routing.voyager.voyagerNavigator + +internal actual fun ApplicationCall.shouldNeglect(): Boolean = false + +internal actual suspend fun ApplicationCall.platformPush( + routing: Routing, + body: suspend () -> Screen, +) { + voyagerNavigator.push(body()) +} + +internal actual suspend fun ApplicationCall.platformReplace( + routing: Routing, + body: suspend () -> Screen, +) { + voyagerNavigator.replace(body()) +} + +internal actual suspend fun ApplicationCall.platformReplaceAll( + routing: Routing, + body: suspend () -> Screen, +) { + voyagerNavigator.replaceAll(body()) +} + +@Composable +internal actual fun Routing.restoreState(onState: (Any) -> Unit) { + // No-op + // Voyager has its internal state restoration +}