From cac0cc15eb9975b92a353364824c7c5bcef990d8 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Thu, 19 Mar 2026 22:49:16 -0700 Subject: [PATCH 1/5] feat: Enhance privacy settings and telemetry (#308) * feat: Enhance privacy settings and telemetry - Implemented telemetry options for contacts, allowing users to enable or disable telemetry data sharing. - Introduced a clear chat option in the chat interface for better message management. - Updated the telemetry screen to handle telemetry data for contacts, including battery level. - Refactored contact settings to include telemetry options and improved UI for better user experience. * feat: Refactor repeater resolution logic across multiple screens --- lib/l10n/app_es.arb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4a49e1b..1a3475a 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1918,6 +1918,7 @@ "tcpConnectionFailed": "Error en la conexión TCP: {error}", "map_showDiscoveryContacts": "Mostrar Contactos de Descubrimiento", "map_setAsMyLocation": "Establecer mi ubicación", +<<<<<<< HEAD "@path_routeWeight": { "placeholders": { "weight": { @@ -1928,6 +1929,8 @@ } } }, +======= +>>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308)) "settings_privacySubtitle": "Controlar qué información se comparte.", "settings_allowByContact": "Permitir por banderas de contacto", "settings_denyAll": "Denegar todo", @@ -1957,6 +1960,7 @@ } } }, +<<<<<<< HEAD "appSettings_initialRouteWeight": "Peso inicial de la ruta", "appSettings_maxRouteWeight": "Peso máximo permitido para la ruta", "appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas", @@ -1970,4 +1974,9 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetría actualizado", "settings_multiAck": "Multi-ACKs: {value}" -} \ No newline at end of file +} +======= + "settings_telemetryModeUpdated": "Modo de telemetría actualizado", + "settings_multiAck": "Multi-ACKs: {value}" +} +>>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308)) From c81c3efe7c2c807a885bf9711a6765da898c05e5 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sun, 22 Feb 2026 16:00:51 -0800 Subject: [PATCH 2/5] Add show overlaps in public keys of repeaters functionality and localization support --- lib/connector/meshcore_connector.dart | 4 + lib/l10n/app_bg.arb | 3 +- lib/l10n/app_de.arb | 3 +- lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 11 +- lib/l10n/app_fr.arb | 3 +- lib/l10n/app_it.arb | 3 +- lib/l10n/app_localizations.dart | 6 + lib/l10n/app_localizations_bg.dart | 3 + lib/l10n/app_localizations_de.dart | 3 + lib/l10n/app_localizations_en.dart | 3 + lib/l10n/app_localizations_es.dart | 3 + lib/l10n/app_localizations_fr.dart | 3 + lib/l10n/app_localizations_it.dart | 3 + lib/l10n/app_localizations_nl.dart | 3 + lib/l10n/app_localizations_pl.dart | 3 + lib/l10n/app_localizations_pt.dart | 3 + lib/l10n/app_localizations_ru.dart | 3 + lib/l10n/app_localizations_sk.dart | 3 + lib/l10n/app_localizations_sl.dart | 3 + lib/l10n/app_localizations_sv.dart | 3 + lib/l10n/app_localizations_uk.dart | 3 + lib/l10n/app_localizations_zh.dart | 3 + lib/l10n/app_nl.arb | 3 +- lib/l10n/app_pl.arb | 3 +- lib/l10n/app_pt.arb | 3 +- lib/l10n/app_ru.arb | 3 +- lib/l10n/app_sk.arb | 3 +- lib/l10n/app_sl.arb | 3 +- lib/l10n/app_sv.arb | 3 +- lib/l10n/app_uk.arb | 3 +- lib/l10n/app_zh.arb | 3 +- lib/models/app_settings.dart | 6 + lib/screens/map_screen.dart | 182 ++++++++++++++++++------- lib/screens/settings_screen.dart | 11 +- lib/services/app_settings_service.dart | 4 + lib/services/path_history_service.dart | 10 ++ 37 files changed, 242 insertions(+), 77 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 87a6755..7b974f3 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -5196,6 +5196,10 @@ class MeshCoreConnector extends ChangeNotifier { markChannelRead(channelIndex); notifyListeners(); } + + void deleteAllPaths() { + _pathHistoryService?.clearAllHistories(); + } } const int _phRouteMask = 0x03; diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 13788b8..d2c5f8b 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Мулти-потвърди: {value}", - "settings_telemetryModeUpdated": "Режим на телеметрията е обновен" + "settings_telemetryModeUpdated": "Режим на телеметрията е обновен", + "map_showOverlaps": "Покриване на ключа на повтаряча" } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 6745054..0b3af08 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1969,5 +1969,6 @@ "appSettings_maxMessageRetriesSubtitle": "Anzahl der Versuche, eine Nachricht erneut zu senden, bevor sie als fehlgeschlagen markiert wird.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetriemodus aktualisiert", - "settings_multiAck": "Mehrfach-Bestätigungen: {value}" + "settings_multiAck": "Mehrfach-Bestätigungen: {value}", + "map_showOverlaps": "Überlappungen der Repeater-Taste" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2b263c6..18d8c2f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -878,6 +878,7 @@ "map_chatNodes": "Chat Nodes", "map_repeaters": "Repeaters", "map_otherNodes": "Other Nodes", + "map_showOverlaps": "Repeater Key Overlaps", "map_keyPrefix": "Key Prefix", "map_filterByKeyPrefix": "Filter by key prefix", "map_publicKeyPrefix": "Public key prefix", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 1a3475a..4a49e1b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1918,7 +1918,6 @@ "tcpConnectionFailed": "Error en la conexión TCP: {error}", "map_showDiscoveryContacts": "Mostrar Contactos de Descubrimiento", "map_setAsMyLocation": "Establecer mi ubicación", -<<<<<<< HEAD "@path_routeWeight": { "placeholders": { "weight": { @@ -1929,8 +1928,6 @@ } } }, -======= ->>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308)) "settings_privacySubtitle": "Controlar qué información se comparte.", "settings_allowByContact": "Permitir por banderas de contacto", "settings_denyAll": "Denegar todo", @@ -1960,7 +1957,6 @@ } } }, -<<<<<<< HEAD "appSettings_initialRouteWeight": "Peso inicial de la ruta", "appSettings_maxRouteWeight": "Peso máximo permitido para la ruta", "appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas", @@ -1974,9 +1970,4 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetría actualizado", "settings_multiAck": "Multi-ACKs: {value}" -} -======= - "settings_telemetryModeUpdated": "Modo de telemetría actualizado", - "settings_multiAck": "Multi-ACKs: {value}" -} ->>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308)) +} \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e98e317..ba929ae 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Multi-ACKs : {value}", - "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour" + "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour", + "map_showOverlaps": "Chevauchement de la touche répétitive" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index f11cde5..b4414d6 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Numero di tentativi di riprova prima di considerare un messaggio come fallito.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modalità telemetria aggiornata", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Sovrapposizioni della chiave ripetitore" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 4bb6936..7c2488e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3052,6 +3052,12 @@ abstract class AppLocalizations { /// **'Other Nodes'** String get map_otherNodes; + /// No description provided for @map_showOverlaps. + /// + /// In en, this message translates to: + /// **'Repeater Key Overlaps'** + String get map_showOverlaps; + /// No description provided for @map_keyPrefix. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index d6537f9..9915d06 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1689,6 +1689,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get map_otherNodes => 'Други възли'; + @override + String get map_showOverlaps => 'Покриване на ключа на повтаряча'; + @override String get map_keyPrefix => 'Префикс на ключа'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 87fab6f..721730b 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1686,6 +1686,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get map_otherNodes => 'Andere Knoten'; + @override + String get map_showOverlaps => 'Überlappungen der Repeater-Taste'; + @override String get map_keyPrefix => 'Schlüsselpräfix'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 1e0196b..d8b2abe 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1656,6 +1656,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get map_otherNodes => 'Other Nodes'; + @override + String get map_showOverlaps => 'Repeater Key Overlaps'; + @override String get map_keyPrefix => 'Key Prefix'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index dff2e5e..e5c2817 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1685,6 +1685,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get map_otherNodes => 'Otros Nodos'; + @override + String get map_showOverlaps => 'Superposiciones de tecla repetidora'; + @override String get map_keyPrefix => 'Prefijo de clave'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 91bf4f4..3570527 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1695,6 +1695,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get map_otherNodes => 'Autres nœuds'; + @override + String get map_showOverlaps => 'Chevauchement de la touche répétitive'; + @override String get map_keyPrefix => 'Préfixe clé'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index b688c06..1bf328d 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1687,6 +1687,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get map_otherNodes => 'Altri Nodi'; + @override + String get map_showOverlaps => 'Sovrapposizioni della chiave ripetitore'; + @override String get map_keyPrefix => 'Prefisso Chiave'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 1530886..947fd27 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1674,6 +1674,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get map_otherNodes => 'Andere Nodes'; + @override + String get map_showOverlaps => 'Herhalingssleutel overlapt'; + @override String get map_keyPrefix => 'Prefix sleutel'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 16b6512..aa408c1 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1688,6 +1688,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get map_otherNodes => 'Inne węzły'; + @override + String get map_showOverlaps => 'Nakładające się klucze powtarzalne'; + @override String get map_keyPrefix => 'Prefiks klucza'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 87b44ca..42e91f9 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1686,6 +1686,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get map_otherNodes => 'Outros Nós'; + @override + String get map_showOverlaps => 'Sobreposições da Chave Repeater'; + @override String get map_keyPrefix => 'Prefixo Chave'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 72d2e1c..4cd5e43 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1689,6 +1689,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_otherNodes => 'Другие ноды'; + @override + String get map_showOverlaps => 'Перекрытия ключа повтора'; + @override String get map_keyPrefix => 'Префикс ключа'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 7817af6..b0c0750 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1675,6 +1675,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get map_otherNodes => 'Ostatné uzly'; + @override + String get map_showOverlaps => 'Prekrývanie opakovača kľúča'; + @override String get map_keyPrefix => 'Päťciferné predpona'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 6032ee0..3651f3c 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1671,6 +1671,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get map_otherNodes => 'Druge vozlišča'; + @override + String get map_showOverlaps => 'Prekrivanje ključa ponovnega predvajanja'; + @override String get map_keyPrefix => 'Predpona ključa'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 5b19be3..e3c7c7d 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1664,6 +1664,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get map_otherNodes => 'Andra noder'; + @override + String get map_showOverlaps => 'Repeater-nyckelöverlappningar'; + @override String get map_keyPrefix => 'Nyckelprefix'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 096e470..f56455a 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1684,6 +1684,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_otherNodes => 'Інші вузли'; + @override + String get map_showOverlaps => 'Перекриття ключа повторювача'; + @override String get map_keyPrefix => 'Префікс ключа'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index f142763..54b86a3 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1582,6 +1582,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get map_otherNodes => '其他节点'; + @override + String get map_showOverlaps => '重复键重叠'; + @override String get map_keyPrefix => '关键字前缀'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 1b5e78c..9bf6283 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Herhalingssleutel overlapt" } \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index ac95748..9f7e7fd 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany", - "settings_multiAck": "Wiele potwierdzeń: {value}" + "settings_multiAck": "Wiele potwierdzeń: {value}", + "map_showOverlaps": "Nakładające się klucze powtarzalne" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index adddd13..2ac9b9d 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Número de tentativas de reenvio antes de classificar uma mensagem como falha.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetria atualizado", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Sobreposições da Chave Repeater" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 2d3df51..9d45622 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1181,5 +1181,6 @@ "appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", - "settings_multiAck": "Мульти-ACK: {value}" + "settings_multiAck": "Мульти-ACK: {value}", + "map_showOverlaps": "Перекрытия ключа повтора" } \ No newline at end of file diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 57eb285..1a79f85 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Počet pokusov o odošleť pred označením správy ako neúspešnej", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný", - "settings_multiAck": "Viaceré ACK: {value}" + "settings_multiAck": "Viaceré ACK: {value}", + "map_showOverlaps": "Prekrývanie opakovača kľúča" } \ No newline at end of file diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 355f8d8..c09e2ec 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Večkratni potrditvi: {value}", - "settings_telemetryModeUpdated": "Način telemetrije posodobljen" + "settings_telemetryModeUpdated": "Način telemetrije posodobljen", + "map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja" } \ No newline at end of file diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 84f4e5e..11a8631 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Antal försök att skicka om ett meddelande innan det markeras som misslyckat.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetri-läge uppdaterat", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Repeater-nyckelöverlappningar" } \ No newline at end of file diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index be1eaa8..c381c8c 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", - "settings_multiAck": "Багатократне підтвердження: {value}" + "settings_multiAck": "Багатократне підтвердження: {value}", + "map_showOverlaps": "Перекриття ключа повторювача" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9493b27..b0e7c61 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1946,5 +1946,6 @@ "appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "多重ACK:{value}", - "settings_telemetryModeUpdated": "遥测模式已更新" + "settings_telemetryModeUpdated": "遥测模式已更新", + "map_showOverlaps": "重复键重叠" } \ No newline at end of file diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart index 8ee904d..31c1741 100644 --- a/lib/models/app_settings.dart +++ b/lib/models/app_settings.dart @@ -18,6 +18,7 @@ class AppSettings { final bool mapShowRepeaters; final bool mapShowChatNodes; final bool mapShowOtherNodes; + final bool mapShowOverlaps; final double mapTimeFilterHours; // 0 = all time final bool mapKeyPrefixEnabled; final String mapKeyPrefix; @@ -53,6 +54,7 @@ class AppSettings { this.mapShowRepeaters = true, this.mapShowChatNodes = true, this.mapShowOtherNodes = true, + this.mapShowOverlaps = false, this.mapTimeFilterHours = 0, // Default to all time this.mapKeyPrefixEnabled = false, this.mapKeyPrefix = '', @@ -92,6 +94,7 @@ class AppSettings { 'map_show_repeaters': mapShowRepeaters, 'map_show_chat_nodes': mapShowChatNodes, 'map_show_other_nodes': mapShowOtherNodes, + 'map_show_overlaps': mapShowOverlaps, 'map_time_filter_hours': mapTimeFilterHours, 'map_key_prefix_enabled': mapKeyPrefixEnabled, 'map_key_prefix': mapKeyPrefix, @@ -137,6 +140,7 @@ class AppSettings { mapShowRepeaters: json['map_show_repeaters'] as bool? ?? true, mapShowChatNodes: json['map_show_chat_nodes'] as bool? ?? true, mapShowOtherNodes: json['map_show_other_nodes'] as bool? ?? true, + mapShowOverlaps: json['map_show_overlaps'] as bool? ?? false, mapTimeFilterHours: (json['map_time_filter_hours'] as num?)?.toDouble() ?? 0, mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false, @@ -196,6 +200,7 @@ class AppSettings { bool? mapShowRepeaters, bool? mapShowChatNodes, bool? mapShowOtherNodes, + bool? mapShowOverlaps, double? mapTimeFilterHours, bool? mapKeyPrefixEnabled, String? mapKeyPrefix, @@ -231,6 +236,7 @@ class AppSettings { mapShowRepeaters: mapShowRepeaters ?? this.mapShowRepeaters, mapShowChatNodes: mapShowChatNodes ?? this.mapShowChatNodes, mapShowOtherNodes: mapShowOtherNodes ?? this.mapShowOtherNodes, + mapShowOverlaps: mapShowOverlaps ?? this.mapShowOverlaps, mapTimeFilterHours: mapTimeFilterHours ?? this.mapTimeFilterHours, mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled, mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix, diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 6aaebf0..e7558c5 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:math'; import 'dart:typed_data'; @@ -52,7 +53,7 @@ class MapScreen extends StatefulWidget { class _MapScreenState extends State { // Zoom level at which node labels start to appear - static const double _labelZoomThreshold = 12.0; + static const double _labelZoomThreshold = 14.0; final MapController _mapController = MapController(); final MapMarkerService _markerService = MapMarkerService(); @@ -329,7 +330,9 @@ class _MapScreenState extends State { if (!_isBuildingPathTrace) IconButton( icon: const Icon(Icons.radar), - onPressed: () => _startPath(), + onPressed: () => _startPath( + LatLng(connector.selfLatitude!, connector.selfLongitude!), + ), tooltip: context.l10n.contacts_pathTrace, ), if (!_isBuildingPathTrace) @@ -580,6 +583,7 @@ class _MapScreenState extends State { // Index known-location repeaters by their 1-byte hash. // null value = two repeaters share the same hash byte (ambiguous collision). final repeaterByHash = {}; + for (final c in withLocation) { if (c.type == advTypeRepeater) { if (repeaterByHash.containsKey(c.publicKey[0])) { @@ -595,6 +599,11 @@ class _MapScreenState extends State { for (final contact in allContacts) { if (contact.hasLocation) continue; + if (contact.lastSeen.isBefore( + DateTime.now().subtract(const Duration(hours: 24)), + )) { + continue; // skip stale contacts + } final anchorSet = {}; @@ -641,10 +650,19 @@ class _MapScreenState extends State { continue; // discard implausible guesses near (0, 0) } } else { - double lat = 0, lon = 0; + double lat = 0, lon = 0, weight = 1.0; + int counted = 0; for (final a in anchors) { - lat += a.latitude; - lon += a.longitude; + if (counted == 0) { + lat = a.latitude; + lon = a.longitude; + } else { + lat += a.latitude * weight; + lon += a.longitude * weight; + } + // weight subsequent anchors less to create a bias towards the first (if more than 2) + weight = weight / 2; + counted++; } position = _offsetGuessedPosition( LatLng(lat / anchors.length, lon / anchors.length), @@ -812,31 +830,67 @@ class _MapScreenState extends State { return markers; } + List _filterContactsBySettings( + List contacts, + dynamic settings, + ) { + List filtered = []; + bool addContact = false; + for (final contact in contacts) { + addContact = false; + if (!contact.hasLocation) continue; + + // Apply node type filters + if (contact.type == advTypeRepeater && + (settings.mapShowRepeaters || + _isBuildingPathTrace || + settings.mapShowOverlaps)) { + addContact = true; + } + if (contact.type == advTypeChat && + (settings.mapShowChatNodes || _isBuildingPathTrace)) { + addContact = true; + } + if (contact.type != advTypeChat && + contact.type != advTypeRepeater && + (settings.mapShowOtherNodes || + _isBuildingPathTrace || + settings.mapShowOverlaps)) { + addContact = true; + } + + final hasOverlap = contacts + .where( + (c) => + c.publicKeyHex != contact.publicKeyHex && + c.publicKey.first == contact.publicKey.first && + (c.type == advTypeRepeater || c.type == advTypeRoom) && + (contact.type == advTypeRepeater || + contact.type == advTypeRoom), + ) + .firstOrNull; + + if (hasOverlap == null && + settings.mapShowOverlaps && + !_isBuildingPathTrace) { + addContact = false; + } + + if (addContact) { + filtered.add(contact); + } + } + return filtered; + } + List _buildMarkers( List contacts, settings, { required bool showLabels, }) { final markers = []; - - for (final contact in contacts) { - if (!contact.hasLocation) continue; - - // Apply node type filters - if (contact.type == advTypeRepeater && - (!settings.mapShowRepeaters && !_isBuildingPathTrace)) { - continue; - } - if (contact.type == advTypeChat && - !(settings.mapShowChatNodes && !_isBuildingPathTrace)) { - continue; - } - if (contact.type != advTypeChat && - contact.type != advTypeRepeater && - (!settings.mapShowOtherNodes && !_isBuildingPathTrace)) { - continue; - } - + final filteredContacts = _filterContactsBySettings(contacts, settings); + for (final contact in filteredContacts) { final marker = Marker( point: LatLng(contact.latitude!, contact.longitude!), width: 35, @@ -852,7 +906,9 @@ class _MapScreenState extends State { Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: _getNodeColor(contact.type), + color: settings.mapShowOverlaps && !_isBuildingPathTrace + ? Colors.red + : _getNodeColor(contact.type), shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), boxShadow: [ @@ -879,7 +935,9 @@ class _MapScreenState extends State { markers.add( _buildNodeLabelMarker( point: LatLng(contact.latitude!, contact.longitude!), - label: contact.name, + label: settings.mapShowOverlaps && !_isBuildingPathTrace + ? "${contact.publicKeyHex.substring(0, 2)}:${contact.name}" + : contact.name, ), ); } @@ -959,20 +1017,12 @@ class _MapScreenState extends State { int markerCount, int guessedCount, ) { - int nodeCount = 0; - for (final contact in contactsWithLocation) { - // Apply node type filters - if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) { - continue; - } - if (contact.type == advTypeChat && !settings.mapShowChatNodes) continue; - if (contact.type != advTypeChat && - contact.type != advTypeRepeater && - !settings.mapShowOtherNodes) { - continue; - } - nodeCount++; - } + final filteredContacts = _filterContactsBySettings( + contactsWithLocation, + settings, + ); + + final nodeCount = filteredContacts.length; return Positioned( top: 16, @@ -1846,6 +1896,15 @@ class _MapScreenState extends State { }, contentPadding: EdgeInsets.zero, ), + CheckboxListTile( + title: Text(context.l10n.map_showOverlaps), + value: settings.mapShowOverlaps, + onChanged: (value) { + service.setMapShowOverlaps(value ?? true); + }, + contentPadding: EdgeInsets.zero, + ), + const SizedBox(height: 16), Text( context.l10n.map_keyPrefix, @@ -2004,12 +2063,13 @@ class _MapScreenState extends State { }); } - void _startPath() { + void _startPath(LatLng position) { setState(() { _isBuildingPathTrace = true; _pathTrace.clear(); _points.clear(); _polylines.clear(); + _points.add(position); }); } @@ -2055,14 +2115,14 @@ class _MapScreenState extends State { .join(','), style: TextStyle(fontSize: 18), ), - const SizedBox(height: 6), + // const SizedBox(height: 6), Wrap( alignment: WrapAlignment.center, - spacing: 8, - runSpacing: 8, + spacing: 1, + runSpacing: 1, children: [ if (_pathTrace.isNotEmpty) - ElevatedButton( + IconButton( onPressed: () { Navigator.push( context, @@ -2077,15 +2137,37 @@ class _MapScreenState extends State { _isBuildingPathTrace = false; }); }, - child: Text(l10n.map_runTrace), + tooltip: "Path Trace", + icon: const Icon(Icons.arrow_forward_outlined), ), if (_pathTrace.isNotEmpty) - ElevatedButton( + IconButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PathTraceMapScreen( + title: l10n.contacts_pathTrace, + path: Uint8List.fromList(_pathTrace), + flipPathAround: true, + ), + ), + ); + setState(() { + _isBuildingPathTrace = false; + }); + }, + tooltip: "Build Return Path", + icon: const Icon(Icons.replay), + ), + if (_pathTrace.isNotEmpty) + IconButton( onPressed: _removePath, - child: Text(l10n.map_removeLast), + tooltip: "Remove Last Point", + icon: const Icon(Icons.delete), ), if (_pathTrace.isEmpty) - ElevatedButton( + IconButton( onPressed: () { setState(() { _isBuildingPathTrace = false; @@ -2097,7 +2179,7 @@ class _MapScreenState extends State { SnackBar(content: Text(l10n.map_pathTraceCancelled)), ); }, - child: Text(l10n.common_cancel), + icon: const Icon(Icons.close), ), ], ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index cc61143..c42d4e3 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -311,10 +311,13 @@ class _SettingsScreenState extends State { ), ), ListTile( - leading: const Icon(Icons.cell_tower), - title: Text(l10n.settings_sendAdvertisement), - subtitle: Text(l10n.settings_sendAdvertisementSubtitle), - onTap: () => _sendAdvert(context, connector), + leading: const Icon(Icons.delete_outline, color: Colors.red), + title: Text("Delete All Paths"), + subtitle: Text( + "Clear all path data from contacts.", + style: TextStyle(color: Colors.red[700]), + ), + onTap: () => connector.deleteAllPaths(), ), const Divider(height: 1), ListTile( diff --git a/lib/services/app_settings_service.dart b/lib/services/app_settings_service.dart index e6697f4..6414617 100644 --- a/lib/services/app_settings_service.dart +++ b/lib/services/app_settings_service.dart @@ -64,6 +64,10 @@ class AppSettingsService extends ChangeNotifier { await updateSettings(_settings.copyWith(mapShowOtherNodes: value)); } + Future setMapShowOverlaps(bool value) async { + await updateSettings(_settings.copyWith(mapShowOverlaps: value)); + } + Future setMapTimeFilterHours(double value) async { await updateSettings(_settings.copyWith(mapTimeFilterHours: value)); } diff --git a/lib/services/path_history_service.dart b/lib/services/path_history_service.dart index 68a9245..fc81c56 100644 --- a/lib/services/path_history_service.dart +++ b/lib/services/path_history_service.dart @@ -565,6 +565,16 @@ class PathHistoryService extends ChangeNotifier { _floodStats.remove(oldest); } } + + void clearAllHistories() { + _cache.clear(); + _cacheAccessOrder.clear(); + _autoRotationIndex.clear(); + _floodStats.clear(); + _storage.clearAllPathHistories(); + _version = 0; + notifyListeners(); + } } class _DeferredPathRecord { From 77be2b8e6fc47cd326425ab7abcb060564014d8f Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Tue, 24 Feb 2026 19:01:22 -0800 Subject: [PATCH 3/5] Refactor code structure for improved readability and maintainability --- lib/l10n/app_bg.arb | 3 +- lib/l10n/app_de.arb | 3 +- lib/l10n/app_en.arb | 3 +- lib/l10n/app_es.arb | 4 +- lib/l10n/app_fr.arb | 3 +- lib/l10n/app_it.arb | 3 +- lib/l10n/app_localizations.dart | 8 +- lib/l10n/app_localizations_bg.dart | 3 + lib/l10n/app_localizations_de.dart | 4 + lib/l10n/app_localizations_en.dart | 5 +- lib/l10n/app_localizations_es.dart | 3 + lib/l10n/app_localizations_fr.dart | 3 + lib/l10n/app_localizations_it.dart | 4 + lib/l10n/app_localizations_nl.dart | 3 + lib/l10n/app_localizations_pl.dart | 3 + lib/l10n/app_localizations_pt.dart | 3 + lib/l10n/app_localizations_ru.dart | 3 + lib/l10n/app_localizations_sk.dart | 3 + lib/l10n/app_localizations_sl.dart | 3 + lib/l10n/app_localizations_sv.dart | 3 + lib/l10n/app_localizations_uk.dart | 3 + lib/l10n/app_localizations_zh.dart | 3 + lib/l10n/app_nl.arb | 3 +- lib/l10n/app_pl.arb | 3 +- lib/l10n/app_pt.arb | 3 +- lib/l10n/app_ru.arb | 3 +- lib/l10n/app_sk.arb | 3 +- lib/l10n/app_sl.arb | 3 +- lib/l10n/app_sv.arb | 3 +- lib/l10n/app_uk.arb | 3 +- lib/l10n/app_zh.arb | 3 +- lib/screens/map_screen.dart | 134 ++++++++++++++++++++++------- 32 files changed, 187 insertions(+), 50 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index d2c5f8b..c0479d1 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Мулти-потвърди: {value}", "settings_telemetryModeUpdated": "Режим на телеметрията е обновен", - "map_showOverlaps": "Покриване на ключа на повтаряча" + "map_showOverlaps": "Покриване на ключа на повтаряча", + "map_runTraceWithReturnPath": "Върни се по същия път." } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 0b3af08..98ddb93 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1970,5 +1970,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetriemodus aktualisiert", "settings_multiAck": "Mehrfach-Bestätigungen: {value}", - "map_showOverlaps": "Überlappungen der Repeater-Taste" + "map_showOverlaps": "Überlappungen der Repeater-Taste", + "map_runTraceWithReturnPath": "Auf dem gleichen Pfad zurückkehren." } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 18d8c2f..d8623d3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -892,7 +892,8 @@ "map_joinRoom": "Join Room", "map_manageRepeater": "Manage Repeater", "map_tapToAdd": "Tap on nodes to add them to the path.", - "map_runTrace": "Run Path Trace", + "map_runTrace": "Run path trace", + "map_runTraceWithReturnPath": "Return back on the same path.", "map_removeLast": "Remove Last", "map_pathTraceCancelled": "Path trace cancelled.", "mapCache_title": "Offline Map Cache", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4a49e1b..a65d80f 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1969,5 +1969,7 @@ "appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetría actualizado", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Superposiciones de tecla repetidora", + "map_runTraceWithReturnPath": "Volver atrás por el mismo camino." } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ba929ae..8bb0a46 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Multi-ACKs : {value}", "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour", - "map_showOverlaps": "Chevauchement de la touche répétitive" + "map_showOverlaps": "Chevauchement de la touche répétitive", + "map_runTraceWithReturnPath": "Revenir sur le même chemin." } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index b4414d6..b168e75 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modalità telemetria aggiornata", "settings_multiAck": "Multi-ACKs: {value}", - "map_showOverlaps": "Sovrapposizioni della chiave ripetitore" + "map_showOverlaps": "Sovrapposizioni della chiave ripetitore", + "map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 7c2488e..ce5833a 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3139,9 +3139,15 @@ abstract class AppLocalizations { /// No description provided for @map_runTrace. /// /// In en, this message translates to: - /// **'Run Path Trace'** + /// **'Run path trace'** String get map_runTrace; + /// No description provided for @map_runTraceWithReturnPath. + /// + /// In en, this message translates to: + /// **'Return back on the same path.'** + String get map_runTraceWithReturnPath; + /// No description provided for @map_removeLast. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 9915d06..53d8ef2 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1736,6 +1736,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get map_runTrace => 'Изпълни Път на Следване'; + @override + String get map_runTraceWithReturnPath => 'Върни се по същия път.'; + @override String get map_removeLast => 'Премахни Последно'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 721730b..535eb45 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1733,6 +1733,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get map_runTrace => 'Pfadverlauf ausführen'; + @override + String get map_runTraceWithReturnPath => + 'Auf dem gleichen Pfad zurückkehren.'; + @override String get map_removeLast => 'Letztes Entfernen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index d8b2abe..2fe75ec 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1699,7 +1699,10 @@ class AppLocalizationsEn extends AppLocalizations { String get map_tapToAdd => 'Tap on nodes to add them to the path.'; @override - String get map_runTrace => 'Run Path Trace'; + String get map_runTrace => 'Run path trace'; + + @override + String get map_runTraceWithReturnPath => 'Return back on the same path.'; @override String get map_removeLast => 'Remove Last'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index e5c2817..70e0a79 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1731,6 +1731,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get map_runTrace => 'Ejecutar Rastreo de Ruta'; + @override + String get map_runTraceWithReturnPath => 'Volver atrás por el mismo camino.'; + @override String get map_removeLast => 'Eliminar último'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 3570527..5be46c8 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1742,6 +1742,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get map_runTrace => 'Exécuter la traçage de chemin'; + @override + String get map_runTraceWithReturnPath => 'Revenir sur le même chemin.'; + @override String get map_removeLast => 'Supprimer le dernier'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 1bf328d..63b501f 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1732,6 +1732,10 @@ class AppLocalizationsIt extends AppLocalizations { @override String get map_runTrace => 'Esegui Path Trace'; + @override + String get map_runTraceWithReturnPath => + 'Tornare indietro sullo stesso percorso'; + @override String get map_removeLast => 'Rimuovi ultimo'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 947fd27..adf2392 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get map_runTrace => 'Padeshulp traceren'; + @override + String get map_runTraceWithReturnPath => 'Terugkeren op hetzelfde pad.'; + @override String get map_removeLast => 'Verwijder Laatste'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index aa408c1..b2e5e7e 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1734,6 +1734,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get map_runTrace => 'Uruchom ślad ścieżki'; + @override + String get map_runTraceWithReturnPath => 'Wróć z powrotem tą samą ścieżką'; + @override String get map_removeLast => 'Usuń ostatni'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 42e91f9..1bc971a 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1732,6 +1732,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get map_runTrace => 'Executar Traçado de Caminho'; + @override + String get map_runTraceWithReturnPath => 'Retornar ao mesmo caminho.'; + @override String get map_removeLast => 'Remover Último'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4cd5e43..f71bff0 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1735,6 +1735,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_runTrace => 'Запустить трассировку пути'; + @override + String get map_runTraceWithReturnPath => 'Вернуться обратно по тому же пути'; + @override String get map_removeLast => 'Удалить последний'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index b0c0750..d5d00a0 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get map_runTrace => 'Spustiť trasovaním cesty'; + @override + String get map_runTraceWithReturnPath => 'Vráťte sa späť po tej istej ceste.'; + @override String get map_removeLast => 'Odstrániť posledný'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 3651f3c..47066c9 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1716,6 +1716,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get map_runTrace => 'Zaženi sledenje poti'; + @override + String get map_runTraceWithReturnPath => 'Vrni se nazaj po isti poti.'; + @override String get map_removeLast => 'Odstrani Zadnji'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index e3c7c7d..6e5fd20 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1710,6 +1710,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get map_runTrace => 'Kör spårsökning'; + @override + String get map_runTraceWithReturnPath => 'Gå tillbaka på samma väg'; + @override String get map_removeLast => 'Ta bort sista'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index f56455a..c068ed3 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1730,6 +1730,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_runTrace => 'Виконати трасування шляху'; + @override + String get map_runTraceWithReturnPath => 'Повернутися назад тим же шляхом'; + @override String get map_removeLast => 'Видалити останній'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 54b86a3..78e9c94 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1627,6 +1627,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get map_runTrace => '运行路径追踪'; + @override + String get map_runTraceWithReturnPath => '沿着相同的路径返回'; + @override String get map_removeLast => '移除最后一个'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 9bf6283..43c08b1 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt", "settings_multiAck": "Multi-ACKs: {value}", - "map_showOverlaps": "Herhalingssleutel overlapt" + "map_showOverlaps": "Herhalingssleutel overlapt", + "map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad." } \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 9f7e7fd..1c08c8a 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany", "settings_multiAck": "Wiele potwierdzeń: {value}", - "map_showOverlaps": "Nakładające się klucze powtarzalne" + "map_showOverlaps": "Nakładające się klucze powtarzalne", + "map_runTraceWithReturnPath": "Wróć z powrotem tą samą ścieżką" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 2ac9b9d..1ee4130 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetria atualizado", "settings_multiAck": "Multi-ACKs: {value}", - "map_showOverlaps": "Sobreposições da Chave Repeater" + "map_showOverlaps": "Sobreposições da Chave Repeater", + "map_runTraceWithReturnPath": "Retornar ao mesmo caminho." } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 9d45622..ab32362 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1182,5 +1182,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", "settings_multiAck": "Мульти-ACK: {value}", - "map_showOverlaps": "Перекрытия ключа повтора" + "map_showOverlaps": "Перекрытия ключа повтора", + "map_runTraceWithReturnPath": "Вернуться обратно по тому же пути" } \ No newline at end of file diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 1a79f85..12c2f9a 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný", "settings_multiAck": "Viaceré ACK: {value}", - "map_showOverlaps": "Prekrývanie opakovača kľúča" + "map_showOverlaps": "Prekrývanie opakovača kľúča", + "map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste." } \ No newline at end of file diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index c09e2ec..54ea1f5 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Večkratni potrditvi: {value}", "settings_telemetryModeUpdated": "Način telemetrije posodobljen", - "map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja" + "map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja", + "map_runTraceWithReturnPath": "Vrni se nazaj po isti poti." } \ No newline at end of file diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 11a8631..1bb0c8a 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetri-läge uppdaterat", "settings_multiAck": "Multi-ACKs: {value}", - "map_showOverlaps": "Repeater-nyckelöverlappningar" + "map_showOverlaps": "Repeater-nyckelöverlappningar", + "map_runTraceWithReturnPath": "Gå tillbaka på samma väg" } \ No newline at end of file diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index c381c8c..e55a582 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", "settings_multiAck": "Багатократне підтвердження: {value}", - "map_showOverlaps": "Перекриття ключа повторювача" + "map_showOverlaps": "Перекриття ключа повторювача", + "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index b0e7c61..b415904 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1947,5 +1947,6 @@ "path_routeWeight": "{weight}/{max}", "settings_multiAck": "多重ACK:{value}", "settings_telemetryModeUpdated": "遥测模式已更新", - "map_showOverlaps": "重复键重叠" + "map_showOverlaps": "重复键重叠", + "map_runTraceWithReturnPath": "沿着相同的路径返回" } \ No newline at end of file diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index e7558c5..f5efd3b 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -480,10 +480,12 @@ class _MapScreenState extends State { point: highlightPosition, width: 40, height: 40, - child: Icon( - Icons.location_on_outlined, - color: Colors.red[600], - size: 34, + child: IgnorePointer( + child: Icon( + Icons.location_on_outlined, + color: Colors.red[600], + size: 34, + ), ), ), if (!_isBuildingPathTrace) @@ -506,28 +508,33 @@ class _MapScreenState extends State { ), width: 40, height: 40, - child: Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: Colors.teal, - shape: BoxShape.circle, - border: Border.all( - color: Colors.white, - width: 2, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.3), - blurRadius: 4, - offset: const Offset(0, 2), + child: IgnorePointer( + ignoring: true, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.teal, + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, + width: 2, ), - ], - ), - alignment: Alignment.center, - child: const Icon( - Icons.person_pin_circle, - color: Colors.white, - size: 20, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues( + alpha: 0.3, + ), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + alignment: Alignment.center, + child: const Icon( + Icons.person_pin_circle, + color: Colors.white, + size: 20, + ), ), ), ), @@ -547,6 +554,7 @@ class _MapScreenState extends State { ), if (!_isBuildingPathTrace) _buildLegend( + contacts, contactsWithLocation, settings, sharedMarkers.length, @@ -832,13 +840,16 @@ class _MapScreenState extends State { List _filterContactsBySettings( List contacts, - dynamic settings, - ) { + dynamic settings, { + bool noLocations = false, + }) { List filtered = []; bool addContact = false; for (final contact in contacts) { addContact = false; - if (!contact.hasLocation) continue; + if (!contact.hasLocation && !noLocations) { + continue; + } // Apply node type filters if (contact.type == advTypeRepeater && @@ -1012,17 +1023,25 @@ class _MapScreenState extends State { } Widget _buildLegend( + List contacts, List contactsWithLocation, settings, int markerCount, int guessedCount, ) { final filteredContacts = _filterContactsBySettings( - contactsWithLocation, + contacts, settings, + noLocations: false, + ); + final filteredContactsAll = _filterContactsBySettings( + contacts, + settings, + noLocations: true, ); final nodeCount = filteredContacts.length; + final nodeCountAll = filteredContactsAll.length; return Positioned( top: 16, @@ -1058,6 +1077,54 @@ class _MapScreenState extends State { fontSize: 14, ), ), + Row( + children: [ + Icon( + Icons.location_on, + size: 16, + color: Colors.grey, + ), + Text( + ": $nodeCount", + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), + Row( + children: [ + const Icon( + Icons.wrong_location, + size: 16, + color: Colors.grey, + ), + Text( + ": ${nodeCountAll - nodeCount}", + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), + Row( + children: [ + const Icon( + Icons.add_outlined, + size: 16, + color: Colors.grey, + ), + Text( + ": $nodeCountAll", + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), Text( context.l10n.map_pinsCount(markerCount), style: const TextStyle( @@ -2137,7 +2204,7 @@ class _MapScreenState extends State { _isBuildingPathTrace = false; }); }, - tooltip: "Path Trace", + tooltip: l10n.map_runTrace, icon: const Icon(Icons.arrow_forward_outlined), ), if (_pathTrace.isNotEmpty) @@ -2157,14 +2224,14 @@ class _MapScreenState extends State { _isBuildingPathTrace = false; }); }, - tooltip: "Build Return Path", + tooltip: l10n.map_runTraceWithReturnPath, icon: const Icon(Icons.replay), ), if (_pathTrace.isNotEmpty) IconButton( onPressed: _removePath, - tooltip: "Remove Last Point", - icon: const Icon(Icons.delete), + tooltip: l10n.map_removeLast, + icon: const Icon(Icons.undo), ), if (_pathTrace.isEmpty) IconButton( @@ -2179,6 +2246,7 @@ class _MapScreenState extends State { SnackBar(content: Text(l10n.map_pathTraceCancelled)), ); }, + tooltip: l10n.common_cancel, icon: const Icon(Icons.close), ), ], From e313bea3fc549467b6f668537f58122376a09936 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sat, 21 Mar 2026 09:38:52 -0700 Subject: [PATCH 4/5] Remove unused _sendAdvert method from SettingsScreen --- lib/screens/settings_screen.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index c42d4e3..310cf49 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -660,14 +660,6 @@ class _SettingsScreenState extends State { ); } - void _sendAdvert(BuildContext context, MeshCoreConnector connector) { - final l10n = context.l10n; - connector.sendSelfAdvert(flood: true); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(l10n.settings_advertisementSent))); - } - void _syncTime(BuildContext context, MeshCoreConnector connector) { final l10n = context.l10n; connector.syncTime(); From 4f609f160ffbd95c5f33e0753f188cef203115ce Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sat, 21 Mar 2026 09:39:03 -0700 Subject: [PATCH 5/5] feat: Add location validation and improve contact latitude/longitude handling --- lib/connector/meshcore_connector.dart | 30 ++++++++++++++------------- lib/models/contact.dart | 13 ++++++------ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 7b974f3..d2dedce 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -4913,6 +4913,17 @@ class MeshCoreConnector extends ChangeNotifier { ); } + bool hasValidLocation(double? latitude, double? longitude) { + const double epsilon = 1e-6; + final lat = latitude ?? 0.0; + final lon = longitude ?? 0.0; + return (lat.abs() > epsilon || lon.abs() > epsilon) && + lat >= -90.0 && + lat <= 90.0 && + lon >= -180.0 && + lon <= 180.0; + } + void _handlePayloadAdvertReceived( Uint8List rawPacket, Uint8List payload, @@ -4950,6 +4961,9 @@ class MeshCoreConnector extends ChangeNotifier { latitude = advert.readInt32LE() / 1e6; longitude = advert.readInt32LE() / 1e6; } + // Validate location values if present + hasLocation = hasValidLocation(latitude, longitude); + if (hasName && advert.remaining > 0) { name = advert.readString(); } @@ -5015,20 +5029,8 @@ class MeshCoreConnector extends ChangeNotifier { // CRITICAL: Preserve user's path override when contact is refreshed from device _contacts[existingIndex] = existing.copyWith( - latitude: - hasLocation && - latitude != null && - latitude.abs() <= 90 && - (latitude != 0 || longitude != 0) - ? latitude - : existing.latitude, - longitude: - hasLocation && - longitude != null && - longitude.abs() <= 180 && - (latitude != 0 || longitude != 0) - ? longitude - : existing.longitude, + latitude: hasLocation ? latitude : existing.latitude, + longitude: hasLocation ? longitude : existing.longitude, name: hasName ? name : existing.name, path: Uint8List.fromList(path.reversed.toList()), pathLength: path.length, diff --git a/lib/models/contact.dart b/lib/models/contact.dart index acd1da9..9a5c19c 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -181,12 +181,13 @@ class Contact { final lastMod = reader.readUInt32LE(); double? lat, lon; - final latRaw = reader.readInt32LE(); - final lonRaw = reader.readInt32LE(); - - if (latRaw != 0 || lonRaw != 0) { - lat = latRaw / 1e6; - lon = lonRaw / 1e6; + if (reader.remaining >= 8) { + final latRaw = reader.readInt32LE(); + final lonRaw = reader.readInt32LE(); + if (latRaw != 0 || lonRaw != 0) { + lat = latRaw / 1e6; + lon = lonRaw / 1e6; + } } return Contact(