feat(ui): implement adaptive extra pane strategy for navigation

This commit enhances the navigation system to support adaptive layouts by integrating `SupportingPaneSceneStrategy` and marking specific routes to utilize the "extra pane" in list-detail scenarios.

Specific changes include:
- **Navigation Infrastructure**: Added `supportingPaneSceneStrategy` to `MeshtasticNavDisplay` and updated documentation to reflect support for `extraPane()` and supporting pane layouts.
- **Node Feature**: Updated `NodeMap`, `TracerouteLog`, `TracerouteMap`, and generic node detail screens in `NodesNavigation.kt` to use `ListDetailSceneStrategy.extraPane()`.
- **Messaging Feature**: Updated `Share` and `QuickChat` routes in `ContactsNavigation.kt` to use `ListDetailSceneStrategy.extraPane()`.
- **API Integration**: Applied `ExperimentalMaterial3AdaptiveApi` where necessary to support the new adaptive navigation strategies.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich
2026-03-26 18:57:18 -05:00
parent 0bcd35e616
commit bffbe9ffbc
3 changed files with 18 additions and 8 deletions

View File

@@ -23,6 +23,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
import androidx.compose.material3.adaptive.navigation3.rememberSupportingPaneSceneStrategy
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
@@ -54,8 +55,10 @@ private const val TRANSITION_DURATION_MS = 350
* **Scene strategies** (evaluated in order):
* - [DialogSceneStrategy] — entries annotated with `metadata = DialogSceneStrategy.dialog()` render as overlay
* [Dialog][androidx.compose.ui.window.Dialog] windows with proper backstack lifecycle.
* - [ListDetailSceneStrategy] — entries annotated with `listPane()` / `detailPane()` render in adaptive list-detail
* - [androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy] — entries annotated with `listPane()`, `detailPane()`, or `extraPane()` render in adaptive list-detail
* layout on wider screens.
* - [androidx.compose.material3.adaptive.navigation3.SupportingPaneSceneStrategy] — entries annotated with `mainPane()`, `supportingPane()`, or `extraPane()` render in adaptive
* supporting pane layout.
* - [SinglePaneSceneStrategy] — default single-pane fallback.
*
* **Transitions**: A uniform 350 ms crossfade for both forward and pop navigation.
@@ -72,12 +75,18 @@ fun MeshtasticNavDisplay(
modifier: Modifier = Modifier,
) {
val listDetailSceneStrategy = rememberListDetailSceneStrategy<NavKey>()
val supportingPaneSceneStrategy = rememberSupportingPaneSceneStrategy<NavKey>()
NavDisplay(
backStack = backStack,
entryProvider = entryProvider,
entryDecorators =
listOf(rememberSaveableStateHolderNavEntryDecorator(), rememberViewModelStoreNavEntryDecorator()),
sceneStrategies = listOf(DialogSceneStrategy(), listDetailSceneStrategy, SinglePaneSceneStrategy()),
sceneStrategies = listOf(
DialogSceneStrategy(),
listDetailSceneStrategy,
supportingPaneSceneStrategy,
SinglePaneSceneStrategy()
),
transitionSpec = meshtasticTransitionSpec(),
popTransitionSpec = meshtasticTransitionSpec(),
modifier = modifier,

View File

@@ -68,7 +68,7 @@ fun EntryProviderScope<NavKey>.contactsGraph(
)
}
entry<ContactsRoutes.Share> { args ->
entry<ContactsRoutes.Share>(metadata = { ListDetailSceneStrategy.extraPane() }) { args ->
val message = args.message
val viewModel = koinViewModel<ContactsViewModel>()
ShareScreen(
@@ -80,7 +80,7 @@ fun EntryProviderScope<NavKey>.contactsGraph(
)
}
entry<ContactsRoutes.QuickChat> {
entry<ContactsRoutes.QuickChat>(metadata = { ListDetailSceneStrategy.extraPane() }) {
val viewModel = koinViewModel<QuickChatViewModel>()
QuickChatScreen(viewModel = viewModel, onNavigateUp = { backStack.removeLastOrNull() })
}

View File

@@ -123,12 +123,12 @@ fun EntryProviderScope<NavKey>.nodeDetailGraph(
)
}
entry<NodeDetailRoutes.NodeMap> { args ->
entry<NodeDetailRoutes.NodeMap>(metadata = { ListDetailSceneStrategy.extraPane() }) { args ->
val mapScreen = org.meshtastic.core.ui.util.LocalNodeMapScreenProvider.current
mapScreen(args.destNum) { backStack.removeLastOrNull() }
}
entry<NodeDetailRoutes.TracerouteLog> { args ->
entry<NodeDetailRoutes.TracerouteLog>(metadata = { ListDetailSceneStrategy.extraPane() }) { args ->
val metricsViewModel =
koinViewModel<MetricsViewModel>(key = "metrics-${args.destNum}") { parametersOf(args.destNum) }
metricsViewModel.setNodeId(args.destNum)
@@ -148,7 +148,7 @@ fun EntryProviderScope<NavKey>.nodeDetailGraph(
)
}
entry<NodeDetailRoutes.TracerouteMap> { args ->
entry<NodeDetailRoutes.TracerouteMap>(metadata = { ListDetailSceneStrategy.extraPane() }) { args ->
val tracerouteMapScreen = org.meshtastic.core.ui.util.LocalTracerouteMapScreenProvider.current
tracerouteMapScreen(args.destNum, args.requestId, args.logUuid) { backStack.removeLastOrNull() }
}
@@ -178,12 +178,13 @@ fun EntryProviderScope<NavKey>.nodeDetailGraph(
fun NavKey.isNodeDetailRoute(): Boolean = NodeDetailRoute.entries.any { this::class == it.routeClass }
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
private inline fun <reified R : Route> EntryProviderScope<NavKey>.addNodeDetailScreenComposable(
backStack: NavBackStack<NavKey>,
routeInfo: NodeDetailRoute,
crossinline getDestNum: (R) -> Int,
) {
entry<R> { args ->
entry<R>(metadata = { ListDetailSceneStrategy.extraPane() }) { args ->
val destNum = getDestNum(args)
val metricsViewModel = koinViewModel<MetricsViewModel>(key = "metrics-$destNum") { parametersOf(destNum) }
metricsViewModel.setNodeId(destNum)