refactor: leverage CMP 1.11 + Lifecycle 2.11 — v2 test API, Json privacy, dropUnlessResumed nav guards (#5112)

This commit is contained in:
James Rich 2026-04-13 15:02:31 -05:00 committed by GitHub
parent 76386e419c
commit 938a951737
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 114 additions and 63 deletions

View file

@ -32,6 +32,7 @@ class DeviceHardwareJsonDataSourceImpl(private val application: Application) : D
private val json = Json {
ignoreUnknownKeys = true
isLenient = true
exceptionsWithDebugInfo = false
}
@OptIn(ExperimentalSerializationApi::class)

View file

@ -32,6 +32,7 @@ class FirmwareReleaseJsonDataSourceImpl(private val application: Application) :
private val json = Json {
ignoreUnknownKeys = true
isLenient = true
exceptionsWithDebugInfo = false
}
@OptIn(ExperimentalSerializationApi::class)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -32,8 +32,8 @@ fun EntryProviderScope<NavKey>.connectionsGraph(backStack: NavBackStack<NavKey>)
ConnectionsScreen(
scanModel = koinViewModel<ScannerViewModel>(),
radioConfigViewModel = koinViewModel<RadioConfigViewModel>(),
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<NavKey>.connectionsGraph(backStack: NavBackStack<NavKey>)
ConnectionsScreen(
scanModel = koinViewModel<ScannerViewModel>(),
radioConfigViewModel = koinViewModel<RadioConfigViewModel>(),
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) },
)
}

View file

@ -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

View file

@ -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<NavKey>.firmwareGraph(backStack: NavBackStack<NavKey>) {
entry<FirmwareRoute.FirmwareGraph> { FirmwareScreen(onNavigateUp = { backStack.removeLastOrNull() }) }
entry<FirmwareRoute.FirmwareUpdate> { FirmwareScreen(onNavigateUp = { backStack.removeLastOrNull() }) }
entry<FirmwareRoute.FirmwareGraph> {
FirmwareScreen(onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() })
}
entry<FirmwareRoute.FirmwareUpdate> {
FirmwareScreen(onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() })
}
}
@Composable

View file

@ -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].

View file

@ -26,8 +26,8 @@ fun EntryProviderScope<NavKey>.mapGraph(backStack: NavBackStack<NavKey>) {
entry<MapRoute.Map> { 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,
)
}

View file

@ -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<NavKey>.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<NavKey>.contactsGraph(
ShareScreen(
viewModel = viewModel,
onConfirm = { contactKey -> backStack.replaceLast(ContactsRoute.Messages(contactKey, message)) },
onNavigateUp = { backStack.removeLastOrNull() },
onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() },
)
}
entry<ContactsRoute.QuickChat>(metadata = { ListDetailSceneStrategy.extraPane() }) {
val viewModel = koinViewModel<QuickChatViewModel>()
QuickChatScreen(viewModel = viewModel, onNavigateUp = { backStack.removeLastOrNull() })
QuickChatScreen(viewModel = viewModel, onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() })
}
}

View file

@ -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

View file

@ -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<NavKey>.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<NavKey>.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 <reified R : Route> EntryProviderScope<NavKey>.addNodeDetailS
val metricsViewModel = koinViewModel<MetricsViewModel> { parametersOf(destNum) }
metricsViewModel.setNodeId(destNum)
routeInfo.screenComposable(metricsViewModel) { backStack.removeLastOrNull() }
routeInfo.screenComposable(metricsViewModel, dropUnlessResumed { backStack.removeLastOrNull() })
}
}

View file

@ -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<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
entry<SettingsRoute.DeviceConfiguration> {
DeviceConfigurationScreen(
viewModel = getRadioConfigViewModel(backStack),
onBack = { backStack.removeLastOrNull() },
onBack = dropUnlessResumed { backStack.removeLastOrNull() },
onNavigate = { route -> backStack.add(route) },
)
}
@ -117,13 +118,16 @@ fun EntryProviderScope<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
ModuleConfigurationScreen(
viewModel = getRadioConfigViewModel(backStack),
excludedModulesUnlocked = excludedModulesUnlocked,
onBack = { backStack.removeLastOrNull() },
onBack = dropUnlessResumed { backStack.removeLastOrNull() },
onNavigate = { route -> backStack.add(route) },
)
}
entry<SettingsRoute.Administration> {
AdministrationScreen(viewModel = getRadioConfigViewModel(backStack), onBack = { backStack.removeLastOrNull() })
AdministrationScreen(
viewModel = getRadioConfigViewModel(backStack),
onBack = dropUnlessResumed { backStack.removeLastOrNull() },
)
}
entry<SettingsRoute.CleanNodeDb> {
@ -135,16 +139,26 @@ fun EntryProviderScope<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
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<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
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<SettingsRoute.DebugPanel> {
val viewModel: DebugViewModel = koinViewModel()
DebugScreen(viewModel = viewModel, onNavigateUp = { backStack.removeLastOrNull() })
DebugScreen(viewModel = viewModel, onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() })
}
entry<SettingsRoute.About> {
AboutScreen(onNavigateUp = { backStack.removeLastOrNull() }, jsonProvider = { getAboutLibrariesJson() })
AboutScreen(
onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() },
jsonProvider = { getAboutLibrariesJson() },
)
}
entry<SettingsRoute.FilterSettings> {
val viewModel: FilterSettingsViewModel = koinViewModel()
FilterSettingsScreen(viewModel = viewModel, onBack = { backStack.removeLastOrNull() })
FilterSettingsScreen(viewModel = viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() })
}
}

View file

@ -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<NavKey>.channelsGraph(backStack: NavBackStack<NavKey>) {
ChannelScreen(
radioConfigViewModel = koinViewModel<RadioConfigViewModel>(),
onNavigate = { route -> backStack.add(route) },
onNavigateUp = { backStack.removeLastOrNull() },
onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() },
)
}
@ -37,7 +38,7 @@ fun EntryProviderScope<NavKey>.channelsGraph(backStack: NavBackStack<NavKey>) {
ChannelScreen(
radioConfigViewModel = koinViewModel<RadioConfigViewModel>(),
onNavigate = { route -> backStack.add(route) },
onNavigateUp = { backStack.removeLastOrNull() },
onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() },
)
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}
// ---------------------------------------------------------------------------

View file

@ -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<NavKey>.wifiProvisionGraph(backStack: NavBackStack<NavKey>) {
entry<WifiProvisionRoute.WifiProvisionGraph> {
WifiProvisionScreen(onNavigateUp = { backStack.removeLastOrNull() })
WifiProvisionScreen(onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() })
}
entry<WifiProvisionRoute.WifiProvision> { key ->
WifiProvisionScreen(onNavigateUp = { backStack.removeLastOrNull() }, address = key.address)
WifiProvisionScreen(onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() }, address = key.address)
}
}