@@ -30,22 +30,22 @@ import androidx.compose.runtime.Composable
3030import androidx.compose.runtime.remember
3131import androidx.compose.ui.graphics.vector.ImageVector
3232import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
33- import androidx.navigation.NavBackStackEntry
3433import androidx.navigation.NavDestination
3534import androidx.navigation.NavDestination.Companion.hasRoute
3635import androidx.navigation.NavGraphBuilder
3736import androidx.navigation.NavHostController
3837import androidx.navigation.compose.composable
3938import androidx.navigation.compose.navigation
4039import androidx.navigation.navDeepLink
40+ import androidx.navigation.toRoute
41+ import com.geeksville.mesh.ui.node.AdaptiveNodeListScreen
4142import kotlinx.coroutines.flow.Flow
4243import org.jetbrains.compose.resources.StringResource
4344import org.meshtastic.core.navigation.ContactsRoutes
4445import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
4546import org.meshtastic.core.navigation.NodeDetailRoutes
4647import org.meshtastic.core.navigation.NodesRoutes
4748import org.meshtastic.core.navigation.Route
48- import org.meshtastic.core.strings.R
4949import org.meshtastic.core.strings.Res
5050import org.meshtastic.core.strings.device
5151import org.meshtastic.core.strings.environment
@@ -58,8 +58,6 @@ import org.meshtastic.core.strings.traceroute
5858import org.meshtastic.core.ui.component.ScrollToTopEvent
5959import org.meshtastic.feature.map.node.NodeMapScreen
6060import org.meshtastic.feature.map.node.NodeMapViewModel
61- import org.meshtastic.feature.node.detail.NodeDetailScreen
62- import org.meshtastic.feature.node.list.NodeListScreen
6361import org.meshtastic.feature.node.metrics.DeviceMetricsScreen
6462import org.meshtastic.feature.node.metrics.EnvironmentMetricsScreen
6563import org.meshtastic.feature.node.metrics.HostMetricsLogScreen
@@ -69,23 +67,27 @@ import org.meshtastic.feature.node.metrics.PositionLogScreen
6967import org.meshtastic.feature.node.metrics.PowerMetricsScreen
7068import org.meshtastic.feature.node.metrics.SignalMetricsScreen
7169import org.meshtastic.feature.node.metrics.TracerouteLogScreen
70+ import kotlin.reflect.KClass
7271
7372fun NavGraphBuilder.nodesGraph (navController : NavHostController , scrollToTopEvents : Flow <ScrollToTopEvent >) {
7473 navigation<NodesRoutes .NodesGraph >(startDestination = NodesRoutes .Nodes ) {
7574 composable<NodesRoutes .Nodes >(
7675 deepLinks = listOf (navDeepLink<NodesRoutes .Nodes >(basePath = " $DEEP_LINK_BASE_URI /nodes" )),
7776 ) {
78- NodeListScreen (
79- navigateToNodeDetails = { navController.navigate( NodesRoutes . NodeDetailGraph (it)) } ,
77+ AdaptiveNodeListScreen (
78+ navController = navController,
8079 scrollToTopEvents = scrollToTopEvents,
80+ onNavigateToMessages = { navController.navigate(ContactsRoutes .Messages (it)) },
8181 )
8282 }
83- nodeDetailGraph(navController)
83+ nodeDetailGraph(navController, scrollToTopEvents )
8484 }
8585}
8686
8787@Suppress(" LongMethod" )
88- fun NavGraphBuilder.nodeDetailGraph (navController : NavHostController ) {
88+ fun NavGraphBuilder.nodeDetailGraph (navController : NavHostController , scrollToTopEvents : Flow <ScrollToTopEvent >) {
89+ // We keep this route for deep linking or direct navigation to details,
90+ // but typically users will navigate via the Adaptive screen in NodesRoutes.Nodes
8991 navigation<NodesRoutes .NodeDetailGraph >(startDestination = NodesRoutes .NodeDetail ()) {
9092 composable<NodesRoutes .NodeDetail >(
9193 deepLinks =
@@ -95,13 +97,14 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController) {
9597 ),
9698 ),
9799 ) { backStackEntry ->
98- val parentEntry =
99- remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes .NodeDetailGraph ::class ) }
100- NodeDetailScreen (
101- navigateToMessages = { navController.navigate(ContactsRoutes .Messages (it)) },
102- onNavigate = { navController.navigate(it) },
103- onNavigateUp = { navController.navigateUp() },
104- viewModel = hiltViewModel(parentEntry),
100+ val args = backStackEntry.toRoute<NodesRoutes .NodeDetail >()
101+ // When navigating directly to NodeDetail (e.g. from Map or deep link),
102+ // we use the Adaptive screen initialized with the specific node ID.
103+ AdaptiveNodeListScreen (
104+ navController = navController,
105+ scrollToTopEvents = scrollToTopEvents,
106+ initialNodeId = args.destNum,
107+ onNavigateToMessages = { navController.navigate(ContactsRoutes .Messages (it)) },
105108 )
106109 }
107110
@@ -114,88 +117,98 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController) {
114117 ) { backStackEntry ->
115118 val parentGraphBackStackEntry =
116119 remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes .NodeDetailGraph ::class ) }
117- NodeMapScreen (
118- hiltViewModel<NodeMapViewModel >(parentGraphBackStackEntry),
119- onNavigateUp = navController::navigateUp,
120- )
120+ val vm = hiltViewModel<NodeMapViewModel >(parentGraphBackStackEntry)
121+ NodeMapScreen (vm, onNavigateUp = navController::navigateUp)
121122 }
122123
123124 NodeDetailRoute .entries.forEach { entry ->
124- when (entry.route ) {
125- is NodeDetailRoutes .DeviceMetrics ->
125+ when (entry.routeClass ) {
126+ NodeDetailRoutes .DeviceMetrics :: class ->
126127 addNodeDetailScreenComposable<NodeDetailRoutes .DeviceMetrics >(
127128 navController,
128129 entry,
129130 entry.screenComposable,
130- )
131- is NodeDetailRoutes .PositionLog ->
131+ ) {
132+ it.destNum
133+ }
134+ NodeDetailRoutes .PositionLog ::class ->
132135 addNodeDetailScreenComposable<NodeDetailRoutes .PositionLog >(
133136 navController,
134137 entry,
135138 entry.screenComposable,
136- )
137- is NodeDetailRoutes .EnvironmentMetrics ->
139+ ) {
140+ it.destNum
141+ }
142+ NodeDetailRoutes .EnvironmentMetrics ::class ->
138143 addNodeDetailScreenComposable<NodeDetailRoutes .EnvironmentMetrics >(
139144 navController,
140145 entry,
141146 entry.screenComposable,
142- )
143- is NodeDetailRoutes .SignalMetrics ->
147+ ) {
148+ it.destNum
149+ }
150+ NodeDetailRoutes .SignalMetrics ::class ->
144151 addNodeDetailScreenComposable<NodeDetailRoutes .SignalMetrics >(
145152 navController,
146153 entry,
147154 entry.screenComposable,
148- )
149- is NodeDetailRoutes .PowerMetrics ->
155+ ) {
156+ it.destNum
157+ }
158+ NodeDetailRoutes .PowerMetrics ::class ->
150159 addNodeDetailScreenComposable<NodeDetailRoutes .PowerMetrics >(
151160 navController,
152161 entry,
153162 entry.screenComposable,
154- )
155- is NodeDetailRoutes .TracerouteLog ->
163+ ) {
164+ it.destNum
165+ }
166+ NodeDetailRoutes .TracerouteLog ::class ->
156167 addNodeDetailScreenComposable<NodeDetailRoutes .TracerouteLog >(
157168 navController,
158169 entry,
159170 entry.screenComposable,
160- )
161- is NodeDetailRoutes .HostMetricsLog ->
171+ ) {
172+ it.destNum
173+ }
174+ NodeDetailRoutes .HostMetricsLog ::class ->
162175 addNodeDetailScreenComposable<NodeDetailRoutes .HostMetricsLog >(
163176 navController,
164177 entry,
165178 entry.screenComposable,
166- )
167- is NodeDetailRoutes .PaxMetrics ->
179+ ) {
180+ it.destNum
181+ }
182+ NodeDetailRoutes .PaxMetrics ::class ->
168183 addNodeDetailScreenComposable<NodeDetailRoutes .PaxMetrics >(
169184 navController,
170185 entry,
171186 entry.screenComposable,
172- )
187+ ) {
188+ it.destNum
189+ }
173190 else -> Unit
174191 }
175192 }
176193 }
177194}
178195
179- fun NavDestination.isNodeDetailRoute (): Boolean = NodeDetailRoute .entries.any { hasRoute(it.route:: class ) }
196+ fun NavDestination.isNodeDetailRoute (): Boolean = NodeDetailRoute .entries.any { hasRoute(it.routeClass ) }
180197
181198/* *
182199 * Helper to define a composable route for a screen within the node detail graph.
183200 *
184- * This function simplifies adding screens by handling common tasks like:
185- * - Setting up deep links based on the [NodeDetailRoute] definition.
186- * - Retrieving the parent [NavBackStackEntry] for the [NodesRoutes.NodeDetailGraph].
187- * - Providing the [MetricsViewModel] scoped to the parent graph.
188- *
189201 * @param R The type of the [Route] object, must be serializable.
190202 * @param navController The [NavHostController] for navigation.
191203 * @param routeInfo The [NodeDetailRoute] enum entry that defines the path and metadata for this route.
192- * @param screenContent A lambda that defines the composable content for the screen. It receives the shared
193- * [MetricsViewModel] .
204+ * @param screenContent A lambda that defines the composable content for the screen.
205+ * @param getDestNum A lambda to extract the destination number from the route arguments .
194206 */
195207private inline fun <reified R : Route > NavGraphBuilder.addNodeDetailScreenComposable (
196208 navController : NavHostController ,
197209 routeInfo : NodeDetailRoute ,
198210 crossinline screenContent : @Composable (metricsViewModel: MetricsViewModel , onNavigateUp: () -> Unit ) -> Unit ,
211+ crossinline getDestNum : (R ) -> Int ,
199212) {
200213 composable<R >(
201214 deepLinks =
@@ -207,61 +220,66 @@ private inline fun <reified R : Route> NavGraphBuilder.addNodeDetailScreenCompos
207220 val parentGraphBackStackEntry =
208221 remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes .NodeDetailGraph ::class ) }
209222 val metricsViewModel = hiltViewModel<MetricsViewModel >(parentGraphBackStackEntry)
223+
224+ val args = backStackEntry.toRoute<R >()
225+ val destNum = getDestNum(args)
226+ metricsViewModel.setNodeId(destNum)
227+
210228 screenContent(metricsViewModel, navController::navigateUp)
211229 }
212230}
213231
214232enum class NodeDetailRoute (
215233 val title : StringResource ,
216- val route : Route ,
234+ val routeClass : KClass < out Route > ,
217235 val icon : ImageVector ? ,
218236 val screenComposable : @Composable (metricsViewModel: MetricsViewModel , onNavigateUp: () -> Unit ) -> Unit ,
219237) {
220238 DEVICE (
221239 Res .string.device,
222- NodeDetailRoutes .DeviceMetrics ,
240+ NodeDetailRoutes .DeviceMetrics :: class ,
223241 Icons .Default .Router ,
224242 { metricsVM, onNavigateUp -> DeviceMetricsScreen (metricsVM, onNavigateUp) },
225243 ),
226244 POSITION_LOG (
227245 Res .string.position_log,
228- NodeDetailRoutes .PositionLog ,
246+ NodeDetailRoutes .PositionLog :: class ,
229247 Icons .Default .LocationOn ,
230248 { metricsVM, onNavigateUp -> PositionLogScreen (metricsVM, onNavigateUp) },
231249 ),
232250 ENVIRONMENT (
233251 Res .string.environment,
234- NodeDetailRoutes .EnvironmentMetrics ,
252+ NodeDetailRoutes .EnvironmentMetrics :: class ,
235253 Icons .Default .LightMode ,
236254 { metricsVM, onNavigateUp -> EnvironmentMetricsScreen (metricsVM, onNavigateUp) },
237255 ),
238256 SIGNAL (
239257 Res .string.signal,
240- NodeDetailRoutes .SignalMetrics ,
258+ NodeDetailRoutes .SignalMetrics :: class ,
241259 Icons .Default .CellTower ,
242260 { metricsVM, onNavigateUp -> SignalMetricsScreen (metricsVM, onNavigateUp) },
243261 ),
244262 TRACEROUTE (
245263 Res .string.traceroute,
246- NodeDetailRoutes .TracerouteLog ,
264+ NodeDetailRoutes .TracerouteLog :: class ,
247265 Icons .Default .PermScanWifi ,
248266 { metricsVM, onNavigateUp -> TracerouteLogScreen (viewModel = metricsVM, onNavigateUp = onNavigateUp) },
249267 ),
250268 POWER (
251269 Res .string.power,
252- NodeDetailRoutes .PowerMetrics ,
270+ NodeDetailRoutes .PowerMetrics :: class ,
253271 Icons .Default .Power ,
254272 { metricsVM, onNavigateUp -> PowerMetricsScreen (metricsVM, onNavigateUp) },
255273 ),
256274 HOST (
257275 Res .string.host,
258- NodeDetailRoutes .HostMetricsLog ,
276+ NodeDetailRoutes .HostMetricsLog :: class ,
259277 Icons .Default .Memory ,
260278 { metricsVM, onNavigateUp -> HostMetricsLogScreen (metricsVM, onNavigateUp) },
261279 ),
262280 PAX (
263281 Res .string.pax,
264- NodeDetailRoutes .PaxMetrics ,
282+ NodeDetailRoutes .PaxMetrics :: class ,
265283 Icons .Default .People ,
266284 { metricsVM, onNavigateUp -> PaxMetricsScreen (metricsVM, onNavigateUp) },
267285 ),
0 commit comments