From c81c3efe7c2c807a885bf9711a6765da898c05e5 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sun, 22 Feb 2026 16:00:51 -0800 Subject: [PATCH] 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 {