mirror of
https://github.com/AChep/keyguard-app.git
synced 2025-02-02 15:26:53 +01:00
experiment: Multi-stack navigation tabs
This commit is contained in:
parent
0586153b7b
commit
2481122cf1
@ -100,7 +100,6 @@ import com.artemchep.keyguard.feature.navigation.NavigationIntent
|
||||
import com.artemchep.keyguard.feature.navigation.NavigationNode
|
||||
import com.artemchep.keyguard.feature.navigation.NavigationRouter
|
||||
import com.artemchep.keyguard.feature.navigation.Route
|
||||
import com.artemchep.keyguard.feature.navigation.popById
|
||||
import com.artemchep.keyguard.feature.send.SendRoute
|
||||
import com.artemchep.keyguard.feature.sync.SyncRoute
|
||||
import com.artemchep.keyguard.platform.LocalLeContext
|
||||
@ -162,6 +161,16 @@ private val generatorRoute = GeneratorRoute(
|
||||
|
||||
private val watchtowerRoute = WatchtowerRoute()
|
||||
|
||||
private val settingsRoute = SettingsRoute
|
||||
|
||||
val homeRoutes = listOf(
|
||||
vaultRoute,
|
||||
sendsRoute,
|
||||
generatorRoute,
|
||||
watchtowerRoute,
|
||||
settingsRoute,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
navBarVisible: Boolean = true,
|
||||
@ -213,7 +222,7 @@ fun HomeScreen(
|
||||
),
|
||||
Rail(
|
||||
key = "settings",
|
||||
route = SettingsRoute,
|
||||
route = settingsRoute,
|
||||
icon = Icons.Outlined.Settings,
|
||||
iconSelected = Icons.Filled.Settings,
|
||||
label = TextHolder.Res(Res.string.home_settings_label),
|
||||
@ -829,7 +838,10 @@ private fun isSelected(
|
||||
backStack: List<NavigationEntry>,
|
||||
route: Route,
|
||||
) = run {
|
||||
val entry = backStack.getOrNull(1) ?: backStack.firstOrNull()
|
||||
val entry = backStack
|
||||
.lastOrNull { entry ->
|
||||
homeRoutes.any { it === entry.route }
|
||||
}
|
||||
entry?.route === route
|
||||
}
|
||||
|
||||
@ -864,18 +876,35 @@ private fun navigateOnClick(
|
||||
route: Route,
|
||||
) {
|
||||
val intent = NavigationIntent.Manual { factory ->
|
||||
// If the route exists in the stack, then simply
|
||||
// navigate back to it.
|
||||
val indexOfRoute = backStack.indexOfFirst { it.route === route }
|
||||
if (indexOfRoute != -1 && indexOfRoute <= 1) {
|
||||
return@Manual subList(0, indexOfRoute + 1)
|
||||
.toPersistentList()
|
||||
kotlin.run {
|
||||
val index = this.backStack.indexOfFirst { it.route === route }
|
||||
if (index != -1) {
|
||||
val end = this.backStack
|
||||
.drop(index + 1)
|
||||
.indexOfFirst { entry ->
|
||||
homeRoutes.any { it === entry.route }
|
||||
}
|
||||
// Check if that's the end of list
|
||||
// or a middle part.
|
||||
.let {
|
||||
if (it != -1) {
|
||||
index + it + 1
|
||||
} else {
|
||||
this.backStack.size
|
||||
}
|
||||
}
|
||||
|
||||
val left = this.backStack.subList(0, index)
|
||||
val center = this.backStack.subList(index, end)
|
||||
val right = this.backStack.subList(end, this.backStack.size)
|
||||
return@Manual left.toPersistentList()
|
||||
.addAll(right)
|
||||
.addAll(center)
|
||||
}
|
||||
}
|
||||
|
||||
val stack = popById(ROUTE_NAME, exclusive = true)
|
||||
.orEmpty()
|
||||
.toPersistentList()
|
||||
stack.add(factory(route))
|
||||
val entry = factory(route)
|
||||
this.backStack.add(entry)
|
||||
}
|
||||
controller.queue(intent)
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ interface NavigationEntry : BackPressInterceptorHost {
|
||||
|
||||
val activeBackPressInterceptorsStateFlow: StateFlow<ImmutableMap<String, BackPressInterceptorRegistration>>
|
||||
|
||||
fun getOrCreate(id: String, create: () -> NavigationEntry): NavigationStack
|
||||
|
||||
fun destroy()
|
||||
}
|
||||
|
||||
@ -96,6 +98,14 @@ data class NavigationEntryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
private val subStacks = mutableMapOf<String, NavigationStack>()
|
||||
|
||||
override fun getOrCreate(id: String, create: () -> NavigationEntry): NavigationStack = subStacks
|
||||
.getOrPut(id) {
|
||||
val navEntry = create()
|
||||
NavigationStack(navEntry)
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
vm.destroy()
|
||||
job.cancel()
|
||||
|
@ -97,6 +97,6 @@ sealed interface NavigationIntent {
|
||||
// manual
|
||||
|
||||
data class Manual(
|
||||
val handle: PersistentList<NavigationEntry>.((Route) -> NavigationEntry) -> PersistentList<NavigationEntry>,
|
||||
val handle: NavigationIntentScope.((Route) -> NavigationEntry) -> PersistentList<NavigationEntry>,
|
||||
) : NavigationIntent
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
package com.artemchep.keyguard.feature.navigation
|
||||
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
||||
interface NavigationIntentScope {
|
||||
val backStack: PersistentList<NavigationEntry>
|
||||
}
|
@ -22,19 +22,18 @@ fun NavigationRouter(
|
||||
initial: Route,
|
||||
content: @Composable (PersistentList<NavigationEntry>) -> Unit,
|
||||
) {
|
||||
// Fid the top-level router and link the entry's lifecycle
|
||||
// Find the top-level router and link the entry's lifecycle
|
||||
// to it, so if the top level gets destroyed we also get
|
||||
// destroyed.
|
||||
val f = LocalNavigationNodeLogicalStack.current.last()
|
||||
val parentScope = f.scope
|
||||
val navStack = remember(id) {
|
||||
val navEntry = NavigationEntryImpl(
|
||||
val navStack = f.getOrCreate(id) {
|
||||
NavigationEntryImpl(
|
||||
source = "router root",
|
||||
id = id,
|
||||
parent = parentScope,
|
||||
route = initial,
|
||||
)
|
||||
NavigationStack(navEntry)
|
||||
}
|
||||
val canPop = remember(navStack) {
|
||||
snapshotFlow { navStack.value }
|
||||
@ -63,7 +62,8 @@ fun NavigationRouter(
|
||||
val backPressInterceptorRegistration = backStack
|
||||
.asReversed()
|
||||
.firstNotNullOfOrNull { navEntry ->
|
||||
val backPressInterceptors = navEntry.activeBackPressInterceptorsStateFlow.value
|
||||
val backPressInterceptors =
|
||||
navEntry.activeBackPressInterceptorsStateFlow.value
|
||||
backPressInterceptors.values.firstOrNull()
|
||||
}
|
||||
if (backPressInterceptorRegistration != null) {
|
||||
@ -72,7 +72,10 @@ fun NavigationRouter(
|
||||
}
|
||||
}
|
||||
|
||||
val newBackStack = backStack
|
||||
val scope = object : NavigationIntentScope {
|
||||
override val backStack: PersistentList<NavigationEntry> = backStack
|
||||
}
|
||||
val newBackStack = scope
|
||||
.exec(
|
||||
intent = intent,
|
||||
scope = parentScope,
|
||||
@ -146,29 +149,32 @@ class NavigationStack(
|
||||
}
|
||||
}
|
||||
|
||||
private fun PersistentList<NavigationEntry>.exec(
|
||||
private fun NavigationIntentScope.exec(
|
||||
intent: NavigationIntent,
|
||||
scope: CoroutineScope,
|
||||
): PersistentList<NavigationEntry>? = when (intent) {
|
||||
is NavigationIntent.Composite -> run compose@{
|
||||
var backStack = this
|
||||
var ns = this
|
||||
for (subIntent in intent.list) {
|
||||
val new = backStack.exec(
|
||||
val new = ns.exec(
|
||||
intent = subIntent,
|
||||
scope = scope,
|
||||
)
|
||||
backStack = new
|
||||
?: return@compose null
|
||||
) ?: return@compose null
|
||||
ns = object : NavigationIntentScope {
|
||||
override val backStack: PersistentList<NavigationEntry>
|
||||
get() = new
|
||||
}
|
||||
}
|
||||
backStack
|
||||
ns.backStack
|
||||
}
|
||||
|
||||
is NavigationIntent.NavigateToRoute -> kotlin.run {
|
||||
val backStack = when (intent.launchMode) {
|
||||
NavigationIntent.NavigateToRoute.LaunchMode.DEFAULT -> this
|
||||
NavigationIntent.NavigateToRoute.LaunchMode.DEFAULT -> backStack
|
||||
NavigationIntent.NavigateToRoute.LaunchMode.SINGLE -> {
|
||||
val clearedBackStack = removeAll { it.route::class == intent.route::class }
|
||||
val existingEntry = lastOrNull { it.route == intent.route }
|
||||
val clearedBackStack =
|
||||
backStack.removeAll { it.route::class == intent.route::class }
|
||||
val existingEntry = backStack.lastOrNull { it.route == intent.route }
|
||||
if (existingEntry != null) {
|
||||
// Fast path if existing route matches the new route.
|
||||
return@run clearedBackStack.add(existingEntry)
|
||||
@ -201,14 +207,14 @@ private fun PersistentList<NavigationEntry>.exec(
|
||||
|
||||
is NavigationIntent.NavigateToStack -> intent.stack.toPersistentList()
|
||||
is NavigationIntent.Pop -> {
|
||||
if (size > 0) {
|
||||
removeAt(size - 1)
|
||||
if (backStack.size > 0) {
|
||||
backStack.removeAt(backStack.size - 1)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
is NavigationIntent.PopById -> popById(intent.id, intent.exclusive)
|
||||
is NavigationIntent.PopById -> backStack.popById(intent.id, intent.exclusive)
|
||||
is NavigationIntent.Manual -> {
|
||||
val factory = fun(route: Route): NavigationEntry =
|
||||
NavigationEntryImpl(
|
||||
|
Loading…
x
Reference in New Issue
Block a user