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, )