mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-27 12:47:31 +10:00
Merge pull request #264 from zjs81/dev-guessed-locations
Dev guessed locations
This commit is contained in:
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Когато списъкът с контакти е пълен, най-старият неключов контакт ще бъде заменен.",
|
"contactsSettings_overwriteOldestSubtitle": "Когато списъкът с контакти е пълен, най-старият неключов контакт ще бъде заменен.",
|
||||||
"discoveredContacts_deleteContactAll": "Изтриване на Всички Открити Контакти",
|
"discoveredContacts_deleteContactAll": "Изтриване на Всички Открити Контакти",
|
||||||
"discoveredContacts_deleteContactAllContent": "Сигурни ли сте, че искате да изтриете всички открити контакти?",
|
"discoveredContacts_deleteContactAllContent": "Сигурни ли сте, че искате да изтриете всички открити контакти?",
|
||||||
"common_deleteAll": "Изтрий всичко"
|
"common_deleteAll": "Изтрий всичко",
|
||||||
|
"map_guessedLocation": "Предполагано местоположение",
|
||||||
|
"map_showGuessedLocations": "Покажете местоположенията на предположените възли."
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1854,5 +1854,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Wenn die Kontaktliste voll ist, wird der älteste nicht favorisierte Kontakt ersetzt.",
|
"contactsSettings_overwriteOldestSubtitle": "Wenn die Kontaktliste voll ist, wird der älteste nicht favorisierte Kontakt ersetzt.",
|
||||||
"common_deleteAll": "Alles löschen",
|
"common_deleteAll": "Alles löschen",
|
||||||
"discoveredContacts_deleteContactAllContent": "Sind Sie sicher, dass Sie alle gefundenen Kontakte löschen möchten?",
|
"discoveredContacts_deleteContactAllContent": "Sind Sie sicher, dass Sie alle gefundenen Kontakte löschen möchten?",
|
||||||
"discoveredContacts_deleteContactAll": "Alle entdeckten Kontakte löschen"
|
"discoveredContacts_deleteContactAll": "Alle entdeckten Kontakte löschen",
|
||||||
|
"map_showGuessedLocations": "Zeige die vermuteten Knotenpositionen",
|
||||||
|
"map_guessedLocation": "Geschätzter Ort"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -775,6 +775,8 @@
|
|||||||
"map_publicKeyPrefix": "Public key prefix",
|
"map_publicKeyPrefix": "Public key prefix",
|
||||||
"map_markers": "Markers",
|
"map_markers": "Markers",
|
||||||
"map_showSharedMarkers": "Show shared markers",
|
"map_showSharedMarkers": "Show shared markers",
|
||||||
|
"map_showGuessedLocations": "Show guessed node locations",
|
||||||
|
"map_guessedLocation": "Guessed location",
|
||||||
"map_lastSeenTime": "Last Seen Time",
|
"map_lastSeenTime": "Last Seen Time",
|
||||||
"map_sharedPin": "Shared pin",
|
"map_sharedPin": "Shared pin",
|
||||||
"map_joinRoom": "Join Room",
|
"map_joinRoom": "Join Room",
|
||||||
|
|||||||
+3
-1
@@ -1854,5 +1854,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Cuando la lista de contactos esté llena, se reemplazará el contacto no favorito más antiguo.",
|
"contactsSettings_overwriteOldestSubtitle": "Cuando la lista de contactos esté llena, se reemplazará el contacto no favorito más antiguo.",
|
||||||
"common_deleteAll": "Eliminar todo",
|
"common_deleteAll": "Eliminar todo",
|
||||||
"discoveredContacts_deleteContactAll": "Eliminar Todos los Contactos Descubiertos",
|
"discoveredContacts_deleteContactAll": "Eliminar Todos los Contactos Descubiertos",
|
||||||
"discoveredContacts_deleteContactAllContent": "¿Está seguro de que desea eliminar todos los contactos descubiertos!"
|
"discoveredContacts_deleteContactAllContent": "¿Está seguro de que desea eliminar todos los contactos descubiertos!",
|
||||||
|
"map_guessedLocation": "Ubicación estimada",
|
||||||
|
"map_showGuessedLocations": "Mostrar las ubicaciones estimadas de los nodos."
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Lorsque la liste de contacts est pleine, le contact le plus ancien non favori sera remplacé.",
|
"contactsSettings_overwriteOldestSubtitle": "Lorsque la liste de contacts est pleine, le contact le plus ancien non favori sera remplacé.",
|
||||||
"common_deleteAll": "Supprimer tout",
|
"common_deleteAll": "Supprimer tout",
|
||||||
"discoveredContacts_deleteContactAll": "Supprimer tous les contacts découverts",
|
"discoveredContacts_deleteContactAll": "Supprimer tous les contacts découverts",
|
||||||
"discoveredContacts_deleteContactAllContent": "Êtes-vous sûr de vouloir supprimer tous les contacts découverts ?"
|
"discoveredContacts_deleteContactAllContent": "Êtes-vous sûr de vouloir supprimer tous les contacts découverts ?",
|
||||||
|
"map_showGuessedLocations": "Afficher les emplacements des nœuds estimés",
|
||||||
|
"map_guessedLocation": "Lieu deviné"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Quando l'elenco dei contatti è pieno, il contatto più vecchio non tra i preferiti verrà sostituito.",
|
"contactsSettings_overwriteOldestSubtitle": "Quando l'elenco dei contatti è pieno, il contatto più vecchio non tra i preferiti verrà sostituito.",
|
||||||
"common_deleteAll": "Elimina tutto",
|
"common_deleteAll": "Elimina tutto",
|
||||||
"discoveredContacts_deleteContactAllContent": "Sei sicuro di voler eliminare tutti i contatti scoperti?",
|
"discoveredContacts_deleteContactAllContent": "Sei sicuro di voler eliminare tutti i contatti scoperti?",
|
||||||
"discoveredContacts_deleteContactAll": "Eliminare tutti i contatti scoperti"
|
"discoveredContacts_deleteContactAll": "Eliminare tutti i contatti scoperti",
|
||||||
|
"map_guessedLocation": "Località indovinata",
|
||||||
|
"map_showGuessedLocations": "Mostra le posizioni stimate dei nodi"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2638,6 +2638,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Show shared markers'**
|
/// **'Show shared markers'**
|
||||||
String get map_showSharedMarkers;
|
String get map_showSharedMarkers;
|
||||||
|
|
||||||
|
/// No description provided for @map_showGuessedLocations.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Show guessed node locations'**
|
||||||
|
String get map_showGuessedLocations;
|
||||||
|
|
||||||
|
/// No description provided for @map_guessedLocation.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Guessed location'**
|
||||||
|
String get map_guessedLocation;
|
||||||
|
|
||||||
/// No description provided for @map_lastSeenTime.
|
/// No description provided for @map_lastSeenTime.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -1443,6 +1443,13 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Покажи споделени маркери';
|
String get map_showSharedMarkers => 'Покажи споделени маркери';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Покажете местоположенията на предположените възли.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Предполагано местоположение';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Последна видяна дата';
|
String get map_lastSeenTime => 'Последна видяна дата';
|
||||||
|
|
||||||
|
|||||||
@@ -1442,6 +1442,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Zeige gemeinsam genutzte Marker';
|
String get map_showSharedMarkers => 'Zeige gemeinsam genutzte Marker';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Zeige die vermuteten Knotenpositionen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Geschätzter Ort';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Letzte Sichtung';
|
String get map_lastSeenTime => 'Letzte Sichtung';
|
||||||
|
|
||||||
|
|||||||
@@ -1421,6 +1421,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Show shared markers';
|
String get map_showSharedMarkers => 'Show shared markers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations => 'Show guessed node locations';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Guessed location';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Last Seen Time';
|
String get map_lastSeenTime => 'Last Seen Time';
|
||||||
|
|
||||||
|
|||||||
@@ -1440,6 +1440,13 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Mostrar marcadores compartidos';
|
String get map_showSharedMarkers => 'Mostrar marcadores compartidos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Mostrar las ubicaciones estimadas de los nodos.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Ubicación estimada';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Última vez que se vio';
|
String get map_lastSeenTime => 'Última vez que se vio';
|
||||||
|
|
||||||
|
|||||||
@@ -1447,6 +1447,13 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Afficher les marqueurs partagés';
|
String get map_showSharedMarkers => 'Afficher les marqueurs partagés';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Afficher les emplacements des nœuds estimés';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Lieu deviné';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Dernière fois vu';
|
String get map_lastSeenTime => 'Dernière fois vu';
|
||||||
|
|
||||||
|
|||||||
@@ -1439,6 +1439,12 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Mostra i segnaposto condivisi';
|
String get map_showSharedMarkers => 'Mostra i segnaposto condivisi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations => 'Mostra le posizioni stimate dei nodi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Località indovinata';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Ultimo Tempo di Visualizzazione';
|
String get map_lastSeenTime => 'Ultimo Tempo di Visualizzazione';
|
||||||
|
|
||||||
|
|||||||
@@ -1434,6 +1434,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Toon gedeelde markeringen';
|
String get map_showSharedMarkers => 'Toon gedeelde markeringen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Toon de voorspelde locaties van de knopen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Geroerde locatie';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Laatste Bekeken Tijd';
|
String get map_lastSeenTime => 'Laatste Bekeken Tijd';
|
||||||
|
|
||||||
|
|||||||
@@ -1440,6 +1440,13 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Pokaż współdzielone znaki.';
|
String get map_showSharedMarkers => 'Pokaż współdzielone znaki.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Wyświetl lokalizacje zgadanych węzłów';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Wydana lokalizacja';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Ostatni raz widiany';
|
String get map_lastSeenTime => 'Ostatni raz widiany';
|
||||||
|
|
||||||
|
|||||||
@@ -1441,6 +1441,13 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Mostrar marcadores compartilhados';
|
String get map_showSharedMarkers => 'Mostrar marcadores compartilhados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Mostrar as localizações dos nós estimados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Localização estimada';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Último Tempo de Visualização';
|
String get map_lastSeenTime => 'Último Tempo de Visualização';
|
||||||
|
|
||||||
|
|||||||
@@ -1442,6 +1442,13 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Показывать общие метки';
|
String get map_showSharedMarkers => 'Показывать общие метки';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Отобразить предполагаемые места расположения узлов';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Угаданное место';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Время последнего появления';
|
String get map_lastSeenTime => 'Время последнего появления';
|
||||||
|
|
||||||
|
|||||||
@@ -1435,6 +1435,13 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Zobraziť zdieľané značky';
|
String get map_showSharedMarkers => 'Zobraziť zdieľané značky';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Zobraziť umiestnenia odhadnutých uzlov';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Odhadnutá lokalita';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Posledný čas sledovania';
|
String get map_lastSeenTime => 'Posledný čas sledovania';
|
||||||
|
|
||||||
|
|||||||
@@ -1431,6 +1431,12 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Pokaži skupno označenja';
|
String get map_showSharedMarkers => 'Pokaži skupno označenja';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations => 'Pokaži lokacije domnevnih not.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Predpostavljena lokacija';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Datum zadnjega vpogleda';
|
String get map_lastSeenTime => 'Datum zadnjega vpogleda';
|
||||||
|
|
||||||
|
|||||||
@@ -1427,6 +1427,13 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Visa delade markörer';
|
String get map_showSharedMarkers => 'Visa delade markörer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Visa upp de antagna nodernas placeringar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Gissad plats';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Senaste Visats Tid';
|
String get map_lastSeenTime => 'Senaste Visats Tid';
|
||||||
|
|
||||||
|
|||||||
@@ -1441,6 +1441,13 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => 'Показувати спільні маркери';
|
String get map_showSharedMarkers => 'Показувати спільні маркери';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations =>
|
||||||
|
'Показати місцезнаходження передбачених вузлів';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => 'Визначено місцезнаходження';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => 'Час останньої активності';
|
String get map_lastSeenTime => 'Час останньої активності';
|
||||||
|
|
||||||
|
|||||||
@@ -1363,6 +1363,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_showSharedMarkers => '显示共享标记';
|
String get map_showSharedMarkers => '显示共享标记';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showGuessedLocations => '显示猜测的节点位置';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_guessedLocation => '猜测的位置';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_lastSeenTime => '最后在线时间';
|
String get map_lastSeenTime => '最后在线时间';
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Wanneer de contactenlijst vol is, wordt de oudste niet-favoriete contactpersoon vervangen.",
|
"contactsSettings_overwriteOldestSubtitle": "Wanneer de contactenlijst vol is, wordt de oudste niet-favoriete contactpersoon vervangen.",
|
||||||
"common_deleteAll": "Alles verwijderen",
|
"common_deleteAll": "Alles verwijderen",
|
||||||
"discoveredContacts_deleteContactAll": "Verwijder alle ontdekte contacten",
|
"discoveredContacts_deleteContactAll": "Verwijder alle ontdekte contacten",
|
||||||
"discoveredContacts_deleteContactAllContent": "Weet u zeker dat u alle ontdekte contacten wilt verwijderen?"
|
"discoveredContacts_deleteContactAllContent": "Weet u zeker dat u alle ontdekte contacten wilt verwijderen?",
|
||||||
|
"map_guessedLocation": "Geroerde locatie",
|
||||||
|
"map_showGuessedLocations": "Toon de voorspelde locaties van de knopen"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Gdy lista kontaktów jest pełna, najstarszy nieulubiony kontakt zostanie zastąpiony.",
|
"contactsSettings_overwriteOldestSubtitle": "Gdy lista kontaktów jest pełna, najstarszy nieulubiony kontakt zostanie zastąpiony.",
|
||||||
"common_deleteAll": "Usuń wszystko",
|
"common_deleteAll": "Usuń wszystko",
|
||||||
"discoveredContacts_deleteContactAllContent": "Czy na pewno chcesz usunąć wszystkie znalezione kontakty?",
|
"discoveredContacts_deleteContactAllContent": "Czy na pewno chcesz usunąć wszystkie znalezione kontakty?",
|
||||||
"discoveredContacts_deleteContactAll": "Usuń wszystkie odkryte kontakty"
|
"discoveredContacts_deleteContactAll": "Usuń wszystkie odkryte kontakty",
|
||||||
|
"map_guessedLocation": "Wydana lokalizacja",
|
||||||
|
"map_showGuessedLocations": "Wyświetl lokalizacje zgadanych węzłów"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Quando a lista de contatos estiver cheia, o contato mais antigo não favoritado será substituído.",
|
"contactsSettings_overwriteOldestSubtitle": "Quando a lista de contatos estiver cheia, o contato mais antigo não favoritado será substituído.",
|
||||||
"common_deleteAll": "Excluir Tudo",
|
"common_deleteAll": "Excluir Tudo",
|
||||||
"discoveredContacts_deleteContactAll": "Excluir Todos os Contatos Descobertos",
|
"discoveredContacts_deleteContactAll": "Excluir Todos os Contatos Descobertos",
|
||||||
"discoveredContacts_deleteContactAllContent": "Tem certeza de que deseja excluir todos os contatos descobertos?"
|
"discoveredContacts_deleteContactAllContent": "Tem certeza de que deseja excluir todos os contatos descobertos?",
|
||||||
|
"map_guessedLocation": "Localização estimada",
|
||||||
|
"map_showGuessedLocations": "Mostrar as localizações dos nós estimados"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1066,5 +1066,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Когда список контактов заполнен, будет заменен самый старый контакт, который не находится в избранном.",
|
"contactsSettings_overwriteOldestSubtitle": "Когда список контактов заполнен, будет заменен самый старый контакт, который не находится в избранном.",
|
||||||
"common_deleteAll": "Удалить все",
|
"common_deleteAll": "Удалить все",
|
||||||
"discoveredContacts_deleteContactAllContent": "Вы уверены, что хотите удалить все обнаруженные контакты?",
|
"discoveredContacts_deleteContactAllContent": "Вы уверены, что хотите удалить все обнаруженные контакты?",
|
||||||
"discoveredContacts_deleteContactAll": "Удалить Все Обнаруженные Контакты"
|
"discoveredContacts_deleteContactAll": "Удалить Все Обнаруженные Контакты",
|
||||||
|
"map_guessedLocation": "Угаданное место",
|
||||||
|
"map_showGuessedLocations": "Отобразить предполагаемые места расположения узлов"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Keď je zoznam kontaktov plný, bude nahradený najstarší neoznačený kontakt.",
|
"contactsSettings_overwriteOldestSubtitle": "Keď je zoznam kontaktov plný, bude nahradený najstarší neoznačený kontakt.",
|
||||||
"discoveredContacts_deleteContactAll": "Zmazať všetky objavené kontakty",
|
"discoveredContacts_deleteContactAll": "Zmazať všetky objavené kontakty",
|
||||||
"common_deleteAll": "Zmazať všetko",
|
"common_deleteAll": "Zmazať všetko",
|
||||||
"discoveredContacts_deleteContactAllContent": "Ste si istí, že chcete zmazať všetky objavené kontakty?"
|
"discoveredContacts_deleteContactAllContent": "Ste si istí, že chcete zmazať všetky objavené kontakty?",
|
||||||
|
"map_showGuessedLocations": "Zobraziť umiestnenia odhadnutých uzlov",
|
||||||
|
"map_guessedLocation": "Odhadnutá lokalita"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Ko je seznam stikov poln, bo najstarejši nestarševski stik zamenjan.",
|
"contactsSettings_overwriteOldestSubtitle": "Ko je seznam stikov poln, bo najstarejši nestarševski stik zamenjan.",
|
||||||
"common_deleteAll": "Izbriši vse",
|
"common_deleteAll": "Izbriši vse",
|
||||||
"discoveredContacts_deleteContactAllContent": "Ste prepričani, da želite izbrisati vse odkrite kontakte?",
|
"discoveredContacts_deleteContactAllContent": "Ste prepričani, da želite izbrisati vse odkrite kontakte?",
|
||||||
"discoveredContacts_deleteContactAll": "Izbriši vse odkrite kontakte"
|
"discoveredContacts_deleteContactAll": "Izbriši vse odkrite kontakte",
|
||||||
|
"map_guessedLocation": "Predpostavljena lokacija",
|
||||||
|
"map_showGuessedLocations": "Pokaži lokacije domnevnih not."
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "När kontaktlistan är full ersätts den äldsta icke-favoriterade kontakten.",
|
"contactsSettings_overwriteOldestSubtitle": "När kontaktlistan är full ersätts den äldsta icke-favoriterade kontakten.",
|
||||||
"common_deleteAll": "Ta bort alla",
|
"common_deleteAll": "Ta bort alla",
|
||||||
"discoveredContacts_deleteContactAllContent": "Är du säker på att du vill ta bort alla upptäckta kontakter?",
|
"discoveredContacts_deleteContactAllContent": "Är du säker på att du vill ta bort alla upptäckta kontakter?",
|
||||||
"discoveredContacts_deleteContactAll": "Ta bort alla upptäckta kontakter"
|
"discoveredContacts_deleteContactAll": "Ta bort alla upptäckta kontakter",
|
||||||
|
"map_guessedLocation": "Gissad plats",
|
||||||
|
"map_showGuessedLocations": "Visa upp de antagna nodernas placeringar"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1826,5 +1826,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "Коли список контактів заповнений, найстарший контакт без позначки улюбленого буде замінений.",
|
"contactsSettings_overwriteOldestSubtitle": "Коли список контактів заповнений, найстарший контакт без позначки улюбленого буде замінений.",
|
||||||
"common_deleteAll": "Видалити все",
|
"common_deleteAll": "Видалити все",
|
||||||
"discoveredContacts_deleteContactAll": "Видалити всі виявлені контакти",
|
"discoveredContacts_deleteContactAll": "Видалити всі виявлені контакти",
|
||||||
"discoveredContacts_deleteContactAllContent": "Ви впевнені, що хочете видалити всі виявлені контакти?"
|
"discoveredContacts_deleteContactAllContent": "Ви впевнені, що хочете видалити всі виявлені контакти?",
|
||||||
|
"map_showGuessedLocations": "Показати місцезнаходження передбачених вузлів",
|
||||||
|
"map_guessedLocation": "Визначено місцезнаходження"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1831,5 +1831,7 @@
|
|||||||
"contactsSettings_overwriteOldestSubtitle": "当联系人列表已满时,将替换最老的非收藏联系人。",
|
"contactsSettings_overwriteOldestSubtitle": "当联系人列表已满时,将替换最老的非收藏联系人。",
|
||||||
"common_deleteAll": "删除全部",
|
"common_deleteAll": "删除全部",
|
||||||
"discoveredContacts_deleteContactAllContent": "您确定要删除所有发现的联系人吗?",
|
"discoveredContacts_deleteContactAllContent": "您确定要删除所有发现的联系人吗?",
|
||||||
"discoveredContacts_deleteContactAll": "删除所有发现的联系人"
|
"discoveredContacts_deleteContactAll": "删除所有发现的联系人",
|
||||||
|
"map_showGuessedLocations": "显示猜测的节点位置",
|
||||||
|
"map_guessedLocation": "猜测的位置"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class AppSettings {
|
|||||||
final bool mapKeyPrefixEnabled;
|
final bool mapKeyPrefixEnabled;
|
||||||
final String mapKeyPrefix;
|
final String mapKeyPrefix;
|
||||||
final bool mapShowMarkers;
|
final bool mapShowMarkers;
|
||||||
|
final bool mapShowGuessedLocations;
|
||||||
final bool enableMessageTracing;
|
final bool enableMessageTracing;
|
||||||
final Map<String, double>? mapCacheBounds;
|
final Map<String, double>? mapCacheBounds;
|
||||||
final int mapCacheMinZoom;
|
final int mapCacheMinZoom;
|
||||||
@@ -48,6 +49,7 @@ class AppSettings {
|
|||||||
this.mapKeyPrefixEnabled = false,
|
this.mapKeyPrefixEnabled = false,
|
||||||
this.mapKeyPrefix = '',
|
this.mapKeyPrefix = '',
|
||||||
this.mapShowMarkers = true,
|
this.mapShowMarkers = true,
|
||||||
|
this.mapShowGuessedLocations = true,
|
||||||
this.enableMessageTracing = false,
|
this.enableMessageTracing = false,
|
||||||
this.mapCacheBounds,
|
this.mapCacheBounds,
|
||||||
this.mapCacheMinZoom = 10,
|
this.mapCacheMinZoom = 10,
|
||||||
@@ -78,6 +80,7 @@ class AppSettings {
|
|||||||
'map_key_prefix_enabled': mapKeyPrefixEnabled,
|
'map_key_prefix_enabled': mapKeyPrefixEnabled,
|
||||||
'map_key_prefix': mapKeyPrefix,
|
'map_key_prefix': mapKeyPrefix,
|
||||||
'map_show_markers': mapShowMarkers,
|
'map_show_markers': mapShowMarkers,
|
||||||
|
'map_show_guessed_locations': mapShowGuessedLocations,
|
||||||
'enable_message_tracing': enableMessageTracing,
|
'enable_message_tracing': enableMessageTracing,
|
||||||
'map_cache_bounds': mapCacheBounds,
|
'map_cache_bounds': mapCacheBounds,
|
||||||
'map_cache_min_zoom': mapCacheMinZoom,
|
'map_cache_min_zoom': mapCacheMinZoom,
|
||||||
@@ -115,6 +118,8 @@ class AppSettings {
|
|||||||
mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false,
|
mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false,
|
||||||
mapKeyPrefix: json['map_key_prefix'] as String? ?? '',
|
mapKeyPrefix: json['map_key_prefix'] as String? ?? '',
|
||||||
mapShowMarkers: json['map_show_markers'] as bool? ?? true,
|
mapShowMarkers: json['map_show_markers'] as bool? ?? true,
|
||||||
|
mapShowGuessedLocations:
|
||||||
|
json['map_show_guessed_locations'] as bool? ?? true,
|
||||||
enableMessageTracing: json['enable_message_tracing'] as bool? ?? false,
|
enableMessageTracing: json['enable_message_tracing'] as bool? ?? false,
|
||||||
mapCacheBounds: (json['map_cache_bounds'] as Map?)?.map(
|
mapCacheBounds: (json['map_cache_bounds'] as Map?)?.map(
|
||||||
(key, value) => MapEntry(key.toString(), (value as num).toDouble()),
|
(key, value) => MapEntry(key.toString(), (value as num).toDouble()),
|
||||||
@@ -159,6 +164,7 @@ class AppSettings {
|
|||||||
bool? mapKeyPrefixEnabled,
|
bool? mapKeyPrefixEnabled,
|
||||||
String? mapKeyPrefix,
|
String? mapKeyPrefix,
|
||||||
bool? mapShowMarkers,
|
bool? mapShowMarkers,
|
||||||
|
bool? mapShowGuessedLocations,
|
||||||
bool? enableMessageTracing,
|
bool? enableMessageTracing,
|
||||||
Object? mapCacheBounds = _unset,
|
Object? mapCacheBounds = _unset,
|
||||||
int? mapCacheMinZoom,
|
int? mapCacheMinZoom,
|
||||||
@@ -185,6 +191,8 @@ class AppSettings {
|
|||||||
mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled,
|
mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled,
|
||||||
mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix,
|
mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix,
|
||||||
mapShowMarkers: mapShowMarkers ?? this.mapShowMarkers,
|
mapShowMarkers: mapShowMarkers ?? this.mapShowMarkers,
|
||||||
|
mapShowGuessedLocations:
|
||||||
|
mapShowGuessedLocations ?? this.mapShowGuessedLocations,
|
||||||
enableMessageTracing: enableMessageTracing ?? this.enableMessageTracing,
|
enableMessageTracing: enableMessageTracing ?? this.enableMessageTracing,
|
||||||
mapCacheBounds: mapCacheBounds == _unset
|
mapCacheBounds: mapCacheBounds == _unset
|
||||||
? this.mapCacheBounds
|
? this.mapCacheBounds
|
||||||
|
|||||||
@@ -818,6 +818,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
title: context.l10n.contacts_repeaterPathTrace,
|
title: context.l10n.contacts_repeaterPathTrace,
|
||||||
path: Uint8List.fromList(pathBytes),
|
path: Uint8List.fromList(pathBytes),
|
||||||
flipPathRound: true,
|
flipPathRound: true,
|
||||||
|
targetContact: widget.contact,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1131,6 +1131,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
contact.name,
|
contact.name,
|
||||||
),
|
),
|
||||||
path: contact.traceRouteBytes ?? Uint8List(0),
|
path: contact.traceRouteBytes ?? Uint8List(0),
|
||||||
|
targetContact: contact,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+284
-8
@@ -15,6 +15,7 @@ import '../models/app_settings.dart';
|
|||||||
import '../models/channel.dart';
|
import '../models/channel.dart';
|
||||||
import '../models/contact.dart';
|
import '../models/contact.dart';
|
||||||
import '../services/app_settings_service.dart';
|
import '../services/app_settings_service.dart';
|
||||||
|
import '../services/path_history_service.dart';
|
||||||
import '../services/map_marker_service.dart';
|
import '../services/map_marker_service.dart';
|
||||||
import '../services/map_tile_cache_service.dart';
|
import '../services/map_tile_cache_service.dart';
|
||||||
import '../utils/contact_search.dart';
|
import '../utils/contact_search.dart';
|
||||||
@@ -64,6 +65,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
final List<Polyline> _polylines = [];
|
final List<Polyline> _polylines = [];
|
||||||
bool _legendExpanded = false;
|
bool _legendExpanded = false;
|
||||||
bool _showNodeLabels = true;
|
bool _showNodeLabels = true;
|
||||||
|
List<_GuessedLocation> _cachedGuessedLocations = [];
|
||||||
|
String _guessedLocationsCacheKey = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -119,8 +122,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer2<MeshCoreConnector, AppSettingsService>(
|
return Consumer3<MeshCoreConnector, AppSettingsService, PathHistoryService>(
|
||||||
builder: (context, connector, settingsService, child) {
|
builder: (context, connector, settingsService, pathHistory, child) {
|
||||||
final tileCache = context.read<MapTileCacheService>();
|
final tileCache = context.read<MapTileCacheService>();
|
||||||
final settings = settingsService.settings;
|
final settings = settingsService.settings;
|
||||||
final contacts = connector.contacts;
|
final contacts = connector.contacts;
|
||||||
@@ -160,6 +163,40 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
.where((c) => c.hasLocation)
|
.where((c) => c.hasLocation)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
// All contacts with a known location — used as anchors regardless of
|
||||||
|
// time/key-prefix filters so that repeaters are always available.
|
||||||
|
final allContactsWithLocation = contacts
|
||||||
|
.where((c) => c.hasLocation)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Compute guessed locations with caching
|
||||||
|
final maxRangeKm = _estimateLoRaRangeKm(connector);
|
||||||
|
final filteredKeys = filteredByKeyPrefix
|
||||||
|
.map((c) => '${c.publicKeyHex}:${c.path.join("-")}')
|
||||||
|
.join(',');
|
||||||
|
final anchorKeys = allContactsWithLocation
|
||||||
|
.map(
|
||||||
|
(c) =>
|
||||||
|
'${c.publicKeyHex}:${c.latitude}:${c.longitude}:${c.path.isNotEmpty ? c.path.last : ""}',
|
||||||
|
)
|
||||||
|
.join(',');
|
||||||
|
final cacheKey =
|
||||||
|
'$filteredKeys|$anchorKeys|${pathHistory.version}:${connector.currentSf}:${connector.currentBwHz}:${connector.currentTxPower}:${settings.mapShowGuessedLocations}';
|
||||||
|
if (cacheKey != _guessedLocationsCacheKey) {
|
||||||
|
_guessedLocationsCacheKey = cacheKey;
|
||||||
|
_cachedGuessedLocations = settings.mapShowGuessedLocations
|
||||||
|
? _computeGuessedLocations(
|
||||||
|
filteredByKeyPrefix,
|
||||||
|
allContactsWithLocation,
|
||||||
|
pathHistory,
|
||||||
|
maxRangeKm,
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
final guessedLocations = settings.mapShowGuessedLocations
|
||||||
|
? _cachedGuessedLocations
|
||||||
|
: <_GuessedLocation>[];
|
||||||
|
|
||||||
_polylines.clear();
|
_polylines.clear();
|
||||||
_polylines.addAll(
|
_polylines.addAll(
|
||||||
_points.length > 1
|
_points.length > 1
|
||||||
@@ -430,6 +467,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
size: 34,
|
size: 34,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (!_isBuildingPathTrace)
|
||||||
|
...guessedLocations.map(_buildGuessedMarker),
|
||||||
..._buildMarkers(
|
..._buildMarkers(
|
||||||
contactsWithLocation,
|
contactsWithLocation,
|
||||||
settings,
|
settings,
|
||||||
@@ -489,6 +528,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
contactsWithLocation,
|
contactsWithLocation,
|
||||||
settings,
|
settings,
|
||||||
sharedMarkers.length,
|
sharedMarkers.length,
|
||||||
|
guessedLocations.length,
|
||||||
),
|
),
|
||||||
if (_isBuildingPathTrace) _buildPathTraceOverlay(),
|
if (_isBuildingPathTrace) _buildPathTraceOverlay(),
|
||||||
],
|
],
|
||||||
@@ -512,6 +552,200 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<_GuessedLocation> _computeGuessedLocations(
|
||||||
|
List<Contact> allContacts,
|
||||||
|
List<Contact> withLocation,
|
||||||
|
PathHistoryService pathHistory,
|
||||||
|
double? maxRangeKm,
|
||||||
|
) {
|
||||||
|
// Index known-location repeaters by their 1-byte hash.
|
||||||
|
// null value = two repeaters share the same hash byte (ambiguous collision).
|
||||||
|
final repeaterByHash = <int, Contact?>{};
|
||||||
|
for (final c in withLocation) {
|
||||||
|
if (c.type == advTypeRepeater) {
|
||||||
|
if (repeaterByHash.containsKey(c.publicKey[0])) {
|
||||||
|
repeaterByHash[c.publicKey[0]] =
|
||||||
|
null; // collision: can't disambiguate
|
||||||
|
} else {
|
||||||
|
repeaterByHash[c.publicKey[0]] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = <_GuessedLocation>[];
|
||||||
|
|
||||||
|
for (final contact in allContacts) {
|
||||||
|
if (contact.hasLocation) continue;
|
||||||
|
|
||||||
|
final anchorSet = <LatLng>{};
|
||||||
|
|
||||||
|
// Collect the contact-side (last-hop) repeater from every known path.
|
||||||
|
// path = [device-side hop, ..., contact-side hop]
|
||||||
|
// Only path.last is actually within radio range of the contact — using
|
||||||
|
// earlier bytes would anchor against our own side of the network.
|
||||||
|
final pathSets = <List<int>>[
|
||||||
|
contact.path.toList(),
|
||||||
|
...pathHistory
|
||||||
|
.getRecentPaths(contact.publicKeyHex)
|
||||||
|
.map((r) => r.pathBytes),
|
||||||
|
];
|
||||||
|
final lastHopBytes = <int>{};
|
||||||
|
for (final pathBytes in pathSets) {
|
||||||
|
if (pathBytes.isEmpty) continue;
|
||||||
|
final lastHop = pathBytes.last;
|
||||||
|
lastHopBytes.add(lastHop);
|
||||||
|
final r = repeaterByHash[lastHop];
|
||||||
|
if (r != null) anchorSet.add(LatLng(r.latitude!, r.longitude!));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: for any last-hop byte with no GPS repeater, average the
|
||||||
|
// positions of contacts with known GPS that share the same last hop.
|
||||||
|
// Those contacts are all adjacent to the same unknown repeater, so their
|
||||||
|
// centroid is a reasonable proxy for its location.
|
||||||
|
for (final byte in lastHopBytes) {
|
||||||
|
if (repeaterByHash.containsKey(byte)) continue;
|
||||||
|
for (final c in withLocation) {
|
||||||
|
if (c.path.isNotEmpty && c.path.last == byte) {
|
||||||
|
anchorSet.add(LatLng(c.latitude!, c.longitude!));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter anchors that are geometrically inconsistent with radio range.
|
||||||
|
// Two anchors more than 2 * maxRange apart cannot both be in direct radio
|
||||||
|
// range of the same node, so isolated outliers are removed.
|
||||||
|
final anchors = maxRangeKm != null && anchorSet.length > 1
|
||||||
|
? _filterConsistentAnchors(anchorSet.toList(), maxRangeKm)
|
||||||
|
: anchorSet.toList();
|
||||||
|
|
||||||
|
if (anchors.isEmpty) continue;
|
||||||
|
|
||||||
|
final LatLng position;
|
||||||
|
if (anchors.length == 1) {
|
||||||
|
// Offset single-anchor guesses so they don't overlap the repeater marker.
|
||||||
|
// Use the contact's public key byte as a deterministic angle seed.
|
||||||
|
const offsetDeg = 0.003; // ~330 m at the equator
|
||||||
|
final angle = (contact.publicKey[1] / 255.0) * 2 * pi;
|
||||||
|
position = LatLng(
|
||||||
|
anchors[0].latitude + offsetDeg * cos(angle),
|
||||||
|
anchors[0].longitude + offsetDeg * sin(angle),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
double lat = 0, lon = 0;
|
||||||
|
for (final a in anchors) {
|
||||||
|
lat += a.latitude;
|
||||||
|
lon += a.longitude;
|
||||||
|
}
|
||||||
|
position = LatLng(lat / anchors.length, lon / anchors.length);
|
||||||
|
}
|
||||||
|
result.add(
|
||||||
|
_GuessedLocation(
|
||||||
|
contact: contact,
|
||||||
|
position: position,
|
||||||
|
highConfidence: anchors.length >= 2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Estimates the free-space maximum LoRa range in km from the connected
|
||||||
|
/// device's current radio parameters. Returns null if parameters are unknown.
|
||||||
|
double? _estimateLoRaRangeKm(MeshCoreConnector connector) {
|
||||||
|
final freqHz = connector.currentFreqHz;
|
||||||
|
final bwHz = connector.currentBwHz;
|
||||||
|
final sf = connector.currentSf;
|
||||||
|
final txPower = connector.currentTxPower;
|
||||||
|
if (freqHz == null || bwHz == null || sf == null || txPower == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// LoRa receiver sensitivity = thermal noise + NF + required demod SNR
|
||||||
|
const noiseFigureDb = 6.0;
|
||||||
|
final thermalNoiseDbm = -174.0 + 10 * log(bwHz.toDouble()) / ln10;
|
||||||
|
final sensitivityDbm =
|
||||||
|
thermalNoiseDbm + noiseFigureDb + _sfToRequiredSnrDb(sf);
|
||||||
|
// FSPL at max range equals link budget:
|
||||||
|
// FSPL = 20*log10(d_m) + 20*log10(f_hz) - 147.55
|
||||||
|
final linkBudgetDb = txPower.toDouble() - sensitivityDbm;
|
||||||
|
final exponent =
|
||||||
|
(linkBudgetDb + 147.55 - 20 * log(freqHz.toDouble()) / ln10) / 20;
|
||||||
|
return pow(10, exponent) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _sfToRequiredSnrDb(int sf) {
|
||||||
|
switch (sf) {
|
||||||
|
case 5:
|
||||||
|
return -2.5;
|
||||||
|
case 6:
|
||||||
|
return -5.0;
|
||||||
|
case 7:
|
||||||
|
return -7.5;
|
||||||
|
case 8:
|
||||||
|
return -10.0;
|
||||||
|
case 9:
|
||||||
|
return -12.5;
|
||||||
|
case 10:
|
||||||
|
return -15.0;
|
||||||
|
case 11:
|
||||||
|
return -17.5;
|
||||||
|
case 12:
|
||||||
|
return -20.0;
|
||||||
|
default:
|
||||||
|
return -10.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes anchors that have no neighbour within 2 * maxRangeKm.
|
||||||
|
/// A node cannot be simultaneously in radio range of two points farther apart
|
||||||
|
/// than twice the expected maximum range.
|
||||||
|
List<LatLng> _filterConsistentAnchors(
|
||||||
|
List<LatLng> anchors,
|
||||||
|
double maxRangeKm,
|
||||||
|
) {
|
||||||
|
const distance = Distance();
|
||||||
|
final maxDistM = maxRangeKm * 2000;
|
||||||
|
return anchors
|
||||||
|
.where((a) => anchors.any((b) => b != a && distance(a, b) <= maxDistM))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Marker _buildGuessedMarker(_GuessedLocation guess) {
|
||||||
|
final color = _getNodeColor(guess.contact.type);
|
||||||
|
return Marker(
|
||||||
|
point: guess.position,
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _showNodeInfo(
|
||||||
|
context,
|
||||||
|
guess.contact,
|
||||||
|
guessedPosition: guess.position,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withValues(alpha: guess.highConfidence ? 0.55 : 0.30),
|
||||||
|
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: const Icon(
|
||||||
|
Icons.not_listed_location,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<Marker> _buildMarkers(
|
List<Marker> _buildMarkers(
|
||||||
List<Contact> contacts,
|
List<Contact> contacts,
|
||||||
settings, {
|
settings, {
|
||||||
@@ -657,6 +891,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
List<Contact> contactsWithLocation,
|
List<Contact> contactsWithLocation,
|
||||||
settings,
|
settings,
|
||||||
int markerCount,
|
int markerCount,
|
||||||
|
int guessedCount,
|
||||||
) {
|
) {
|
||||||
int nodeCount = 0;
|
int nodeCount = 0;
|
||||||
for (final contact in contactsWithLocation) {
|
for (final contact in contactsWithLocation) {
|
||||||
@@ -696,7 +931,12 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
context.l10n.map_nodesCount(nodeCount),
|
context.l10n.map_nodesCount(
|
||||||
|
nodeCount +
|
||||||
|
(settings.mapShowGuessedLocations
|
||||||
|
? guessedCount
|
||||||
|
: 0),
|
||||||
|
),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -764,6 +1004,12 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
context.l10n.map_pinPublic,
|
context.l10n.map_pinPublic,
|
||||||
Colors.orange,
|
Colors.orange,
|
||||||
),
|
),
|
||||||
|
if (settings.mapShowGuessedLocations && guessedCount > 0)
|
||||||
|
_buildLegendItem(
|
||||||
|
Icons.not_listed_location,
|
||||||
|
context.l10n.map_guessedLocation,
|
||||||
|
Colors.grey,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -952,7 +1198,11 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showNodeInfo(BuildContext context, Contact contact) {
|
void _showNodeInfo(
|
||||||
|
BuildContext context,
|
||||||
|
Contact contact, {
|
||||||
|
LatLng? guessedPosition,
|
||||||
|
}) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) => AlertDialog(
|
builder: (dialogContext) => AlertDialog(
|
||||||
@@ -972,10 +1222,16 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
children: [
|
children: [
|
||||||
_buildInfoRow('Type', contact.typeLabel),
|
_buildInfoRow('Type', contact.typeLabel),
|
||||||
_buildInfoRow('Path', contact.pathLabel),
|
_buildInfoRow('Path', contact.pathLabel),
|
||||||
_buildInfoRow(
|
if (contact.hasLocation)
|
||||||
'Location',
|
_buildInfoRow(
|
||||||
'${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}',
|
'Location',
|
||||||
),
|
'${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}',
|
||||||
|
)
|
||||||
|
else if (guessedPosition != null)
|
||||||
|
_buildInfoRow(
|
||||||
|
'Est. Location',
|
||||||
|
'~${guessedPosition.latitude.toStringAsFixed(6)}, ${guessedPosition.longitude.toStringAsFixed(6)}',
|
||||||
|
),
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
context.l10n.map_lastSeen,
|
context.l10n.map_lastSeen,
|
||||||
_formatLastSeen(contact.lastSeen),
|
_formatLastSeen(contact.lastSeen),
|
||||||
@@ -1481,6 +1737,14 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
},
|
},
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: Text(context.l10n.map_showGuessedLocations),
|
||||||
|
value: settings.mapShowGuessedLocations,
|
||||||
|
onChanged: (value) {
|
||||||
|
service.setMapShowGuessedLocations(value ?? true);
|
||||||
|
},
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
context.l10n.map_keyPrefix,
|
context.l10n.map_keyPrefix,
|
||||||
@@ -1744,6 +2008,18 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _GuessedLocation {
|
||||||
|
final Contact contact;
|
||||||
|
final LatLng position;
|
||||||
|
final bool highConfidence;
|
||||||
|
|
||||||
|
_GuessedLocation({
|
||||||
|
required this.contact,
|
||||||
|
required this.position,
|
||||||
|
required this.highConfidence,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class _MarkerPayload {
|
class _MarkerPayload {
|
||||||
final LatLng position;
|
final LatLng position;
|
||||||
final String label;
|
final String label;
|
||||||
|
|||||||
+135
-12
@@ -54,6 +54,7 @@ class PathTraceMapScreen extends StatefulWidget {
|
|||||||
final int? repeaterId;
|
final int? repeaterId;
|
||||||
final bool flipPathRound;
|
final bool flipPathRound;
|
||||||
final bool reversePathRound;
|
final bool reversePathRound;
|
||||||
|
final Contact? targetContact;
|
||||||
|
|
||||||
const PathTraceMapScreen({
|
const PathTraceMapScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -62,6 +63,7 @@ class PathTraceMapScreen extends StatefulWidget {
|
|||||||
this.repeaterId,
|
this.repeaterId,
|
||||||
this.flipPathRound = false,
|
this.flipPathRound = false,
|
||||||
this.reversePathRound = false,
|
this.reversePathRound = false,
|
||||||
|
this.targetContact,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -78,6 +80,11 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
bool _failed2Loaded = false;
|
bool _failed2Loaded = false;
|
||||||
bool _hasData = false;
|
bool _hasData = false;
|
||||||
PathTraceData? _traceData;
|
PathTraceData? _traceData;
|
||||||
|
// Inferred positions for hops that have no GPS location, keyed by hop byte.
|
||||||
|
Map<int, LatLng> _inferredHopPositions = {};
|
||||||
|
// Endpoint position for the target contact (GPS or guessed).
|
||||||
|
LatLng? _targetContactPosition;
|
||||||
|
bool _targetContactIsGuessed = false;
|
||||||
List<LatLng> _points = <LatLng>[];
|
List<LatLng> _points = <LatLng>[];
|
||||||
List<Polyline> _polylines = [];
|
List<Polyline> _polylines = [];
|
||||||
LatLng? _initialCenter = LatLng(0, 0);
|
LatLng? _initialCenter = LatLng(0, 0);
|
||||||
@@ -242,25 +249,91 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// For hops with no GPS contact, infer position from other contacts
|
||||||
|
// with known GPS that share the same last-hop byte.
|
||||||
|
final Map<int, LatLng> inferredPositions = {};
|
||||||
|
for (final hop in pathData) {
|
||||||
|
final contact = pathContacts[hop];
|
||||||
|
if (contact != null && contact.hasLocation) continue;
|
||||||
|
final peers = connector.contacts
|
||||||
|
.where(
|
||||||
|
(c) => c.hasLocation && c.path.isNotEmpty && c.path.last == hop,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
if (peers.isNotEmpty) {
|
||||||
|
final lat =
|
||||||
|
peers.map((c) => c.latitude!).reduce((a, b) => a + b) /
|
||||||
|
peers.length;
|
||||||
|
final lon =
|
||||||
|
peers.map((c) => c.longitude!).reduce((a, b) => a + b) /
|
||||||
|
peers.length;
|
||||||
|
inferredPositions[hop] = LatLng(lat, lon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
_hasData = true;
|
_hasData = true;
|
||||||
|
_inferredHopPositions = inferredPositions;
|
||||||
_traceData = PathTraceData(
|
_traceData = PathTraceData(
|
||||||
pathData: pathData,
|
pathData: pathData,
|
||||||
snrData: snrData,
|
snrData: snrData,
|
||||||
pathContacts: pathContacts,
|
pathContacts: pathContacts,
|
||||||
);
|
);
|
||||||
|
// Compute endpoint position for the target contact.
|
||||||
|
LatLng? targetPos;
|
||||||
|
bool targetGuessed = false;
|
||||||
|
final target = widget.targetContact;
|
||||||
|
if (target != null) {
|
||||||
|
if (target.hasLocation) {
|
||||||
|
targetPos = LatLng(target.latitude!, target.longitude!);
|
||||||
|
} else if (pathData.isNotEmpty) {
|
||||||
|
// Infer from the last hop: average GPS contacts sharing that hop.
|
||||||
|
// For a round-trip path (flipPathRound), the target-side hop sits
|
||||||
|
// in the middle of the symmetric sequence; .last is the local side.
|
||||||
|
final lastHop = (widget.flipPathRound && pathData.length > 1)
|
||||||
|
? pathData[(pathData.length - 1) ~/ 2]
|
||||||
|
: pathData.last;
|
||||||
|
final peers = connector.contacts
|
||||||
|
.where(
|
||||||
|
(c) =>
|
||||||
|
c.hasLocation &&
|
||||||
|
c.path.isNotEmpty &&
|
||||||
|
c.path.last == lastHop,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
if (peers.isNotEmpty) {
|
||||||
|
final lat =
|
||||||
|
peers.map((c) => c.latitude!).reduce((a, b) => a + b) /
|
||||||
|
peers.length;
|
||||||
|
final lon =
|
||||||
|
peers.map((c) => c.longitude!).reduce((a, b) => a + b) /
|
||||||
|
peers.length;
|
||||||
|
const offsetDeg = 0.003;
|
||||||
|
final angle = (target.publicKey[1] / 255.0) * 2 * pi;
|
||||||
|
targetPos = LatLng(
|
||||||
|
lat + offsetDeg * cos(angle),
|
||||||
|
lon + offsetDeg * sin(angle),
|
||||||
|
);
|
||||||
|
targetGuessed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_targetContactPosition = targetPos;
|
||||||
|
_targetContactIsGuessed = targetGuessed;
|
||||||
|
|
||||||
_points = <LatLng>[];
|
_points = <LatLng>[];
|
||||||
_points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
|
_points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
|
||||||
for (final hop in _traceData!.pathData) {
|
for (final hop in _traceData!.pathData) {
|
||||||
final contact = _traceData!.pathContacts[hop];
|
final contact = _traceData!.pathContacts[hop];
|
||||||
if (contact != null &&
|
if (contact != null && contact.hasLocation) {
|
||||||
contact.hasLocation &&
|
|
||||||
contact.latitude != null &&
|
|
||||||
contact.longitude != null) {
|
|
||||||
_points.add(LatLng(contact.latitude!, contact.longitude!));
|
_points.add(LatLng(contact.latitude!, contact.longitude!));
|
||||||
|
} else {
|
||||||
|
final inferred = inferredPositions[hop];
|
||||||
|
if (inferred != null) _points.add(inferred);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (targetPos != null) _points.add(targetPos);
|
||||||
_polylines = _points.length > 1
|
_polylines = _points.length > 1
|
||||||
? [
|
? [
|
||||||
Polyline(
|
Polyline(
|
||||||
@@ -382,8 +455,13 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
final markers = <Marker>[];
|
final markers = <Marker>[];
|
||||||
for (final hop in pathData) {
|
for (final hop in pathData) {
|
||||||
final contact = _traceData!.pathContacts[hop];
|
final contact = _traceData!.pathContacts[hop];
|
||||||
if (contact == null || !contact.hasLocation) continue;
|
final inferred = _inferredHopPositions[hop];
|
||||||
final point = LatLng(contact.latitude!, contact.longitude!);
|
final hasGps = contact != null && contact.hasLocation;
|
||||||
|
if (!hasGps && inferred == null) continue;
|
||||||
|
final point = hasGps
|
||||||
|
? LatLng(contact.latitude!, contact.longitude!)
|
||||||
|
: inferred!;
|
||||||
|
final label = hop.toRadixString(16).padLeft(2, '0').toUpperCase();
|
||||||
markers.add(
|
markers.add(
|
||||||
Marker(
|
Marker(
|
||||||
point: point,
|
point: point,
|
||||||
@@ -392,7 +470,9 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.green,
|
color: hasGps
|
||||||
|
? Colors.green
|
||||||
|
: Colors.orange.withValues(alpha: 0.75),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
border: Border.all(color: Colors.white, width: 2),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
@@ -405,10 +485,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
),
|
),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
contact.publicKey
|
hasGps ? label : '~$label',
|
||||||
.sublist(0, 1)
|
|
||||||
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
|
|
||||||
.join(),
|
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -419,7 +496,12 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (showLabels) {
|
if (showLabels) {
|
||||||
markers.add(_buildNodeLabelMarker(point: point, label: contact.name));
|
markers.add(
|
||||||
|
_buildNodeLabelMarker(
|
||||||
|
point: point,
|
||||||
|
label: contact?.name ?? '~$label',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,6 +550,47 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add target contact endpoint marker.
|
||||||
|
final targetPos = _targetContactPosition;
|
||||||
|
if (targetPos != null) {
|
||||||
|
final isGuessed = _targetContactIsGuessed;
|
||||||
|
final targetName = widget.targetContact?.name ?? '?';
|
||||||
|
markers.add(
|
||||||
|
Marker(
|
||||||
|
point: targetPos,
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isGuessed
|
||||||
|
? Colors.purple.withValues(alpha: 0.55)
|
||||||
|
: Colors.red,
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: const Icon(Icons.person, color: Colors.white, size: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (showLabels) {
|
||||||
|
markers.add(
|
||||||
|
_buildNodeLabelMarker(
|
||||||
|
point: targetPos,
|
||||||
|
label: isGuessed ? '~$targetName' : targetName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return markers;
|
return markers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ class AppSettingsService extends ChangeNotifier {
|
|||||||
await updateSettings(_settings.copyWith(mapShowMarkers: value));
|
await updateSettings(_settings.copyWith(mapShowMarkers: value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setMapShowGuessedLocations(bool value) async {
|
||||||
|
await updateSettings(_settings.copyWith(mapShowGuessedLocations: value));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> setEnableMessageTracing(bool value) async {
|
Future<void> setEnableMessageTracing(bool value) async {
|
||||||
await updateSettings(_settings.copyWith(enableMessageTracing: value));
|
await updateSettings(_settings.copyWith(enableMessageTracing: value));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ class PathHistoryService extends ChangeNotifier {
|
|||||||
final List<String> _cacheAccessOrder = [];
|
final List<String> _cacheAccessOrder = [];
|
||||||
|
|
||||||
static const int _maxHistoryEntries = 100;
|
static const int _maxHistoryEntries = 100;
|
||||||
|
|
||||||
|
int _version = 0;
|
||||||
|
int get version => _version;
|
||||||
static const int _autoRotationTopCount = 3;
|
static const int _autoRotationTopCount = 3;
|
||||||
|
|
||||||
PathHistoryService(this._storage);
|
PathHistoryService(this._storage);
|
||||||
@@ -185,6 +188,7 @@ class PathHistoryService extends ChangeNotifier {
|
|||||||
) {
|
) {
|
||||||
var history = _cache[contactPubKeyHex];
|
var history = _cache[contactPubKeyHex];
|
||||||
if (history == null) return;
|
if (history == null) return;
|
||||||
|
_version++;
|
||||||
|
|
||||||
final existing = _findPathRecord(contactPubKeyHex, pathBytes);
|
final existing = _findPathRecord(contactPubKeyHex, pathBytes);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
@@ -241,6 +245,7 @@ class PathHistoryService extends ChangeNotifier {
|
|||||||
_cache[contactPubKeyHex] = loaded;
|
_cache[contactPubKeyHex] = loaded;
|
||||||
_trackAccess(contactPubKeyHex);
|
_trackAccess(contactPubKeyHex);
|
||||||
_evictIfNeeded();
|
_evictIfNeeded();
|
||||||
|
_version++;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -276,6 +281,7 @@ class PathHistoryService extends ChangeNotifier {
|
|||||||
_autoRotationIndex.remove(contactPubKeyHex);
|
_autoRotationIndex.remove(contactPubKeyHex);
|
||||||
_floodStats.remove(contactPubKeyHex);
|
_floodStats.remove(contactPubKeyHex);
|
||||||
await _storage.clearPathHistory(contactPubKeyHex);
|
await _storage.clearPathHistory(contactPubKeyHex);
|
||||||
|
_version++;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +301,7 @@ class PathHistoryService extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await _storage.savePathHistory(contactPubKeyHex, _cache[contactPubKeyHex]!);
|
await _storage.savePathHistory(contactPubKeyHex, _cache[contactPubKeyHex]!);
|
||||||
|
_version++;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
|
|||||||
title: context.l10n.contacts_repeaterPathTrace,
|
title: context.l10n.contacts_repeaterPathTrace,
|
||||||
path: Uint8List.fromList(pathBytes),
|
path: Uint8List.fromList(pathBytes),
|
||||||
flipPathRound: true,
|
flipPathRound: true,
|
||||||
|
targetContact: widget.contact,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user