From 6a5115b897147631d84c3cca314ca6f1de7c49ef Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:03:02 -0500 Subject: [PATCH] Refactor navigation to use NodeDetail route and fix radio settings (#4960) --- .../core/network/radio/BleRadioInterface.kt | 12 ------------ .../navigation/ConnectionsNavigation.kt | 8 ++++---- .../feature/map/navigation/MapNavigation.kt | 4 ++-- .../messaging/navigation/ContactsNavigation.kt | 3 ++- .../messaging/ui/contact/AdaptiveContactsScreen.kt | 4 ++-- .../settings/navigation/SettingsNavigation.kt | 14 ++++++++------ .../feature/settings/radio/RadioConfigViewModel.kt | 2 +- 7 files changed, 19 insertions(+), 28 deletions(-) diff --git a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioInterface.kt b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioInterface.kt index 68cd0307b..987779864 100644 --- a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioInterface.kt +++ b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioInterface.kt @@ -445,18 +445,6 @@ class BleRadioInterface( // onDisconnect is handled by SharedRadioInterfaceService.stopInterfaceLocked() directly. serviceScope.launch { withContext(NonCancellable) { - // Send ToRadio.disconnect before dropping the BLE link. The firmware calls its - // own close() immediately on receipt, resetting the PhoneAPI state machine - // (config nonce, packet queue, observers) without waiting for the 6-second BLE - // supervision timeout. Best-effort: if the write fails we still disconnect below. - val currentService = radioService - if (currentService != null) { - try { - withTimeoutOrNull(2_000L) { currentService.sendToRadio(ToRadio(disconnect = true).encode()) } - } catch (@Suppress("TooGenericExceptionCaught") e: Exception) { - Logger.w(e) { "[$address] Failed to send disconnect signal" } - } - } try { bleConnection.disconnect() } catch (@Suppress("TooGenericExceptionCaught") e: Exception) { diff --git a/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/navigation/ConnectionsNavigation.kt b/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/navigation/ConnectionsNavigation.kt index d239dcf00..eabd920eb 100644 --- a/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/navigation/ConnectionsNavigation.kt +++ b/feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/navigation/ConnectionsNavigation.kt @@ -32,8 +32,8 @@ fun EntryProviderScope.connectionsGraph(backStack: NavBackStack) ConnectionsScreen( scanModel = koinViewModel(), radioConfigViewModel = koinViewModel(), - onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, - onNavigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, + onClickNodeChip = { backStack.add(NodesRoutes.NodeDetail(it)) }, + onNavigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetail(it)) }, onConfigNavigate = { route -> backStack.add(route) }, ) } @@ -42,8 +42,8 @@ fun EntryProviderScope.connectionsGraph(backStack: NavBackStack) ConnectionsScreen( scanModel = koinViewModel(), radioConfigViewModel = koinViewModel(), - onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, - onNavigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, + onClickNodeChip = { backStack.add(NodesRoutes.NodeDetail(it)) }, + onNavigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetail(it)) }, onConfigNavigate = { route -> backStack.add(route) }, ) } diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/navigation/MapNavigation.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/navigation/MapNavigation.kt index e13106104..fb921bdde 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/navigation/MapNavigation.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/navigation/MapNavigation.kt @@ -26,8 +26,8 @@ fun EntryProviderScope.mapGraph(backStack: NavBackStack) { entry { args -> val mapScreen = org.meshtastic.core.ui.util.LocalMapMainScreenProvider.current mapScreen( - { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, // onClickNodeChip - { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, // navigateToNodeDetails + { backStack.add(NodesRoutes.NodeDetail(it)) }, // onClickNodeChip + { backStack.add(NodesRoutes.NodeDetail(it)) }, // navigateToNodeDetails args.waypointId, ) } diff --git a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/navigation/ContactsNavigation.kt b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/navigation/ContactsNavigation.kt index 4c79ddd8c..1e83f8039 100644 --- a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/navigation/ContactsNavigation.kt +++ b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/navigation/ContactsNavigation.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import org.koin.compose.viewmodel.koinViewModel import org.meshtastic.core.navigation.ContactsRoutes +import org.meshtastic.core.navigation.NodesRoutes import org.meshtastic.core.navigation.replaceLast import org.meshtastic.core.ui.component.ScrollToTopEvent import org.meshtastic.feature.messaging.QuickChatScreen @@ -60,7 +61,7 @@ fun EntryProviderScope.contactsGraph( contactKey = contactKey, message = args.message, viewModel = messageViewModel, - navigateToNodeDetails = { backStack.add(org.meshtastic.core.navigation.NodesRoutes.NodeDetailGraph(it)) }, + navigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetail(it)) }, navigateToQuickChatOptions = { backStack.add(org.meshtastic.core.navigation.ContactsRoutes.QuickChat) }, onNavigateBack = { backStack.removeLastOrNull() }, ) diff --git a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt index 441042e66..df3d5a7ad 100644 --- a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt +++ b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt @@ -47,9 +47,9 @@ fun AdaptiveContactsScreen( onClearSharedContactRequested = onClearSharedContactRequested, onClearRequestChannelUrl = onClearRequestChannelUrl, viewModel = contactsViewModel, - onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, + onClickNodeChip = { backStack.add(NodesRoutes.NodeDetail(it)) }, onNavigateToMessages = { contactKey -> backStack.add(ContactsRoutes.Messages(contactKey)) }, - onNavigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, + onNavigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetail(it)) }, scrollToTopEvents = scrollToTopEvents, activeContactKey = null, ) diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt index edf6caeb7..ac713ae7e 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt @@ -18,7 +18,9 @@ package org.meshtastic.feature.settings.navigation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavBackStack @@ -70,14 +72,14 @@ import kotlin.reflect.KClass @Composable fun getRadioConfigViewModel(backStack: NavBackStack): RadioConfigViewModel { val viewModel = koinViewModel() - LaunchedEffect(backStack) { - val destNum = + val destNum = + remember(backStack.toList()) { backStack.lastOrNull { it is SettingsRoutes.Settings }?.let { (it as SettingsRoutes.Settings).destNum } ?: backStack .lastOrNull { it is SettingsRoutes.SettingsGraph } ?.let { (it as SettingsRoutes.SettingsGraph).destNum } - viewModel.initDestNum(destNum) - } + } + SideEffect { viewModel.initDestNum(destNum) } return viewModel } @@ -87,7 +89,7 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { SettingsMainScreen( settingsViewModel = koinViewModel(), radioConfigViewModel = getRadioConfigViewModel(backStack), - onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, + onClickNodeChip = { backStack.add(NodesRoutes.NodeDetail(it)) }, onNavigate = { backStack.add(it) }, ) } @@ -96,7 +98,7 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { SettingsMainScreen( settingsViewModel = koinViewModel(), radioConfigViewModel = getRadioConfigViewModel(backStack), - onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, + onClickNodeChip = { backStack.add(NodesRoutes.NodeDetail(it)) }, onNavigate = { backStack.add(it) }, ) } diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt index 9b385fb58..037717143 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt @@ -433,7 +433,7 @@ open class RadioConfigViewModel( } fun setResponseStateLoading(route: Enum<*>) { - val destNum = destNode.value?.num ?: return + val destNum = destNumFlow.value ?: destNode.value?.num ?: return _radioConfigState.update { it.copy(route = route.name, responseState = ResponseState.Loading()) }