diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/Capabilities.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/Capabilities.kt index 5e129b863d..147c81a34b 100644 --- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/Capabilities.kt +++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/Capabilities.kt @@ -17,6 +17,7 @@ package org.meshtastic.core.model import org.meshtastic.core.model.util.isDebug +import org.meshtastic.proto.FieldMetadata /** * Defines the capabilities and feature support based on the device firmware version. @@ -31,6 +32,22 @@ data class Capabilities(val firmwareVersion: String?, internal val forceEnableAl private fun atLeast(min: DeviceVersion): Boolean = forceEnableAll || (version != null && version >= min) + /** + * Whether a config field is worth offering on this firmware, from the version gates its schema declares. Below + * `since_firmware` the node ignores the field. At or above `deprecated_since` it is shown only while [isSet], so a + * value the node still holds stays visible instead of being silently kept. + */ + fun offers(field: FieldMetadata, isSet: Boolean = false): Boolean { + val arrived = field.since_firmware?.let(::gate)?.let(::atLeast) ?: true + val retired = + field.deprecated_since?.let(::gate)?.let { !forceEnableAll && version != null && version >= it } ?: false + return arrived && (!retired || isSet) + } + + // The schema declares these; an unparseable one must fail here rather than silently pass every gate. + private fun gate(declared: String): DeviceVersion = + DeviceVersion(declared).also { require(it.isValid) { "Unparseable firmware version in schema: $declared" } } + /** Ability to mute notifications from specific nodes via admin messages. */ val canMuteNode = atLeast(V2_7_18) @@ -40,17 +57,14 @@ data class Capabilities(val firmwareVersion: String?, internal val forceEnableAl /** Ability to send verified shared contacts. Supported since firmware v2.7.12. */ val canSendVerifiedContacts = atLeast(V2_7_12) - /** Ability to toggle device telemetry globally via module config. Supported since firmware v2.7.12. */ - val canToggleTelemetryEnabled = atLeast(V2_7_12) - /** Ability to toggle the 'is_unmessageable' flag in user config. Supported since firmware v2.6.9. */ val canToggleUnmessageable = atLeast(V2_6_9) /** Support for sharing contact information via QR codes. Supported since firmware v2.6.8. */ val supportsQrCodeSharing = atLeast(V2_6_8) - /** Support for Status Message module. Supported since firmware v2.8.0. */ - val supportsStatusMessage = atLeast(V2_8_0) + /** Support for Status Message module. Supported since firmware v2.7.20. */ + val supportsStatusMessage = atLeast(V2_7_20) /** * Support for TAK (ATAK) module configuration. Gated to firmware v2.8.0. @@ -125,6 +139,7 @@ data class Capabilities(val firmwareVersion: String?, internal val forceEnableAl private val V2_6_10 = DeviceVersion("2.6.10") private val V2_7_12 = DeviceVersion("2.7.12") private val V2_7_18 = DeviceVersion("2.7.18") + private val V2_7_20 = DeviceVersion("2.7.20") private val V2_8_0 = DeviceVersion("2.8.0") private val UNRELEASED = DeviceVersion("9.9.9") } diff --git a/core/model/src/commonTest/kotlin/org/meshtastic/core/model/CapabilitiesTest.kt b/core/model/src/commonTest/kotlin/org/meshtastic/core/model/CapabilitiesTest.kt index b57dd8edeb..1ed982a00d 100644 --- a/core/model/src/commonTest/kotlin/org/meshtastic/core/model/CapabilitiesTest.kt +++ b/core/model/src/commonTest/kotlin/org/meshtastic/core/model/CapabilitiesTest.kt @@ -16,8 +16,10 @@ */ package org.meshtastic.core.model +import org.meshtastic.proto.FieldMetadata import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -50,12 +52,6 @@ class CapabilitiesTest { assertTrue(caps("2.7.12").canSendVerifiedContacts) } - @Test - fun canToggleTelemetryEnabled_requires_V2_7_12() { - assertFalse(caps("2.7.11").canToggleTelemetryEnabled) - assertTrue(caps("2.7.12").canToggleTelemetryEnabled) - } - @Test fun canToggleUnmessageable_requires_V2_6_9() { assertFalse(caps("2.6.8").canToggleUnmessageable) @@ -75,9 +71,47 @@ class CapabilitiesTest { } @Test - fun supportsStatusMessage_requires_V2_8_0() { - assertFalse(caps("2.7.21").supportsStatusMessage) - assertTrue(caps("2.8.0").supportsStatusMessage) + fun supportsStatusMessage_requires_V2_7_20() { + assertFalse(caps("2.7.19").supportsStatusMessage) + assertTrue(caps("2.7.20").supportsStatusMessage) + } + + @Test + fun offers_hides_a_field_below_since_firmware() { + val field = FieldMetadata.Builder().since_firmware("2.7.13").build() + assertFalse(caps("2.7.12").offers(field)) + assertTrue(caps("2.7.13").offers(field)) + assertFalse(caps(null).offers(field)) + } + + @Test + fun offers_hides_a_deprecated_field_at_deprecated_since_unless_set() { + val field = FieldMetadata.Builder().deprecated_since("2.8.0").build() + assertTrue(caps("2.7.26").offers(field)) + assertFalse(caps("2.8.0").offers(field)) + assertTrue(caps("2.8.0").offers(field, isSet = true)) + // Unknown firmware cannot be shown to have retired the field. + assertTrue(caps(null).offers(field)) + } + + @Test + fun offers_an_unannotated_field_everywhere() { + val field = FieldMetadata.Builder().build() + assertTrue(caps(null).offers(field)) + assertTrue(caps("2.3.15").offers(field)) + } + + @Test + fun offers_everything_when_forceEnableAll() { + val c = Capabilities(firmwareVersion = null, forceEnableAll = true) + assertTrue(c.offers(FieldMetadata.Builder().since_firmware("9.9.9").build())) + assertTrue(c.offers(FieldMetadata.Builder().deprecated_since("1.0.0").build())) + } + + @Test + fun offers_rejects_an_unparseable_schema_version() { + val field = FieldMetadata.Builder().since_firmware("soon").build() + assertFailsWith { caps("2.8.0").offers(field) } } @Test @@ -164,7 +198,6 @@ class CapabilitiesTest { assertFalse(c.canMuteNode) assertFalse(c.canRequestNeighborInfo) assertFalse(c.canSendVerifiedContacts) - assertFalse(c.canToggleTelemetryEnabled) assertFalse(c.canToggleUnmessageable) assertFalse(c.supportsQrCodeSharing) assertFalse(c.supportsSecondaryChannelLocation) diff --git a/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/list/StatusMessageActionGateTest.kt b/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/list/StatusMessageActionGateTest.kt index a3ce85c8ab..5090fa85f0 100644 --- a/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/list/StatusMessageActionGateTest.kt +++ b/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/list/StatusMessageActionGateTest.kt @@ -50,7 +50,7 @@ class StatusMessageActionGateTest { @Test fun `older firmware does not offer the action`() { - val old = node(num = 1, firmware = "2.7.21") + val old = node(num = 1, firmware = "2.7.19") assertFalse(canEditStatusMessage(old, old, ConnectionState.Connected)) } diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/CannedMessageConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/CannedMessageConfigItemList.kt index ca7661f264..61427873b5 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/CannedMessageConfigItemList.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/CannedMessageConfigItemList.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalFocusManager @@ -30,6 +31,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.jetbrains.compose.resources.stringResource +import org.meshtastic.core.model.Capabilities import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.allow_input_source import org.meshtastic.core.resources.canned_message @@ -52,11 +54,14 @@ import org.meshtastic.core.ui.component.TitledCard import org.meshtastic.feature.settings.radio.RadioConfigViewModel import org.meshtastic.feature.settings.radio.RebootBehavior import org.meshtastic.proto.ModuleConfig +import org.meshtastic.proto.allow_input_source @Suppress("DEPRECATION", "LongMethod") @Composable fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { val state by viewModel.radioConfigState.collectAsStateWithLifecycle() + val firmwareVersion = state.metadata?.firmware_version + val capabilities = remember(firmwareVersion) { Capabilities(firmwareVersion) } val cannedMessageConfig = state.moduleConfig.canned_message ?: ModuleConfig.CannedMessageConfig.Builder().build() val messages = state.cannedMessageMessages val formState = rememberConfigState(initialValue = cannedMessageConfig) @@ -176,19 +181,27 @@ fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Uni containerColor = CardDefaults.cardColors().containerColor, ) HorizontalDivider() - EditTextPreference( - title = stringResource(Res.string.allow_input_source), - value = formState.value.allow_input_source, - maxSize = 63, // allow_input_source max_size:16 - enabled = state.connected, - isError = false, - keyboardOptions = - KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - formState.value = formState.value.newBuilder().also { wb -> wb.allow_input_source = it }.build() - }, - ) + if ( + capabilities.offers( + ModuleConfig.CannedMessageConfig.allow_input_source, + isSet = formState.value.allow_input_source.isNotEmpty(), + ) + ) { + EditTextPreference( + title = stringResource(Res.string.allow_input_source), + value = formState.value.allow_input_source, + maxSize = 63, // allow_input_source max_size:16 + enabled = state.connected, + isError = false, + keyboardOptions = + KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + formState.value = + formState.value.newBuilder().also { wb -> wb.allow_input_source = it }.build() + }, + ) + } SwitchPreference( title = stringResource(Res.string.send_bell), checked = formState.value.send_bell, diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/DisplayConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/DisplayConfigItemList.kt index f4b76e63e3..5ed3ea4438 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/DisplayConfigItemList.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/DisplayConfigItemList.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.jetbrains.compose.resources.stringResource +import org.meshtastic.core.model.Capabilities import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.advanced import org.meshtastic.core.resources.always_point_north @@ -55,11 +56,15 @@ import org.meshtastic.feature.settings.radio.RadioConfigViewModel import org.meshtastic.feature.settings.util.IntervalConfiguration import org.meshtastic.feature.settings.util.toDisplayString import org.meshtastic.proto.Config +import org.meshtastic.proto.compass_north_top +import org.meshtastic.proto.use_12h_clock @Suppress("DEPRECATION", "LongMethod") @Composable fun DisplayConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { val state by viewModel.radioConfigState.collectAsStateWithLifecycle() + val firmwareVersion = state.metadata?.firmware_version + val capabilities = remember(firmwareVersion) { Capabilities(firmwareVersion) } val displayConfig = state.radioConfig.display ?: Config.DisplayConfig.Builder().build() val formState = rememberConfigState(initialValue = displayConfig) @@ -77,28 +82,38 @@ fun DisplayConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { ) { item { TitledCard(title = stringResource(Res.string.display_config)) { - SwitchPreference( - title = stringResource(Res.string.always_point_north), - summary = stringResource(Res.string.config_display_compass_north_top_summary), - checked = formState.value.compass_north_top, - enabled = state.connected, - onCheckedChange = { - formState.value = formState.value.newBuilder().also { wb -> wb.compass_north_top = it }.build() - }, - containerColor = CardDefaults.cardColors().containerColor, - ) - HorizontalDivider() - SwitchPreference( - title = stringResource(Res.string.use_12h_format), - summary = stringResource(Res.string.display_time_in_12h_format), - enabled = state.connected, - checked = formState.value.use_12h_clock, - onCheckedChange = { - formState.value = formState.value.newBuilder().also { wb -> wb.use_12h_clock = it }.build() - }, - containerColor = CardDefaults.cardColors().containerColor, - ) - HorizontalDivider() + if ( + capabilities.offers( + Config.DisplayConfig.compass_north_top, + isSet = formState.value.compass_north_top, + ) + ) { + SwitchPreference( + title = stringResource(Res.string.always_point_north), + summary = stringResource(Res.string.config_display_compass_north_top_summary), + checked = formState.value.compass_north_top, + enabled = state.connected, + onCheckedChange = { + formState.value = + formState.value.newBuilder().also { wb -> wb.compass_north_top = it }.build() + }, + containerColor = CardDefaults.cardColors().containerColor, + ) + HorizontalDivider() + } + if (capabilities.offers(Config.DisplayConfig.use_12h_clock)) { + SwitchPreference( + title = stringResource(Res.string.use_12h_format), + summary = stringResource(Res.string.display_time_in_12h_format), + enabled = state.connected, + checked = formState.value.use_12h_clock, + onCheckedChange = { + formState.value = formState.value.newBuilder().also { wb -> wb.use_12h_clock = it }.build() + }, + containerColor = CardDefaults.cardColors().containerColor, + ) + HorizontalDivider() + } SwitchPreference( title = stringResource(Res.string.bold_heading), summary = stringResource(Res.string.config_display_heading_bold_summary), diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/MQTTConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/MQTTConfigItemList.kt index 0b6e3a901b..615b4e57d0 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/MQTTConfigItemList.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/MQTTConfigItemList.kt @@ -36,6 +36,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -48,6 +49,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.jetbrains.compose.resources.stringResource +import org.meshtastic.core.model.Capabilities import org.meshtastic.core.model.MqttConnectionState import org.meshtastic.core.model.MqttProbeStatus import org.meshtastic.core.network.repository.effectiveTlsEnabled @@ -91,13 +93,16 @@ import org.meshtastic.core.ui.component.TitledCard import org.meshtastic.feature.settings.radio.RadioConfigViewModel import org.meshtastic.feature.settings.radio.RebootBehavior import org.meshtastic.proto.ModuleConfig +import org.meshtastic.proto.json_enabled -// json_enabled is deprecated in the protobuf schema but remains the only toggle for MQTT JSON -// publish/consume, so the settings UI must keep exposing it until the proto provides a replacement. +// json_enabled still drives MQTT JSON on firmware below its deprecated_since; beyond that the schema gate hides it +// unless the node still holds it set, so a stale value stays visible. @Suppress("DEPRECATION") @Composable fun MQTTConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { val state by viewModel.radioConfigState.collectAsStateWithLifecycle() + val firmwareVersion = state.metadata?.firmware_version + val capabilities = remember(firmwareVersion) { Capabilities(firmwareVersion) } val destNode by viewModel.destNode.collectAsStateWithLifecycle() val mqttProxyState by viewModel.mqttConnectionState.collectAsStateWithLifecycle() val mqttProxyActive by viewModel.mqttProxyActive.collectAsStateWithLifecycle() @@ -217,16 +222,18 @@ fun MQTTConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { containerColor = CardDefaults.cardColors().containerColor, ) HorizontalDivider() - SwitchPreference( - title = stringResource(Res.string.json_output_enabled), - checked = formState.value.json_enabled, - enabled = state.connected, - onCheckedChange = { - formState.value = formState.value.newBuilder().also { wb -> wb.json_enabled = it }.build() - }, - containerColor = CardDefaults.cardColors().containerColor, - ) - HorizontalDivider() + if (capabilities.offers(ModuleConfig.MQTTConfig.json_enabled, isSet = formState.value.json_enabled)) { + SwitchPreference( + title = stringResource(Res.string.json_output_enabled), + checked = formState.value.json_enabled, + enabled = state.connected, + onCheckedChange = { + formState.value = formState.value.newBuilder().also { wb -> wb.json_enabled = it }.build() + }, + containerColor = CardDefaults.cardColors().containerColor, + ) + HorizontalDivider() + } MqttTlsPreference( enabled = state.connected, address = formState.value.address, diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/NetworkConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/NetworkConfigItemList.kt index 1bc285d3f8..9fa2a8b1fe 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/NetworkConfigItemList.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/NetworkConfigItemList.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -39,6 +40,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.jetbrains.compose.resources.stringResource import org.meshtastic.core.common.util.CommonUri import org.meshtastic.core.common.util.extractWifiCredentials +import org.meshtastic.core.model.Capabilities import org.meshtastic.core.model.util.handleMeshtasticUri import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.advanced @@ -83,6 +85,7 @@ import org.meshtastic.core.ui.util.LocalNfcScannerSupported import org.meshtastic.feature.settings.radio.RadioConfigViewModel import org.meshtastic.feature.settings.radio.RebootBehavior import org.meshtastic.proto.Config +import org.meshtastic.proto.enabled_protocols @Composable private fun ScanErrorDialog(onDismiss: () -> Unit = {}) = @@ -98,6 +101,8 @@ private fun formatIpAddress(ipAddress: Int): String = "${(ipAddress) and 0xFF}." @Composable fun NetworkConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit, onOpenNfcSettings: () -> Unit = {}) { val state by viewModel.radioConfigState.collectAsStateWithLifecycle() + val firmwareVersion = state.metadata?.firmware_version + val capabilities = remember(firmwareVersion) { Capabilities(firmwareVersion) } val networkConfig = state.radioConfig.network ?: Config.NetworkConfig.Builder().build() val formState = rememberConfigState(initialValue = networkConfig) @@ -296,27 +301,29 @@ fun NetworkConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit, onO }, ) HorizontalDivider() - SwitchPreference( - title = stringResource(Res.string.udp_enabled), - summary = stringResource(Res.string.config_network_udp_enabled_summary), - checked = - formState.value.enabled_protocols and Config.NetworkConfig.ProtocolFlags.UDP_BROADCAST.value != - 0, - onCheckedChange = { enabled -> - val flags = - if (enabled) { - formState.value.enabled_protocols or - Config.NetworkConfig.ProtocolFlags.UDP_BROADCAST.value - } else { - formState.value.enabled_protocols and - Config.NetworkConfig.ProtocolFlags.UDP_BROADCAST.value.inv() - } - formState.value = - formState.value.newBuilder().also { wb -> wb.enabled_protocols = flags }.build() - }, - enabled = state.connected, - ) - HorizontalDivider() + if (capabilities.offers(Config.NetworkConfig.enabled_protocols)) { + SwitchPreference( + title = stringResource(Res.string.udp_enabled), + summary = stringResource(Res.string.config_network_udp_enabled_summary), + checked = + formState.value.enabled_protocols and + Config.NetworkConfig.ProtocolFlags.UDP_BROADCAST.value != 0, + onCheckedChange = { enabled -> + val flags = + if (enabled) { + formState.value.enabled_protocols or + Config.NetworkConfig.ProtocolFlags.UDP_BROADCAST.value + } else { + formState.value.enabled_protocols and + Config.NetworkConfig.ProtocolFlags.UDP_BROADCAST.value.inv() + } + formState.value = + formState.value.newBuilder().also { wb -> wb.enabled_protocols = flags }.build() + }, + enabled = state.connected, + ) + HorizontalDivider() + } DropDownPreference( title = stringResource(Res.string.ipv4_mode), enabled = state.connected, diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/TelemetryConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/TelemetryConfigItemList.kt index 13b85be7d7..5424b53486 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/TelemetryConfigItemList.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/TelemetryConfigItemList.kt @@ -47,6 +47,7 @@ import org.meshtastic.feature.settings.radio.RebootBehavior import org.meshtastic.feature.settings.util.IntervalConfiguration import org.meshtastic.feature.settings.util.toDisplayString import org.meshtastic.proto.ModuleConfig +import org.meshtastic.proto.device_telemetry_enabled @Composable fun TelemetryConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { @@ -72,7 +73,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { ) { item { TitledCard(title = stringResource(Res.string.telemetry_config)) { - if (capabilities.canToggleTelemetryEnabled) { + if (capabilities.offers(ModuleConfig.TelemetryConfig.device_telemetry_enabled)) { SwitchPreference( title = stringResource(Res.string.device_telemetry_enabled), summary = stringResource(Res.string.device_telemetry_enabled_summary), diff --git a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModelTest.kt b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModelTest.kt index 138cd86e8f..7d65902828 100644 --- a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModelTest.kt +++ b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModelTest.kt @@ -149,7 +149,7 @@ class RadioConfigViewModelTest { Node( num = 123, user = User.Builder().also { wb -> wb.id = "!123" }.build(), - metadata = DeviceMetadata.Builder().also { wb -> wb.firmware_version = "2.7.21" }.build(), + metadata = DeviceMetadata.Builder().also { wb -> wb.firmware_version = "2.7.19" }.build(), ) nodeRepository.setNodes(listOf(node)) viewModel = createViewModel(destNum = 123) @@ -196,7 +196,7 @@ class RadioConfigViewModelTest { Node( num = 456, user = User.Builder().also { wb -> wb.id = "!456" }.build(), - metadata = DeviceMetadata.Builder().also { wb -> wb.firmware_version = "2.7.21" }.build(), + metadata = DeviceMetadata.Builder().also { wb -> wb.firmware_version = "2.7.19" }.build(), ) nodeRepository.setNodes(listOf(localNode, remoteNode)) nodeRepository.setMyNodeInfo(myNodeInfo(myNodeNum = 100)) @@ -2035,7 +2035,7 @@ class RadioConfigViewModelTest { Node( num = 456, user = User.Builder().also { wb -> wb.id = "!456" }.build(), - metadata = DeviceMetadata.Builder().also { wb -> wb.firmware_version = "2.7.21" }.build(), + metadata = DeviceMetadata.Builder().also { wb -> wb.firmware_version = "2.7.19" }.build(), ) val packetFlow = MutableSharedFlow() val maxRetransmit = org.meshtastic.core.resources.UiText.DynamicString("Max Retransmission Reached")