diff --git a/.skills/compose-ui/strings-index.txt b/.skills/compose-ui/strings-index.txt index 2e02059559..d749810954 100644 --- a/.skills/compose-ui/strings-index.txt +++ b/.skills/compose-ui/strings-index.txt @@ -1217,6 +1217,19 @@ override_console_serial_port override_duty_cycle override_frequency_mhz pa_fan_disabled +### PACKET ### +packet_authenticity +packet_authenticity_balanced +packet_authenticity_balanced_summary +packet_authenticity_compatible +packet_authenticity_compatible_summary +packet_authenticity_level +packet_authenticity_strict +packet_authenticity_strict_confirm +packet_authenticity_strict_confirmation +packet_authenticity_strict_summary +packet_authenticity_strict_title +packet_authenticity_unsupported pairing_mode password ### PAX ### diff --git a/core/resources/src/commonMain/composeResources/values/strings.xml b/core/resources/src/commonMain/composeResources/values/strings.xml index 252be871cb..62571fc790 100644 --- a/core/resources/src/commonMain/composeResources/values/strings.xml +++ b/core/resources/src/commonMain/composeResources/values/strings.xml @@ -1247,6 +1247,19 @@ Override Duty Cycle Frequency Override PA fan disabled + + Packet authenticity + Balanced — Prefer authenticated + Recommended. Reject unsigned downgrade attempts from nodes known to sign. + Compatible — Accept unsigned + Authenticate packets when possible, but accept unsigned traffic for maximum compatibility. + Protection level + Strict — Require authentication + Enable Strict + Strict ignores every remote mesh packet that is not cryptographically authenticated. Older firmware, licensed or ham nodes without PKI keys, and oversized packets may disappear. PKI-authenticated direct messages remain available. + Only show and process cryptographically authenticated mesh packets. Older nodes and oversized packets may disappear. + Enable Strict authentication? + This connected device does not support packet signature verification. Pairing mode Password @@ -1836,4 +1849,3 @@ 简体中文 繁體中文 - diff --git a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/DropDownPreference.kt b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/DropDownPreference.kt index 6cec427ce0..b518cdd77b 100644 --- a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/DropDownPreference.kt +++ b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/DropDownPreference.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.testTag import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.meshtastic.core.ui.theme.AppTheme @@ -89,6 +90,8 @@ data class DropDownItem( val color: Color? = null, /** When false, the item is shown greyed-out and cannot be selected. */ val enabled: Boolean = true, + /** Optional stable semantics tag for automated tests. */ + val testTag: String? = null, ) @JvmName("DropDownPreferencePairs") @@ -177,6 +180,7 @@ fun DropDownPreference( ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { items.forEach { selectionOption -> DropdownMenuItem( + modifier = selectionOption.testTag?.let { Modifier.testTag(it) } ?: Modifier, text = { Row(verticalAlignment = Alignment.CenterVertically) { if (selectionOption.icon != null) { diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/PacketAuthenticityPreviews.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/PacketAuthenticityPreviews.kt new file mode 100644 index 0000000000..578eee42be --- /dev/null +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/PacketAuthenticityPreviews.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +@file:Suppress("PreviewPublic") + +package org.meshtastic.feature.settings.radio.component + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import org.meshtastic.core.ui.theme.AppTheme +import org.meshtastic.proto.Config + +@PreviewLightDark +@Composable +fun PacketAuthenticityDefaultPreview() { + PacketAuthenticitySettingPreview(Config.SecurityConfig().packet_signature_policy) +} + +@PreviewLightDark +@Composable +fun PacketAuthenticityBalancedPreview() { + PacketAuthenticitySettingPreview(PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED) +} + +@PreviewLightDark +@Composable +fun PacketAuthenticityStrictPreview() { + PacketAuthenticitySettingPreview(PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT) +} + +@PreviewLightDark +@Composable +fun PacketAuthenticityUnsupportedPreview() { + AppTheme { + Surface(modifier = Modifier.padding(16.dp)) { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, + connected = true, + supported = false, + onPolicyChange = {}, + ) + } + } +} + +@PreviewLightDark +@Composable +fun PacketAuthenticityStrictConfirmationPreview() { + AppTheme { PacketAuthenticityStrictConfirmationDialog(show = true, onConfirm = {}, onDismiss = {}) } +} + +@Composable +private fun PacketAuthenticitySettingPreview(policy: PacketSignaturePolicy) { + AppTheme { + Surface(modifier = Modifier.padding(16.dp)) { + PacketAuthenticitySetting(selectedPolicy = policy, connected = true, supported = true, onPolicyChange = {}) + } + } +} diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/PacketAuthenticitySetting.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/PacketAuthenticitySetting.kt new file mode 100644 index 0000000000..0efe393eb2 --- /dev/null +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/PacketAuthenticitySetting.kt @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2026 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.meshtastic.feature.settings.radio.component + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.disabled +import androidx.compose.ui.semantics.semantics +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.stringResource +import org.meshtastic.core.resources.Res +import org.meshtastic.core.resources.packet_authenticity +import org.meshtastic.core.resources.packet_authenticity_balanced +import org.meshtastic.core.resources.packet_authenticity_balanced_summary +import org.meshtastic.core.resources.packet_authenticity_compatible +import org.meshtastic.core.resources.packet_authenticity_compatible_summary +import org.meshtastic.core.resources.packet_authenticity_level +import org.meshtastic.core.resources.packet_authenticity_strict +import org.meshtastic.core.resources.packet_authenticity_strict_confirm +import org.meshtastic.core.resources.packet_authenticity_strict_confirmation +import org.meshtastic.core.resources.packet_authenticity_strict_summary +import org.meshtastic.core.resources.packet_authenticity_strict_title +import org.meshtastic.core.resources.packet_authenticity_unsupported +import org.meshtastic.core.ui.component.DropDownItem +import org.meshtastic.core.ui.component.DropDownPreference +import org.meshtastic.core.ui.component.MeshtasticResourceDialog +import org.meshtastic.core.ui.component.TitledCard +import org.meshtastic.proto.Config + +internal typealias PacketSignaturePolicy = Config.SecurityConfig.PacketSignaturePolicy + +internal const val PACKET_AUTHENTICITY_SELECTOR_TEST_TAG = "packet_authenticity_selector" +internal const val PACKET_AUTHENTICITY_STRICT_POLICY_TEST_TAG = "packet_authenticity_policy_strict" + +@Composable +internal fun PacketAuthenticitySetting( + selectedPolicy: PacketSignaturePolicy, + connected: Boolean, + supported: Boolean?, + onPolicyChange: (PacketSignaturePolicy) -> Unit, +) { + var showStrictConfirmation by rememberSaveable { mutableStateOf(false) } + val canConfigurePolicy = connected && supported == true + + LaunchedEffect(canConfigurePolicy) { + if (!canConfigurePolicy) { + showStrictConfirmation = false + } + } + + PacketAuthenticityStrictConfirmationDialog( + show = showStrictConfirmation, + onConfirm = { + showStrictConfirmation = false + if (canConfigurePolicy) { + onPolicyChange(PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT) + } + }, + onDismiss = { showStrictConfirmation = false }, + ) + + val items = + packetSignaturePolicies.map { policy -> + DropDownItem(value = policy, label = stringResource(policy.labelResource()), testTag = policy.testTag()) + } + val summaryResource = + if (supported == false) { + Res.string.packet_authenticity_unsupported + } else { + selectedPolicy.summaryResource() + } + + TitledCard(title = stringResource(Res.string.packet_authenticity)) { + DropDownPreference( + title = stringResource(Res.string.packet_authenticity_level), + enabled = canConfigurePolicy, + items = items, + selectedItem = selectedPolicy, + modifier = + Modifier.testTag(PACKET_AUTHENTICITY_SELECTOR_TEST_TAG).semantics { + if (!canConfigurePolicy) disabled() + }, + summary = stringResource(summaryResource), + onItemSelected = { policy -> + if ( + policy == PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT && + selectedPolicy != PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT + ) { + showStrictConfirmation = true + } else { + onPolicyChange(policy) + } + }, + ) + } +} + +@Composable +internal fun PacketAuthenticityStrictConfirmationDialog(show: Boolean, onConfirm: () -> Unit, onDismiss: () -> Unit) { + if (show) { + MeshtasticResourceDialog( + titleRes = Res.string.packet_authenticity_strict_title, + messageRes = Res.string.packet_authenticity_strict_confirmation, + confirmTextRes = Res.string.packet_authenticity_strict_confirm, + onConfirm = onConfirm, + onDismiss = onDismiss, + ) + } +} + +private val packetSignaturePolicies = + listOf( + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_COMPATIBLE, + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT, + ) + +private fun PacketSignaturePolicy.labelResource(): StringResource = when (this) { + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_COMPATIBLE -> Res.string.packet_authenticity_compatible + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED -> Res.string.packet_authenticity_balanced + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT -> Res.string.packet_authenticity_strict +} + +private fun PacketSignaturePolicy.summaryResource(): StringResource = when (this) { + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_COMPATIBLE -> Res.string.packet_authenticity_compatible_summary + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED -> Res.string.packet_authenticity_balanced_summary + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT -> Res.string.packet_authenticity_strict_summary +} + +private fun PacketSignaturePolicy.testTag(): String? = when (this) { + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT -> PACKET_AUTHENTICITY_STRICT_POLICY_TEST_TAG + else -> null +} diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigScreen.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigScreen.kt index fe75ec7cec..2e2731ac4e 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigScreen.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigScreen.kt @@ -119,6 +119,14 @@ fun SecurityConfigScreenCommon(viewModel: RadioConfigViewModel, onBack: () -> Un viewModel.setConfig(config) }, ) { + item { + PacketAuthenticitySetting( + selectedPolicy = formState.value.packet_signature_policy, + connected = state.connected, + supported = state.metadata?.has_xeddsa, + onPolicyChange = { policy -> formState.value = formState.value.copy(packet_signature_policy = policy) }, + ) + } item { TitledCard(title = stringResource(Res.string.direct_message_key)) { EditBase64Preference( diff --git a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/PacketAuthenticitySettingTest.kt b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/PacketAuthenticitySettingTest.kt new file mode 100644 index 0000000000..d16f9e3d48 --- /dev/null +++ b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/component/PacketAuthenticitySettingTest.kt @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2026 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.meshtastic.feature.settings.radio.component + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +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 +import org.meshtastic.core.resources.packet_authenticity_balanced +import org.meshtastic.core.resources.packet_authenticity_balanced_summary +import org.meshtastic.core.resources.packet_authenticity_compatible +import org.meshtastic.core.resources.packet_authenticity_strict +import org.meshtastic.core.resources.packet_authenticity_strict_confirm +import org.meshtastic.core.resources.packet_authenticity_strict_summary +import org.meshtastic.core.resources.packet_authenticity_unsupported +import org.meshtastic.core.ui.theme.AppTheme +import org.meshtastic.proto.Config +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +@OptIn(ExperimentalTestApi::class) +class PacketAuthenticitySettingTest { + @Test + fun `protobuf default is compatible`() { + assertEquals( + PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_COMPATIBLE, + Config.SecurityConfig().packet_signature_policy, + ) + } + + @Test + fun `balanced selection updates the device config field`() = runComposeUiTest { + var updatedConfig = Config.SecurityConfig() + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = updatedConfig.packet_signature_policy, + connected = true, + supported = true, + onPolicyChange = { policy -> updatedConfig = updatedConfig.copy(packet_signature_policy = policy) }, + ) + } + } + + onNodeWithText(getString(Res.string.packet_authenticity_compatible)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_balanced)).performClick() + + assertEquals(PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, updatedConfig.packet_signature_policy) + } + + @Test + fun `strict selection requires confirmation`() = runComposeUiTest { + var selectedPolicy: PacketSignaturePolicy? = null + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, + connected = true, + supported = true, + onPolicyChange = { selectedPolicy = it }, + ) + } + } + + onNodeWithText(getString(Res.string.packet_authenticity_balanced)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_strict)).performClick() + + assertNull(selectedPolicy) + onNodeWithText(getString(Res.string.packet_authenticity_strict_confirm)).assertIsDisplayed() + onNodeWithText(getString(Res.string.cancel)).performClick() + assertNull(selectedPolicy) + onNodeWithText(getString(Res.string.packet_authenticity_strict_confirm)).assertDoesNotExist() + } + + @Test + fun `confirmed strict selection updates policy`() = runComposeUiTest { + var selectedPolicy: PacketSignaturePolicy? = null + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, + connected = true, + supported = true, + onPolicyChange = { selectedPolicy = it }, + ) + } + } + + onNodeWithText(getString(Res.string.packet_authenticity_balanced)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_strict)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_strict_confirm)).performClick() + + assertEquals(PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT, selectedPolicy) + } + + @Test + fun `support loss dismisses strict confirmation without changing policy`() = runComposeUiTest { + var supported by mutableStateOf(true) + var selectedPolicy: PacketSignaturePolicy? = null + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, + connected = true, + supported = supported, + onPolicyChange = { selectedPolicy = it }, + ) + } + } + + onNodeWithText(getString(Res.string.packet_authenticity_balanced)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_strict)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_strict_confirm)).assertIsDisplayed() + + supported = false + waitForIdle() + + onNodeWithText(getString(Res.string.packet_authenticity_strict_confirm)).assertDoesNotExist() + assertNull(selectedPolicy) + } + + @Test + fun `connection loss dismisses strict confirmation without changing policy`() = runComposeUiTest { + var connected by mutableStateOf(true) + var selectedPolicy: PacketSignaturePolicy? = null + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, + connected = connected, + supported = true, + onPolicyChange = { selectedPolicy = it }, + ) + } + } + + onNodeWithText(getString(Res.string.packet_authenticity_balanced)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_strict)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_strict_confirm)).assertIsDisplayed() + + connected = false + waitForIdle() + + onNodeWithText(getString(Res.string.packet_authenticity_strict_confirm)).assertDoesNotExist() + assertNull(selectedPolicy) + } + + @Test + fun `selecting current strict policy does not prompt again`() = runComposeUiTest { + var selectedPolicy: PacketSignaturePolicy? = null + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT, + connected = true, + supported = true, + onPolicyChange = { selectedPolicy = it }, + ) + } + } + + val strictLabel = getString(Res.string.packet_authenticity_strict) + onNodeWithText(strictLabel).performClick() + onNodeWithTag(PACKET_AUTHENTICITY_STRICT_POLICY_TEST_TAG).performClick() + + onNodeWithText(getString(Res.string.packet_authenticity_strict_confirm)).assertDoesNotExist() + assertEquals(PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT, selectedPolicy) + } + + @Test + fun `unsupported firmware cannot open selector`() = runComposeUiTest { + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, + connected = true, + supported = false, + onPolicyChange = {}, + ) + } + } + + onNodeWithText(getString(Res.string.packet_authenticity_unsupported)).assertIsDisplayed() + onNodeWithTag(PACKET_AUTHENTICITY_SELECTOR_TEST_TAG).assertIsNotEnabled() + onNodeWithText(getString(Res.string.packet_authenticity_balanced)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_strict)).assertDoesNotExist() + } + + @Test + fun `unknown capability keeps current policy summary while disabled`() = runComposeUiTest { + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, + connected = true, + supported = null, + onPolicyChange = {}, + ) + } + } + + onNodeWithText(getString(Res.string.packet_authenticity_balanced_summary)).assertIsDisplayed() + onNodeWithText(getString(Res.string.packet_authenticity_unsupported)).assertDoesNotExist() + onNodeWithTag(PACKET_AUTHENTICITY_SELECTOR_TEST_TAG).assertIsNotEnabled() + } + + @Test + fun `disconnected supported device cannot open selector`() = runComposeUiTest { + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_BALANCED, + connected = false, + supported = true, + onPolicyChange = {}, + ) + } + } + + onNodeWithTag(PACKET_AUTHENTICITY_SELECTOR_TEST_TAG).assertIsNotEnabled() + onNodeWithText(getString(Res.string.packet_authenticity_balanced)).performClick() + onNodeWithText(getString(Res.string.packet_authenticity_strict)).assertDoesNotExist() + } + + @Test + fun `strict summary covers all authenticated mesh packets`() = runComposeUiTest { + setContent { + AppTheme { + PacketAuthenticitySetting( + selectedPolicy = PacketSignaturePolicy.PACKET_SIGNATURE_POLICY_STRICT, + connected = true, + supported = true, + onPolicyChange = {}, + ) + } + } + + onNodeWithText(getString(Res.string.packet_authenticity_strict_summary)).assertIsDisplayed() + } +} diff --git a/screenshot-tests/src/screenshotTest/kotlin/org/meshtastic/screenshots/feature/SettingsScreenshotTests.kt b/screenshot-tests/src/screenshotTest/kotlin/org/meshtastic/screenshots/feature/SettingsScreenshotTests.kt index 35721e061d..efef7740fe 100644 --- a/screenshot-tests/src/screenshotTest/kotlin/org/meshtastic/screenshots/feature/SettingsScreenshotTests.kt +++ b/screenshot-tests/src/screenshotTest/kotlin/org/meshtastic/screenshots/feature/SettingsScreenshotTests.kt @@ -36,6 +36,11 @@ import org.meshtastic.feature.settings.component.SampleNodeCompleteFahrenheitPre import org.meshtastic.feature.settings.component.SampleNodeCompleteImperialPreview import org.meshtastic.feature.settings.component.SampleNodeCompletePreview import org.meshtastic.feature.settings.component.SampleNodeCompleteToggleMatrixPreview +import org.meshtastic.feature.settings.radio.component.PacketAuthenticityBalancedPreview +import org.meshtastic.feature.settings.radio.component.PacketAuthenticityDefaultPreview +import org.meshtastic.feature.settings.radio.component.PacketAuthenticityStrictConfirmationPreview +import org.meshtastic.feature.settings.radio.component.PacketAuthenticityStrictPreview +import org.meshtastic.feature.settings.radio.component.PacketAuthenticityUnsupportedPreview import org.meshtastic.feature.settings.radio.component.TakConfigCardPreview import org.meshtastic.feature.settings.radio.component.TakServerSectionDisabledPreview import org.meshtastic.feature.settings.radio.component.TakServerSectionEnabledPreview @@ -113,6 +118,41 @@ fun ScreenshotTakTestCardResults() { TakTestCardResultsPreview() } +@PreviewTest +@PreviewLightDark +@Composable +fun ScreenshotPacketAuthenticityDefault() { + PacketAuthenticityDefaultPreview() +} + +@PreviewTest +@PreviewLightDark +@Composable +fun ScreenshotPacketAuthenticityBalanced() { + PacketAuthenticityBalancedPreview() +} + +@PreviewTest +@PreviewLightDark +@Composable +fun ScreenshotPacketAuthenticityStrict() { + PacketAuthenticityStrictPreview() +} + +@PreviewTest +@PreviewLightDark +@Composable +fun ScreenshotPacketAuthenticityUnsupported() { + PacketAuthenticityUnsupportedPreview() +} + +@PreviewTest +@PreviewLightDark +@Composable +fun ScreenshotPacketAuthenticityStrictConfirmation() { + PacketAuthenticityStrictConfirmationPreview() +} + // --------------------------------------------------------------------------- // Node layout settings screenshots // --------------------------------------------------------------------------- diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityBalanced_Dark_d19fbf1f_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityBalanced_Dark_d19fbf1f_0.png new file mode 100644 index 0000000000..02881d2550 Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityBalanced_Dark_d19fbf1f_0.png differ diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityBalanced_Light_b29dc7a7_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityBalanced_Light_b29dc7a7_0.png new file mode 100644 index 0000000000..73d6fcec99 Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityBalanced_Light_b29dc7a7_0.png differ diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityDefault_Dark_d19fbf1f_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityDefault_Dark_d19fbf1f_0.png new file mode 100644 index 0000000000..92c9412c83 Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityDefault_Dark_d19fbf1f_0.png differ diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityDefault_Light_b29dc7a7_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityDefault_Light_b29dc7a7_0.png new file mode 100644 index 0000000000..9de9164ed3 Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityDefault_Light_b29dc7a7_0.png differ diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrictConfirmation_Dark_d19fbf1f_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrictConfirmation_Dark_d19fbf1f_0.png new file mode 100644 index 0000000000..1924b18c00 Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrictConfirmation_Dark_d19fbf1f_0.png differ diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrictConfirmation_Light_b29dc7a7_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrictConfirmation_Light_b29dc7a7_0.png new file mode 100644 index 0000000000..596c5c6663 Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrictConfirmation_Light_b29dc7a7_0.png differ diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrict_Dark_d19fbf1f_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrict_Dark_d19fbf1f_0.png new file mode 100644 index 0000000000..3122b9bd92 Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrict_Dark_d19fbf1f_0.png differ diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrict_Light_b29dc7a7_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrict_Light_b29dc7a7_0.png new file mode 100644 index 0000000000..071f8380ed Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityStrict_Light_b29dc7a7_0.png differ diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityUnsupported_Dark_d19fbf1f_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityUnsupported_Dark_d19fbf1f_0.png new file mode 100644 index 0000000000..2c3d0c088f Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityUnsupported_Dark_d19fbf1f_0.png differ diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityUnsupported_Light_b29dc7a7_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityUnsupported_Light_b29dc7a7_0.png new file mode 100644 index 0000000000..a0199d7ef9 Binary files /dev/null and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotPacketAuthenticityUnsupported_Light_b29dc7a7_0.png differ