mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: adaptive UI components for Navigation 3 (#4891)
This commit is contained in:
parent
b3b38acc0b
commit
7b327215f3
35 changed files with 978 additions and 1751 deletions
|
|
@ -17,21 +17,12 @@
|
|||
package org.meshtastic.feature.node.navigation
|
||||
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.layout.AnimatedPane
|
||||
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
|
||||
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
|
||||
import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
|
||||
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigationevent.NavigationEventInfo
|
||||
import androidx.navigationevent.compose.NavigationBackHandler
|
||||
import androidx.navigationevent.compose.rememberNavigationEventState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
|
@ -41,6 +32,7 @@ import org.meshtastic.core.navigation.NodesRoutes
|
|||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.nodes
|
||||
import org.meshtastic.core.ui.component.AdaptiveListDetailScaffold
|
||||
import org.meshtastic.core.ui.component.EmptyDetailPlaceholder
|
||||
import org.meshtastic.core.ui.component.ScrollToTopEvent
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
|
|
@ -64,9 +56,8 @@ fun AdaptiveNodeListScreen(
|
|||
val nodeListViewModel: NodeListViewModel = koinViewModel()
|
||||
val navigator = rememberListDetailPaneScaffoldNavigator<Int>()
|
||||
val scope = rememberCoroutineScope()
|
||||
val backNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange
|
||||
|
||||
val handleBack: () -> Unit = {
|
||||
val onBackToGraph: () -> Unit = {
|
||||
val currentKey = backStack.lastOrNull()
|
||||
val isNodesRoute = currentKey is NodesRoutes.Nodes || currentKey is NodesRoutes.NodesGraph
|
||||
val previousKey = if (backStack.size > 1) backStack[backStack.size - 2] else null
|
||||
|
|
@ -76,80 +67,40 @@ fun AdaptiveNodeListScreen(
|
|||
if (isFromDifferentGraph && !isNodesRoute) {
|
||||
// Navigate back via NavController to return to the previous screen
|
||||
backStack.removeLastOrNull()
|
||||
} else {
|
||||
// Close the detail pane within the adaptive scaffold
|
||||
scope.launch { navigator.navigateBack(backNavigationBehavior) }
|
||||
}
|
||||
}
|
||||
|
||||
val navState = rememberNavigationEventState(NavigationEventInfo.None)
|
||||
NavigationBackHandler(
|
||||
state = navState,
|
||||
isBackEnabled = navigator.currentDestination?.pane == ListDetailPaneScaffoldRole.Detail,
|
||||
onBackCancelled = {},
|
||||
onBackCompleted = { handleBack() },
|
||||
)
|
||||
|
||||
LaunchedEffect(initialNodeId) {
|
||||
if (initialNodeId != null) {
|
||||
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, initialNodeId)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(scrollToTopEvents) {
|
||||
scrollToTopEvents.collect { event ->
|
||||
if (
|
||||
event is ScrollToTopEvent.NodesTabPressed &&
|
||||
navigator.currentDestination?.pane == ListDetailPaneScaffoldRole.Detail
|
||||
) {
|
||||
if (navigator.canNavigateBack(backNavigationBehavior)) {
|
||||
navigator.navigateBack(backNavigationBehavior)
|
||||
} else {
|
||||
navigator.navigateTo(ListDetailPaneScaffoldRole.List)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListDetailPaneScaffold(
|
||||
directive = navigator.scaffoldDirective,
|
||||
value = navigator.scaffoldValue,
|
||||
listPane = {
|
||||
AnimatedPane {
|
||||
val focusManager = LocalFocusManager.current
|
||||
// Prevent TextFields from auto-focusing when pane animates in
|
||||
LaunchedEffect(Unit) { focusManager.clearFocus() }
|
||||
NodeListScreen(
|
||||
viewModel = nodeListViewModel,
|
||||
navigateToNodeDetails = { nodeId ->
|
||||
scope.launch { navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, nodeId) }
|
||||
},
|
||||
onNavigateToChannels = { backStack.add(ChannelsRoutes.ChannelsGraph) },
|
||||
scrollToTopEvents = scrollToTopEvents,
|
||||
activeNodeId = navigator.currentDestination?.contentKey,
|
||||
)
|
||||
}
|
||||
AdaptiveListDetailScaffold(
|
||||
navigator = navigator,
|
||||
scrollToTopEvents = scrollToTopEvents,
|
||||
onBackToGraph = onBackToGraph,
|
||||
onTabPressedEvent = { it is ScrollToTopEvent.NodesTabPressed },
|
||||
initialKey = initialNodeId,
|
||||
listPane = { isActive, activeNodeId ->
|
||||
NodeListScreen(
|
||||
viewModel = nodeListViewModel,
|
||||
navigateToNodeDetails = { nodeId ->
|
||||
scope.launch { navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, nodeId) }
|
||||
},
|
||||
onNavigateToChannels = { backStack.add(ChannelsRoutes.ChannelsGraph) },
|
||||
scrollToTopEvents = scrollToTopEvents,
|
||||
activeNodeId = activeNodeId,
|
||||
)
|
||||
},
|
||||
detailPane = {
|
||||
AnimatedPane {
|
||||
val focusManager = LocalFocusManager.current
|
||||
// Prevent TextFields from auto-focusing when pane animates in
|
||||
navigator.currentDestination?.contentKey?.let { nodeId ->
|
||||
key(nodeId) {
|
||||
LaunchedEffect(nodeId) { focusManager.clearFocus() }
|
||||
val nodeDetailViewModel: NodeDetailViewModel = koinViewModel()
|
||||
val compassViewModel: CompassViewModel = koinViewModel()
|
||||
NodeDetailScreen(
|
||||
nodeId = nodeId,
|
||||
viewModel = nodeDetailViewModel,
|
||||
compassViewModel = compassViewModel,
|
||||
navigateToMessages = onNavigateToMessages,
|
||||
onNavigate = onNavigate,
|
||||
onNavigateUp = handleBack,
|
||||
)
|
||||
}
|
||||
} ?: EmptyDetailPlaceholder(icon = MeshtasticIcons.Nodes, title = stringResource(Res.string.nodes))
|
||||
}
|
||||
detailPane = { contentKey, handleBack ->
|
||||
val nodeDetailViewModel: NodeDetailViewModel = koinViewModel()
|
||||
val compassViewModel: CompassViewModel = koinViewModel()
|
||||
NodeDetailScreen(
|
||||
nodeId = contentKey,
|
||||
viewModel = nodeDetailViewModel,
|
||||
compassViewModel = compassViewModel,
|
||||
navigateToMessages = onNavigateToMessages,
|
||||
onNavigate = onNavigate,
|
||||
onNavigateUp = handleBack,
|
||||
)
|
||||
},
|
||||
emptyDetailPane = {
|
||||
EmptyDetailPlaceholder(icon = MeshtasticIcons.Nodes, title = stringResource(Res.string.nodes))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue