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"