From 938a951737be15498679c40fa1014ef7aaec3c03 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:02:31 -0500 Subject: [PATCH] =?UTF-8?q?refactor:=20leverage=20CMP=201.11=20+=20Lifecyc?= =?UTF-8?q?le=202.11=20=E2=80=94=20v2=20test=20API,=20Json=20privacy,=20dr?= =?UTF-8?q?opUnlessResumed=20nav=20guards=20(#5112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeviceHardwareJsonDataSourceImpl.kt | 1 + .../FirmwareReleaseJsonDataSourceImpl.kt | 1 + .../core/ui/component/AlertHostTest.kt | 2 +- .../core/ui/component/ImportFabUiTest.kt | 2 +- .../core/ui/util/AlertManagerUiTest.kt | 2 +- .../navigation/ConnectionsNavigation.kt | 8 +- .../feature/firmware/FirmwareRetriever.kt | 7 +- .../firmware/navigation/FirmwareNavigation.kt | 9 +- .../feature/firmware/ota/dfu/DfuZipParser.kt | 6 +- .../feature/map/navigation/MapNavigation.kt | 4 +- .../navigation/ContactsNavigation.kt | 12 ++- .../messaging/component/MessageItemTest.kt | 2 +- .../node/navigation/NodesNavigation.kt | 11 ++- .../settings/navigation/SettingsNavigation.kt | 91 ++++++++++++------- .../radio/channel/ChannelsNavigation.kt | 5 +- .../settings/debugging/DebugSearchTest.kt | 2 +- .../component/EditDeviceProfileDialogTest.kt | 2 +- .../component/MapReportingPreferenceTest.kt | 2 +- .../wifiprovision/domain/NymeaProtocol.kt | 3 + .../navigation/WifiProvisionNavigation.kt | 5 +- 20 files changed, 114 insertions(+), 63 deletions(-) diff --git a/core/data/src/androidMain/kotlin/org/meshtastic/core/data/datasource/DeviceHardwareJsonDataSourceImpl.kt b/core/data/src/androidMain/kotlin/org/meshtastic/core/data/datasource/DeviceHardwareJsonDataSourceImpl.kt index 327cddcae..e20944f4e 100644 --- a/core/data/src/androidMain/kotlin/org/meshtastic/core/data/datasource/DeviceHardwareJsonDataSourceImpl.kt +++ b/core/data/src/androidMain/kotlin/org/meshtastic/core/data/datasource/DeviceHardwareJsonDataSourceImpl.kt @@ -32,6 +32,7 @@ class DeviceHardwareJsonDataSourceImpl(private val application: Application) : D private val json = Json { ignoreUnknownKeys = true isLenient = true + exceptionsWithDebugInfo = false } @OptIn(ExperimentalSerializationApi::class) diff --git a/core/data/src/androidMain/kotlin/org/meshtastic/core/data/datasource/FirmwareReleaseJsonDataSourceImpl.kt b/core/data/src/androidMain/kotlin/org/meshtastic/core/data/datasource/FirmwareReleaseJsonDataSourceImpl.kt index c060f4b21..d437937d4 100644 --- a/core/data/src/androidMain/kotlin/org/meshtastic/core/data/datasource/FirmwareReleaseJsonDataSourceImpl.kt +++ b/core/data/src/androidMain/kotlin/org/meshtastic/core/data/datasource/FirmwareReleaseJsonDataSourceImpl.kt @@ -32,6 +32,7 @@ class FirmwareReleaseJsonDataSourceImpl(private val application: Application) : private val json = Json { ignoreUnknownKeys = true isLenient = true + exceptionsWithDebugInfo = false } @OptIn(ExperimentalSerializationApi::class) diff --git a/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/AlertHostTest.kt b/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/AlertHostTest.kt index ab0f1a80f..7a442980f 100644 --- a/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/AlertHostTest.kt +++ b/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/AlertHostTest.kt @@ -19,7 +19,7 @@ package org.meshtastic.core.ui.component import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.runComposeUiTest +import androidx.compose.ui.test.v2.runComposeUiTest import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher diff --git a/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/ImportFabUiTest.kt b/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/ImportFabUiTest.kt index 650671de2..8380aabcb 100644 --- a/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/ImportFabUiTest.kt +++ b/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/ImportFabUiTest.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.runComposeUiTest +import androidx.compose.ui.test.v2.runComposeUiTest import org.meshtastic.core.ui.util.LocalBarcodeScannerSupported import org.meshtastic.core.ui.util.LocalNfcScannerSupported import org.meshtastic.proto.SharedContact diff --git a/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/util/AlertManagerUiTest.kt b/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/util/AlertManagerUiTest.kt index 7d2e1d1a4..2090736b1 100644 --- a/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/util/AlertManagerUiTest.kt +++ b/core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/util/AlertManagerUiTest.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.runComposeUiTest +import androidx.compose.ui.test.v2.runComposeUiTest import kotlin.test.Test import kotlin.test.assertTrue 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 152e880cb..c6962c8c0 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(NodesRoute.NodeDetail(it)) }, - onNavigateToNodeDetails = { backStack.add(NodesRoute.NodeDetail(it)) }, + onClickNodeChip = { id -> backStack.add(NodesRoute.NodeDetail(id)) }, + onNavigateToNodeDetails = { id -> backStack.add(NodesRoute.NodeDetail(id)) }, onConfigNavigate = { route -> backStack.add(route) }, ) } @@ -42,8 +42,8 @@ fun EntryProviderScope.connectionsGraph(backStack: NavBackStack) ConnectionsScreen( scanModel = koinViewModel(), radioConfigViewModel = koinViewModel(), - onClickNodeChip = { backStack.add(NodesRoute.NodeDetail(it)) }, - onNavigateToNodeDetails = { backStack.add(NodesRoute.NodeDetail(it)) }, + onClickNodeChip = { id -> backStack.add(NodesRoute.NodeDetail(id)) }, + onNavigateToNodeDetails = { id -> backStack.add(NodesRoute.NodeDetail(id)) }, onConfigNavigate = { route -> backStack.add(route) }, ) } diff --git a/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/FirmwareRetriever.kt b/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/FirmwareRetriever.kt index 64d550a79..1dcb7ba69 100644 --- a/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/FirmwareRetriever.kt +++ b/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/FirmwareRetriever.kt @@ -17,6 +17,7 @@ package org.meshtastic.feature.firmware import co.touchlab.kermit.Logger +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import org.koin.core.annotation.Single import org.meshtastic.core.database.entity.FirmwareRelease @@ -29,7 +30,11 @@ private const val FIRMWARE_BASE_URL = "https://raw.githubusercontent.com/meshtas /** OTA partition role in .mt.json manifests — the main application firmware. */ private const val OTA_PART_NAME = "app0" -private val manifestJson = Json { ignoreUnknownKeys = true } +@OptIn(ExperimentalSerializationApi::class) +private val manifestJson = Json { + ignoreUnknownKeys = true + exceptionsWithDebugInfo = false +} /** Retrieves firmware files, either by direct download or by extracting from a release asset zip. */ @Single diff --git a/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/navigation/FirmwareNavigation.kt b/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/navigation/FirmwareNavigation.kt index 7980ad96a..40c6ad904 100644 --- a/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/navigation/FirmwareNavigation.kt +++ b/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/navigation/FirmwareNavigation.kt @@ -17,6 +17,7 @@ package org.meshtastic.feature.firmware.navigation import androidx.compose.runtime.Composable +import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey @@ -27,8 +28,12 @@ import org.meshtastic.feature.firmware.FirmwareUpdateViewModel /** Registers the firmware update screen entries into the Navigation 3 entry provider. */ fun EntryProviderScope.firmwareGraph(backStack: NavBackStack) { - entry { FirmwareScreen(onNavigateUp = { backStack.removeLastOrNull() }) } - entry { FirmwareScreen(onNavigateUp = { backStack.removeLastOrNull() }) } + entry { + FirmwareScreen(onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }) + } + entry { + FirmwareScreen(onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }) + } } @Composable diff --git a/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/DfuZipParser.kt b/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/DfuZipParser.kt index 10a0a5154..43f6804e1 100644 --- a/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/DfuZipParser.kt +++ b/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/DfuZipParser.kt @@ -21,7 +21,11 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonDecodingException -private val json = Json { ignoreUnknownKeys = true } +@OptIn(ExperimentalSerializationApi::class) +private val json = Json { + ignoreUnknownKeys = true + exceptionsWithDebugInfo = false +} /** * Parse pre-extracted zip entries into a [DfuZipPackage]. 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 2c0b5e7b8..8d2af9c4d 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(NodesRoute.NodeDetail(it)) }, // onClickNodeChip - { backStack.add(NodesRoute.NodeDetail(it)) }, // navigateToNodeDetails + { id -> backStack.add(NodesRoute.NodeDetail(id)) }, // onClickNodeChip + { id -> backStack.add(NodesRoute.NodeDetail(id)) }, // 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 0f347f980..62b57d3a8 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 @@ -21,6 +21,7 @@ import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey @@ -61,9 +62,10 @@ fun EntryProviderScope.contactsGraph( contactKey = contactKey, message = args.message, viewModel = messageViewModel, - navigateToNodeDetails = { backStack.add(NodesRoute.NodeDetail(it)) }, - navigateToQuickChatOptions = { backStack.add(org.meshtastic.core.navigation.ContactsRoute.QuickChat) }, - onNavigateBack = { backStack.removeLastOrNull() }, + navigateToNodeDetails = { id -> backStack.add(NodesRoute.NodeDetail(id)) }, + navigateToQuickChatOptions = + dropUnlessResumed { backStack.add(org.meshtastic.core.navigation.ContactsRoute.QuickChat) }, + onNavigateBack = dropUnlessResumed { backStack.removeLastOrNull() }, ) } @@ -73,13 +75,13 @@ fun EntryProviderScope.contactsGraph( ShareScreen( viewModel = viewModel, onConfirm = { contactKey -> backStack.replaceLast(ContactsRoute.Messages(contactKey, message)) }, - onNavigateUp = { backStack.removeLastOrNull() }, + onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }, ) } entry(metadata = { ListDetailSceneStrategy.extraPane() }) { val viewModel = koinViewModel() - QuickChatScreen(viewModel = viewModel, onNavigateUp = { backStack.removeLastOrNull() }) + QuickChatScreen(viewModel = viewModel, onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }) } } diff --git a/feature/messaging/src/commonTest/kotlin/org/meshtastic/feature/messaging/component/MessageItemTest.kt b/feature/messaging/src/commonTest/kotlin/org/meshtastic/feature/messaging/component/MessageItemTest.kt index 68f7817aa..cf45cb1ec 100644 --- a/feature/messaging/src/commonTest/kotlin/org/meshtastic/feature/messaging/component/MessageItemTest.kt +++ b/feature/messaging/src/commonTest/kotlin/org/meshtastic/feature/messaging/component/MessageItemTest.kt @@ -19,7 +19,7 @@ package org.meshtastic.feature.messaging.component import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.onNodeWithContentDescription -import androidx.compose.ui.test.runComposeUiTest +import androidx.compose.ui.test.v2.runComposeUiTest import org.meshtastic.core.common.util.nowMillis import org.meshtastic.core.model.Message import org.meshtastic.core.model.MessageStatus diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/navigation/NodesNavigation.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/navigation/NodesNavigation.kt index facb5a9d7..778c8b220 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/navigation/NodesNavigation.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/navigation/NodesNavigation.kt @@ -19,6 +19,7 @@ package org.meshtastic.feature.node.navigation import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy import androidx.compose.runtime.Composable +import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey @@ -116,9 +117,9 @@ fun EntryProviderScope.nodeDetailGraph( nodeId = destNum, viewModel = nodeDetailViewModel, compassViewModel = compassViewModel, - navigateToMessages = { backStack.add(ContactsRoute.Messages(it)) }, - onNavigate = { backStack.add(it) }, - onNavigateUp = { backStack.removeLastOrNull() }, + navigateToMessages = { key -> backStack.add(ContactsRoute.Messages(key)) }, + onNavigate = { route -> backStack.add(route) }, + onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }, ) } @@ -128,7 +129,7 @@ fun EntryProviderScope.nodeDetailGraph( TracerouteLogScreen( viewModel = metricsViewModel, - onNavigateUp = { backStack.removeLastOrNull() }, + onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }, onViewOnMap = { requestId, responseLogUuid -> backStack.add( NodeDetailRoute.TracerouteMap( @@ -182,7 +183,7 @@ private inline fun EntryProviderScope.addNodeDetailS val metricsViewModel = koinViewModel { parametersOf(destNum) } metricsViewModel.setNodeId(destNum) - routeInfo.screenComposable(metricsViewModel) { backStack.removeLastOrNull() } + routeInfo.screenComposable(metricsViewModel, dropUnlessResumed { backStack.removeLastOrNull() }) } } 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 1409f6bdf..54f0f7100 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 @@ -22,6 +22,7 @@ import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey @@ -106,7 +107,7 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { entry { DeviceConfigurationScreen( viewModel = getRadioConfigViewModel(backStack), - onBack = { backStack.removeLastOrNull() }, + onBack = dropUnlessResumed { backStack.removeLastOrNull() }, onNavigate = { route -> backStack.add(route) }, ) } @@ -117,13 +118,16 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { ModuleConfigurationScreen( viewModel = getRadioConfigViewModel(backStack), excludedModulesUnlocked = excludedModulesUnlocked, - onBack = { backStack.removeLastOrNull() }, + onBack = dropUnlessResumed { backStack.removeLastOrNull() }, onNavigate = { route -> backStack.add(route) }, ) } entry { - AdministrationScreen(viewModel = getRadioConfigViewModel(backStack), onBack = { backStack.removeLastOrNull() }) + AdministrationScreen( + viewModel = getRadioConfigViewModel(backStack), + onBack = dropUnlessResumed { backStack.removeLastOrNull() }, + ) } entry { @@ -135,16 +139,26 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { configComposable(routeInfo.route::class, backStack) { viewModel -> LaunchedEffect(Unit) { viewModel.setResponseStateLoading(routeInfo) } when (routeInfo) { - ConfigRoute.USER -> UserConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ConfigRoute.CHANNELS -> ChannelConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ConfigRoute.DEVICE -> DeviceConfigScreenCommon(viewModel, onBack = { backStack.removeLastOrNull() }) - ConfigRoute.POSITION -> PositionConfigScreenCommon(viewModel, onBack = { backStack.removeLastOrNull() }) - ConfigRoute.POWER -> PowerConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ConfigRoute.NETWORK -> NetworkConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ConfigRoute.DISPLAY -> DisplayConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ConfigRoute.LORA -> LoRaConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ConfigRoute.BLUETOOTH -> BluetoothConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ConfigRoute.SECURITY -> SecurityConfigScreenCommon(viewModel, onBack = { backStack.removeLastOrNull() }) + ConfigRoute.USER -> + UserConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ConfigRoute.CHANNELS -> + ChannelConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ConfigRoute.DEVICE -> + DeviceConfigScreenCommon(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ConfigRoute.POSITION -> + PositionConfigScreenCommon(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ConfigRoute.POWER -> + PowerConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ConfigRoute.NETWORK -> + NetworkConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ConfigRoute.DISPLAY -> + DisplayConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ConfigRoute.LORA -> + LoRaConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ConfigRoute.BLUETOOTH -> + BluetoothConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ConfigRoute.SECURITY -> + SecurityConfigScreenCommon(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) } } } @@ -153,50 +167,63 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { configComposable(routeInfo.route::class, backStack) { viewModel -> LaunchedEffect(Unit) { viewModel.setResponseStateLoading(routeInfo) } when (routeInfo) { - ModuleRoute.MQTT -> MQTTConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ModuleRoute.SERIAL -> SerialConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) + ModuleRoute.MQTT -> + MQTTConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ModuleRoute.SERIAL -> + SerialConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) ModuleRoute.EXT_NOTIFICATION -> ExternalNotificationConfigScreenCommon( viewModel = viewModel, - onBack = { backStack.removeLastOrNull() }, + onBack = dropUnlessResumed { backStack.removeLastOrNull() }, ) ModuleRoute.STORE_FORWARD -> - StoreForwardConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ModuleRoute.RANGE_TEST -> RangeTestConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ModuleRoute.TELEMETRY -> TelemetryConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) + StoreForwardConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ModuleRoute.RANGE_TEST -> + RangeTestConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ModuleRoute.TELEMETRY -> + TelemetryConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) ModuleRoute.CANNED_MESSAGE -> - CannedMessageConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ModuleRoute.AUDIO -> AudioConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) + CannedMessageConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ModuleRoute.AUDIO -> + AudioConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) ModuleRoute.REMOTE_HARDWARE -> - RemoteHardwareConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) + RemoteHardwareConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) ModuleRoute.NEIGHBOR_INFO -> - NeighborInfoConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) + NeighborInfoConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) ModuleRoute.AMBIENT_LIGHTING -> - AmbientLightingConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) + AmbientLightingConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) ModuleRoute.DETECTION_SENSOR -> - DetectionSensorConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ModuleRoute.PAXCOUNTER -> PaxcounterConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) + DetectionSensorConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) + ModuleRoute.PAXCOUNTER -> + PaxcounterConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) ModuleRoute.STATUS_MESSAGE -> - StatusMessageConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) + StatusMessageConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) ModuleRoute.TRAFFIC_MANAGEMENT -> - TrafficManagementConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) - ModuleRoute.TAK -> TAKConfigScreen(viewModel, onBack = { backStack.removeLastOrNull() }) + TrafficManagementConfigScreen( + viewModel, + onBack = dropUnlessResumed { backStack.removeLastOrNull() }, + ) + ModuleRoute.TAK -> + TAKConfigScreen(viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) } } } entry { val viewModel: DebugViewModel = koinViewModel() - DebugScreen(viewModel = viewModel, onNavigateUp = { backStack.removeLastOrNull() }) + DebugScreen(viewModel = viewModel, onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }) } entry { - AboutScreen(onNavigateUp = { backStack.removeLastOrNull() }, jsonProvider = { getAboutLibrariesJson() }) + AboutScreen( + onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }, + jsonProvider = { getAboutLibrariesJson() }, + ) } entry { val viewModel: FilterSettingsViewModel = koinViewModel() - FilterSettingsScreen(viewModel = viewModel, onBack = { backStack.removeLastOrNull() }) + FilterSettingsScreen(viewModel = viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() }) } } diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/channel/ChannelsNavigation.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/channel/ChannelsNavigation.kt index f73b6b731..8ec5f593e 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/channel/ChannelsNavigation.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/channel/ChannelsNavigation.kt @@ -16,6 +16,7 @@ */ package org.meshtastic.feature.settings.radio.channel +import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey @@ -29,7 +30,7 @@ fun EntryProviderScope.channelsGraph(backStack: NavBackStack) { ChannelScreen( radioConfigViewModel = koinViewModel(), onNavigate = { route -> backStack.add(route) }, - onNavigateUp = { backStack.removeLastOrNull() }, + onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }, ) } @@ -37,7 +38,7 @@ fun EntryProviderScope.channelsGraph(backStack: NavBackStack) { ChannelScreen( radioConfigViewModel = koinViewModel(), onNavigate = { route -> backStack.add(route) }, - onNavigateUp = { backStack.removeLastOrNull() }, + onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }, ) } } diff --git a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/debugging/DebugSearchTest.kt b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/debugging/DebugSearchTest.kt index f68a79f23..83bcddee1 100644 --- a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/debugging/DebugSearchTest.kt +++ b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/debugging/DebugSearchTest.kt @@ -29,7 +29,7 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput -import androidx.compose.ui.test.runComposeUiTest +import androidx.compose.ui.test.v2.runComposeUiTest import androidx.compose.ui.unit.dp import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.debug_active_filters diff --git a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/EditDeviceProfileDialogTest.kt b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/EditDeviceProfileDialogTest.kt index 61d3b1219..cffeab006 100644 --- a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/EditDeviceProfileDialogTest.kt +++ b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/EditDeviceProfileDialogTest.kt @@ -20,7 +20,7 @@ import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.runComposeUiTest +import androidx.compose.ui.test.v2.runComposeUiTest import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.cancel import org.meshtastic.core.resources.getString diff --git a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/MapReportingPreferenceTest.kt b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/MapReportingPreferenceTest.kt index 850cc93e7..42a67a6a0 100644 --- a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/MapReportingPreferenceTest.kt +++ b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/MapReportingPreferenceTest.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.runComposeUiTest +import androidx.compose.ui.test.v2.runComposeUiTest import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.getString import org.meshtastic.core.resources.i_agree diff --git a/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/domain/NymeaProtocol.kt b/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/domain/NymeaProtocol.kt index 2519595d1..71fe68f79 100644 --- a/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/domain/NymeaProtocol.kt +++ b/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/domain/NymeaProtocol.kt @@ -16,6 +16,7 @@ */ package org.meshtastic.feature.wifiprovision.domain +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -33,9 +34,11 @@ import kotlinx.serialization.json.Json // Shared JSON codec — lenient so unknown fields are silently ignored // --------------------------------------------------------------------------- +@OptIn(ExperimentalSerializationApi::class) internal val NymeaJson = Json { ignoreUnknownKeys = true isLenient = true + exceptionsWithDebugInfo = false } // --------------------------------------------------------------------------- diff --git a/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/navigation/WifiProvisionNavigation.kt b/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/navigation/WifiProvisionNavigation.kt index ea30112c7..a79d32b25 100644 --- a/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/navigation/WifiProvisionNavigation.kt +++ b/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/navigation/WifiProvisionNavigation.kt @@ -16,6 +16,7 @@ */ package org.meshtastic.feature.wifiprovision.navigation +import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey @@ -31,9 +32,9 @@ import org.meshtastic.feature.wifiprovision.ui.WifiProvisionScreen */ fun EntryProviderScope.wifiProvisionGraph(backStack: NavBackStack) { entry { - WifiProvisionScreen(onNavigateUp = { backStack.removeLastOrNull() }) + WifiProvisionScreen(onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }) } entry { key -> - WifiProvisionScreen(onNavigateUp = { backStack.removeLastOrNull() }, address = key.address) + WifiProvisionScreen(onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }, address = key.address) } }