From 9d0c9d7a24fc302d545de70bf21f207d66149ae5 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Fri, 5 Sep 2025 06:29:50 -0500 Subject: [PATCH 01/11] New Crowdin updates (#2975) --- app/src/main/res/values-zh-rTW/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 17f49dbf8..178924126 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -312,7 +312,7 @@ UDP設置 最後接收: %2$s
最後位置: %3$s
電量: %4$s]]>
切換我的位置 - Orient north + 以北為上 用戶 頻道 裝置 @@ -337,7 +337,7 @@ 檢測傳感器 客流量計數 音頻設置 - 啟用 CODEC2 + CODEC 2 已啟用 PTT針腳 CODEC2 取樣率 I2S WS 訊號選擇 @@ -475,8 +475,8 @@ 子網 Paxcount設置 啟用Paxcount - WiFi RSSI 閾值(缺省-80) - 藍牙 RSSI 閾值(缺省-80) + WiFi RSSI 閾值(預設為-80) + 藍牙 RSSI 閾值(預設為-80) 位置設定 位置廣播間隔(秒) 啟用智慧位置 From 4f49e98dd63dd9755a1cfd60bf250e2a9a6a1acd Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Fri, 5 Sep 2025 07:16:40 -0500 Subject: [PATCH 02/11] chore: Scheduled updates (Firmware, Hardware) (#2978) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- app/src/main/assets/firmware_releases.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/src/main/assets/firmware_releases.json b/app/src/main/assets/firmware_releases.json index 6193440df..a7d6e8002 100644 --- a/app/src/main/assets/firmware_releases.json +++ b/app/src/main/assets/firmware_releases.json @@ -193,12 +193,6 @@ "title": "the original ZPS module from https://github.com/a-f-G-U-C/Meshtastic-ZPS", "page_url": "https://github.com/meshtastic/firmware/pull/7658", "zip_url": "https://github.com/meshtastic/firmware/actions/runs/17074730483" - }, - { - "id": "7583", - "title": "chore(deps): update meshtastic/web to v2.6.6", - "page_url": "https://github.com/meshtastic/firmware/pull/7583", - "zip_url": "https://github.com/meshtastic/firmware/actions/runs/17070663764" } ] } \ No newline at end of file From 653f7a1b3a49c2570bf555a111fa3a32ae304fc2 Mon Sep 17 00:00:00 2001 From: DaneEvans Date: Fri, 5 Sep 2025 23:01:09 +1000 Subject: [PATCH 03/11] fix units on current (#2980) --- .../java/com/geeksville/mesh/ui/metrics/EnvironmentMetrics.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/metrics/EnvironmentMetrics.kt b/app/src/main/java/com/geeksville/mesh/ui/metrics/EnvironmentMetrics.kt index 3cd153d32..3a4308998 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/metrics/EnvironmentMetrics.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/metrics/EnvironmentMetrics.kt @@ -256,7 +256,7 @@ private fun VoltageCurrentDisplay(envMetrics: TelemetryProtos.EnvironmentMetrics Spacer(modifier = Modifier.height(4.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text( - text = "%s %.2f A".format(stringResource(R.string.current), current), + text = "%s %.2f mA".format(stringResource(R.string.current), current), color = MaterialTheme.colorScheme.onSurface, fontSize = MaterialTheme.typography.labelLarge.fontSize, ) From dc9c325e1e8d2559140485df1f398fb729c0a665 Mon Sep 17 00:00:00 2001 From: Robert-0410 <62630290+Robert-0410@users.noreply.github.com> Date: Fri, 5 Sep 2025 06:14:32 -0700 Subject: [PATCH 04/11] Improvements to Channel management (#2935) Co-authored-by: DaneEvans Co-authored-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- .../mesh/ui/common/components/SecurityIcon.kt | 29 ++- .../radio/components/ChannelLegend.kt | 180 ++++++++++++++++++ .../components/ChannelSettingsItemList.kt | 119 ++++++++---- app/src/main/res/values/strings.xml | 7 + 4 files changed, 294 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/ChannelLegend.kt diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/components/SecurityIcon.kt b/app/src/main/java/com/geeksville/mesh/ui/common/components/SecurityIcon.kt index a95788346..e94588f12 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/common/components/SecurityIcon.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/common/components/SecurityIcon.kt @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +@file:Suppress("TooManyFunctions") + package com.geeksville.mesh.ui.common.components import androidx.annotation.StringRes @@ -59,6 +61,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.geeksville.mesh.AppOnlyProtos +import com.geeksville.mesh.ChannelProtos.ChannelSettings +import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig import com.geeksville.mesh.R import com.geeksville.mesh.model.Channel import com.geeksville.mesh.model.getChannel @@ -160,7 +164,7 @@ private fun SecurityIconDisplay( Icon( imageVector = badgeIcon, contentDescription = stringResource(R.string.security_icon_badge_warning_description), - tint = badgeIconColor ?: MaterialTheme.colorScheme.onError, // Default for contrast + tint = badgeIconColor ?: colorScheme.onError, // Default for contrast modifier = Modifier.size(16.dp), // Adjusted badge icon size ) } @@ -291,6 +295,29 @@ fun SecurityIcon( externalOnClick = externalOnClick, ) +/** + * Overload for [SecurityIcon] that enables recomposition when making changes to the [ChannelSettings]. + * + * @param baseContentDescription The base content description for the icon. + * @param externalOnClick Optional lambda for external actions, invoked when the icon is clicked. + */ +@Composable +fun SecurityIcon( + channelSettings: ChannelSettings, + loraConfig: LoRaConfig, + baseContentDescription: String = stringResource(id = R.string.security_icon_description), + externalOnClick: (() -> Unit)? = null, +) { + val channel = Channel(channelSettings, loraConfig) + SecurityIcon( + isLowEntropyKey = channel.isLowEntropyKey, + isPreciseLocation = channel.isPreciseLocation, + isMqttEnabled = channel.isMqttEnabled, + baseContentDescription = baseContentDescription, + externalOnClick = externalOnClick, + ) +} + /** * Overload for [SecurityIcon] that takes an [AppOnlyProtos.ChannelSet] and a channel index. If the channel at the given * index is not found, nothing is rendered. diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/ChannelLegend.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/ChannelLegend.kt new file mode 100644 index 000000000..37255780a --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/ChannelLegend.kt @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2025 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 com.geeksville.mesh.ui.settings.radio.components + +import androidx.annotation.StringRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CloudDownload +import androidx.compose.material.icons.filled.CloudUpload +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.LocationOn +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.geeksville.mesh.R +import com.geeksville.mesh.model.DeviceVersion + +/** + * At this firmware version periodic position sharing on a secondary channel was implemented. To enable this feature the + * user must disable position on the primary channel and enable on a secondary channel. The lowest indexed secondary + * channel with the position enabled will conduct the automatic position broadcasts. + */ +internal const val SECONDARY_CHANNEL_EPOCH = "2.6.10" + +internal enum class ChannelIcons( + val icon: ImageVector, + @StringRes val descriptionResId: Int, + @StringRes val additionalInfoResId: Int, +) { + LOCATION( + icon = Icons.Filled.LocationOn, + descriptionResId = R.string.location_sharing, + additionalInfoResId = R.string.periodic_position_broadcast, + ), + UPLINK( + icon = Icons.Filled.CloudUpload, + descriptionResId = R.string.uplink_enabled, + additionalInfoResId = R.string.uplink_feature_description, + ), + DOWNLINK( + icon = Icons.Filled.CloudDownload, + descriptionResId = R.string.downlink_enabled, + additionalInfoResId = R.string.downlink_feature_description, + ), +} + +@Composable +internal fun ChannelLegend(onClick: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth().clickable { onClick.invoke() }, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Row { + Icon(imageVector = Icons.Filled.Info, contentDescription = stringResource(R.string.info)) + Text( + text = stringResource(R.string.primary), + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(start = 16.dp), + ) + } + Text( + text = stringResource(R.string.secondary), + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.padding(start = 16.dp), + ) + } +} + +@Composable +internal fun ChannelLegendDialog(firmwareVersion: DeviceVersion, onDismiss: () -> Unit) { + AlertDialog( + modifier = Modifier.fillMaxSize(), + onDismissRequest = onDismiss, + title = { Text(stringResource(R.string.channel_features)) }, + text = { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.verticalScroll(rememberScrollState()), + ) { + Text( + text = stringResource(R.string.primary), + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.titleMedium, + ) + Text( + text = "- ${stringResource(R.string.primary_channel_feature)}", + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.bodyMedium, + ) + Text( + text = stringResource(R.string.secondary), + color = MaterialTheme.colorScheme.onBackground, + style = MaterialTheme.typography.titleMedium, + ) + Text( + text = "- ${stringResource(R.string.secondary_no_telemetry)}", + color = MaterialTheme.colorScheme.onBackground, + style = MaterialTheme.typography.bodyMedium, + ) + Text( + text = + if (firmwareVersion >= DeviceVersion(asString = SECONDARY_CHANNEL_EPOCH)) { + /* 2.6.10+ */ + "- ${stringResource(R.string.secondary_channel_position_feature)}" + } else { + "- ${stringResource(R.string.manual_position_request)}" + }, + color = MaterialTheme.colorScheme.onBackground, + style = MaterialTheme.typography.bodyMedium, + ) + IconDefinitions() + } + }, + confirmButton = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + TextButton(onClick = onDismiss) { Text(stringResource(R.string.security_icon_help_dismiss)) } + } + }, + ) +} + +@Composable +private fun IconDefinitions() { + Text(text = stringResource(R.string.icon_meanings), style = MaterialTheme.typography.titleLarge) + ChannelIcons.entries.forEach { icon -> + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(imageVector = icon.icon, contentDescription = stringResource(icon.descriptionResId)) + Column(modifier = Modifier.padding(start = 16.dp)) { + Text(text = stringResource(icon.descriptionResId), style = MaterialTheme.typography.titleMedium) + Text(text = stringResource(icon.additionalInfoResId), style = MaterialTheme.typography.bodyMedium) + } + } + if (icon != ChannelIcons.entries.lastOrNull()) { + HorizontalDivider(modifier = Modifier.padding(top = 8.dp)) + } + } +} + +@Preview +@Composable +private fun PreviewChannelLegendDialog() { + ChannelLegendDialog(firmwareVersion = DeviceVersion(asString = SECONDARY_CHANNEL_EPOCH)) {} +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/ChannelSettingsItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/ChannelSettingsItemList.kt index 2115bc6b6..01b5e2769 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/ChannelSettingsItemList.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/ChannelSettingsItemList.kt @@ -32,7 +32,6 @@ import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize @@ -59,6 +58,7 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource @@ -73,6 +73,7 @@ import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig import com.geeksville.mesh.R import com.geeksville.mesh.channelSettings import com.geeksville.mesh.model.Channel +import com.geeksville.mesh.model.DeviceVersion import com.geeksville.mesh.ui.common.components.PreferenceCategory import com.geeksville.mesh.ui.common.components.PreferenceFooter import com.geeksville.mesh.ui.common.components.SecurityIcon @@ -89,18 +90,20 @@ private fun ChannelItem( onClick: () -> Unit = {}, content: @Composable RowScope.() -> Unit, ) { + val fontColor = if (index == 0) MaterialTheme.colorScheme.primary else Color.Unspecified Card(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp).clickable(enabled = enabled) { onClick() }) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 4.dp, horizontal = 4.dp), ) { - AssistChip(onClick = onClick, label = { Text(text = "$index") }) + AssistChip(onClick = onClick, label = { Text(text = "$index", color = fontColor) }) Text( text = title, modifier = Modifier.weight(1f), overflow = TextOverflow.Ellipsis, maxLines = 1, style = MaterialTheme.typography.bodyLarge, + color = fontColor, ) content() } @@ -112,11 +115,34 @@ private fun ChannelCard( index: Int, title: String, enabled: Boolean, + channelSettings: ChannelSettings, + loraConfig: LoRaConfig, onEditClick: () -> Unit, onDeleteClick: () -> Unit, - channel: Channel, + sharesLocation: Boolean, ) = ChannelItem(index = index, title = title, enabled = enabled, onClick = onEditClick) { - SecurityIcon(channel) + if (sharesLocation) { + Icon( + imageVector = ChannelIcons.LOCATION.icon, + contentDescription = stringResource(ChannelIcons.LOCATION.descriptionResId), + modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp), + ) + } + if (channelSettings.uplinkEnabled) { + Icon( + imageVector = ChannelIcons.UPLINK.icon, + contentDescription = stringResource(ChannelIcons.UPLINK.descriptionResId), + modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp), + ) + } + if (channelSettings.downlinkEnabled) { + Icon( + imageVector = ChannelIcons.DOWNLINK.icon, + contentDescription = stringResource(ChannelIcons.DOWNLINK.descriptionResId), + modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp), + ) + } + SecurityIcon(channelSettings, loraConfig) Spacer(modifier = Modifier.width(10.dp)) IconButton(onClick = { onDeleteClick() }) { Icon( @@ -135,7 +161,7 @@ fun ChannelSelection( isSelected: Boolean, onSelected: (Boolean) -> Unit, channel: Channel, -) = ChannelItem(index = index, title = title, enabled = enabled, onClick = {}) { +) = ChannelItem(index = index, title = title, enabled = enabled) { SecurityIcon(channel) Spacer(modifier = Modifier.width(10.dp)) Checkbox(enabled = enabled, checked = isSelected, onCheckedChange = onSelected) @@ -152,25 +178,28 @@ fun ChannelConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) { ChannelSettingsItemList( settingsList = state.channelList, loraConfig = state.radioConfig.lora, - enabled = state.connected, maxChannels = viewModel.maxChannels, + firmwareVersion = state.metadata?.firmwareVersion ?: "0.0.0", + enabled = state.connected, onPositiveClicked = { channelListInput -> viewModel.updateChannels(channelListInput, state.channelList) }, ) } @Suppress("LongMethod", "CyclomaticComplexMethod") @Composable -fun ChannelSettingsItemList( +private fun ChannelSettingsItemList( settingsList: List, loraConfig: LoRaConfig, maxChannels: Int = 8, + firmwareVersion: String, enabled: Boolean, - onNegativeClicked: () -> Unit = {}, onPositiveClicked: (List) -> Unit, ) { val primarySettings = settingsList.getOrNull(0) ?: return val modemPresetName by remember(loraConfig) { mutableStateOf(Channel(loraConfig = loraConfig).name) } val primaryChannel by remember(loraConfig) { mutableStateOf(Channel(primarySettings, loraConfig)) } + val fwVersion by + remember(firmwareVersion) { mutableStateOf(DeviceVersion(firmwareVersion.substringBeforeLast("."))) } val focusManager = LocalFocusManager.current val settingsListInput = @@ -180,7 +209,7 @@ fun ChannelSettingsItemList( val listState = rememberLazyListState() val dragDropState = - rememberDragDropState(listState, headerCount = 1) { fromIndex, toIndex -> + rememberDragDropState(listState) { fromIndex, toIndex -> if (toIndex in settingsListInput.indices && fromIndex in settingsListInput.indices) { settingsListInput.apply { add(toIndex, removeAt(fromIndex)) } } @@ -191,6 +220,7 @@ fun ChannelSettingsItemList( settingsList.zip(settingsListInput).any { (item1, item2) -> item1 != item2 } var showEditChannelDialog: Int? by rememberSaveable { mutableStateOf(null) } + var showChannelLegendDialog by rememberSaveable { mutableStateOf(false) } if (showEditChannelDialog != null) { val index = showEditChannelDialog ?: return @@ -209,6 +239,10 @@ fun ChannelSettingsItemList( ) } + if (showChannelLegendDialog) { + ChannelLegendDialog(fwVersion) { showChannelLegendDialog = false } + } + Box(modifier = Modifier.fillMaxSize().clickable(onClick = {}, enabled = false)) { Column { ChannelsConfigHeader( @@ -230,11 +264,11 @@ fun ChannelSettingsItemList( fontSize = 11.sp, modifier = Modifier.padding(start = 16.dp), ) - Text( - text = stringResource(R.string.primary), - color = MaterialTheme.colorScheme.primary, - modifier = Modifier.padding(start = 16.dp), - ) + + ChannelLegend { showChannelLegendDialog = true } + + val locationChannel = determineLocationSharingChannel(fwVersion, settingsListInput.toList()) + LazyColumn( modifier = Modifier.dragContainer(dragDropState = dragDropState, haptics = LocalHapticFeedback.current), state = listState, @@ -245,38 +279,16 @@ fun ChannelSettingsItemList( channel, isDragging, -> - val channelObj = Channel(channel, loraConfig) ChannelCard( index = index, title = channel.name.ifEmpty { modemPresetName }, enabled = enabled, + channelSettings = channel, + loraConfig = loraConfig, onEditClick = { showEditChannelDialog = index }, onDeleteClick = { settingsListInput.removeAt(index) }, - channel = channelObj, + sharesLocation = locationChannel == index, ) - if (index == 0 && !isDragging) { - Text( - text = stringResource(R.string.primary_channel_feature), - color = MaterialTheme.colorScheme.primary, - fontSize = 10.sp, - ) - Spacer(modifier = Modifier.height(16.dp)) - Text(text = stringResource(R.string.secondary), color = MaterialTheme.colorScheme.onBackground) - } - } - item { - Column { - Text( - text = stringResource(R.string.secondary_no_telemetry), - color = MaterialTheme.colorScheme.onBackground, - fontSize = 10.sp, - ) - Text( - text = stringResource(R.string.manual_position_request), - color = MaterialTheme.colorScheme.onBackground, - fontSize = 10.sp, - ) - } } item { PreferenceFooter( @@ -286,7 +298,6 @@ fun ChannelSettingsItemList( focusManager.clearFocus() settingsListInput.clear() settingsListInput.addAll(settingsList) - onNegativeClicked() }, positiveText = R.string.send, onPositiveClicked = { @@ -338,6 +349,33 @@ private fun ChannelsConfigHeader(frequency: Float, slot: Int) { } } +/** + * Determines what [Channel] if any is enabled to conduct automatic location sharing. + * + * @param firmwareVersion of the connected node. + * @param settingsList Current list of channels on the node. + * @return the index of the channel within `settingsList`. + */ +private fun determineLocationSharingChannel(firmwareVersion: DeviceVersion, settingsList: List): Int { + var output = -1 + if (firmwareVersion >= DeviceVersion(asString = SECONDARY_CHANNEL_EPOCH)) { + /* Essentially the first index with the setting enabled */ + for ((i, settings) in settingsList.withIndex()) { + if (settings.moduleSettings.positionPrecision > 0) { + output = i + break + } + } + } else { + /* Only the primary channel at index 0 can share locations automatically */ + val primary = settingsList[0] + if (primary.moduleSettings.positionPrecision > 0) { + output = 0 + } + } + return output +} + @Preview(showBackground = true) @Composable private fun ChannelSettingsPreview() { @@ -351,6 +389,7 @@ private fun ChannelSettingsPreview() { channelSettings { name = stringResource(R.string.channel_name) }, ), loraConfig = Channel.default.loraConfig, + firmwareVersion = "1.3.2", enabled = true, onPositiveClicked = {}, ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bd65b2ca9..77b4473b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -802,4 +802,11 @@ https://a.tile.openstreetmap.org/{z}/{x}/{y}.png track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. From be4862882e236b349c88c815abcfa79ecb78d916 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:47:22 -0500 Subject: [PATCH 05/11] New Crowdin updates (#2982) --- app/src/main/res/values-ar-rSA/strings.xml | 7 +++++++ app/src/main/res/values-b+sr+Latn/strings.xml | 7 +++++++ app/src/main/res/values-bg-rBG/strings.xml | 7 +++++++ app/src/main/res/values-ca-rES/strings.xml | 7 +++++++ app/src/main/res/values-cs-rCZ/strings.xml | 7 +++++++ app/src/main/res/values-de-rDE/strings.xml | 7 +++++++ app/src/main/res/values-el-rGR/strings.xml | 7 +++++++ app/src/main/res/values-es-rES/strings.xml | 7 +++++++ app/src/main/res/values-et-rEE/strings.xml | 7 +++++++ app/src/main/res/values-fi-rFI/strings.xml | 7 +++++++ app/src/main/res/values-fr-rFR/strings.xml | 7 +++++++ app/src/main/res/values-ga-rIE/strings.xml | 7 +++++++ app/src/main/res/values-gl-rES/strings.xml | 7 +++++++ app/src/main/res/values-hr-rHR/strings.xml | 7 +++++++ app/src/main/res/values-ht-rHT/strings.xml | 7 +++++++ app/src/main/res/values-hu-rHU/strings.xml | 7 +++++++ app/src/main/res/values-is-rIS/strings.xml | 7 +++++++ app/src/main/res/values-it-rIT/strings.xml | 7 +++++++ app/src/main/res/values-iw-rIL/strings.xml | 7 +++++++ app/src/main/res/values-ja-rJP/strings.xml | 7 +++++++ app/src/main/res/values-ko-rKR/strings.xml | 7 +++++++ app/src/main/res/values-lt-rLT/strings.xml | 7 +++++++ app/src/main/res/values-nl-rNL/strings.xml | 7 +++++++ app/src/main/res/values-no-rNO/strings.xml | 7 +++++++ app/src/main/res/values-pl-rPL/strings.xml | 7 +++++++ app/src/main/res/values-pt-rBR/strings.xml | 7 +++++++ app/src/main/res/values-pt-rPT/strings.xml | 7 +++++++ app/src/main/res/values-ro-rRO/strings.xml | 7 +++++++ app/src/main/res/values-ru-rRU/strings.xml | 7 +++++++ app/src/main/res/values-sk-rSK/strings.xml | 7 +++++++ app/src/main/res/values-sl-rSI/strings.xml | 7 +++++++ app/src/main/res/values-sq-rAL/strings.xml | 7 +++++++ app/src/main/res/values-srp/strings.xml | 7 +++++++ app/src/main/res/values-sv-rSE/strings.xml | 7 +++++++ app/src/main/res/values-tr-rTR/strings.xml | 7 +++++++ app/src/main/res/values-uk-rUA/strings.xml | 7 +++++++ app/src/main/res/values-zh-rCN/strings.xml | 7 +++++++ app/src/main/res/values-zh-rTW/strings.xml | 7 +++++++ 38 files changed, 266 insertions(+) diff --git a/app/src/main/res/values-ar-rSA/strings.xml b/app/src/main/res/values-ar-rSA/strings.xml index 907d3f667..67e4f1501 100644 --- a/app/src/main/res/values-ar-rSA/strings.xml +++ b/app/src/main/res/values-ar-rSA/strings.xml @@ -782,4 +782,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-b+sr+Latn/strings.xml b/app/src/main/res/values-b+sr+Latn/strings.xml index fd688abb5..ece294f7b 100644 --- a/app/src/main/res/values-b+sr+Latn/strings.xml +++ b/app/src/main/res/values-b+sr+Latn/strings.xml @@ -776,4 +776,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index 527ac9756..983850e96 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -774,4 +774,11 @@ Шаблон за URL track point Настройки на телефона + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-ca-rES/strings.xml b/app/src/main/res/values-ca-rES/strings.xml index f97780d87..9664b376a 100644 --- a/app/src/main/res/values-ca-rES/strings.xml +++ b/app/src/main/res/values-ca-rES/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 1e3dec05f..ed0a0bbcc 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -778,4 +778,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 2ed67df92..e8391a0b4 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -774,4 +774,11 @@ URL Vorlage Verlaufspunkt Telefoneinstellungen + Kanalfunktionen + Standortfreigabe + Regelmäßige Standortübertragung + Wenn aktiviert, werden Nachrichten aus dem Mesh über das konfigurierte Gateway eines beliebigen Knotens an das **öffentliche** Internet gesendet. + Nachrichten von einem öffentlichen Internet Gateway werden an das lokale Mesh weitergeleitet. Aufgrund der Nullsprungrichtlinie wird der Datenverkehr vom MQTT Standardserver nicht über dieses Gerät hinaus weitergeleitet. + Symbolbedeutung + Durch Deaktivieren des Standortes auf dem primären Kanal werden regelmäßige Standortübertragungen auf dem ersten sekundären Kanal mit aktiviertem Standort ermöglicht, andernfalls ist eine manuelle Standortanforderung erforderlich. diff --git a/app/src/main/res/values-el-rGR/strings.xml b/app/src/main/res/values-el-rGR/strings.xml index 55cae6318..1c031dc77 100644 --- a/app/src/main/res/values-el-rGR/strings.xml +++ b/app/src/main/res/values-el-rGR/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index bceac83f2..343697f8f 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -775,4 +775,11 @@ Rango de Valores 0 - 500. URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-et-rEE/strings.xml b/app/src/main/res/values-et-rEE/strings.xml index ca359314e..70770206d 100644 --- a/app/src/main/res/values-et-rEE/strings.xml +++ b/app/src/main/res/values-et-rEE/strings.xml @@ -774,4 +774,11 @@ URL mall jälgimispunkt Telefoni seaded + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 69a0a8a42..59429bb5c 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -774,4 +774,11 @@ URL-mallipohja seurantapiste Puhelimen asetukset + Kanavan ominaisuudet + Sijainnin jakaminen + Sijainnin toistuva lähetys + Verkosta tulevat viestit lähetetään julkiseen internetiin minkä tahansa laitteen määritetyn yhdyskäytävän kautta. + Julkisesta internet-yhdyskäytävästä tulevat viestit välitetään paikalliseen mesh-verkkoon. Nollahyppysääntöjen vuoksi oletuksena MQTT-palvelimelta tuleva liikenne ei etene tätä laitetta pidemmälle. + Kuvakkeiden merkitykset + Sijainnin poistaminen käytöstä ensisijaisella kanavalla mahdollistaa sijainnin jaksottaisen lähetyksen ensimmäisellä toissijaisella kanavalla, jossa sijainti on käytössä. Muussa tapauksessa vaaditaan manuaalinen sijaintipyyntö. diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 92402f576..22af876ff 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -771,4 +771,11 @@ Modèle d\'URL Point de suivi Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-ga-rIE/strings.xml b/app/src/main/res/values-ga-rIE/strings.xml index 33e5567df..01b1d6eab 100644 --- a/app/src/main/res/values-ga-rIE/strings.xml +++ b/app/src/main/res/values-ga-rIE/strings.xml @@ -780,4 +780,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-gl-rES/strings.xml b/app/src/main/res/values-gl-rES/strings.xml index e0d182f68..b14d960ba 100644 --- a/app/src/main/res/values-gl-rES/strings.xml +++ b/app/src/main/res/values-gl-rES/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-hr-rHR/strings.xml b/app/src/main/res/values-hr-rHR/strings.xml index 856cca42d..7bf3fd8f9 100644 --- a/app/src/main/res/values-hr-rHR/strings.xml +++ b/app/src/main/res/values-hr-rHR/strings.xml @@ -776,4 +776,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-ht-rHT/strings.xml b/app/src/main/res/values-ht-rHT/strings.xml index f9d173bf2..df2554214 100644 --- a/app/src/main/res/values-ht-rHT/strings.xml +++ b/app/src/main/res/values-ht-rHT/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index 903460cd9..322f49125 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-is-rIS/strings.xml b/app/src/main/res/values-is-rIS/strings.xml index c337ccd04..92e4c059a 100644 --- a/app/src/main/res/values-is-rIS/strings.xml +++ b/app/src/main/res/values-is-rIS/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index a0eabae2d..bb8980165 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -774,4 +774,11 @@ Template dell\'URL punto di interesse Impostazioni telefono + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-iw-rIL/strings.xml b/app/src/main/res/values-iw-rIL/strings.xml index e851cc386..036af49c6 100644 --- a/app/src/main/res/values-iw-rIL/strings.xml +++ b/app/src/main/res/values-iw-rIL/strings.xml @@ -778,4 +778,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index fe8dc8501..31876ef99 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -773,4 +773,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index a58e1c48e..7e8a377ad 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -772,4 +772,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-lt-rLT/strings.xml b/app/src/main/res/values-lt-rLT/strings.xml index 318e32fa8..fd3277832 100644 --- a/app/src/main/res/values-lt-rLT/strings.xml +++ b/app/src/main/res/values-lt-rLT/strings.xml @@ -778,4 +778,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml index a1fa3d9bc..dc061c427 100644 --- a/app/src/main/res/values-nl-rNL/strings.xml +++ b/app/src/main/res/values-nl-rNL/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-no-rNO/strings.xml b/app/src/main/res/values-no-rNO/strings.xml index 685303ef7..9b6e0a0c4 100644 --- a/app/src/main/res/values-no-rNO/strings.xml +++ b/app/src/main/res/values-no-rNO/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 912b58983..86ba7fa63 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -778,4 +778,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 9152d2882..fe00a5cd4 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -774,4 +774,11 @@ Modelo de URL ponto de rastreamento Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 3a0fe90c0..91af48882 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index d3e74a91e..1d2c5c0b7 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -776,4 +776,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 093d9faa5..11369fc32 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -775,4 +775,11 @@ Шаблон URL track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml index 5c9573d3e..730a5795f 100644 --- a/app/src/main/res/values-sk-rSK/strings.xml +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -778,4 +778,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-sl-rSI/strings.xml b/app/src/main/res/values-sl-rSI/strings.xml index 212c3cc0f..4b4d8761a 100644 --- a/app/src/main/res/values-sl-rSI/strings.xml +++ b/app/src/main/res/values-sl-rSI/strings.xml @@ -778,4 +778,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-sq-rAL/strings.xml b/app/src/main/res/values-sq-rAL/strings.xml index f30d73205..66d299f0e 100644 --- a/app/src/main/res/values-sq-rAL/strings.xml +++ b/app/src/main/res/values-sq-rAL/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-srp/strings.xml b/app/src/main/res/values-srp/strings.xml index 5a904c1f7..f63ea5839 100644 --- a/app/src/main/res/values-srp/strings.xml +++ b/app/src/main/res/values-srp/strings.xml @@ -776,4 +776,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 2cef79d53..a217aa3cc 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index c16dcc706..99de1de98 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -774,4 +774,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 9418e318a..b567fef4d 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -778,4 +778,11 @@ URL Template track point Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index da11a3d18..0a711def8 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -773,4 +773,11 @@ URL 模板 轨迹点 Phone Settings + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 178924126..c719c11f6 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -772,4 +772,11 @@ URL 範本 軌跡點 手機設定 + Channel Features + Location Sharing + Periodic position broadcast + Messages from the mesh will be sent to the public internet through any node\'s configured gateway. + Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device. + Icon Meanings + Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required. From fd40f8679b4cc9916c5c4fb6d31de1038be5cb81 Mon Sep 17 00:00:00 2001 From: DaneEvans Date: Sat, 6 Sep 2025 02:47:48 +1000 Subject: [PATCH 06/11] drop the glitchy animation (#2981) --- .../mesh/ui/common/components/PositionPrecisionPreference.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/components/PositionPrecisionPreference.kt b/app/src/main/java/com/geeksville/mesh/ui/common/components/PositionPrecisionPreference.kt index 14093f2ad..3ae201c76 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/common/components/PositionPrecisionPreference.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/common/components/PositionPrecisionPreference.kt @@ -17,7 +17,6 @@ package com.geeksville.mesh.ui.common.components -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding @@ -68,7 +67,7 @@ fun PositionPrecisionPreference( }, padding = PaddingValues(0.dp), ) - AnimatedVisibility(visible = value != POSITION_DISABLED) { + if (value != POSITION_DISABLED) { SwitchPreference( title = stringResource(R.string.precise_location), checked = value == POSITION_ENABLED, @@ -80,7 +79,7 @@ fun PositionPrecisionPreference( padding = PaddingValues(0.dp), ) } - AnimatedVisibility(visible = value in (POSITION_DISABLED + 1).. Date: Sat, 6 Sep 2025 02:53:39 +1000 Subject: [PATCH 07/11] Fix/2640 rangetest hops (#2979) --- app/src/main/java/com/geeksville/mesh/service/MeshService.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index d07af0884..fe6d4151c 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1177,7 +1177,9 @@ class MeshService : // Generate our own hopsAway, comparing hopStart to hopLimit. it.hopsAway = - if (packet.hopStart == 0 || packet.hopLimit > packet.hopStart) { + if (packet.decoded.portnumValue == Portnums.PortNum.RANGE_TEST_APP_VALUE) { + 0 // These don't come with the .hop params, but do not propogate, so they must be 0 + } else if (packet.hopStart == 0 || packet.hopLimit > packet.hopStart) { -1 } else { packet.hopStart - packet.hopLimit From 0cb0b19128eaeb8d874d17b4ae9032c69d605613 Mon Sep 17 00:00:00 2001 From: Phil Oliver <3497406+poliver@users.noreply.github.com> Date: Fri, 5 Sep 2025 13:24:41 -0400 Subject: [PATCH 08/11] Move "show app intro" to phone settings (#2984) --- app/src/main/java/com/geeksville/mesh/model/UIState.kt | 9 ++------- app/src/main/java/com/geeksville/mesh/ui/Main.kt | 1 - .../geeksville/mesh/ui/common/components/MainAppBar.kt | 1 - .../com/geeksville/mesh/ui/settings/SettingsScreen.kt | 9 +++++++++ 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index ea1d2e328..dfed41f21 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -63,7 +63,6 @@ import com.geeksville.mesh.repository.radio.MeshActivity import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.service.MeshServiceNotifications import com.geeksville.mesh.service.ServiceAction -import com.geeksville.mesh.ui.common.components.MainMenuAction import com.geeksville.mesh.ui.node.components.NodeMenuAction import com.geeksville.mesh.util.getShortDate import com.geeksville.mesh.util.positionToMeter @@ -1001,12 +1000,8 @@ constructor( private val _showAppIntro: MutableStateFlow = MutableStateFlow(!uiPrefs.appIntroCompleted) val showAppIntro: StateFlow = _showAppIntro.asStateFlow() - fun onMainMenuAction(action: MainMenuAction) { - when (action) { - MainMenuAction.SHOW_INTRO -> _showAppIntro.update { true } - - else -> Unit - } + fun showAppIntro() { + _showAppIntro.update { true } } // endregion diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt index 5a062ffbc..3abb9431d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -352,7 +352,6 @@ fun MainScreen( if (action is MainMenuAction) { when (action) { MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat) - MainMenuAction.SHOW_INTRO -> uIViewModel.onMainMenuAction(action) else -> onAction(action) } } else if (action is NodeMenuAction) { diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt b/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt index 723369b66..f3ae7fd26 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt @@ -206,7 +206,6 @@ private fun TopBarActions( enum class MainMenuAction(@StringRes val stringRes: Int) { EXPORT_RANGETEST(R.string.save_rangetest), - SHOW_INTRO(R.string.intro_show), QUICK_CHAT(R.string.quick_chat), } diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt index a4c3ba2b3..096889890 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.rounded.FormatPaint import androidx.compose.material.icons.rounded.Language +import androidx.compose.material.icons.rounded.WavingHand import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -212,6 +213,14 @@ fun SettingsScreen( choices = themeMap.mapValues { (_, value) -> { uiViewModel.setTheme(value) } }, ) } + + SettingsItem( + text = stringResource(R.string.intro_show), + leadingIcon = Icons.Rounded.WavingHand, + trailingIcon = null, + ) { + uiViewModel.showAppIntro() + } } } } From 4ab588cdaaf0642d3ee4fd5e96935068ee383634 Mon Sep 17 00:00:00 2001 From: Phil Oliver <3497406+poliver@users.noreply.github.com> Date: Fri, 5 Sep 2025 13:44:54 -0400 Subject: [PATCH 09/11] Migrate App Intro to Navigation 3 (#2983) --- app/build.gradle.kts | 1 + .../mesh/ui/intro/AppIntroductionScreen.kt | 132 ++++++++++-------- .../geeksville/mesh/ui/intro/IntroRoute.kt | 29 ---- gradle/libs.versions.toml | 6 + 4 files changed, 84 insertions(+), 84 deletions(-) delete mode 100644 app/src/main/java/com/geeksville/mesh/ui/intro/IntroRoute.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ff7d3730f..e8faff6df 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -232,6 +232,7 @@ dependencies { implementation(libs.bundles.adaptive) implementation(libs.bundles.lifecycle) implementation(libs.bundles.navigation) + implementation(libs.bundles.navigation3) implementation(libs.bundles.coroutines) implementation(libs.bundles.datastore) implementation(libs.bundles.room) diff --git a/app/src/main/java/com/geeksville/mesh/ui/intro/AppIntroductionScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/intro/AppIntroductionScreen.kt index 8fe04634b..b7eea81f8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/intro/AppIntroductionScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/intro/AppIntroductionScreen.kt @@ -23,14 +23,17 @@ import android.os.Build import android.provider.Settings import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entry +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.ui.NavDisplay import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionState import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.permissions.rememberPermissionState +import kotlinx.serialization.Serializable /** * Composable function for the main application introduction screen. This screen guides the user through initial setup @@ -43,7 +46,6 @@ import com.google.accompanist.permissions.rememberPermissionState @Composable fun AppIntroductionScreen(onDone: () -> Unit) { val context = LocalContext.current - val navController = rememberNavController() val notificationPermissionState: PermissionState? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -56,56 +58,76 @@ fun AppIntroductionScreen(onDone: () -> Unit) { listOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION) val locationPermissionState = rememberMultiplePermissionsState(permissions = locationPermissions) - NavHost(navController = navController, startDestination = IntroRoute.Welcome.route) { - composable(IntroRoute.Welcome.route) { - WelcomeScreen(onGetStarted = { navController.navigate(IntroRoute.Notifications.route) }) - } - composable(IntroRoute.Notifications.route) { - val notificationsAlreadyGranted = notificationPermissionState?.status?.isGranted ?: true - NotificationsScreen( - showNextButton = notificationsAlreadyGranted, - onSkip = { navController.navigate(IntroRoute.Location.route) }, - onConfigure = { - if (notificationsAlreadyGranted) { - navController.navigate(IntroRoute.CriticalAlerts.route) - } else { - // For Android Tiramisu (API 33) and above, this requests POST_NOTIFICATIONS - // For lower versions, notificationPermissionState will be null, and this branch isn't taken. - notificationPermissionState.launchPermissionRequest() - } - }, - ) - } - composable(IntroRoute.CriticalAlerts.route) { - CriticalAlertsScreen( - onSkip = { navController.navigate(IntroRoute.Location.route) }, - onConfigure = { - // Intent to open the specific notification channel settings for "my_alerts" - // This allows the user to enable critical alerts if they were initially denied - // or to adjust settings for notifications that can bypass Do Not Disturb. - val intent = - Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { - putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) - putExtra(Settings.EXTRA_CHANNEL_ID, "my_alerts") + val backStack = rememberNavBackStack(Welcome) + + NavDisplay( + backStack = backStack, + onBack = { backStack.removeLastOrNull() }, + entryProvider = + entryProvider { + entry { WelcomeScreen(onGetStarted = { backStack.add(Notifications) }) } + + entry { + val notificationsAlreadyGranted = notificationPermissionState?.status?.isGranted ?: true + NotificationsScreen( + showNextButton = notificationsAlreadyGranted, + onSkip = { + // Skip this screen and the Critical Alerts screen. Proceed to Location screen. + backStack.add(Location) + }, + onConfigure = { + if (notificationsAlreadyGranted) { + backStack.add(CriticalAlerts) + } else { + // For Android Tiramisu (API 33) and above, this requests POST_NOTIFICATIONS + // For lower versions, notificationPermissionState will be null, and this branch isn't + // taken. + notificationPermissionState?.launchPermissionRequest() } - context.startActivity(intent) - navController.navigate(IntroRoute.Location.route) - }, - ) - } - composable(IntroRoute.Location.route) { - val locationAlreadyGranted = locationPermissionState.allPermissionsGranted - LocationScreen( - showNextButton = locationAlreadyGranted, - onSkip = onDone, // Callback to signify completion of the intro flow - onConfigure = { - if (locationAlreadyGranted) { - onDone() // Permissions already granted, proceed to finish - } else { - locationPermissionState.launchMultiplePermissionRequest() - } - }, - ) - } - } + }, + ) + } + + entry { + CriticalAlertsScreen( + onSkip = { backStack.add(Location) }, + onConfigure = { + // Intent to open the specific notification channel settings for "my_alerts" + // This allows the user to enable critical alerts if they were initially denied + // or to adjust settings for notifications that can bypass Do Not Disturb. + val intent = + Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + putExtra(Settings.EXTRA_CHANNEL_ID, "my_alerts") + } + context.startActivity(intent) + backStack.add(Location) + }, + ) + } + + entry { + val locationAlreadyGranted = locationPermissionState.allPermissionsGranted + LocationScreen( + showNextButton = locationAlreadyGranted, + onSkip = onDone, // Callback to signify completion of the intro flow + onConfigure = { + if (locationAlreadyGranted) { + onDone() // Permissions already granted, proceed to finish + } else { + locationPermissionState.launchMultiplePermissionRequest() + } + }, + ) + } + }, + ) } + +@Serializable private data object Welcome : NavKey + +@Serializable private data object Notifications : NavKey + +@Serializable private data object CriticalAlerts : NavKey + +@Serializable private data object Location : NavKey diff --git a/app/src/main/java/com/geeksville/mesh/ui/intro/IntroRoute.kt b/app/src/main/java/com/geeksville/mesh/ui/intro/IntroRoute.kt deleted file mode 100644 index 4b2cc11ad..000000000 --- a/app/src/main/java/com/geeksville/mesh/ui/intro/IntroRoute.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2025 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 com.geeksville.mesh.ui.intro - -/** Sealed class defining type-safe navigation routes for the app introduction flow. */ -sealed class IntroRoute(val route: String) { - object Welcome : IntroRoute("welcome") - - object Notifications : IntroRoute("notifications") - - object Location : IntroRoute("location") - - object CriticalAlerts : IntroRoute("critical_alerts") -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f53839430..81ebbca7a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,6 +38,7 @@ material = "1.13.0" material3 = "1.5.0-alpha03" mgrs = "2.1.3" navigation = "2.9.3" +navigation3 = "1.0.0-alpha08" okhttp = "5.1.0" org-eclipse-paho-client-mqttv3 = "1.2.5" osmbonuspack = "6.9.0" @@ -129,6 +130,8 @@ material = { group = "com.google.android.material", name = "material", version.r mgrs = { group = "mil.nga", name = "mgrs", version.ref = "mgrs" } navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "navigation" } +navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3" } +navigation3-ui = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "navigation3" } okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } okhttp3-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } org-eclipse-paho-client-mqttv3 = { group = "org.eclipse.paho", name = "org.eclipse.paho.client.mqttv3", version.ref = "org-eclipse-paho-client-mqttv3" } @@ -166,6 +169,9 @@ lifecycle = ["lifecycle-runtime-ktx", "lifecycle-livedata-ktx", "lifecycle-viewm # Navigation navigation = ["navigation-compose"] +# Navigation 3 +navigation3 = ["navigation3-runtime", "navigation3-ui"] + # Coroutines coroutines = ["kotlinx-coroutines-android", "kotlinx-coroutines-guava"] From 08ced486529d87b4e4b8b324478830566fabcd3f Mon Sep 17 00:00:00 2001 From: Phil Oliver <3497406+poliver@users.noreply.github.com> Date: Fri, 5 Sep 2025 15:51:09 -0400 Subject: [PATCH 10/11] Move remaining 3-dot menu items to Settings (#2985) --- .../java/com/geeksville/mesh/MainActivity.kt | 31 +------- .../java/com/geeksville/mesh/model/UIState.kt | 2 +- .../main/java/com/geeksville/mesh/ui/Main.kt | 31 +++----- .../mesh/ui/common/components/MainAppBar.kt | 71 +++++------------- .../mesh/ui/connections/Connections.kt | 14 ++-- .../mesh/ui/settings/SettingsScreen.kt | 73 ++++++++++++++++++- .../mesh/ui/settings/radio/RadioConfig.kt | 34 --------- app/src/main/res/values/strings.xml | 4 +- 8 files changed, 111 insertions(+), 149 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index ecb15d6fc..2c54c6fc0 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -28,7 +28,6 @@ import android.os.Bundle import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate @@ -47,7 +46,6 @@ import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.navigation.DEEP_LINK_BASE_URI import com.geeksville.mesh.ui.MainScreen -import com.geeksville.mesh.ui.common.components.MainMenuAction import com.geeksville.mesh.ui.common.theme.AppTheme import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC import com.geeksville.mesh.ui.intro.AppIntroductionScreen @@ -117,11 +115,7 @@ class MainActivity : }, ) } else { - MainScreen( - uIViewModel = model, - bluetoothViewModel = bluetoothViewModel, - onAction = ::onMainMenuAction, - ) + MainScreen(uIViewModel = model, bluetoothViewModel = bluetoothViewModel) } } } @@ -204,30 +198,7 @@ class MainActivity : return resultPendingIntent!! } - private val createRangetestLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == RESULT_OK) { - it.data?.data?.let { file_uri -> model.saveRangetestCSV(file_uri) } - } - } - private fun showSettingsPage() { createSettingsIntent().send() } - - private fun onMainMenuAction(action: MainMenuAction) { - when (action) { - MainMenuAction.EXPORT_RANGETEST -> { - val intent = - Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "application/csv" - putExtra(Intent.EXTRA_TITLE, "rangetest.csv") - } - createRangetestLauncher.launch(intent) - } - - else -> warn("Unexpected action: $action") - } - } } diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index dfed41f21..4f677308f 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -847,7 +847,7 @@ constructor( /** Write the persisted packet data out to a CSV file in the specified location. */ @Suppress("detekt:CyclomaticComplexMethod", "detekt:LongMethod") - fun saveRangetestCSV(uri: Uri) { + fun saveRangeTestCsv(uri: Uri) { viewModelScope.launch(Dispatchers.Main) { // Extract distances to this device from position messages and put (node,SNR,distance) // in diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt index 3abb9431d..3e8e0f0e8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -94,7 +94,6 @@ import com.geeksville.mesh.repository.radio.MeshActivity import com.geeksville.mesh.service.ConnectionState import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.ui.common.components.MainAppBar -import com.geeksville.mesh.ui.common.components.MainMenuAction import com.geeksville.mesh.ui.common.components.MultipleChoiceAlertDialog import com.geeksville.mesh.ui.common.components.ScannedQrCodeDialog import com.geeksville.mesh.ui.common.components.SimpleAlertDialog @@ -146,7 +145,6 @@ fun MainScreen( uIViewModel: UIViewModel = hiltViewModel(), bluetoothViewModel: BluetoothViewModel = hiltViewModel(), scanModel: BTScanModel = hiltViewModel(), - onAction: (MainMenuAction) -> Unit, ) { val navController = rememberNavController() val connectionState by uIViewModel.connectionState.collectAsStateWithLifecycle() @@ -349,26 +347,19 @@ fun MainScreen( viewModel = uIViewModel, navController = navController, onAction = { action -> - if (action is MainMenuAction) { - when (action) { - MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat) - else -> onAction(action) + when (action) { + is NodeMenuAction.MoreDetails -> { + navController.navigate( + NodesRoutes.NodeDetailGraph(action.node.num), + { + launchSingleTop = true + restoreState = true + }, + ) } - } else if (action is NodeMenuAction) { - when (action) { - is NodeMenuAction.MoreDetails -> { - navController.navigate( - NodesRoutes.NodeDetailGraph(action.node.num), - { - launchSingleTop = true - restoreState = true - }, - ) - } - is NodeMenuAction.Share -> sharedContact = action.node - else -> {} - } + is NodeMenuAction.Share -> sharedContact = action.node + else -> {} } }, ) diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt b/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt index f3ae7fd26..35030ddc0 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt @@ -17,27 +17,21 @@ package com.geeksville.mesh.ui.common.components -import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.background +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource @@ -45,6 +39,7 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavDestination.Companion.hasRoute @@ -61,7 +56,7 @@ import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel import com.geeksville.mesh.ui.common.theme.AppTheme import com.geeksville.mesh.ui.debug.DebugMenuActions import com.geeksville.mesh.ui.node.components.NodeChip -import com.geeksville.mesh.ui.settings.radio.RadioConfigMenuActions +import com.geeksville.mesh.ui.node.components.NodeMenuAction @Suppress("CyclomaticComplexMethod") @Composable @@ -69,7 +64,7 @@ fun MainAppBar( modifier: Modifier = Modifier, viewModel: UIViewModel = hiltViewModel(), navController: NavHostController, - onAction: (Any?) -> Unit, + onAction: (NodeMenuAction) -> Unit, ) { val backStackEntry by navController.currentBackStackEntryAsState() val currentDestination = backStackEntry?.destination @@ -117,13 +112,7 @@ fun MainAppBar( actions = { currentDestination?.let { when { - it.isTopLevel() -> MainMenuActions(onAction) - currentDestination.hasRoute() -> DebugMenuActions() - - currentDestination.hasRoute() -> - RadioConfigMenuActions(viewModel = viewModel) - else -> {} } } @@ -144,7 +133,7 @@ private fun MainAppBar( canNavigateUp: Boolean, onNavigateUp: () -> Unit, actions: @Composable () -> Unit, - onAction: (Any?) -> Unit, + onAction: (NodeMenuAction) -> Unit, ) { TopAppBar( title = { @@ -195,43 +184,21 @@ private fun TopBarActions( isConnected: Boolean, showNodeChip: Boolean, actions: @Composable () -> Unit, - onAction: (Any?) -> Unit, + onAction: (NodeMenuAction) -> Unit, ) { - AnimatedVisibility(showNodeChip) { - ourNode?.let { NodeChip(node = it, isThisNode = true, isConnected = isConnected, onAction = onAction) } - } - - actions() -} - -enum class MainMenuAction(@StringRes val stringRes: Int) { - EXPORT_RANGETEST(R.string.save_rangetest), - QUICK_CHAT(R.string.quick_chat), -} - -@Composable -private fun MainMenuActions(onAction: (MainMenuAction) -> Unit) { - var showMenu by remember { mutableStateOf(false) } - IconButton(onClick = { showMenu = true }) { - Icon(imageVector = Icons.Default.MoreVert, contentDescription = stringResource(R.string.overflow_menu)) - } - - DropdownMenu( - expanded = showMenu, - onDismissRequest = { showMenu = false }, - modifier = Modifier.background(colorScheme.background.copy(alpha = 1f)), - ) { - MainMenuAction.entries.forEach { action -> - DropdownMenuItem( - text = { Text(stringResource(id = action.stringRes)) }, - onClick = { - onAction(action) - showMenu = false - }, - enabled = true, + AnimatedVisibility(visible = showNodeChip, enter = fadeIn(), exit = fadeOut()) { + ourNode?.let { + NodeChip( + modifier = Modifier.padding(horizontal = 16.dp), + node = it, + isThisNode = true, + isConnected = isConnected, + onAction = onAction, ) } } + + actions() } @PreviewLightDark @@ -246,7 +213,7 @@ private fun MainAppBarPreview(@PreviewParameter(BooleanProvider::class) canNavig showNodeChip = true, canNavigateUp = canNavigateUp, onNavigateUp = {}, - actions = { MainMenuActions(onAction = {}) }, + actions = {}, ) {} } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt index 20a8f3ac6..30a9d6815 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt @@ -25,7 +25,6 @@ import android.net.InetAddresses import android.os.Build import android.util.Patterns import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize @@ -77,7 +76,6 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.geeksville.mesh.BuildConfig import com.geeksville.mesh.ConfigProtos import com.geeksville.mesh.R import com.geeksville.mesh.android.gpsDisabled @@ -472,14 +470,12 @@ fun ConnectionsScreen( } Box(modifier = Modifier.fillMaxWidth().padding(8.dp)) { - Row( + Text( + text = scanStatusText.orEmpty(), modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text(text = BuildConfig.VERSION_NAME, fontSize = 10.sp, textAlign = TextAlign.Start) - Text(text = scanStatusText.orEmpty(), fontSize = 10.sp, textAlign = TextAlign.End) - } + fontSize = 10.sp, + textAlign = TextAlign.End, + ) } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt index 096889890..eede0aafc 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt @@ -19,8 +19,10 @@ package com.geeksville.mesh.ui.settings import android.app.Activity import android.content.Intent +import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity.RESULT_OK import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -30,9 +32,13 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.rounded.FormatPaint import androidx.compose.material.icons.rounded.Language +import androidx.compose.material.icons.rounded.Memory +import androidx.compose.material.icons.rounded.Output import androidx.compose.material.icons.rounded.WavingHand import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -42,8 +48,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.geeksville.mesh.BuildConfig import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile -import com.geeksville.mesh.DeviceUIProtos.Language import com.geeksville.mesh.R import com.geeksville.mesh.android.BuildUtils.debug import com.geeksville.mesh.model.UIViewModel @@ -52,12 +58,15 @@ import com.geeksville.mesh.navigation.getNavRouteFrom import com.geeksville.mesh.ui.common.components.TitledCard import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC import com.geeksville.mesh.ui.settings.components.SettingsItem +import com.geeksville.mesh.ui.settings.components.SettingsItemDetail import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch import com.geeksville.mesh.ui.settings.radio.RadioConfigItemList import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel import com.geeksville.mesh.ui.settings.radio.components.EditDeviceProfileDialog import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog import com.geeksville.mesh.util.LanguageUtils +import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.seconds @Suppress("LongMethod", "CyclomaticComplexMethod") @Composable @@ -166,7 +175,7 @@ fun SettingsScreen( onNavigate = onNavigate, ) - TitledCard(title = stringResource(R.string.phone_settings), modifier = Modifier.padding(top = 16.dp)) { + TitledCard(title = stringResource(R.string.app_settings), modifier = Modifier.padding(top = 16.dp)) { if (state.analyticsAvailable) { SettingsItemSwitch( text = stringResource(R.string.analytics_okay), @@ -214,6 +223,26 @@ fun SettingsScreen( ) } + val exportRangeTestLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == RESULT_OK) { + it.data?.data?.let { uri -> uiViewModel.saveRangeTestCsv(uri) } + } + } + SettingsItem( + text = stringResource(R.string.save_rangetest), + leadingIcon = Icons.Rounded.Output, + trailingIcon = null, + ) { + val intent = + Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/csv" + putExtra(Intent.EXTRA_TITLE, "rangetest.csv") + } + exportRangeTestLauncher.launch(intent) + } + SettingsItem( text = stringResource(R.string.intro_show), leadingIcon = Icons.Rounded.WavingHand, @@ -221,6 +250,46 @@ fun SettingsScreen( ) { uiViewModel.showAppIntro() } + + AppVersionButton(excludedModulesUnlocked) { uiViewModel.unlockExcludedModules() } + } + } +} + +private const val UNLOCK_CLICK_COUNT = 5 // Number of clicks required to unlock excluded modules. +private const val UNLOCKED_CLICK_COUNT = 3 // Number of clicks before we toast that modules are already unlocked. +private const val UNLOCK_TIMEOUT_SECONDS = 1 // Timeout in seconds to reset the click counter. + +/** A button to display the app version. Clicking it 5 times will unlock the excluded modules. */ +@Composable +private fun AppVersionButton(excludedModulesUnlocked: Boolean, onUnlockExcludedModules: () -> Unit) { + val context = LocalContext.current + var clickCount by remember { mutableIntStateOf(0) } + + LaunchedEffect(clickCount) { + if (clickCount in 1.. { + clickCount = 0 + Toast.makeText(context, context.getString(R.string.modules_already_unlocked), Toast.LENGTH_LONG).show() + } + clickCount == UNLOCK_CLICK_COUNT -> { + clickCount = 0 + onUnlockExcludedModules() + Toast.makeText(context, context.getString(R.string.modules_unlocked), Toast.LENGTH_LONG).show() + } } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfig.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfig.kt index e61ebe5b6..2cf523b5d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfig.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfig.kt @@ -17,7 +17,6 @@ package com.geeksville.mesh.ui.settings.radio -import android.widget.Toast import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons @@ -25,24 +24,19 @@ import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Upload import androidx.compose.material.icons.rounded.BugReport import androidx.compose.material.icons.rounded.CleaningServices -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import com.geeksville.mesh.R -import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.navigation.AdminRoute import com.geeksville.mesh.navigation.ConfigRoute import com.geeksville.mesh.navigation.ModuleRoute @@ -53,8 +47,6 @@ import com.geeksville.mesh.ui.common.theme.AppTheme import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed import com.geeksville.mesh.ui.settings.components.SettingsItem import com.geeksville.mesh.ui.settings.radio.components.WarningDialog -import kotlinx.coroutines.delay -import kotlin.time.Duration.Companion.seconds @Suppress("LongMethod", "CyclomaticComplexMethod") @Composable @@ -168,32 +160,6 @@ fun RadioConfigItemList( } } -private const val UNLOCK_CLICK_COUNT = 5 // Number of clicks required to unlock excluded modules. -private const val UNLOCK_TIMEOUT_SECONDS = 3 // Timeout in seconds to reset the click counter. - -@Composable -fun RadioConfigMenuActions(modifier: Modifier = Modifier, viewModel: UIViewModel = hiltViewModel()) { - val context = LocalContext.current - var counter by remember { mutableIntStateOf(0) } - LaunchedEffect(counter) { - if (counter > 0 && counter < UNLOCK_CLICK_COUNT) { - delay(UNLOCK_TIMEOUT_SECONDS.seconds) - counter = 0 - } - } - IconButton( - enabled = counter < UNLOCK_CLICK_COUNT, - onClick = { - counter++ - if (counter == UNLOCK_CLICK_COUNT) { - viewModel.unlockExcludedModules() - Toast.makeText(context, context.getString(R.string.modules_unlocked), Toast.LENGTH_LONG).show() - } - }, - modifier = modifier, - ) {} -} - @Preview(showBackground = true) @Composable private fun RadioSettingsScreenPreview() = AppTheme { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 77b4473b9..51faa4391 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -653,6 +653,7 @@ Export Keys Exports public and private keys to a file. Please store somewhere securely. Modules unlocked + Modules already unlocked Remote (%1$d online / %2$d total) React @@ -801,7 +802,8 @@ URL Template https://a.tile.openstreetmap.org/{z}/{x}/{y}.png track point - Phone Settings + App + Version Channel Features Location Sharing Periodic position broadcast From 0b34943f25a1b2eed7c18d383fa7cda8a1aff290 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:00:45 +0000 Subject: [PATCH 11/11] chore(deps): update google maps compose to v6.9.0 (#2986) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 81ebbca7a..9f93b1da6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ kotlinx-coroutines-android = "1.10.2" kotlinx-serialization-json = "1.9.0" lifecycle = "2.9.3" location-services = "21.3.0" -maps-compose = "6.8.0" +maps-compose = "6.9.0" markdownRenderer = "0.35.0" material = "1.13.0" material3 = "1.5.0-alpha03"