mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-30 16:19:25 +00:00
fix(navigation): defer route cleanup until pop completes
This commit is contained in:
parent
cc13ee9c9f
commit
d514468fef
4 changed files with 87 additions and 9 deletions
|
|
@ -74,6 +74,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||
import androidx.navigation3.ui.LocalNavAnimatedContentScope
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import androidx.savedstate.serialization.SavedStateConfiguration
|
||||
|
|
@ -761,13 +762,17 @@ private fun MainAppContent(
|
|||
onSwitchProfile: () -> Unit = {},
|
||||
) {
|
||||
val navBackStack = rememberNavBackStack(navigationSavedStateConfiguration, initialRoute)
|
||||
val routeDisposalDecorator = remember {
|
||||
RouteDisposalNavEntryDecorator<NavKey> { key ->
|
||||
if (key is AppRoute) disposeRoute(key)
|
||||
}
|
||||
}
|
||||
val navController = remember(navBackStack, onNavigate, onGoBack, onReplace) {
|
||||
NuvioNavigator(
|
||||
backStack = navBackStack,
|
||||
onExternalNavigate = onNavigate,
|
||||
onExternalBack = onGoBack,
|
||||
onExternalReplace = onReplace,
|
||||
onRouteRemoved = ::disposeRoute,
|
||||
)
|
||||
}
|
||||
val appUpdaterController = rememberAppUpdaterController()
|
||||
|
|
@ -1827,8 +1832,12 @@ private fun MainAppContent(
|
|||
backStack = navBackStack,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onBack = { navController.popBackStack() },
|
||||
entryDecorators = listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator<NavKey>(),
|
||||
routeDisposalDecorator,
|
||||
),
|
||||
sharedTransitionScope = this@SharedTransitionLayout,
|
||||
entryProvider = entryProvider {
|
||||
entryProvider = entryProvider<NavKey> {
|
||||
entry<TabsRoute> {
|
||||
PlatformBackHandler(
|
||||
enabled = true,
|
||||
|
|
@ -3263,6 +3272,13 @@ private fun MainAppContent(
|
|||
},
|
||||
)
|
||||
}
|
||||
}.let { provider ->
|
||||
{ key ->
|
||||
routeDisposalDecorator.register(
|
||||
key = key,
|
||||
entry = provider(key),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ internal class NuvioNavigator(
|
|||
private val onExternalNavigate: ((AppRoute, launchSingleTop: Boolean) -> Unit)? = null,
|
||||
private val onExternalBack: (() -> Unit)? = null,
|
||||
private val onExternalReplace: ((AppRoute) -> Unit)? = null,
|
||||
private val onRouteRemoved: (AppRoute) -> Unit = {},
|
||||
) {
|
||||
val currentRoute: AppRoute?
|
||||
get() = backStack.lastOrNull() as? AppRoute
|
||||
|
|
@ -37,12 +36,7 @@ internal class NuvioNavigator(
|
|||
|
||||
val firstRemovedIndex = if (resolvedOptions.popUpToInclusive) targetIndex else targetIndex + 1
|
||||
if (firstRemovedIndex <= backStack.lastIndex) {
|
||||
val removedRoutes = backStack
|
||||
.subList(firstRemovedIndex, backStack.size)
|
||||
.filterIsInstance<AppRoute>()
|
||||
.toList()
|
||||
backStack.subList(firstRemovedIndex, backStack.size).clear()
|
||||
removedRoutes.forEach(onRouteRemoved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +48,7 @@ internal class NuvioNavigator(
|
|||
fun popBackStack(expectedRoute: AppRoute? = null): Boolean {
|
||||
if (expectedRoute != null && currentRoute != expectedRoute) return false
|
||||
if (backStack.size > 1) {
|
||||
(backStack.removeAt(backStack.lastIndex) as? AppRoute)?.let(onRouteRemoved)
|
||||
backStack.removeAt(backStack.lastIndex)
|
||||
return true
|
||||
}
|
||||
onExternalBack?.invoke()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package com.nuvio.app.navigation
|
||||
|
||||
import androidx.navigation3.runtime.NavEntry
|
||||
import androidx.navigation3.runtime.NavEntryDecorator
|
||||
|
||||
internal class RouteDisposalNavEntryDecorator<T : Any> private constructor(
|
||||
private val registry: RouteDisposalRegistry<T>,
|
||||
) : NavEntryDecorator<T>(
|
||||
onPop = registry::dispose,
|
||||
decorate = { entry -> entry.Content() },
|
||||
) {
|
||||
constructor(onDispose: (T) -> Unit) : this(RouteDisposalRegistry(onDispose))
|
||||
|
||||
fun register(key: T, entry: NavEntry<T>): NavEntry<T> = entry.also {
|
||||
registry.register(contentKey = entry.contentKey, key = key)
|
||||
}
|
||||
}
|
||||
|
||||
internal class RouteDisposalRegistry<T : Any>(
|
||||
private val onDispose: (T) -> Unit,
|
||||
) {
|
||||
private val keysByContentKey = mutableMapOf<Any, T>()
|
||||
|
||||
fun register(contentKey: Any, key: T) {
|
||||
val existingKey = keysByContentKey[contentKey]
|
||||
check(existingKey == null || existingKey == key) {
|
||||
"Navigation content key $contentKey is already registered to a different route"
|
||||
}
|
||||
keysByContentKey[contentKey] = key
|
||||
}
|
||||
|
||||
fun dispose(contentKey: Any) {
|
||||
keysByContentKey.remove(contentKey)?.let(onDispose)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.nuvio.app.navigation
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class RouteDisposalRegistryTest {
|
||||
@Test
|
||||
fun disposesRegisteredRouteExactlyOnceWhenNav3ReportsPop() {
|
||||
val disposedRoutes = mutableListOf<String>()
|
||||
val registry = RouteDisposalRegistry<String>(disposedRoutes::add)
|
||||
|
||||
registry.register(contentKey = "stream-entry", key = "stream-route")
|
||||
|
||||
assertTrue(disposedRoutes.isEmpty())
|
||||
|
||||
registry.dispose(contentKey = "stream-entry")
|
||||
registry.dispose(contentKey = "stream-entry")
|
||||
|
||||
assertEquals(listOf("stream-route"), disposedRoutes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsAContentKeySharedByDifferentRoutes() {
|
||||
val registry = RouteDisposalRegistry<String> {}
|
||||
registry.register(contentKey = "shared-entry", key = "first-route")
|
||||
|
||||
assertFailsWith<IllegalStateException> {
|
||||
registry.register(contentKey = "shared-entry", key = "second-route")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue