Skip to content

Commit

Permalink
Display a navigation route as an uri fragment to support static web p…
Browse files Browse the repository at this point in the history
…ages
  • Loading branch information
terrakok committed Oct 14, 2024
1 parent 724a036 commit b311dcf
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import org.w3c.dom.Window
* Binds the browser window state to the given navigation controller.
*
* @param navController The [NavController] instance to bind to browser window navigation.
* @param getBackStackEntryPath An optional function that returns the path to show for a given [NavBackStackEntry].
* @param getBackStackEntryRoute An optional function that returns the route to show for a given [NavBackStackEntry].
*/
suspend fun Window.bindToNavigation(
navController: NavController,
getBackStackEntryPath: (entry: NavBackStackEntry) -> String = {
"/${it.getRouteWithArgs().orEmpty()}"
getBackStackEntryRoute: (entry: NavBackStackEntry) -> String = {
it.getRouteWithArgs().orEmpty()
}
) {
@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
(this as BrowserWindow).bindToNavigation(navController, getBackStackEntryPath)
(this as BrowserWindow).bindToNavigation(navController, getBackStackEntryRoute)
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ class BrowserHistoryTest {
val navController = NavHostController().apply {
navigatorProvider.addNavigator(TestNavigator())
}
val appAddress = with(window.location) { origin + pathname }.removeSuffix("/")
val appAddress = with(window.location) { origin + pathname }

val bind = launch { window.bindToNavigation(navController) }
navController.setGraph(navController.createGraph(), null)
advanceUntilIdle()

assertThat(window.history.length).isEqualTo(1)
assertThat(window.history.state.toString()).isEqualTo("screen_1")
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_1")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_1")

navController.navigate("screen_2")
navController.navigate("screen_4")
Expand All @@ -69,7 +69,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_1", "screen_2", "screen_4")
.inOrder()
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_4")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_4")

navController.navigate("screen_5") {
popUpTo("screen_1") { inclusive = true }
Expand All @@ -81,7 +81,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_5", "screen_2")
.inOrder()
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_2")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_2")

bind.cancel()
}
Expand All @@ -92,7 +92,7 @@ class BrowserHistoryTest {
val navController = NavHostController().apply {
navigatorProvider.addNavigator(TestNavigator())
}
val appAddress = with(window.location) { origin + pathname }.removeSuffix("/")
val appAddress = with(window.location) { origin + pathname }

val bind = launch { window.bindToNavigation(navController) }
navController.setGraph(navController.createGraph(), null)
Expand All @@ -116,7 +116,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_5", "screen_2")
.inOrder()
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_2")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_2")

window.history.back()
waitHistoryStateUpdate()
Expand All @@ -125,7 +125,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_5")
.inOrder()
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_5")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_5")

window.history.back()
waitHistoryStateUpdate()
Expand All @@ -134,7 +134,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_1", "screen_2", "screen_4")
.inOrder()
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_4")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_4")

window.history.back()
waitHistoryStateUpdate()
Expand All @@ -143,7 +143,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_1", "screen_2")
.inOrder()
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_2")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_2")

navController.navigate("screen_2")
advanceUntilIdle()
Expand All @@ -152,7 +152,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_1", "screen_2", "screen_2")
.inOrder()
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_2")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_2")

window.history.back()
waitHistoryStateUpdate()
Expand All @@ -161,7 +161,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_1", "screen_2")
.inOrder()
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_2")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_2")

window.history.forward()
waitHistoryStateUpdate()
Expand All @@ -170,7 +170,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_1", "screen_2", "screen_2")
.inOrder()
assertThat(window.location.toString()).isEqualTo("$appAddress/screen_2")
assertThat(window.location.toString()).isEqualTo("$appAddress#screen_2")

bind.cancel()
}
Expand All @@ -181,10 +181,9 @@ class BrowserHistoryTest {
val navController = NavHostController().apply {
navigatorProvider.addNavigator(TestNavigator())
}
val appAddress = with(window.location) { origin + pathname }.removeSuffix("/")
val hiddenPath = "/hidden"
val appAddress = with(window.location) { origin + pathname }

val bind = launch { window.bindToNavigation(navController) { hiddenPath } }
val bind = launch { window.bindToNavigation(navController) { "" } }
navController.setGraph(navController.createGraph(), null)
advanceUntilIdle()

Expand All @@ -195,7 +194,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_1", "screen_2")
.inOrder()
assertThat(window.location.toString()).isEqualTo(appAddress + hiddenPath)
assertThat(window.location.toString()).isEqualTo(appAddress)

window.history.back()
waitHistoryStateUpdate()
Expand All @@ -204,7 +203,7 @@ class BrowserHistoryTest {
assertThat(window.history.state.toString().lines())
.containsExactly("screen_1")
.inOrder()
assertThat(window.location.toString()).isEqualTo(appAddress + hiddenPath)
assertThat(window.location.toString()).isEqualTo(appAddress)
bind.cancel()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import org.w3c.dom.Window
* Binds the browser window state to the given navigation controller.
*
* @param navController The [NavController] instance to bind to browser window navigation.
* @param getBackStackEntryPath An optional function that returns the path to show for a given [NavBackStackEntry].
* @param getBackStackEntryRoute An optional function that returns the route to show for a given [NavBackStackEntry].
*/
suspend fun Window.bindToNavigation(
navController: NavController,
getBackStackEntryPath: (entry: NavBackStackEntry) -> String = {
"/${it.getRouteWithArgs().orEmpty()}"
getBackStackEntryRoute: (entry: NavBackStackEntry) -> String = {
it.getRouteWithArgs().orEmpty()
}
) {
@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
(this as BrowserWindow).bindToNavigation(navController, getBackStackEntryPath)
(this as BrowserWindow).bindToNavigation(navController, getBackStackEntryRoute)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ import kotlinx.coroutines.launch
* Binds the browser window state to the given navigation controller.
*
* @param navController The [NavController] instance to bind to browser window navigation.
* @param getBackStackEntryPath A function that returns the path to show for a given [NavBackStackEntry].
* @param getBackStackEntryRoute A function that returns the route to show for a given [NavBackStackEntry].
*/
internal suspend fun BrowserWindow.bindToNavigation(
navController: NavController,
getBackStackEntryPath: (entry: NavBackStackEntry) -> String
getBackStackEntryRoute: (entry: NavBackStackEntry) -> String
) {
coroutineScope {
val localWindow = this@bindToNavigation
val appAddress = with(localWindow.location) { origin + pathname }.removeSuffix("/")
val appAddress = with(localWindow.location) { origin + pathname }
var initState = true
var updateState = true

Expand Down Expand Up @@ -89,7 +89,11 @@ internal suspend fun BrowserWindow.bindToNavigation(
if (entries.isEmpty()) return@collect
val routes = entries.map { it.getRouteWithArgs() ?: return@collect }

val newUri = appAddress + getBackStackEntryPath(entries.last())
val routeFragment = getBackStackEntryRoute(entries.last())
.takeIf { it.isNotBlank() }
?.let { "#$it" }
.orEmpty()
val newUri = appAddress + routeFragment
val state = routes.joinToString("\n")


Expand Down

0 comments on commit b311dcf

Please sign in to comment.