From 6b1ce275944e95d25a87a1b4b04962cf0f78ffda Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Thu, 4 Sep 2025 15:17:21 -0500
Subject: [PATCH 01/13] fix: Set 'google' as default product flavor (#2960)
---
app/build.gradle.kts | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index dad29ebbb..ff7d3730f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -114,24 +114,25 @@ android {
)
ndk { abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") }
}
- flavorDimensions.add("default")
+ flavorDimensions += "default"
productFlavors {
// Read versionCode from defaultConfig after it's been potentially set by ENV or fallback
val resolvedVersionCode = defaultConfig.versionCode
val resolvedVersionName = defaultConfig.versionName
- create("fdroid") {
- dimension = "default"
- dependenciesInfo { includeInApk = false }
- versionName = "$resolvedVersionName ($resolvedVersionCode) fdroid"
- }
create("google") {
dimension = "default"
+ isDefault = true
// Enable Firebase Crashlytics for Google Play builds
apply(plugin = libs.plugins.google.services.get().pluginId)
apply(plugin = libs.plugins.firebase.crashlytics.get().pluginId)
versionName = "$resolvedVersionName ($resolvedVersionCode) google"
}
+ create("fdroid") {
+ dimension = "default"
+ dependenciesInfo { includeInApk = false }
+ versionName = "$resolvedVersionName ($resolvedVersionCode) fdroid"
+ }
}
buildTypes {
release {
From ab3c0184c6a9f659c5783d32ccdda56246f5fb68 Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Thu, 4 Sep 2025 16:28:50 -0500
Subject: [PATCH 02/13] New Crowdin updates (#2959)
---
app/src/main/res/values-ar-rSA/strings.xml | 1 +
app/src/main/res/values-b+sr+Latn/strings.xml | 1 +
app/src/main/res/values-bg-rBG/strings.xml | 1 +
app/src/main/res/values-ca-rES/strings.xml | 1 +
app/src/main/res/values-cs-rCZ/strings.xml | 1 +
app/src/main/res/values-de-rDE/strings.xml | 1 +
app/src/main/res/values-el-rGR/strings.xml | 1 +
app/src/main/res/values-es-rES/strings.xml | 1 +
app/src/main/res/values-et-rEE/strings.xml | 1 +
app/src/main/res/values-fi-rFI/strings.xml | 1 +
app/src/main/res/values-fr-rFR/strings.xml | 1 +
app/src/main/res/values-ga-rIE/strings.xml | 1 +
app/src/main/res/values-gl-rES/strings.xml | 1 +
app/src/main/res/values-hr-rHR/strings.xml | 1 +
app/src/main/res/values-ht-rHT/strings.xml | 1 +
app/src/main/res/values-hu-rHU/strings.xml | 1 +
app/src/main/res/values-is-rIS/strings.xml | 1 +
app/src/main/res/values-it-rIT/strings.xml | 1 +
app/src/main/res/values-iw-rIL/strings.xml | 1 +
app/src/main/res/values-ja-rJP/strings.xml | 1 +
app/src/main/res/values-ko-rKR/strings.xml | 1 +
app/src/main/res/values-lt-rLT/strings.xml | 1 +
app/src/main/res/values-nl-rNL/strings.xml | 1 +
app/src/main/res/values-no-rNO/strings.xml | 1 +
app/src/main/res/values-pl-rPL/strings.xml | 1 +
app/src/main/res/values-pt-rBR/strings.xml | 1 +
app/src/main/res/values-pt-rPT/strings.xml | 1 +
app/src/main/res/values-ro-rRO/strings.xml | 1 +
app/src/main/res/values-ru-rRU/strings.xml | 1 +
app/src/main/res/values-sk-rSK/strings.xml | 1 +
app/src/main/res/values-sl-rSI/strings.xml | 1 +
app/src/main/res/values-sq-rAL/strings.xml | 1 +
app/src/main/res/values-srp/strings.xml | 1 +
app/src/main/res/values-sv-rSE/strings.xml | 1 +
app/src/main/res/values-tr-rTR/strings.xml | 1 +
app/src/main/res/values-uk-rUA/strings.xml | 1 +
app/src/main/res/values-zh-rCN/strings.xml | 1 +
app/src/main/res/values-zh-rTW/strings.xml | 1 +
38 files changed, 38 insertions(+)
diff --git a/app/src/main/res/values-ar-rSA/strings.xml b/app/src/main/res/values-ar-rSA/strings.xml
index ce6f5c288..907d3f667 100644
--- a/app/src/main/res/values-ar-rSA/strings.xml
+++ b/app/src/main/res/values-ar-rSA/strings.xml
@@ -322,6 +322,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
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 2aa451cbc..fd688abb5 100644
--- a/app/src/main/res/values-b+sr+Latn/strings.xml
+++ b/app/src/main/res/values-b+sr+Latn/strings.xml
@@ -316,6 +316,7 @@
UDP конфигурација
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
Корисник
Канали
Уређај
diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml
index 8169d01a7..527ac9756 100644
--- a/app/src/main/res/values-bg-rBG/strings.xml
+++ b/app/src/main/res/values-bg-rBG/strings.xml
@@ -314,6 +314,7 @@
Конфигуриране на UDP
Последно чут: %2$s
Последна позиция: %3$s
Батерия: %4$s]]>
Toggle my position
+ Orient north
Потребител
Канали
Устройство
diff --git a/app/src/main/res/values-ca-rES/strings.xml b/app/src/main/res/values-ca-rES/strings.xml
index acf9d7b57..f97780d87 100644
--- a/app/src/main/res/values-ca-rES/strings.xml
+++ b/app/src/main/res/values-ca-rES/strings.xml
@@ -314,6 +314,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml
index 3afc01eb9..1e3dec05f 100644
--- a/app/src/main/res/values-cs-rCZ/strings.xml
+++ b/app/src/main/res/values-cs-rCZ/strings.xml
@@ -318,6 +318,7 @@
UDP Konfigurace
Naposledy slyšen: %2$s
Poslední pozice: %3$s
Baterie: %4$s]]>
Zapnout/vypnout pozici
+ Orient north
Uživatel
Kanály
Zařízení
diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml
index 9a89292f5..2ed67df92 100644
--- a/app/src/main/res/values-de-rDE/strings.xml
+++ b/app/src/main/res/values-de-rDE/strings.xml
@@ -314,6 +314,7 @@
UDP Konfiguration
Zuletzt gehört:%2$s
Letzte Position:%3$s
Akku:%4$s]]>
Position einschalten
+ Ausrichtung Nord
Benutzer
Kanäle
Gerät
diff --git a/app/src/main/res/values-el-rGR/strings.xml b/app/src/main/res/values-el-rGR/strings.xml
index aafdb00e8..55cae6318 100644
--- a/app/src/main/res/values-el-rGR/strings.xml
+++ b/app/src/main/res/values-el-rGR/strings.xml
@@ -314,6 +314,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
Χρήστης
Κανάλια
Συσκευή
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
index 9024b2ccc..bceac83f2 100644
--- a/app/src/main/res/values-es-rES/strings.xml
+++ b/app/src/main/res/values-es-rES/strings.xml
@@ -315,6 +315,7 @@ Rango de Valores 0 - 500.
Configuración UDP
Última escucha: %2$s
Última posición: %3$s
Batería: %4$s]]>
Cambiar mi posición
+ Orient north
Usuario
Canales
Dispositivo
diff --git a/app/src/main/res/values-et-rEE/strings.xml b/app/src/main/res/values-et-rEE/strings.xml
index 5ef256c4d..ca359314e 100644
--- a/app/src/main/res/values-et-rEE/strings.xml
+++ b/app/src/main/res/values-et-rEE/strings.xml
@@ -314,6 +314,7 @@
UDP sätted
Viimati kuuldud: %2$s
viimane asukoht: %3$s
Akupinge: %4$s]]>
Lülita asukoht sisse
+ Orient north
Kasutaja
Kanal
Seade
diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml
index bd49a0b77..69a0a8a42 100644
--- a/app/src/main/res/values-fi-rFI/strings.xml
+++ b/app/src/main/res/values-fi-rFI/strings.xml
@@ -314,6 +314,7 @@
UDP asetukset
Viimeksi kuultu: %2$s
Viimeisin sijainti: %3$s
Akku: %4$s]]>
Kytke sijainti päälle
+ Aseta kompassi pohjoiseen
Käyttäjä
Kanavat
Laite
diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml
index 6a8d48138..92402f576 100644
--- a/app/src/main/res/values-fr-rFR/strings.xml
+++ b/app/src/main/res/values-fr-rFR/strings.xml
@@ -314,6 +314,7 @@
Configuration UDP
Dernière écoute : %2$s
Dernière position : %3$s
Batterie : %4$s]]>
Basculer ma position
+ Orient north
Utilisateur
Canaux
Appareil
diff --git a/app/src/main/res/values-ga-rIE/strings.xml b/app/src/main/res/values-ga-rIE/strings.xml
index 5f7e21fce..33e5567df 100644
--- a/app/src/main/res/values-ga-rIE/strings.xml
+++ b/app/src/main/res/values-ga-rIE/strings.xml
@@ -320,6 +320,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-gl-rES/strings.xml b/app/src/main/res/values-gl-rES/strings.xml
index f8d22398b..e0d182f68 100644
--- a/app/src/main/res/values-gl-rES/strings.xml
+++ b/app/src/main/res/values-gl-rES/strings.xml
@@ -314,6 +314,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-hr-rHR/strings.xml b/app/src/main/res/values-hr-rHR/strings.xml
index 760f015b6..856cca42d 100644
--- a/app/src/main/res/values-hr-rHR/strings.xml
+++ b/app/src/main/res/values-hr-rHR/strings.xml
@@ -316,6 +316,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-ht-rHT/strings.xml b/app/src/main/res/values-ht-rHT/strings.xml
index 83c4272b0..f9d173bf2 100644
--- a/app/src/main/res/values-ht-rHT/strings.xml
+++ b/app/src/main/res/values-ht-rHT/strings.xml
@@ -314,6 +314,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml
index dbb77cb2e..903460cd9 100644
--- a/app/src/main/res/values-hu-rHU/strings.xml
+++ b/app/src/main/res/values-hu-rHU/strings.xml
@@ -314,6 +314,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-is-rIS/strings.xml b/app/src/main/res/values-is-rIS/strings.xml
index 847617df1..c337ccd04 100644
--- a/app/src/main/res/values-is-rIS/strings.xml
+++ b/app/src/main/res/values-is-rIS/strings.xml
@@ -314,6 +314,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml
index 442b297bf..a0eabae2d 100644
--- a/app/src/main/res/values-it-rIT/strings.xml
+++ b/app/src/main/res/values-it-rIT/strings.xml
@@ -314,6 +314,7 @@
Configurazione UDP
Ricevuto l\'ultima volta: %2$s
Posizione più recente: %3$s
Batteria: %4$s]]>
Attiva/disattiva posizione
+ Orient north
Utente
Canali
Dispositivo
diff --git a/app/src/main/res/values-iw-rIL/strings.xml b/app/src/main/res/values-iw-rIL/strings.xml
index dacc85666..e851cc386 100644
--- a/app/src/main/res/values-iw-rIL/strings.xml
+++ b/app/src/main/res/values-iw-rIL/strings.xml
@@ -318,6 +318,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml
index 9bd4d1029..fe8dc8501 100644
--- a/app/src/main/res/values-ja-rJP/strings.xml
+++ b/app/src/main/res/values-ja-rJP/strings.xml
@@ -313,6 +313,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
自分の位置を切り替え
+ Orient north
ユーザー
チャンネル
接続するデバイスを選択
diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml
index 3f449b5c6..a58e1c48e 100644
--- a/app/src/main/res/values-ko-rKR/strings.xml
+++ b/app/src/main/res/values-ko-rKR/strings.xml
@@ -312,6 +312,7 @@
UDP 설정
최근 수신: %2$s
최근 위치: %3$s
배터리: %4$s]]>
내 위치 토글
+ Orient north
사용자
채널
장치
diff --git a/app/src/main/res/values-lt-rLT/strings.xml b/app/src/main/res/values-lt-rLT/strings.xml
index 878801c6d..318e32fa8 100644
--- a/app/src/main/res/values-lt-rLT/strings.xml
+++ b/app/src/main/res/values-lt-rLT/strings.xml
@@ -318,6 +318,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml
index 664a2240d..a1fa3d9bc 100644
--- a/app/src/main/res/values-nl-rNL/strings.xml
+++ b/app/src/main/res/values-nl-rNL/strings.xml
@@ -314,6 +314,7 @@
UDP Configuratie
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Wissel mijn positie
+ Orient north
Gebruiker
Kanalen
Apparaat
diff --git a/app/src/main/res/values-no-rNO/strings.xml b/app/src/main/res/values-no-rNO/strings.xml
index 4cfba8a34..685303ef7 100644
--- a/app/src/main/res/values-no-rNO/strings.xml
+++ b/app/src/main/res/values-no-rNO/strings.xml
@@ -314,6 +314,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml
index 0a0c9a222..912b58983 100644
--- a/app/src/main/res/values-pl-rPL/strings.xml
+++ b/app/src/main/res/values-pl-rPL/strings.xml
@@ -318,6 +318,7 @@
Ustawienia UDP
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Pokaż moją pozycję
+ Orient north
Użytkownik
Kanały
Urządzenie
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 87d237e31..9152d2882 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -314,6 +314,7 @@
Configuração UDP
Última vez: %2$s
Última posição: %3$s
Bateria: %4$s]]>
Ativar minha posição
+ Orient north
Usuário
Canais
Dispositivo
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index cc9f36300..3a0fe90c0 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -314,6 +314,7 @@
Configuração UDP
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
Utilizador
Canal
Dispositivo
diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml
index 68aed05ae..d3e74a91e 100644
--- a/app/src/main/res/values-ro-rRO/strings.xml
+++ b/app/src/main/res/values-ro-rRO/strings.xml
@@ -316,6 +316,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml
index 87db4a36c..093d9faa5 100644
--- a/app/src/main/res/values-ru-rRU/strings.xml
+++ b/app/src/main/res/values-ru-rRU/strings.xml
@@ -318,6 +318,7 @@
UDP Config
Последний приём: %2$s
Последнее местоположение: %3$s
Батарея: %4$s]]>
Переключить мою позицию
+ Orient north
Пользователь
Каналы
Устройство
diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml
index 14ef40866..5c9573d3e 100644
--- a/app/src/main/res/values-sk-rSK/strings.xml
+++ b/app/src/main/res/values-sk-rSK/strings.xml
@@ -318,6 +318,7 @@
Konfigurácia UDP
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Zapnúť lokalizáciu
+ Orient north
Užívateľ
Kanále
Zariadenie
diff --git a/app/src/main/res/values-sl-rSI/strings.xml b/app/src/main/res/values-sl-rSI/strings.xml
index b29e6663e..212c3cc0f 100644
--- a/app/src/main/res/values-sl-rSI/strings.xml
+++ b/app/src/main/res/values-sl-rSI/strings.xml
@@ -318,6 +318,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-sq-rAL/strings.xml b/app/src/main/res/values-sq-rAL/strings.xml
index 22e9ad8cd..f30d73205 100644
--- a/app/src/main/res/values-sq-rAL/strings.xml
+++ b/app/src/main/res/values-sq-rAL/strings.xml
@@ -314,6 +314,7 @@
UDP Config
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Channels
Device
diff --git a/app/src/main/res/values-srp/strings.xml b/app/src/main/res/values-srp/strings.xml
index 47386a942..5a904c1f7 100644
--- a/app/src/main/res/values-srp/strings.xml
+++ b/app/src/main/res/values-srp/strings.xml
@@ -316,6 +316,7 @@
UDP конфигурација
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
Корисник
Канали
Уређај
diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml
index ba6898324..2cef79d53 100644
--- a/app/src/main/res/values-sv-rSE/strings.xml
+++ b/app/src/main/res/values-sv-rSE/strings.xml
@@ -314,6 +314,7 @@
UDP-konfiguration
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
User
Kanaler
Device
diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml
index 18db3dbab..c16dcc706 100644
--- a/app/src/main/res/values-tr-rTR/strings.xml
+++ b/app/src/main/res/values-tr-rTR/strings.xml
@@ -314,6 +314,7 @@
UDP Ayarları
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Konumunumu aç/kapa
+ Orient north
Kullanıcı
Kanallar
Cihaz
diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml
index bbb1f600b..9418e318a 100644
--- a/app/src/main/res/values-uk-rUA/strings.xml
+++ b/app/src/main/res/values-uk-rUA/strings.xml
@@ -318,6 +318,7 @@
Налаштування UDP
Last heard: %2$s
Last position: %3$s
Battery: %4$s]]>
Toggle my position
+ Orient north
Користувач
Канали
Пристрій
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 3d6b3d47e..da11a3d18 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -314,6 +314,7 @@
UDP 设置
最后听到: %2$s
最后位置: %3$s
电量: %4$s]]>
切换我的位置
+ Orient north
用户
频道
设备
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 47762b600..17f49dbf8 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -312,6 +312,7 @@
UDP設置
最後接收: %2$s
最後位置: %3$s
電量: %4$s]]>
切換我的位置
+ Orient north
用戶
頻道
裝置
From 05d2ade9f731a83652f387bbd3d2bae54847d184 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 4 Sep 2025 16:29:13 -0500
Subject: [PATCH 03/13] chore(deps): update actions/github-script action to v8
(#2961)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
.github/workflows/pr_enforce_labels.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/pr_enforce_labels.yml b/.github/workflows/pr_enforce_labels.yml
index 5ef8023b0..bd4072304 100644
--- a/.github/workflows/pr_enforce_labels.yml
+++ b/.github/workflows/pr_enforce_labels.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check for PR labels
- uses: actions/github-script@v7
+ uses: actions/github-script@v8
with:
script: |
const labels = context.payload.pull_request.labels.map(label => label.name);
From 20c87cdafd9b3441396f8d46e93a9990ce744b43 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 4 Sep 2025 16:29:27 -0500
Subject: [PATCH 04/13] chore(deps): update actions/labeler action to v6
(#2963)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
.github/workflows/pull-request-target.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/pull-request-target.yml b/.github/workflows/pull-request-target.yml
index cf0325677..cebe7e588 100644
--- a/.github/workflows/pull-request-target.yml
+++ b/.github/workflows/pull-request-target.yml
@@ -12,4 +12,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: label-the-PR
- uses: actions/labeler@v5
\ No newline at end of file
+ uses: actions/labeler@v6
\ No newline at end of file
From 79eb62edac0394adc7139e2219b69724dd1febf1 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 4 Sep 2025 22:48:06 +0000
Subject: [PATCH 05/13] chore(config): migrate renovate config (#2964)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
.github/renovate.json | 180 ++++++++++++++++++++++++++----------------
1 file changed, 112 insertions(+), 68 deletions(-)
diff --git a/.github/renovate.json b/.github/renovate.json
index dc58ade32..106fa5eee 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -17,7 +17,10 @@
},
"packageRules": [
{
- "matchUpdateTypes": ["minor", "patch"],
+ "matchUpdateTypes": [
+ "minor",
+ "patch"
+ ],
"matchCurrentVersion": "!/^0/",
"automerge": true
},
@@ -37,153 +40,194 @@
},
{
"description": "Group all AndroidX dependencies (excluding more specific AndroidX groups)",
- "matchPackagePatterns": ["^androidx\\."],
- "excludePackagePatterns": [
- "^androidx\\.room",
- "^androidx\\.lifecycle",
- "^androidx\\.navigation",
- "^androidx\\.datastore",
- "^androidx\\.compose\\.material3\\.adaptive",
- "^androidx\\.compose\\.material3:material3-adaptive-navigation-suite$",
- "^androidx\\.test\\.espresso",
- "^androidx\\.test\\.ext",
- "^androidx\\.compose\\.ui:ui-test-junit4$",
- "^androidx\\.hilt"
- ],
"groupName": "AndroidX (General)",
- "groupSlug": "androidx-general"
+ "groupSlug": "androidx-general",
+ "matchPackageNames": [
+ "/^androidx\\./",
+ "!/^androidx\\.room/",
+ "!/^androidx\\.lifecycle/",
+ "!/^androidx\\.navigation/",
+ "!/^androidx\\.datastore/",
+ "!/^androidx\\.compose\\.material3\\.adaptive/",
+ "!/^androidx\\.compose\\.material3:material3-adaptive-navigation-suite$/",
+ "!/^androidx\\.test\\.espresso/",
+ "!/^androidx\\.test\\.ext/",
+ "!/^androidx\\.compose\\.ui:ui-test-junit4$/",
+ "!/^androidx\\.hilt/"
+ ]
},
{
"description": "Group Kotlin standard library, coroutines, and serialization",
- "matchPackagePatterns": ["^org\\.jetbrains\\.kotlin", "^org\\.jetbrains\\.kotlinx"],
"groupName": "Kotlin Ecosystem",
- "groupSlug": "kotlin"
+ "groupSlug": "kotlin",
+ "matchPackageNames": [
+ "/^org\\.jetbrains\\.kotlin/",
+ "/^org\\.jetbrains\\.kotlinx/"
+ ]
},
{
"description": "Group Dagger and Hilt dependencies",
- "matchPackagePatterns": ["^com\\.google\\.dagger", "^androidx\\.hilt"],
"groupName": "Dagger & Hilt",
- "groupSlug": "hilt"
+ "groupSlug": "hilt",
+ "matchPackageNames": [
+ "/^com\\.google\\.dagger/",
+ "/^androidx\\.hilt/"
+ ]
},
{
"description": "Group Accompanist libraries",
- "matchPackagePatterns": ["^com\\.google\\.accompanist"],
"groupName": "Accompanist",
- "groupSlug": "accompanist"
+ "groupSlug": "accompanist",
+ "matchPackageNames": [
+ "/^com\\.google\\.accompanist/"
+ ]
},
{
"description": "Group JVM testing libraries (JUnit, Mockito, Robolectric)",
- "matchPackagePatterns": [
- "^junit:junit$",
- "^org\\.mockito:",
- "^org\\.robolectric:robolectric$"
- ],
"groupName": "JVM Testing Libraries",
- "groupSlug": "jvm-testing"
+ "groupSlug": "jvm-testing",
+ "matchPackageNames": [
+ "/^junit:junit$/",
+ "/^org\\.mockito:/",
+ "/^org\\.robolectric:robolectric$/"
+ ]
},
{
"description": "Group AndroidX Testing libraries",
- "matchPackagePatterns": [
- "^androidx\\.test\\.espresso",
- "^androidx\\.test\\.ext",
- "^androidx\\.compose\\.ui:ui-test-junit4$"
- ],
"groupName": "AndroidX Testing",
- "groupSlug": "androidx-testing"
+ "groupSlug": "androidx-testing",
+ "matchPackageNames": [
+ "/^androidx\\.test\\.espresso/",
+ "/^androidx\\.test\\.ext/",
+ "/^androidx\\.compose\\.ui:ui-test-junit4$/"
+ ]
},
{
"description": "Group Square networking libraries (OkHttp, Retrofit)",
- "matchPackagePatterns": ["^com\\.squareup\\.okhttp3", "^com\\.squareup\\.retrofit2"],
"groupName": "Square Networking",
- "groupSlug": "square-network"
+ "groupSlug": "square-network",
+ "matchPackageNames": [
+ "/^com\\.squareup\\.okhttp3/",
+ "/^com\\.squareup\\.retrofit2/"
+ ]
},
{
"description": "Group Coil image loading library",
- "matchPackagePatterns": ["^io\\.coil-kt\\.coil3"],
"groupName": "Coil",
- "groupSlug": "coil"
+ "groupSlug": "coil",
+ "matchPackageNames": [
+ "/^io\\.coil-kt\\.coil3/"
+ ]
},
{
"description": "Group ZXing barcode scanning libraries",
- "matchPackagePatterns": ["^com\\.journeyapps:zxing-android-embedded", "^com\\.google\\.zxing:core"],
"groupName": "ZXing",
- "groupSlug": "zxing"
+ "groupSlug": "zxing",
+ "matchPackageNames": [
+ "/^com\\.journeyapps:zxing-android-embedded/",
+ "/^com\\.google\\.zxing:core/"
+ ]
},
{
"description": "Group Eclipse Paho MQTT client libraries",
- "matchPackagePatterns": ["^org\\.eclipse\\.paho"],
"groupName": "MQTT Paho Client",
- "groupSlug": "mqtt-paho"
+ "groupSlug": "mqtt-paho",
+ "matchPackageNames": [
+ "/^org\\.eclipse\\.paho/"
+ ]
},
{
"description": "Group Mike Penz Markdown renderer libraries",
- "matchPackagePatterns": ["^com\\.mikepenz"],
"groupName": "Markdown Renderer (Mike Penz)",
- "groupSlug": "markdown-renderer-mikepenz"
+ "groupSlug": "markdown-renderer-mikepenz",
+ "matchPackageNames": [
+ "/^com\\.mikepenz/"
+ ]
},
{
"description": "Group Firebase libraries",
- "matchPackagePatterns": ["^com\\.google\\.firebase"],
"groupName": "Firebase",
- "groupSlug": "firebase"
+ "groupSlug": "firebase",
+ "matchPackageNames": [
+ "/^com\\.google\\.firebase/"
+ ]
},
{
"description": "Group Datadog libraries",
- "matchPackagePatterns": ["^com\\.datadoghq"],
"groupName": "Datadog",
- "groupSlug": "datadog"
+ "groupSlug": "datadog",
+ "matchPackageNames": [
+ "/^com\\.datadoghq/"
+ ]
},
{
"description": "Group OpenStreetMap (OSM) libraries",
- "matchPackagePatterns": ["^org\\.osmdroid", "^com\\.github\\.MKergall\\.osmbonuspack", "^mil\\.nga"],
"groupName": "OSM Libraries",
- "groupSlug": "osm-libraries"
+ "groupSlug": "osm-libraries",
+ "matchPackageNames": [
+ "/^org\\.osmdroid/",
+ "/^com\\.github\\.MKergall\\.osmbonuspack/",
+ "/^mil\\.nga/"
+ ]
},
{
"description": "Group Google Maps Compose libraries",
- "matchPackagePatterns": ["^com\\.google\\.android\\.gms:play-services-location", "^com\\.google\\.maps\\.android"],
"groupName": "Google Maps Compose",
- "groupSlug": "google-maps-compose"
+ "groupSlug": "google-maps-compose",
+ "matchPackageNames": [
+ "/^com\\.google\\.android\\.gms:play-services-location/",
+ "/^com\\.google\\.maps\\.android/"
+ ]
},
{
"description": "Group Google Protobuf runtime libraries",
- "matchPackagePatterns": ["^com\\.google\\.protobuf"],
- "excludePackageNames": ["https://github.com/meshtastic/protobufs.git"],
"groupName": "Protobuf Runtime",
- "groupSlug": "protobuf-runtime"
+ "groupSlug": "protobuf-runtime",
+ "matchPackageNames": [
+ "/^com\\.google\\.protobuf/",
+ "!https://github.com/meshtastic/protobufs.git"
+ ]
},
{
"description": "Group AndroidX Room libraries",
- "matchPackagePatterns": ["^androidx\\.room"],
"groupName": "AndroidX Room",
- "groupSlug": "androidx-room"
+ "groupSlug": "androidx-room",
+ "matchPackageNames": [
+ "/^androidx\\.room/"
+ ]
},
{
"description": "Group AndroidX Lifecycle libraries",
- "matchPackagePatterns": ["^androidx\\.lifecycle"],
"groupName": "AndroidX Lifecycle",
- "groupSlug": "androidx-lifecycle"
+ "groupSlug": "androidx-lifecycle",
+ "matchPackageNames": [
+ "/^androidx\\.lifecycle/"
+ ]
},
{
"description": "Group AndroidX Navigation libraries",
- "matchPackagePatterns": ["^androidx\\.navigation"],
"groupName": "AndroidX Navigation",
- "groupSlug": "androidx-navigation"
+ "groupSlug": "androidx-navigation",
+ "matchPackageNames": [
+ "/^androidx\\.navigation/"
+ ]
},
{
"description": "Group AndroidX DataStore libraries",
- "matchPackagePatterns": ["^androidx\\.datastore"],
"groupName": "AndroidX DataStore",
- "groupSlug": "androidx-datastore"
+ "groupSlug": "androidx-datastore",
+ "matchPackageNames": [
+ "/^androidx\\.datastore/"
+ ]
},
{
"description": "Group AndroidX Adaptive UI libraries",
- "matchPackagePatterns": [
- "^androidx\\.compose\\.material3\\.adaptive",
- "^androidx\\.compose\\.material3:material3-adaptive-navigation-suite$"
- ],
"groupName": "AndroidX Adaptive UI",
- "groupSlug": "androidx-adaptive-ui"
+ "groupSlug": "androidx-adaptive-ui",
+ "matchPackageNames": [
+ "/^androidx\\.compose\\.material3\\.adaptive/",
+ "/^androidx\\.compose\\.material3:material3-adaptive-navigation-suite$/"
+ ]
}
]
-}
\ No newline at end of file
+}
From 2583b3fcf1e896ad5abeb4408a302510a3fba8f6 Mon Sep 17 00:00:00 2001
From: Phil Oliver <3497406+poliver@users.noreply.github.com>
Date: Thu, 4 Sep 2025 20:34:28 -0400
Subject: [PATCH 06/13] Componentize traceroute button (#2965)
---
.../com/geeksville/mesh/ui/node/NodeDetail.kt | 61 +----------
.../ui/node/components/TracerouteButton.kt | 100 ++++++++++++++++++
.../ui/settings/components/SettingsItem.kt | 28 +++--
3 files changed, 123 insertions(+), 66 deletions(-)
create mode 100644 app/src/main/java/com/geeksville/mesh/ui/node/components/TracerouteButton.kt
diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt b/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt
index a3871c453..ef37619f5 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt
@@ -20,8 +20,6 @@ package com.geeksville.mesh.ui.node
import android.content.Intent
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -93,11 +91,9 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.ModalBottomSheet
-import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -150,6 +146,7 @@ import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
import com.geeksville.mesh.ui.node.components.NodeActionDialogs
import com.geeksville.mesh.ui.node.components.NodeMenuAction
+import com.geeksville.mesh.ui.node.components.TracerouteButton
import com.geeksville.mesh.ui.settings.components.SettingsItem
import com.geeksville.mesh.ui.settings.components.SettingsItemDetail
import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch
@@ -631,8 +628,7 @@ private fun RemoteDeviceActions(node: Node, lastTracerouteTime: Long?, onAction:
trailingIcon = null,
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.RequestUserInfo(node))) },
)
- TracerouteActionButton(
- title = stringResource(id = R.string.traceroute),
+ TracerouteButton(
lastTracerouteTime = lastTracerouteTime,
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.TraceRoute(node))) },
)
@@ -1035,59 +1031,6 @@ private fun PowerMetrics(node: Node) {
}
}
-private const val COOL_DOWN_TIME_MS = 30000L
-
-@Composable
-fun TracerouteActionButton(title: String, lastTracerouteTime: Long?, onClick: () -> Unit) {
- val progress = remember { Animatable(0f) }
- var isCoolingDown by remember { mutableStateOf(false) }
-
- LaunchedEffect(lastTracerouteTime) {
- val timeSinceLast = System.currentTimeMillis() - (lastTracerouteTime ?: 0)
- isCoolingDown = timeSinceLast < COOL_DOWN_TIME_MS
-
- if (isCoolingDown) {
- val remainingTime = COOL_DOWN_TIME_MS - timeSinceLast
- progress.snapTo(remainingTime / COOL_DOWN_TIME_MS.toFloat())
- progress.animateTo(
- targetValue = 0f,
- animationSpec = tween(durationMillis = remainingTime.toInt(), easing = { it }),
- )
- isCoolingDown = false
- }
- }
-
- Button(
- onClick = {
- if (!isCoolingDown) {
- onClick()
- }
- },
- enabled = !isCoolingDown,
- modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp).height(48.dp),
- ) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- if (isCoolingDown) {
- CircularProgressIndicator(
- progress = { progress.value },
- modifier = Modifier.size(24.dp),
- strokeWidth = 2.dp,
- trackColor = ProgressIndicatorDefaults.circularDeterminateTrackColor,
- strokeCap = ProgressIndicatorDefaults.CircularDeterminateStrokeCap,
- )
- } else {
- Icon(
- imageVector = Icons.Default.Route,
- contentDescription = stringResource(R.string.traceroute),
- modifier = Modifier.size(24.dp),
- )
- }
- Spacer(modifier = Modifier.width(8.dp))
- Text(text = title, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
- }
- }
-}
-
@Composable
fun NodeActionButton(
modifier: Modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp).height(48.dp),
diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/components/TracerouteButton.kt b/app/src/main/java/com/geeksville/mesh/ui/node/components/TracerouteButton.kt
new file mode 100644
index 000000000..ac1f34805
--- /dev/null
+++ b/app/src/main/java/com/geeksville/mesh/ui/node/components/TracerouteButton.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.node.components
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Route
+import androidx.compose.material3.CircularWavyProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.platform.LocalDensity
+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.ui.common.theme.AppTheme
+import com.geeksville.mesh.ui.settings.components.SettingsItem
+
+private const val COOL_DOWN_TIME_MS = 30000L
+
+@Composable
+fun TracerouteButton(
+ text: String = stringResource(id = R.string.traceroute),
+ lastTracerouteTime: Long?,
+ onClick: () -> Unit,
+) {
+ val progress = remember { Animatable(0f) }
+
+ LaunchedEffect(lastTracerouteTime) {
+ val timeSinceLast = System.currentTimeMillis() - (lastTracerouteTime ?: 0)
+ if (timeSinceLast < COOL_DOWN_TIME_MS) {
+ val remainingTime = COOL_DOWN_TIME_MS - timeSinceLast
+ progress.snapTo(remainingTime / COOL_DOWN_TIME_MS.toFloat())
+ progress.animateTo(
+ targetValue = 0f,
+ animationSpec = tween(durationMillis = remainingTime.toInt(), easing = { it }),
+ )
+ }
+ }
+
+ TracerouteButton(text = text, progress = progress.value, onClick = onClick)
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+private fun TracerouteButton(text: String, progress: Float, onClick: () -> Unit) {
+ val isCoolingDown = progress > 0f
+
+ val stroke = Stroke(width = with(LocalDensity.current) { 2.dp.toPx() }, cap = StrokeCap.Round)
+
+ SettingsItem(
+ text = text,
+ enabled = !isCoolingDown,
+ leadingIcon = Icons.Default.Route,
+ trailingContent = {
+ if (isCoolingDown) {
+ CircularWavyProgressIndicator(
+ progress = { progress },
+ modifier = Modifier.size(24.dp),
+ stroke = stroke,
+ trackStroke = stroke,
+ wavelength = 8.dp,
+ )
+ }
+ },
+ onClick = {
+ if (!isCoolingDown) {
+ onClick()
+ }
+ },
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun TracerouteButtonPreview() {
+ AppTheme { TracerouteButton(text = "Traceroute", progress = .6f, onClick = {}) }
+}
diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/components/SettingsItem.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/components/SettingsItem.kt
index 5617cfb7a..ea0369072 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/settings/components/SettingsItem.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/settings/components/SettingsItem.kt
@@ -26,7 +26,6 @@ import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material.icons.rounded.Android
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
@@ -51,18 +50,33 @@ fun SettingsItem(
trailingIcon: ImageVector? = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
trailingIconTint: Color = LocalContentColor.current,
onClick: () -> Unit,
+) {
+ SettingsItem(
+ text = text,
+ enabled = enabled,
+ leadingIcon = leadingIcon,
+ leadingIconTint = leadingIconTint,
+ trailingContent = { trailingIcon.Icon(trailingIconTint) },
+ onClick = onClick,
+ )
+}
+
+/** A clickable settings button item. */
+@Composable
+fun SettingsItem(
+ text: String,
+ enabled: Boolean = true,
+ leadingIcon: ImageVector? = null,
+ leadingIconTint: Color = LocalContentColor.current,
+ trailingContent: @Composable (() -> Unit),
+ onClick: () -> Unit,
) {
ClickableWrapper(enabled = enabled, onClick = onClick) {
- Content(
- leading = { leadingIcon.Icon(leadingIconTint) },
- text = text,
- trailing = { trailingIcon.Icon(trailingIconTint) },
- )
+ Content(leading = { leadingIcon.Icon(leadingIconTint) }, text = text, trailing = trailingContent)
}
}
/** A toggleable settings switch item. */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SettingsItemSwitch(
checked: Boolean,
From 0645e4855ff8921e9c2a46c4105c23500344b860 Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Thu, 4 Sep 2025 20:04:26 -0500
Subject: [PATCH 07/13] refactor(map): replace VerticalFloatingToolbar with
Horizontal (#2968)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
---
.../com/geeksville/mesh/ui/map/MapView.kt | 37 ++-----------------
.../ui/map/components/MapControlsOverlay.kt | 7 +---
2 files changed, 6 insertions(+), 38 deletions(-)
diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt b/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt
index b5d54ba0a..04a581467 100644
--- a/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt
+++ b/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt
@@ -27,29 +27,23 @@ import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatDelegate
-import androidx.compose.animation.core.animate
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.TripOrigin
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.FloatingToolbarDefaults
-import androidx.compose.material3.FloatingToolbarExitDirection.Companion.End
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
-import androidx.compose.material3.rememberFloatingToolbarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -62,7 +56,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -277,29 +270,6 @@ fun MapView(
// Clean up location tracking on disposal
DisposableEffect(Unit) { onDispose { fusedLocationClient.removeLocationUpdates(locationCallback) } }
- val floatingToolbarState = rememberFloatingToolbarState()
- val exitAlwaysScrollBehavior =
- FloatingToolbarDefaults.exitAlwaysScrollBehavior(exitDirection = End, state = floatingToolbarState)
-
- LaunchedEffect(cameraPositionState.isMoving, floatingToolbarState.offsetLimit) {
- val targetOffset =
- if (cameraPositionState.isMoving) {
- floatingToolbarState.offsetLimit
- } else {
- mapViewModel.onCameraPositionChanged(cameraPositionState.position)
- 0f
- }
- if (floatingToolbarState.offset != targetOffset) {
- if (targetOffset == 0f || floatingToolbarState.offsetLimit != 0f) {
- launch {
- animate(initialValue = floatingToolbarState.offset, targetValue = targetOffset) { value, _ ->
- floatingToolbarState.offset = value
- }
- }
- }
- }
- }
-
val allNodes by
mapViewModel.nodes
.map { nodes -> nodes.filter { node -> node.validPosition != null } }
@@ -369,8 +339,9 @@ fun MapView(
}
var showClusterItemsDialog by remember { mutableStateOf?>(null) }
+ LaunchedEffect(cameraPositionState.position) { mapViewModel.onCameraPositionChanged(cameraPositionState.position) }
- Scaffold(modifier = Modifier.nestedScroll(exitAlwaysScrollBehavior)) { paddingValues ->
+ Scaffold { paddingValues ->
Box(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
GoogleMap(
mapColorScheme = mapColorScheme,
@@ -550,6 +521,7 @@ fun MapView(
}
}
}
+
LayerType.GEOJSON -> {
layerItem.geoJsonLayerData?.let { geoJsonLayer ->
if (layerItem.isVisible && !geoJsonLayer.isLayerOnMap) {
@@ -599,7 +571,7 @@ fun MapView(
}
MapControlsOverlay(
- modifier = Modifier.align(Alignment.TopEnd).padding(top = 50.dp),
+ modifier = Modifier.align(Alignment.TopCenter).padding(top = 8.dp),
mapFilterMenuExpanded = mapFilterMenuExpanded,
onMapFilterMenuDismissRequest = { mapFilterMenuExpanded = false },
onToggleMapFilterMenu = { mapFilterMenuExpanded = true },
@@ -613,7 +585,6 @@ fun MapView(
showCustomTileManagerSheet = true
},
showFilterButton = focusedNodeNum == null,
- scrollBehavior = exitAlwaysScrollBehavior,
hasLocationPermission = hasLocationPermission,
isLocationTrackingEnabled = isLocationTrackingEnabled,
onToggleLocationTracking = {
diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt
index e1f75f2ec..65b49b5d4 100644
--- a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt
+++ b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt
@@ -26,8 +26,7 @@ import androidx.compose.material.icons.outlined.Map
import androidx.compose.material.icons.outlined.MyLocation
import androidx.compose.material.icons.outlined.Tune
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.FloatingToolbarScrollBehavior
-import androidx.compose.material3.VerticalFloatingToolbar
+import androidx.compose.material3.HorizontalFloatingToolbar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -48,19 +47,17 @@ fun MapControlsOverlay(
onManageLayersClicked: () -> Unit,
onManageCustomTileProvidersClicked: () -> Unit, // New parameter
showFilterButton: Boolean,
- scrollBehavior: FloatingToolbarScrollBehavior,
// Location tracking parameters
hasLocationPermission: Boolean = false,
isLocationTrackingEnabled: Boolean = false,
onToggleLocationTracking: () -> Unit = {},
onOrientNorth: () -> Unit = {},
) {
- VerticalFloatingToolbar(
+ HorizontalFloatingToolbar(
modifier = modifier,
expanded = true,
leadingContent = {},
trailingContent = {},
- scrollBehavior = scrollBehavior,
content = {
if (showFilterButton) {
Box {
From 804b1a878e0c1ed8292882821ca5e5d28d99988e Mon Sep 17 00:00:00 2001
From: Phil Oliver <3497406+poliver@users.noreply.github.com>
Date: Thu, 4 Sep 2025 21:47:39 -0400
Subject: [PATCH 08/13] Relocate debug panel to Settings nav graph (#2971)
---
.../java/com/geeksville/mesh/navigation/NavGraph.kt | 13 +------------
.../geeksville/mesh/navigation/SettingsRoutes.kt | 9 +++++++++
app/src/main/java/com/geeksville/mesh/ui/Main.kt | 1 -
.../mesh/ui/common/components/MainAppBar.kt | 6 ++----
.../mesh/ui/settings/radio/RadioConfig.kt | 8 ++++++++
5 files changed, 20 insertions(+), 17 deletions(-)
diff --git a/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt b/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt
index 5e1c266cf..e8ed81e1e 100644
--- a/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt
+++ b/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt
@@ -25,14 +25,11 @@ import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
-import androidx.navigation.navDeepLink
import com.geeksville.mesh.R
import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel
-import com.geeksville.mesh.ui.debug.DebugScreen
import com.geeksville.mesh.ui.map.MapViewModel
import kotlinx.serialization.Serializable
@@ -47,10 +44,7 @@ const val DEEP_LINK_BASE_URI = "meshtastic://meshtastic"
@Serializable sealed interface Graph : Route
-@Serializable
-sealed interface Route {
- @Serializable data object DebugPanel : Route
-}
+@Serializable sealed interface Route
fun NavDestination.isConfigRoute(): Boolean =
ConfigRoute.entries.any { hasRoute(it.route::class) } || ModuleRoute.entries.any { hasRoute(it.route::class) }
@@ -80,11 +74,6 @@ fun NavGraph(
mapGraph(navController, uIViewModel, mapViewModel)
channelsGraph(navController, uIViewModel)
connectionsGraph(navController, uIViewModel, bluetoothViewModel)
- composable(
- deepLinks = listOf(navDeepLink(basePath = "$DEEP_LINK_BASE_URI/debug_panel")),
- ) {
- DebugScreen()
- }
settingsGraph(navController, uIViewModel)
}
}
diff --git a/app/src/main/java/com/geeksville/mesh/navigation/SettingsRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/SettingsRoutes.kt
index 1eb97d589..389a0f109 100644
--- a/app/src/main/java/com/geeksville/mesh/navigation/SettingsRoutes.kt
+++ b/app/src/main/java/com/geeksville/mesh/navigation/SettingsRoutes.kt
@@ -56,6 +56,7 @@ import com.geeksville.mesh.AdminProtos
import com.geeksville.mesh.MeshProtos.DeviceMetadata
import com.geeksville.mesh.R
import com.geeksville.mesh.model.UIViewModel
+import com.geeksville.mesh.ui.debug.DebugScreen
import com.geeksville.mesh.ui.settings.SettingsScreen
import com.geeksville.mesh.ui.settings.radio.CleanNodeDatabaseScreen
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@@ -147,6 +148,8 @@ sealed class SettingsRoutes {
@Serializable data object CleanNodeDb : Route
+ @Serializable data object DebugPanel : Route
+
// endregion
}
@@ -177,6 +180,12 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController, uiViewModel:
}
configRoutesScreens(navController)
moduleRoutesScreens(navController)
+ composable(
+ deepLinks =
+ listOf(navDeepLink(basePath = "$DEEP_LINK_BASE_URI/settings/debug_panel")),
+ ) {
+ DebugScreen()
+ }
}
}
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 28e102abe..5a062ffbc 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt
@@ -351,7 +351,6 @@ fun MainScreen(
onAction = { action ->
if (action is MainMenuAction) {
when (action) {
- MainMenuAction.DEBUG -> navController.navigate(Route.DebugPanel)
MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat)
MainMenuAction.SHOW_INTRO -> uIViewModel.onMainMenuAction(action)
else -> onAction(action)
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 e1fae2155..723369b66 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
@@ -55,7 +55,6 @@ import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.navigation.ContactsRoutes
import com.geeksville.mesh.navigation.NodesRoutes
-import com.geeksville.mesh.navigation.Route
import com.geeksville.mesh.navigation.SettingsRoutes
import com.geeksville.mesh.navigation.showLongNameTitle
import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel
@@ -88,7 +87,7 @@ fun MainAppBar(
when {
currentDestination == null || currentDestination.isTopLevel() -> stringResource(id = R.string.app_name)
- currentDestination.hasRoute() -> stringResource(id = R.string.debug_panel)
+ currentDestination.hasRoute() -> stringResource(id = R.string.debug_panel)
currentDestination.hasRoute() -> stringResource(id = R.string.quick_chat)
@@ -120,7 +119,7 @@ fun MainAppBar(
when {
it.isTopLevel() -> MainMenuActions(onAction)
- currentDestination.hasRoute() -> DebugMenuActions()
+ currentDestination.hasRoute() -> DebugMenuActions()
currentDestination.hasRoute() ->
RadioConfigMenuActions(viewModel = viewModel)
@@ -206,7 +205,6 @@ private fun TopBarActions(
}
enum class MainMenuAction(@StringRes val stringRes: Int) {
- DEBUG(R.string.debug_panel),
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/radio/RadioConfig.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfig.kt
index cd6f7cd78..e6e213bc8 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
@@ -30,6 +30,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
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.twotone.Warning
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
@@ -203,6 +204,13 @@ fun RadioConfigItemList(
enabled = enabled,
onClick = { onNavigate(SettingsRoutes.CleanNodeDb) },
)
+
+ SettingsItem(
+ text = stringResource(R.string.debug_panel),
+ leadingIcon = Icons.Rounded.BugReport,
+ enabled = enabled,
+ onClick = { onNavigate(SettingsRoutes.DebugPanel) },
+ )
}
}
From 161c38f8eeddb9186b807e86c964995c21eae6fb Mon Sep 17 00:00:00 2001
From: Phil Oliver <3497406+poliver@users.noreply.github.com>
Date: Thu, 4 Sep 2025 22:53:44 -0400
Subject: [PATCH 09/13] Align syle of admin settings (#2969)
---
.../geeksville/mesh/navigation/NavGraph.kt | 16 +++-
.../ui/settings/components/SettingsItem.kt | 24 ++++-
.../mesh/ui/settings/radio/RadioConfig.kt | 94 +++++--------------
.../radio/components/WarningDialog.kt | 63 +++++++++++++
4 files changed, 118 insertions(+), 79 deletions(-)
create mode 100644 app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/WarningDialog.kt
diff --git a/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt b/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt
index e8ed81e1e..32da5d844 100644
--- a/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt
+++ b/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt
@@ -18,8 +18,14 @@
package com.geeksville.mesh.navigation
import androidx.annotation.StringRes
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.PowerSettingsNew
+import androidx.compose.material.icons.rounded.RestartAlt
+import androidx.compose.material.icons.rounded.Restore
+import androidx.compose.material.icons.rounded.Storage
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
@@ -33,11 +39,11 @@ import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel
import com.geeksville.mesh.ui.map.MapViewModel
import kotlinx.serialization.Serializable
-enum class AdminRoute(@StringRes val title: Int) {
- REBOOT(R.string.reboot),
- SHUTDOWN(R.string.shutdown),
- FACTORY_RESET(R.string.factory_reset),
- NODEDB_RESET(R.string.nodedb_reset),
+enum class AdminRoute(val icon: ImageVector, @StringRes val title: Int) {
+ REBOOT(Icons.Rounded.RestartAlt, R.string.reboot),
+ SHUTDOWN(Icons.Rounded.PowerSettingsNew, R.string.shutdown),
+ FACTORY_RESET(Icons.Rounded.Restore, R.string.factory_reset),
+ NODEDB_RESET(Icons.Rounded.Storage, R.string.nodedb_reset),
}
const val DEEP_LINK_BASE_URI = "meshtastic://meshtastic"
diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/components/SettingsItem.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/components/SettingsItem.kt
index ea0369072..964e2830f 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/settings/components/SettingsItem.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/settings/components/SettingsItem.kt
@@ -44,6 +44,7 @@ import com.geeksville.mesh.ui.common.theme.AppTheme
@Composable
fun SettingsItem(
text: String,
+ textColor: Color = LocalContentColor.current,
enabled: Boolean = true,
leadingIcon: ImageVector? = null,
leadingIconTint: Color = LocalContentColor.current,
@@ -53,6 +54,7 @@ fun SettingsItem(
) {
SettingsItem(
text = text,
+ textColor = textColor,
enabled = enabled,
leadingIcon = leadingIcon,
leadingIconTint = leadingIconTint,
@@ -65,6 +67,7 @@ fun SettingsItem(
@Composable
fun SettingsItem(
text: String,
+ textColor: Color = LocalContentColor.current,
enabled: Boolean = true,
leadingIcon: ImageVector? = null,
leadingIconTint: Color = LocalContentColor.current,
@@ -72,7 +75,12 @@ fun SettingsItem(
onClick: () -> Unit,
) {
ClickableWrapper(enabled = enabled, onClick = onClick) {
- Content(leading = { leadingIcon.Icon(leadingIconTint) }, text = text, trailing = trailingContent)
+ Content(
+ leading = { leadingIcon.Icon(leadingIconTint) },
+ text = text,
+ textColor = textColor,
+ trailing = trailingContent,
+ )
}
}
@@ -81,6 +89,7 @@ fun SettingsItem(
fun SettingsItemSwitch(
checked: Boolean,
text: String,
+ textColor: Color = LocalContentColor.current,
enabled: Boolean = true,
leadingIcon: ImageVector? = null,
leadingIconTint: Color = LocalContentColor.current,
@@ -90,6 +99,7 @@ fun SettingsItemSwitch(
Content(
leading = { leadingIcon.Icon(leadingIconTint) },
text = text,
+ textColor = textColor,
trailing = { Switch(checked = checked, enabled = enabled, onCheckedChange = null) },
)
}
@@ -99,6 +109,7 @@ fun SettingsItemSwitch(
@Composable
fun SettingsItemDetail(
text: String,
+ textColor: Color = LocalContentColor.current,
icon: ImageVector? = null,
iconTint: Color = LocalContentColor.current,
trailingText: String? = null,
@@ -106,7 +117,12 @@ fun SettingsItemDetail(
onClick: (() -> Unit)? = null,
) {
val content: @Composable ColumnScope.() -> Unit = {
- Content(leading = { icon.Icon(iconTint) }, text = text, trailing = { trailingText?.let { Text(text = it) } })
+ Content(
+ leading = { icon.Icon(iconTint) },
+ text = text,
+ textColor = textColor,
+ trailing = { trailingText?.let { Text(text = it) } },
+ )
}
if (onClick != null) {
@@ -130,11 +146,11 @@ private fun ClickableWrapper(enabled: Boolean, onClick: () -> Unit, content: @Co
/** The row content to display for a settings item. */
@Composable
-private fun Content(leading: @Composable () -> Unit, text: String, trailing: @Composable () -> Unit) {
+private fun Content(leading: @Composable () -> Unit, text: String, textColor: Color, trailing: @Composable () -> Unit) {
ListItem(
modifier = Modifier.padding(horizontal = 8.dp),
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
- headlineContent = { Text(text) },
+ headlineContent = { Text(text = text, color = textColor) },
leadingContent = { leading() },
trailingContent = { trailing() },
)
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 e6e213bc8..e61ebe5b6 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
@@ -18,27 +18,16 @@
package com.geeksville.mesh.ui.settings.radio
import android.widget.Toast
-import androidx.annotation.StringRes
-import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
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.twotone.Warning
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Button
-import androidx.compose.material3.Icon
+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.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -46,7 +35,6 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -64,64 +52,11 @@ import com.geeksville.mesh.ui.common.components.TitledCard
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")
-@Composable
-private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Unit) {
- var showDialog by remember { mutableStateOf(false) }
- if (showDialog) {
- AlertDialog(
- onDismissRequest = {},
- shape = RoundedCornerShape(16.dp),
- title = {
- Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
- Icon(
- imageVector = Icons.TwoTone.Warning,
- contentDescription = stringResource(id = R.string.warning),
- modifier = Modifier.padding(end = 8.dp),
- )
- Text(text = "${stringResource(title)}?\n")
- Icon(
- imageVector = Icons.TwoTone.Warning,
- contentDescription = stringResource(id = R.string.warning),
- modifier = Modifier.padding(start = 8.dp),
- )
- }
- },
- confirmButton = {
- Row(
- modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- TextButton(modifier = Modifier.weight(1f), onClick = { showDialog = false }) {
- Text(stringResource(R.string.cancel))
- }
- Button(
- modifier = Modifier.weight(1f),
- onClick = {
- showDialog = false
- onClick()
- },
- ) {
- Text(stringResource(R.string.send))
- }
- }
- },
- )
- }
-
- Column {
- Spacer(modifier = Modifier.height(4.dp))
- Button(modifier = Modifier.fillMaxWidth().height(48.dp), enabled = enabled, onClick = { showDialog = true }) {
- Text(text = stringResource(title))
- }
- }
-}
-
-@Suppress("LongMethod")
+@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun RadioConfigItemList(
state: RadioConfigState,
@@ -190,8 +125,26 @@ fun RadioConfigItemList(
}
}
- Column(modifier = Modifier.padding(top = 16.dp)) {
- AdminRoute.entries.forEach { NavButton(it.title, enabled) { onRouteClick(it) } }
+ TitledCard(title = stringResource(R.string.administration), modifier = Modifier.padding(top = 16.dp)) {
+ AdminRoute.entries.forEach { route ->
+ var showDialog by remember { mutableStateOf(false) }
+ if (showDialog) {
+ WarningDialog(
+ title = "${stringResource(route.title)}?",
+ onDismiss = { showDialog = false },
+ onConfirm = { onRouteClick(route) },
+ )
+ }
+
+ SettingsItem(
+ enabled = enabled,
+ text = stringResource(route.title),
+ leadingIcon = route.icon,
+ trailingIcon = null,
+ ) {
+ showDialog = true
+ }
+ }
}
TitledCard(title = stringResource(R.string.advanced_title), modifier = Modifier.padding(top = 16.dp)) {
@@ -201,6 +154,7 @@ fun RadioConfigItemList(
SettingsItem(
text = stringResource(R.string.clean_node_database_title),
+ leadingIcon = Icons.Rounded.CleaningServices,
enabled = enabled,
onClick = { onNavigate(SettingsRoutes.CleanNodeDb) },
)
diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/WarningDialog.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/WarningDialog.kt
new file mode 100644
index 000000000..a2efa92e5
--- /dev/null
+++ b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/components/WarningDialog.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Warning
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.geeksville.mesh.R
+import com.geeksville.mesh.ui.common.theme.AppTheme
+
+@Composable
+fun WarningDialog(
+ icon: ImageVector? = Icons.Rounded.Warning,
+ title: String,
+ onDismiss: () -> Unit,
+ onConfirm: () -> Unit,
+) {
+ AlertDialog(
+ onDismissRequest = {},
+ icon = { icon?.let { Icon(imageVector = it, contentDescription = null) } },
+ title = { Text(text = title) },
+ dismissButton = { TextButton(onClick = { onDismiss() }) { Text(stringResource(R.string.cancel)) } },
+ confirmButton = {
+ Button(
+ onClick = {
+ onDismiss()
+ onConfirm()
+ },
+ ) {
+ Text(stringResource(R.string.send))
+ }
+ },
+ )
+}
+
+@Preview
+@Composable
+private fun WarningDialogPreview() {
+ AppTheme { WarningDialog(title = "Factory Reset?", onDismiss = {}, onConfirm = {}) }
+}
From 7802f614da0434edad12f98cdaf140a6089f3e4b Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Thu, 4 Sep 2025 21:58:23 -0500
Subject: [PATCH 10/13] feat(theme): add expressive motion to theme (#2967)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
---
.../geeksville/mesh/ui/common/theme/Theme.kt | 486 +++++++++---------
1 file changed, 245 insertions(+), 241 deletions(-)
diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/theme/Theme.kt b/app/src/main/java/com/geeksville/mesh/ui/common/theme/Theme.kt
index 26e68a4d6..5258680b9 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/common/theme/Theme.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/common/theme/Theme.kt
@@ -23,6 +23,7 @@ import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
+import androidx.compose.material3.MotionScheme.Companion.expressive
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
@@ -32,267 +33,270 @@ import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
-private val lightScheme = lightColorScheme(
- primary = primaryLight,
- onPrimary = onPrimaryLight,
- primaryContainer = primaryContainerLight,
- onPrimaryContainer = onPrimaryContainerLight,
- secondary = secondaryLight,
- onSecondary = onSecondaryLight,
- secondaryContainer = secondaryContainerLight,
- onSecondaryContainer = onSecondaryContainerLight,
- tertiary = tertiaryLight,
- onTertiary = onTertiaryLight,
- tertiaryContainer = tertiaryContainerLight,
- onTertiaryContainer = onTertiaryContainerLight,
- error = errorLight,
- onError = onErrorLight,
- errorContainer = errorContainerLight,
- onErrorContainer = onErrorContainerLight,
- background = backgroundLight,
- onBackground = onBackgroundLight,
- surface = surfaceLight,
- onSurface = onSurfaceLight,
- surfaceVariant = surfaceVariantLight,
- onSurfaceVariant = onSurfaceVariantLight,
- outline = outlineLight,
- outlineVariant = outlineVariantLight,
- scrim = scrimLight,
- inverseSurface = inverseSurfaceLight,
- inverseOnSurface = inverseOnSurfaceLight,
- inversePrimary = inversePrimaryLight,
- surfaceDim = surfaceDimLight,
- surfaceBright = surfaceBrightLight,
- surfaceContainerLowest = surfaceContainerLowestLight,
- surfaceContainerLow = surfaceContainerLowLight,
- surfaceContainer = surfaceContainerLight,
- surfaceContainerHigh = surfaceContainerHighLight,
- surfaceContainerHighest = surfaceContainerHighestLight,
-)
+private val lightScheme =
+ lightColorScheme(
+ primary = primaryLight,
+ onPrimary = onPrimaryLight,
+ primaryContainer = primaryContainerLight,
+ onPrimaryContainer = onPrimaryContainerLight,
+ secondary = secondaryLight,
+ onSecondary = onSecondaryLight,
+ secondaryContainer = secondaryContainerLight,
+ onSecondaryContainer = onSecondaryContainerLight,
+ tertiary = tertiaryLight,
+ onTertiary = onTertiaryLight,
+ tertiaryContainer = tertiaryContainerLight,
+ onTertiaryContainer = onTertiaryContainerLight,
+ error = errorLight,
+ onError = onErrorLight,
+ errorContainer = errorContainerLight,
+ onErrorContainer = onErrorContainerLight,
+ background = backgroundLight,
+ onBackground = onBackgroundLight,
+ surface = surfaceLight,
+ onSurface = onSurfaceLight,
+ surfaceVariant = surfaceVariantLight,
+ onSurfaceVariant = onSurfaceVariantLight,
+ outline = outlineLight,
+ outlineVariant = outlineVariantLight,
+ scrim = scrimLight,
+ inverseSurface = inverseSurfaceLight,
+ inverseOnSurface = inverseOnSurfaceLight,
+ inversePrimary = inversePrimaryLight,
+ surfaceDim = surfaceDimLight,
+ surfaceBright = surfaceBrightLight,
+ surfaceContainerLowest = surfaceContainerLowestLight,
+ surfaceContainerLow = surfaceContainerLowLight,
+ surfaceContainer = surfaceContainerLight,
+ surfaceContainerHigh = surfaceContainerHighLight,
+ surfaceContainerHighest = surfaceContainerHighestLight,
+ )
-private val darkScheme = darkColorScheme(
- primary = primaryDark,
- onPrimary = onPrimaryDark,
- primaryContainer = primaryContainerDark,
- onPrimaryContainer = onPrimaryContainerDark,
- secondary = secondaryDark,
- onSecondary = onSecondaryDark,
- secondaryContainer = secondaryContainerDark,
- onSecondaryContainer = onSecondaryContainerDark,
- tertiary = tertiaryDark,
- onTertiary = onTertiaryDark,
- tertiaryContainer = tertiaryContainerDark,
- onTertiaryContainer = onTertiaryContainerDark,
- error = errorDark,
- onError = onErrorDark,
- errorContainer = errorContainerDark,
- onErrorContainer = onErrorContainerDark,
- background = backgroundDark,
- onBackground = onBackgroundDark,
- surface = surfaceDark,
- onSurface = onSurfaceDark,
- surfaceVariant = surfaceVariantDark,
- onSurfaceVariant = onSurfaceVariantDark,
- outline = outlineDark,
- outlineVariant = outlineVariantDark,
- scrim = scrimDark,
- inverseSurface = inverseSurfaceDark,
- inverseOnSurface = inverseOnSurfaceDark,
- inversePrimary = inversePrimaryDark,
- surfaceDim = surfaceDimDark,
- surfaceBright = surfaceBrightDark,
- surfaceContainerLowest = surfaceContainerLowestDark,
- surfaceContainerLow = surfaceContainerLowDark,
- surfaceContainer = surfaceContainerDark,
- surfaceContainerHigh = surfaceContainerHighDark,
- surfaceContainerHighest = surfaceContainerHighestDark,
-)
+private val darkScheme =
+ darkColorScheme(
+ primary = primaryDark,
+ onPrimary = onPrimaryDark,
+ primaryContainer = primaryContainerDark,
+ onPrimaryContainer = onPrimaryContainerDark,
+ secondary = secondaryDark,
+ onSecondary = onSecondaryDark,
+ secondaryContainer = secondaryContainerDark,
+ onSecondaryContainer = onSecondaryContainerDark,
+ tertiary = tertiaryDark,
+ onTertiary = onTertiaryDark,
+ tertiaryContainer = tertiaryContainerDark,
+ onTertiaryContainer = onTertiaryContainerDark,
+ error = errorDark,
+ onError = onErrorDark,
+ errorContainer = errorContainerDark,
+ onErrorContainer = onErrorContainerDark,
+ background = backgroundDark,
+ onBackground = onBackgroundDark,
+ surface = surfaceDark,
+ onSurface = onSurfaceDark,
+ surfaceVariant = surfaceVariantDark,
+ onSurfaceVariant = onSurfaceVariantDark,
+ outline = outlineDark,
+ outlineVariant = outlineVariantDark,
+ scrim = scrimDark,
+ inverseSurface = inverseSurfaceDark,
+ inverseOnSurface = inverseOnSurfaceDark,
+ inversePrimary = inversePrimaryDark,
+ surfaceDim = surfaceDimDark,
+ surfaceBright = surfaceBrightDark,
+ surfaceContainerLowest = surfaceContainerLowestDark,
+ surfaceContainerLow = surfaceContainerLowDark,
+ surfaceContainer = surfaceContainerDark,
+ surfaceContainerHigh = surfaceContainerHighDark,
+ surfaceContainerHighest = surfaceContainerHighestDark,
+ )
-private val mediumContrastLightColorScheme = lightColorScheme(
- primary = primaryLightMediumContrast,
- onPrimary = onPrimaryLightMediumContrast,
- primaryContainer = primaryContainerLightMediumContrast,
- onPrimaryContainer = onPrimaryContainerLightMediumContrast,
- secondary = secondaryLightMediumContrast,
- onSecondary = onSecondaryLightMediumContrast,
- secondaryContainer = secondaryContainerLightMediumContrast,
- onSecondaryContainer = onSecondaryContainerLightMediumContrast,
- tertiary = tertiaryLightMediumContrast,
- onTertiary = onTertiaryLightMediumContrast,
- tertiaryContainer = tertiaryContainerLightMediumContrast,
- onTertiaryContainer = onTertiaryContainerLightMediumContrast,
- error = errorLightMediumContrast,
- onError = onErrorLightMediumContrast,
- errorContainer = errorContainerLightMediumContrast,
- onErrorContainer = onErrorContainerLightMediumContrast,
- background = backgroundLightMediumContrast,
- onBackground = onBackgroundLightMediumContrast,
- surface = surfaceLightMediumContrast,
- onSurface = onSurfaceLightMediumContrast,
- surfaceVariant = surfaceVariantLightMediumContrast,
- onSurfaceVariant = onSurfaceVariantLightMediumContrast,
- outline = outlineLightMediumContrast,
- outlineVariant = outlineVariantLightMediumContrast,
- scrim = scrimLightMediumContrast,
- inverseSurface = inverseSurfaceLightMediumContrast,
- inverseOnSurface = inverseOnSurfaceLightMediumContrast,
- inversePrimary = inversePrimaryLightMediumContrast,
- surfaceDim = surfaceDimLightMediumContrast,
- surfaceBright = surfaceBrightLightMediumContrast,
- surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
- surfaceContainerLow = surfaceContainerLowLightMediumContrast,
- surfaceContainer = surfaceContainerLightMediumContrast,
- surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
- surfaceContainerHighest = surfaceContainerHighestLightMediumContrast,
-)
+private val mediumContrastLightColorScheme =
+ lightColorScheme(
+ primary = primaryLightMediumContrast,
+ onPrimary = onPrimaryLightMediumContrast,
+ primaryContainer = primaryContainerLightMediumContrast,
+ onPrimaryContainer = onPrimaryContainerLightMediumContrast,
+ secondary = secondaryLightMediumContrast,
+ onSecondary = onSecondaryLightMediumContrast,
+ secondaryContainer = secondaryContainerLightMediumContrast,
+ onSecondaryContainer = onSecondaryContainerLightMediumContrast,
+ tertiary = tertiaryLightMediumContrast,
+ onTertiary = onTertiaryLightMediumContrast,
+ tertiaryContainer = tertiaryContainerLightMediumContrast,
+ onTertiaryContainer = onTertiaryContainerLightMediumContrast,
+ error = errorLightMediumContrast,
+ onError = onErrorLightMediumContrast,
+ errorContainer = errorContainerLightMediumContrast,
+ onErrorContainer = onErrorContainerLightMediumContrast,
+ background = backgroundLightMediumContrast,
+ onBackground = onBackgroundLightMediumContrast,
+ surface = surfaceLightMediumContrast,
+ onSurface = onSurfaceLightMediumContrast,
+ surfaceVariant = surfaceVariantLightMediumContrast,
+ onSurfaceVariant = onSurfaceVariantLightMediumContrast,
+ outline = outlineLightMediumContrast,
+ outlineVariant = outlineVariantLightMediumContrast,
+ scrim = scrimLightMediumContrast,
+ inverseSurface = inverseSurfaceLightMediumContrast,
+ inverseOnSurface = inverseOnSurfaceLightMediumContrast,
+ inversePrimary = inversePrimaryLightMediumContrast,
+ surfaceDim = surfaceDimLightMediumContrast,
+ surfaceBright = surfaceBrightLightMediumContrast,
+ surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
+ surfaceContainerLow = surfaceContainerLowLightMediumContrast,
+ surfaceContainer = surfaceContainerLightMediumContrast,
+ surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
+ surfaceContainerHighest = surfaceContainerHighestLightMediumContrast,
+ )
-private val highContrastLightColorScheme = lightColorScheme(
- primary = primaryLightHighContrast,
- onPrimary = onPrimaryLightHighContrast,
- primaryContainer = primaryContainerLightHighContrast,
- onPrimaryContainer = onPrimaryContainerLightHighContrast,
- secondary = secondaryLightHighContrast,
- onSecondary = onSecondaryLightHighContrast,
- secondaryContainer = secondaryContainerLightHighContrast,
- onSecondaryContainer = onSecondaryContainerLightHighContrast,
- tertiary = tertiaryLightHighContrast,
- onTertiary = onTertiaryLightHighContrast,
- tertiaryContainer = tertiaryContainerLightHighContrast,
- onTertiaryContainer = onTertiaryContainerLightHighContrast,
- error = errorLightHighContrast,
- onError = onErrorLightHighContrast,
- errorContainer = errorContainerLightHighContrast,
- onErrorContainer = onErrorContainerLightHighContrast,
- background = backgroundLightHighContrast,
- onBackground = onBackgroundLightHighContrast,
- surface = surfaceLightHighContrast,
- onSurface = onSurfaceLightHighContrast,
- surfaceVariant = surfaceVariantLightHighContrast,
- onSurfaceVariant = onSurfaceVariantLightHighContrast,
- outline = outlineLightHighContrast,
- outlineVariant = outlineVariantLightHighContrast,
- scrim = scrimLightHighContrast,
- inverseSurface = inverseSurfaceLightHighContrast,
- inverseOnSurface = inverseOnSurfaceLightHighContrast,
- inversePrimary = inversePrimaryLightHighContrast,
- surfaceDim = surfaceDimLightHighContrast,
- surfaceBright = surfaceBrightLightHighContrast,
- surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
- surfaceContainerLow = surfaceContainerLowLightHighContrast,
- surfaceContainer = surfaceContainerLightHighContrast,
- surfaceContainerHigh = surfaceContainerHighLightHighContrast,
- surfaceContainerHighest = surfaceContainerHighestLightHighContrast,
-)
+private val highContrastLightColorScheme =
+ lightColorScheme(
+ primary = primaryLightHighContrast,
+ onPrimary = onPrimaryLightHighContrast,
+ primaryContainer = primaryContainerLightHighContrast,
+ onPrimaryContainer = onPrimaryContainerLightHighContrast,
+ secondary = secondaryLightHighContrast,
+ onSecondary = onSecondaryLightHighContrast,
+ secondaryContainer = secondaryContainerLightHighContrast,
+ onSecondaryContainer = onSecondaryContainerLightHighContrast,
+ tertiary = tertiaryLightHighContrast,
+ onTertiary = onTertiaryLightHighContrast,
+ tertiaryContainer = tertiaryContainerLightHighContrast,
+ onTertiaryContainer = onTertiaryContainerLightHighContrast,
+ error = errorLightHighContrast,
+ onError = onErrorLightHighContrast,
+ errorContainer = errorContainerLightHighContrast,
+ onErrorContainer = onErrorContainerLightHighContrast,
+ background = backgroundLightHighContrast,
+ onBackground = onBackgroundLightHighContrast,
+ surface = surfaceLightHighContrast,
+ onSurface = onSurfaceLightHighContrast,
+ surfaceVariant = surfaceVariantLightHighContrast,
+ onSurfaceVariant = onSurfaceVariantLightHighContrast,
+ outline = outlineLightHighContrast,
+ outlineVariant = outlineVariantLightHighContrast,
+ scrim = scrimLightHighContrast,
+ inverseSurface = inverseSurfaceLightHighContrast,
+ inverseOnSurface = inverseOnSurfaceLightHighContrast,
+ inversePrimary = inversePrimaryLightHighContrast,
+ surfaceDim = surfaceDimLightHighContrast,
+ surfaceBright = surfaceBrightLightHighContrast,
+ surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
+ surfaceContainerLow = surfaceContainerLowLightHighContrast,
+ surfaceContainer = surfaceContainerLightHighContrast,
+ surfaceContainerHigh = surfaceContainerHighLightHighContrast,
+ surfaceContainerHighest = surfaceContainerHighestLightHighContrast,
+ )
-private val mediumContrastDarkColorScheme = darkColorScheme(
- primary = primaryDarkMediumContrast,
- onPrimary = onPrimaryDarkMediumContrast,
- primaryContainer = primaryContainerDarkMediumContrast,
- onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
- secondary = secondaryDarkMediumContrast,
- onSecondary = onSecondaryDarkMediumContrast,
- secondaryContainer = secondaryContainerDarkMediumContrast,
- onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
- tertiary = tertiaryDarkMediumContrast,
- onTertiary = onTertiaryDarkMediumContrast,
- tertiaryContainer = tertiaryContainerDarkMediumContrast,
- onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
- error = errorDarkMediumContrast,
- onError = onErrorDarkMediumContrast,
- errorContainer = errorContainerDarkMediumContrast,
- onErrorContainer = onErrorContainerDarkMediumContrast,
- background = backgroundDarkMediumContrast,
- onBackground = onBackgroundDarkMediumContrast,
- surface = surfaceDarkMediumContrast,
- onSurface = onSurfaceDarkMediumContrast,
- surfaceVariant = surfaceVariantDarkMediumContrast,
- onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
- outline = outlineDarkMediumContrast,
- outlineVariant = outlineVariantDarkMediumContrast,
- scrim = scrimDarkMediumContrast,
- inverseSurface = inverseSurfaceDarkMediumContrast,
- inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
- inversePrimary = inversePrimaryDarkMediumContrast,
- surfaceDim = surfaceDimDarkMediumContrast,
- surfaceBright = surfaceBrightDarkMediumContrast,
- surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
- surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
- surfaceContainer = surfaceContainerDarkMediumContrast,
- surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
- surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast,
-)
+private val mediumContrastDarkColorScheme =
+ darkColorScheme(
+ primary = primaryDarkMediumContrast,
+ onPrimary = onPrimaryDarkMediumContrast,
+ primaryContainer = primaryContainerDarkMediumContrast,
+ onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
+ secondary = secondaryDarkMediumContrast,
+ onSecondary = onSecondaryDarkMediumContrast,
+ secondaryContainer = secondaryContainerDarkMediumContrast,
+ onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
+ tertiary = tertiaryDarkMediumContrast,
+ onTertiary = onTertiaryDarkMediumContrast,
+ tertiaryContainer = tertiaryContainerDarkMediumContrast,
+ onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
+ error = errorDarkMediumContrast,
+ onError = onErrorDarkMediumContrast,
+ errorContainer = errorContainerDarkMediumContrast,
+ onErrorContainer = onErrorContainerDarkMediumContrast,
+ background = backgroundDarkMediumContrast,
+ onBackground = onBackgroundDarkMediumContrast,
+ surface = surfaceDarkMediumContrast,
+ onSurface = onSurfaceDarkMediumContrast,
+ surfaceVariant = surfaceVariantDarkMediumContrast,
+ onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
+ outline = outlineDarkMediumContrast,
+ outlineVariant = outlineVariantDarkMediumContrast,
+ scrim = scrimDarkMediumContrast,
+ inverseSurface = inverseSurfaceDarkMediumContrast,
+ inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
+ inversePrimary = inversePrimaryDarkMediumContrast,
+ surfaceDim = surfaceDimDarkMediumContrast,
+ surfaceBright = surfaceBrightDarkMediumContrast,
+ surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
+ surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
+ surfaceContainer = surfaceContainerDarkMediumContrast,
+ surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
+ surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast,
+ )
-private val highContrastDarkColorScheme = darkColorScheme(
- primary = primaryDarkHighContrast,
- onPrimary = onPrimaryDarkHighContrast,
- primaryContainer = primaryContainerDarkHighContrast,
- onPrimaryContainer = onPrimaryContainerDarkHighContrast,
- secondary = secondaryDarkHighContrast,
- onSecondary = onSecondaryDarkHighContrast,
- secondaryContainer = secondaryContainerDarkHighContrast,
- onSecondaryContainer = onSecondaryContainerDarkHighContrast,
- tertiary = tertiaryDarkHighContrast,
- onTertiary = onTertiaryDarkHighContrast,
- tertiaryContainer = tertiaryContainerDarkHighContrast,
- onTertiaryContainer = onTertiaryContainerDarkHighContrast,
- error = errorDarkHighContrast,
- onError = onErrorDarkHighContrast,
- errorContainer = errorContainerDarkHighContrast,
- onErrorContainer = onErrorContainerDarkHighContrast,
- background = backgroundDarkHighContrast,
- onBackground = onBackgroundDarkHighContrast,
- surface = surfaceDarkHighContrast,
- onSurface = onSurfaceDarkHighContrast,
- surfaceVariant = surfaceVariantDarkHighContrast,
- onSurfaceVariant = onSurfaceVariantDarkHighContrast,
- outline = outlineDarkHighContrast,
- outlineVariant = outlineVariantDarkHighContrast,
- scrim = scrimDarkHighContrast,
- inverseSurface = inverseSurfaceDarkHighContrast,
- inverseOnSurface = inverseOnSurfaceDarkHighContrast,
- inversePrimary = inversePrimaryDarkHighContrast,
- surfaceDim = surfaceDimDarkHighContrast,
- surfaceBright = surfaceBrightDarkHighContrast,
- surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
- surfaceContainerLow = surfaceContainerLowDarkHighContrast,
- surfaceContainer = surfaceContainerDarkHighContrast,
- surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
- surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
-)
+private val highContrastDarkColorScheme =
+ darkColorScheme(
+ primary = primaryDarkHighContrast,
+ onPrimary = onPrimaryDarkHighContrast,
+ primaryContainer = primaryContainerDarkHighContrast,
+ onPrimaryContainer = onPrimaryContainerDarkHighContrast,
+ secondary = secondaryDarkHighContrast,
+ onSecondary = onSecondaryDarkHighContrast,
+ secondaryContainer = secondaryContainerDarkHighContrast,
+ onSecondaryContainer = onSecondaryContainerDarkHighContrast,
+ tertiary = tertiaryDarkHighContrast,
+ onTertiary = onTertiaryDarkHighContrast,
+ tertiaryContainer = tertiaryContainerDarkHighContrast,
+ onTertiaryContainer = onTertiaryContainerDarkHighContrast,
+ error = errorDarkHighContrast,
+ onError = onErrorDarkHighContrast,
+ errorContainer = errorContainerDarkHighContrast,
+ onErrorContainer = onErrorContainerDarkHighContrast,
+ background = backgroundDarkHighContrast,
+ onBackground = onBackgroundDarkHighContrast,
+ surface = surfaceDarkHighContrast,
+ onSurface = onSurfaceDarkHighContrast,
+ surfaceVariant = surfaceVariantDarkHighContrast,
+ onSurfaceVariant = onSurfaceVariantDarkHighContrast,
+ outline = outlineDarkHighContrast,
+ outlineVariant = outlineVariantDarkHighContrast,
+ scrim = scrimDarkHighContrast,
+ inverseSurface = inverseSurfaceDarkHighContrast,
+ inverseOnSurface = inverseOnSurfaceDarkHighContrast,
+ inversePrimary = inversePrimaryDarkHighContrast,
+ surfaceDim = surfaceDimDarkHighContrast,
+ surfaceBright = surfaceBrightDarkHighContrast,
+ surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
+ surfaceContainerLow = surfaceContainerLowDarkHighContrast,
+ surfaceContainer = surfaceContainerDarkHighContrast,
+ surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
+ surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
+ )
@Immutable
-data class ColorFamily(
- val color: Color,
- val onColor: Color,
- val colorContainer: Color,
- val onColorContainer: Color
-)
+data class ColorFamily(val color: Color, val onColor: Color, val colorContainer: Color, val onColorContainer: Color)
-val unspecified_scheme = ColorFamily(
- Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified
-)
+val unspecified_scheme = ColorFamily(Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified)
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
- content: @Composable() () -> Unit
+ content:
+ @Composable()
+ () -> Unit,
) {
- val colorScheme = when {
- dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
- val context = LocalContext.current
- if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
- }
+ val colorScheme =
+ when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
- darkTheme -> darkScheme
- else -> lightScheme
- }
+ darkTheme -> darkScheme
+ else -> lightScheme
+ }
MaterialExpressiveTheme(
colorScheme = colorScheme,
+ motionScheme = expressive(),
typography = AppTypography,
- content = content
+ content = content,
)
}
From f900ff682f9edd71f50ea6e53023af616a32d87d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 5 Sep 2025 13:05:02 +1000
Subject: [PATCH 11/13] chore(deps): update datadog to v3 (major) (#2962)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Phil Oliver <3497406+poliver@users.noreply.github.com>
---
.../com/geeksville/mesh/android/GeeksvilleApplication.kt | 7 +++----
gradle/libs.versions.toml | 5 +++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/app/src/google/java/com/geeksville/mesh/android/GeeksvilleApplication.kt b/app/src/google/java/com/geeksville/mesh/android/GeeksvilleApplication.kt
index 513c27328..d66d03597 100644
--- a/app/src/google/java/com/geeksville/mesh/android/GeeksvilleApplication.kt
+++ b/app/src/google/java/com/geeksville/mesh/android/GeeksvilleApplication.kt
@@ -42,9 +42,9 @@ import com.datadog.android.sessionreplay.SessionReplay
import com.datadog.android.sessionreplay.SessionReplayConfiguration
import com.datadog.android.sessionreplay.compose.ComposeExtensionSupport
import com.datadog.android.timber.DatadogTree
-import com.datadog.android.trace.AndroidTracer
import com.datadog.android.trace.Trace
import com.datadog.android.trace.TraceConfiguration
+import com.datadog.android.trace.opentelemetry.DatadogOpenTelemetry
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.analytics.AnalyticsProvider
import com.geeksville.mesh.analytics.FirebaseAnalytics
@@ -59,7 +59,7 @@ import com.google.firebase.crashlytics.crashlytics
import com.google.firebase.crashlytics.setCustomKeys
import com.google.firebase.initialize
import com.suddenh4x.ratingdialog.AppRating
-import io.opentracing.util.GlobalTracer
+import io.opentelemetry.api.GlobalOpenTelemetry
import timber.log.Timber
abstract class GeeksvilleApplication :
@@ -218,8 +218,7 @@ abstract class GeeksvilleApplication :
val traceConfig = TraceConfiguration.Builder().build()
Trace.enable(traceConfig)
- val tracer = AndroidTracer.Builder().build()
- GlobalTracer.registerIfAbsent(tracer)
+ GlobalOpenTelemetry.set(DatadogOpenTelemetry(BuildConfig.APPLICATION_ID))
val sessionReplayConfig =
SessionReplayConfiguration.Builder(sampleRate = 20.0f)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a648d96cc..f53839430 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -14,7 +14,7 @@ core-location-altitude = "1.0.0-alpha03"
core-splashscreen = "1.0.1"
crashlytics = "3.0.6"
datastore = "1.1.7"
-dd-sdk-android = "2.26.0"
+dd-sdk-android = "3.0.0"
dd-sdk-android-gradle-plugin = "1.19.0"
detekt = "1.23.8"
devtools-ksp = "2.2.10-2.0.2"
@@ -94,6 +94,7 @@ dd-sdk-android-session-replay = { group = "com.datadoghq", name = "dd-sdk-androi
dd-sdk-android-session-replay-compose = { group = "com.datadoghq", name = "dd-sdk-android-session-replay-compose", version.ref = "dd-sdk-android" }
dd-sdk-android-timber = { group = "com.datadoghq", name = "dd-sdk-android-timber", version.ref = "dd-sdk-android" }
dd-sdk-android-trace = { group = "com.datadoghq", name = "dd-sdk-android-trace", version.ref = "dd-sdk-android" }
+dd-sdk-android-trace-otel = { group = "com.datadoghq", name = "dd-sdk-android-trace-otel", version.ref = "dd-sdk-android" }
detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" }
emoji2-emojipicker = { group = "androidx.emoji2", name = "emoji2-emojipicker", version.ref = "emoji2" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
@@ -193,7 +194,7 @@ maps-compose = ["location-services", "maps-compose", "maps-compose-utils", "maps
firebase = ["firebase-analytics", "firebase-crashlytics"]
# Datadog
-datadog = ["dd-sdk-android-compose", "dd-sdk-android-logs", "dd-sdk-android-okhttp", "dd-sdk-android-rum", "dd-sdk-android-session-replay", "dd-sdk-android-session-replay-compose", "dd-sdk-android-timber", "dd-sdk-android-trace"]
+datadog = ["dd-sdk-android-compose", "dd-sdk-android-logs", "dd-sdk-android-okhttp", "dd-sdk-android-rum", "dd-sdk-android-session-replay", "dd-sdk-android-session-replay-compose", "dd-sdk-android-timber", "dd-sdk-android-trace", "dd-sdk-android-trace-otel"]
# Protobuf
protobuf = ["protobuf-kotlin"]
From 755038219b3dfa0c24fdf5513fe56b38aeb05e9a Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Thu, 4 Sep 2025 22:14:11 -0500
Subject: [PATCH 12/13] fix(service): set node time after config completion
(#2970)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
---
.../main/java/com/geeksville/mesh/service/MeshService.kt | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
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 641d6c9ec..d07af0884 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
@@ -199,7 +199,7 @@ class MeshService :
private const val CONFIG_ONLY_NONCE = 69420
private const val NODE_INFO_ONLY_NONCE = 69421
- private const val CONFIG_WAIT_MS = 250L
+ private const val CONFIG_WAIT_MS = 50L
}
private var previousSummary: String? = null
@@ -1750,9 +1750,6 @@ class MeshService :
processQueuedPackets() // send any packets that were queued up
startMqttClientProxy()
serviceBroadcasts.broadcastConnection()
- packetHandler.sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { setTimeOnly = currentSecond() }) {
- connectionState
- }
sendAnalytics()
reportConnection()
}
@@ -1825,6 +1822,9 @@ class MeshService :
sendAnalytics()
onNodeDBChanged()
}
+ packetHandler.sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { setTimeOnly = currentSecond() }) {
+ connectionState
+ }
}
/** Start the modern (REV2) API configuration flow */
From b06ebd4436120fdd4b16db03266b24d5812f9967 Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Thu, 4 Sep 2025 22:18:26 -0500
Subject: [PATCH 13/13] refactor(map): fix scalebar and compass (#2973)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
---
.../com/geeksville/mesh/ui/map/MapView.kt | 66 ++++++++-----------
.../geeksville/mesh/ui/map/MapViewModel.kt | 16 -----
.../mesh/ui/map/components/MapButton.kt | 16 ++++-
.../ui/map/components/MapControlsOverlay.kt | 17 +++--
4 files changed, 52 insertions(+), 63 deletions(-)
diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt b/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt
index 04a581467..4e8066e6f 100644
--- a/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt
+++ b/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt
@@ -116,7 +116,7 @@ import com.google.maps.android.compose.Polyline
import com.google.maps.android.compose.TileOverlay
import com.google.maps.android.compose.rememberCameraPositionState
import com.google.maps.android.compose.rememberUpdatedMarkerState
-import com.google.maps.android.compose.widgets.DisappearingScaleBar
+import com.google.maps.android.compose.widgets.ScaleBar
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import timber.log.Timber
@@ -207,7 +207,6 @@ fun MapView(
val mapFilterState by mapViewModel.mapFilterStateFlow.collectAsStateWithLifecycle()
val ourNodeInfo by uiViewModel.ourNodeInfo.collectAsStateWithLifecycle()
var editingWaypoint by remember { mutableStateOf(null) }
- val savedCameraPosition by mapViewModel.cameraPosition.collectAsStateWithLifecycle()
val selectedGoogleMapType by mapViewModel.selectedGoogleMapType.collectAsStateWithLifecycle()
val currentCustomTileProviderUrl by mapViewModel.selectedCustomTileProviderUrl.collectAsStateWithLifecycle()
@@ -215,13 +214,7 @@ fun MapView(
var mapTypeMenuExpanded by remember { mutableStateOf(false) }
var showCustomTileManagerSheet by remember { mutableStateOf(false) }
- val defaultLatLng = LatLng(0.0, 0.0)
- val cameraPositionState = rememberCameraPositionState {
- position =
- savedCameraPosition?.let {
- CameraPosition(LatLng(it.targetLat, it.targetLng), it.zoom, it.tilt, it.bearing)
- } ?: CameraPosition.fromLatLngZoom(defaultLatLng, 7f)
- }
+ val cameraPositionState = rememberCameraPositionState {}
// Location tracking functionality
val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) }
@@ -339,7 +332,6 @@ fun MapView(
}
var showClusterItemsDialog by remember { mutableStateOf?>(null) }
- LaunchedEffect(cameraPositionState.position) { mapViewModel.onCameraPositionChanged(cameraPositionState.position) }
Scaffold { paddingValues ->
Box(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
@@ -351,7 +343,7 @@ fun MapView(
MapUiSettings(
zoomControlsEnabled = true,
mapToolbarEnabled = true,
- compassEnabled = true,
+ compassEnabled = false,
myLocationButtonEnabled = false, // Disabled - we use custom location button
rotationGesturesEnabled = true,
scrollGesturesEnabled = true,
@@ -370,32 +362,27 @@ fun MapView(
}
},
onMapLoaded = {
- if (
- savedCameraPosition?.targetLat == defaultLatLng.latitude &&
- savedCameraPosition?.targetLng == defaultLatLng.longitude
- ) {
- val pointsToBound: List =
- when {
- !nodeTrack.isNullOrEmpty() -> nodeTrack.map { it.toLatLng() }
+ val pointsToBound: List =
+ when {
+ !nodeTrack.isNullOrEmpty() -> nodeTrack.map { it.toLatLng() }
- allNodes.isNotEmpty() || displayableWaypoints.isNotEmpty() ->
- allNodes.mapNotNull { it.toLatLng() } + displayableWaypoints.map { it.toLatLng() }
+ allNodes.isNotEmpty() || displayableWaypoints.isNotEmpty() ->
+ allNodes.mapNotNull { it.toLatLng() } + displayableWaypoints.map { it.toLatLng() }
- else -> emptyList()
- }
-
- if (pointsToBound.isNotEmpty()) {
- val bounds = LatLngBounds.builder().apply { pointsToBound.forEach(::include) }.build()
-
- val padding = if (!pointsToBound.isEmpty()) 100 else 48
-
- try {
- coroutineScope.launch {
- cameraPositionState.animate(CameraUpdateFactory.newLatLngBounds(bounds, padding))
- }
- } catch (e: IllegalStateException) {
- warn("MapView Could not animate to bounds: ${e.message}")
+ else -> emptyList()
+ }
+
+ if (pointsToBound.isNotEmpty()) {
+ val bounds = LatLngBounds.builder().apply { pointsToBound.forEach(::include) }.build()
+
+ val padding = if (!pointsToBound.isEmpty()) 100 else 48
+
+ try {
+ coroutineScope.launch {
+ cameraPositionState.animate(CameraUpdateFactory.newLatLngBounds(bounds, padding))
}
+ } catch (e: IllegalStateException) {
+ warn("MapView Could not animate to bounds: ${e.message}")
}
}
},
@@ -537,12 +524,10 @@ fun MapView(
}
}
- val currentCameraPosition = cameraPositionState.position
- var displayedZoom by remember { mutableStateOf(currentCameraPosition.zoom) }
-
- if (displayedZoom != 0f) {
- DisappearingScaleBar(cameraPositionState = cameraPositionState)
- }
+ ScaleBar(
+ cameraPositionState = cameraPositionState,
+ modifier = Modifier.align(Alignment.BottomStart).padding(bottom = 48.dp),
+ )
editingWaypoint?.let { waypointToEdit ->
EditWaypointDialog(
waypoint = waypointToEdit,
@@ -613,6 +598,7 @@ fun MapView(
isLocationTrackingEnabled = !isLocationTrackingEnabled
}
},
+ bearing = cameraPositionState.position.bearing,
onOrientNorth = {
coroutineScope.launch {
try {
diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt b/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt
index 2c9b10358..a0beb072c 100644
--- a/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt
+++ b/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt
@@ -30,7 +30,6 @@ import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import com.geeksville.mesh.repository.map.CustomTileProviderRepository
import com.google.android.gms.maps.GoogleMap
-import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.TileProvider
import com.google.android.gms.maps.model.UrlTileProvider
import com.google.maps.android.compose.MapType
@@ -101,10 +100,6 @@ constructor(
private val _selectedGoogleMapType = MutableStateFlow(MapType.NORMAL)
val selectedGoogleMapType: StateFlow = _selectedGoogleMapType.asStateFlow()
- private val _cameraPosition = MutableStateFlow(null)
-
- val cameraPosition: StateFlow = _cameraPosition.asStateFlow()
-
val displayUnits =
radioConfigRepository.deviceProfileFlow
.mapNotNull { it.config.display.units }
@@ -114,17 +109,6 @@ constructor(
initialValue = ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC,
)
- fun onCameraPositionChanged(cameraPosition: CameraPosition) {
- _cameraPosition.value =
- MapCameraPosition(
- targetLat = cameraPosition.target.latitude,
- targetLng = cameraPosition.target.longitude,
- zoom = cameraPosition.zoom,
- tilt = cameraPosition.tilt,
- bearing = cameraPosition.bearing,
- )
- }
-
fun addCustomTileProvider(name: String, urlTemplate: String) {
viewModelScope.launch {
if (name.isBlank() || urlTemplate.isBlank() || !isValidTileUrlTemplate(urlTemplate)) {
diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapButton.kt b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapButton.kt
index a35ff9b7d..6e9af80dc 100644
--- a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapButton.kt
+++ b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapButton.kt
@@ -19,13 +19,25 @@ package com.geeksville.mesh.ui.map.components
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
@Composable
-fun MapButton(icon: ImageVector, contentDescription: String, modifier: Modifier = Modifier, onClick: () -> Unit) {
+fun MapButton(
+ modifier: Modifier = Modifier,
+ icon: ImageVector,
+ iconTint: Color? = null,
+ contentDescription: String,
+ onClick: () -> Unit,
+) {
FilledIconButton(onClick = onClick, modifier = modifier) {
- Icon(imageVector = icon, contentDescription = contentDescription)
+ Icon(
+ imageVector = icon,
+ contentDescription = contentDescription,
+ tint = iconTint ?: IconButtonDefaults.filledIconButtonColors().contentColor,
+ )
}
}
diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt
index 65b49b5d4..34ca2bf70 100644
--- a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt
+++ b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt
@@ -20,17 +20,20 @@ package com.geeksville.mesh.ui.map.components
import androidx.compose.foundation.layout.Box
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.LocationDisabled
-import androidx.compose.material.icons.outlined.Explore
import androidx.compose.material.icons.outlined.Layers
import androidx.compose.material.icons.outlined.Map
import androidx.compose.material.icons.outlined.MyLocation
+import androidx.compose.material.icons.outlined.Navigation
import androidx.compose.material.icons.outlined.Tune
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.HorizontalFloatingToolbar
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.stringResource
import com.geeksville.mesh.R
+import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
import com.geeksville.mesh.ui.map.MapViewModel
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -51,6 +54,7 @@ fun MapControlsOverlay(
hasLocationPermission: Boolean = false,
isLocationTrackingEnabled: Boolean = false,
onToggleLocationTracking: () -> Unit = {},
+ bearing: Float = 0f,
onOrientNorth: () -> Unit = {},
) {
HorizontalFloatingToolbar(
@@ -59,6 +63,7 @@ fun MapControlsOverlay(
leadingContent = {},
trailingContent = {},
content = {
+ CompassButton(onOrientNorth = onOrientNorth, bearing = bearing)
if (showFilterButton) {
Box {
MapButton(
@@ -94,8 +99,6 @@ fun MapControlsOverlay(
onClick = onManageLayersClicked,
)
- CompassButton(onOrientNorth = onOrientNorth)
-
// Location tracking button
if (hasLocationPermission) {
MapButton(
@@ -114,9 +117,13 @@ fun MapControlsOverlay(
}
@Composable
-private fun CompassButton(onOrientNorth: () -> Unit) {
+private fun CompassButton(onOrientNorth: () -> Unit, bearing: Float) {
+ val icon = Icons.Outlined.Navigation
+
MapButton(
- icon = Icons.Outlined.Explore,
+ modifier = Modifier.rotate(-bearing),
+ icon = icon,
+ iconTint = MaterialTheme.colorScheme.StatusRed.takeIf { bearing == 0f },
contentDescription = stringResource(id = R.string.orient_north),
onClick = onOrientNorth,
)