diff --git a/core/domain/src/commonTest/kotlin/org/meshtastic/core/domain/usecase/settings/ProcessRadioResponseUseCaseTest.kt b/core/domain/src/commonTest/kotlin/org/meshtastic/core/domain/usecase/settings/ProcessRadioResponseUseCaseTest.kt index 4bc54ac08..b8fcf6a20 100644 --- a/core/domain/src/commonTest/kotlin/org/meshtastic/core/domain/usecase/settings/ProcessRadioResponseUseCaseTest.kt +++ b/core/domain/src/commonTest/kotlin/org/meshtastic/core/domain/usecase/settings/ProcessRadioResponseUseCaseTest.kt @@ -77,7 +77,7 @@ class ProcessRadioResponseUseCaseTest { // Assert assertTrue(result is RadioResponseResult.Metadata) - assertEquals("2.5.0", (result as RadioResponseResult.Metadata).metadata.firmware_version) + assertEquals("2.5.0", result.metadata.firmware_version) } @Test @@ -99,7 +99,7 @@ class ProcessRadioResponseUseCaseTest { // Assert assertTrue(result is RadioResponseResult.CannedMessages) - assertEquals("Hello World", (result as RadioResponseResult.CannedMessages).messages) + assertEquals("Hello World", result.messages) } @Test @@ -133,7 +133,7 @@ class ProcessRadioResponseUseCaseTest { ) val result = useCase(packet, 123, setOf(42)) assertTrue(result is RadioResponseResult.Owner) - assertEquals("Owner", (result as RadioResponseResult.Owner).user.long_name) + assertEquals("Owner", result.user.long_name) } @Test @@ -186,7 +186,7 @@ class ProcessRadioResponseUseCaseTest { ) val result = useCase(packet, 123, setOf(42)) assertTrue(result is RadioResponseResult.ChannelResponse) - assertEquals("Main", (result as RadioResponseResult.ChannelResponse).channel.settings?.name) + assertEquals("Main", result.channel.settings?.name) } private fun ByteArray.toByteString() = okio.ByteString.of(*this) diff --git a/core/resources/src/commonMain/composeResources/drawable/img_mpwrd_logo.png b/core/resources/src/commonMain/composeResources/drawable/img_mpwrd_logo.png new file mode 100644 index 000000000..224c5add3 Binary files /dev/null and b/core/resources/src/commonMain/composeResources/drawable/img_mpwrd_logo.png differ diff --git a/core/resources/src/commonMain/composeResources/values/strings.xml b/core/resources/src/commonMain/composeResources/values/strings.xml index 692ddcb37..9b8c6d7aa 100644 --- a/core/resources/src/commonMain/composeResources/values/strings.xml +++ b/core/resources/src/commonMain/composeResources/values/strings.xml @@ -940,7 +940,7 @@ PAX Metrics PAX No PAX metrics available. - WiFi Devices + Wi-Fi Provisioning for mPWRD-OS Bluetooth Devices Paired devices Connected Device @@ -1327,8 +1327,9 @@ Connect Done - WiFi Provisioning - Provision WiFi credentials to your Meshtastic device via Bluetooth. + Wi-Fi Provisioning for mPWRD-OS + Provision Wi-Fi credentials to your mPWRD-OS device via Bluetooth. + Learn more about the mPWRD-OS project\nhttps://github.com/mPWRD-OS Searching for device… Device found Ready to scan for WiFi networks. diff --git a/core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/CoTXmlTest.kt b/core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/CoTXmlTest.kt index 2c7669319..7b6aa0ecd 100644 --- a/core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/CoTXmlTest.kt +++ b/core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/CoTXmlTest.kt @@ -81,8 +81,8 @@ class CoTXmlTest { assertEquals("b-t-f", roundTripped.type) assertNotNull(roundTripped.chat) - assertEquals("Hello World", roundTripped.chat?.message) - assertEquals("Alice", roundTripped.chat?.senderCallsign) + assertEquals("Hello World", roundTripped.chat.message) + assertEquals("Alice", roundTripped.chat.senderCallsign) } // ── XML escaping ───────────────────────────────────────────────────────── diff --git a/core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKPacketConversionTest.kt b/core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKPacketConversionTest.kt index 9bab59c03..771f10cfe 100644 --- a/core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKPacketConversionTest.kt +++ b/core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKPacketConversionTest.kt @@ -92,8 +92,8 @@ class TAKPacketConversionTest { assertEquals(85, cot.status?.battery) assertNotNull(cot.track) - assertEquals(5.0, cot.track?.speed) - assertEquals(90.0, cot.track?.course) + assertEquals(5.0, cot.track.speed) + assertEquals(90.0, cot.track.course) } @Test diff --git a/feature/wifi-provision/README.md b/feature/wifi-provision/README.md index 4e61464a0..1403c6d79 100644 --- a/feature/wifi-provision/README.md +++ b/feature/wifi-provision/README.md @@ -23,9 +23,9 @@ classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000; ``` -## WiFi Provisioning System +## WiFi Provisioning System — for mPWRD-OS -The `:feature:wifi-provision` module provides BLE-based WiFi provisioning for Meshtastic devices using the Nymea network manager protocol. It scans for provisioning-capable devices, retrieves available WiFi networks, and applies credentials — all over BLE via the Kable multiplatform library. +The `:feature:wifi-provision` module provides BLE-based WiFi provisioning for [mPWRD-OS](https://github.com/mPWRD-OS/mPWRD-OS) devices using the Nymea network manager protocol. mPWRD-OS is a community project that combines Armbian and Meshtastic for Linux-native mesh networking hardware. This module scans for provisioning-capable devices, retrieves available WiFi networks, and applies credentials — all over BLE via the Kable multiplatform library. ### Architecture diff --git a/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/ui/WifiProvisionPreviews.kt b/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/ui/WifiProvisionPreviews.kt index 0bb2100aa..dc9f62f8d 100644 --- a/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/ui/WifiProvisionPreviews.kt +++ b/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/ui/WifiProvisionPreviews.kt @@ -346,3 +346,13 @@ private fun NetworkRowLongSsidPreview() { } } } + +// --------------------------------------------------------------------------- +// mPWRD-OS disclaimer banner +// --------------------------------------------------------------------------- + +@PreviewLightDark +@Composable +private fun MpwrdDisclaimerBannerPreview() { + AppTheme { Surface { Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { MpwrdDisclaimerBanner() } } } +} diff --git a/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/ui/WifiProvisionScreen.kt b/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/ui/WifiProvisionScreen.kt index 6f9c9dc68..ced6d212c 100644 --- a/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/ui/WifiProvisionScreen.kt +++ b/feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/ui/WifiProvisionScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -38,6 +39,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll @@ -77,6 +79,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.text.input.ImeAction @@ -86,6 +89,7 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel import org.meshtastic.core.resources.Res @@ -93,6 +97,7 @@ import org.meshtastic.core.resources.apply import org.meshtastic.core.resources.back import org.meshtastic.core.resources.cancel import org.meshtastic.core.resources.hide_password +import org.meshtastic.core.resources.img_mpwrd_logo import org.meshtastic.core.resources.password import org.meshtastic.core.resources.show_password import org.meshtastic.core.resources.wifi_provision_available_networks @@ -100,6 +105,7 @@ import org.meshtastic.core.resources.wifi_provision_connect_failed import org.meshtastic.core.resources.wifi_provision_description import org.meshtastic.core.resources.wifi_provision_device_found import org.meshtastic.core.resources.wifi_provision_device_found_detail +import org.meshtastic.core.resources.wifi_provision_mpwrd_disclaimer import org.meshtastic.core.resources.wifi_provision_no_networks import org.meshtastic.core.resources.wifi_provision_scan_failed import org.meshtastic.core.resources.wifi_provision_scan_networks @@ -110,6 +116,7 @@ import org.meshtastic.core.resources.wifi_provision_signal_strength import org.meshtastic.core.resources.wifi_provision_ssid_label import org.meshtastic.core.resources.wifi_provision_ssid_placeholder import org.meshtastic.core.resources.wifi_provisioning +import org.meshtastic.core.ui.component.AutoLinkText import org.meshtastic.feature.wifiprovision.WifiProvisionError import org.meshtastic.feature.wifiprovision.WifiProvisionUiState import org.meshtastic.feature.wifiprovision.WifiProvisionUiState.Phase @@ -164,6 +171,8 @@ fun WifiProvisionScreen( Spacer(Modifier.height(4.dp)) } + MpwrdDisclaimerBanner() + Crossfade(targetState = screenKey(uiState), label = "wifi_provision") { key -> when (key) { ScreenKey.ConnectingBle -> ScanningBleContent() @@ -481,6 +490,40 @@ internal fun NetworkRow(network: WifiNetwork, isSelected: Boolean, onClick: () - ) } +// --------------------------------------------------------------------------- +// mPWRD-OS disclaimer banner +// --------------------------------------------------------------------------- + +private const val MPWRD_LOGO_SIZE_DP = 40 + +/** Branded disclaimer banner shown at the top of the provisioning screen. */ +@Composable +internal fun MpwrdDisclaimerBanner() { + Card( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp), + shape = MaterialTheme.shapes.medium, + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh), + ) { + Row( + modifier = Modifier.padding(12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.Top, + ) { + Image( + painter = painterResource(Res.drawable.img_mpwrd_logo), + contentDescription = "mPWRD-OS", + modifier = Modifier.size(MPWRD_LOGO_SIZE_DP.dp).clip(RoundedCornerShape(8.dp)), + ) + AutoLinkText( + text = stringResource(Res.string.wifi_provision_mpwrd_disclaimer), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + ) + } + } +} + // --------------------------------------------------------------------------- // Shared layout wrapper for centered status screens // ---------------------------------------------------------------------------