diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index ee9533e..60a825c 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -4943,6 +4943,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, @@ -4980,6 +4991,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.readCString(); } @@ -5045,20 +5059,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, @@ -5226,6 +5228,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 545ec9d..7593577 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1941,5 +1941,7 @@ "appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Мулти-потвърди: {value}", - "settings_telemetryModeUpdated": "Режим на телеметрията е обновен" + "settings_telemetryModeUpdated": "Режим на телеметрията е обновен", + "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 6745054..98ddb93 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1969,5 +1969,7 @@ "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", + "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 2b263c6..d8623d3 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", @@ -891,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 e98e317..8bb0a46 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1941,5 +1941,7 @@ "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", + "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 f11cde5..b168e75 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1941,5 +1941,7 @@ "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", + "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 4bb6936..ce5833a 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: @@ -3133,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 d6537f9..53d8ef2 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 => 'Префикс на ключа'; @@ -1733,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 87fab6f..535eb45 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'; @@ -1730,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 1e0196b..2fe75ec 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'; @@ -1696,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 dff2e5e..70e0a79 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'; @@ -1728,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 91bf4f4..5be46c8 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é'; @@ -1739,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 b688c06..63b501f 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'; @@ -1729,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 1530886..adf2392 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'; @@ -1718,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 6938858..e5d7d36 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1698,6 +1698,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'; @@ -1741,6 +1744,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 87b44ca..1bc971a 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'; @@ -1729,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 72d2e1c..f71bff0 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 => 'Префикс ключа'; @@ -1732,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 7817af6..d5d00a0 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'; @@ -1718,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 6032ee0..47066c9 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'; @@ -1713,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 5b19be3..6e5fd20 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'; @@ -1707,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 096e470..c068ed3 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 => 'Префікс ключа'; @@ -1727,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 f142763..78e9c94 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 => '关键字前缀'; @@ -1624,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 1b5e78c..43c08b1 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1941,5 +1941,7 @@ "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", + "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 1ccad59..27c4fc1 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1979,5 +1979,7 @@ "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", + "map_runTraceWithReturnPath": "Wróć z powrotem tą samą ścieżką" } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index adddd13..1ee4130 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1941,5 +1941,7 @@ "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", + "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 2d3df51..ab32362 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1181,5 +1181,7 @@ "appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", - "settings_multiAck": "Мульти-ACK: {value}" + "settings_multiAck": "Мульти-ACK: {value}", + "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 57eb285..12c2f9a 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1941,5 +1941,7 @@ "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", + "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 355f8d8..54ea1f5 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1941,5 +1941,7 @@ "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", + "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 84f4e5e..1bb0c8a 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1941,5 +1941,7 @@ "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", + "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 be1eaa8..e55a582 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1941,5 +1941,7 @@ "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", - "settings_multiAck": "Багатократне підтвердження: {value}" + "settings_multiAck": "Багатократне підтвердження: {value}", + "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 9493b27..b415904 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1946,5 +1946,7 @@ "appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "多重ACK:{value}", - "settings_telemetryModeUpdated": "遥测模式已更新" + "settings_telemetryModeUpdated": "遥测模式已更新", + "map_showOverlaps": "重复键重叠", + "map_runTraceWithReturnPath": "沿着相同的路径返回" } \ 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/models/contact.dart b/lib/models/contact.dart index 5c80893..fe1c915 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -183,12 +183,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( diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 6aaebf0..f5efd3b 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) @@ -477,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) @@ -503,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, + ), ), ), ), @@ -544,6 +554,7 @@ class _MapScreenState extends State { ), if (!_isBuildingPathTrace) _buildLegend( + contacts, contactsWithLocation, settings, sharedMarkers.length, @@ -580,6 +591,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 +607,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 +658,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 +838,70 @@ class _MapScreenState extends State { return markers; } + List _filterContactsBySettings( + List contacts, + dynamic settings, { + bool noLocations = false, + }) { + List filtered = []; + bool addContact = false; + for (final contact in contacts) { + addContact = false; + if (!contact.hasLocation && !noLocations) { + 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 +917,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 +946,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, ), ); } @@ -954,25 +1023,25 @@ class _MapScreenState extends State { } Widget _buildLegend( + List contacts, List contactsWithLocation, settings, 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( + contacts, + settings, + noLocations: false, + ); + final filteredContactsAll = _filterContactsBySettings( + contacts, + settings, + noLocations: true, + ); + + final nodeCount = filteredContacts.length; + final nodeCountAll = filteredContactsAll.length; return Positioned( top: 16, @@ -1008,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( @@ -1846,6 +1963,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 +2130,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 +2182,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 +2204,37 @@ class _MapScreenState extends State { _isBuildingPathTrace = false; }); }, - child: Text(l10n.map_runTrace), + tooltip: l10n.map_runTrace, + 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: l10n.map_runTraceWithReturnPath, + icon: const Icon(Icons.replay), + ), + if (_pathTrace.isNotEmpty) + IconButton( onPressed: _removePath, - child: Text(l10n.map_removeLast), + tooltip: l10n.map_removeLast, + icon: const Icon(Icons.undo), ), if (_pathTrace.isEmpty) - ElevatedButton( + IconButton( onPressed: () { setState(() { _isBuildingPathTrace = false; @@ -2097,7 +2246,8 @@ class _MapScreenState extends State { SnackBar(content: Text(l10n.map_pathTraceCancelled)), ); }, - child: Text(l10n.common_cancel), + tooltip: l10n.common_cancel, + icon: const Icon(Icons.close), ), ], ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 46d6352..5d86b41 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( @@ -657,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(); 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 {