diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 01afb0c..54d792e 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{нов възел} other{нови възли}}", "notification_newTypeDiscovered": "Открит нов {contactType}", "notification_receivedNewMessage": "Получено ново съобщение", - "contacts_contactAdvertCopyFailed": "Копирането на обявата в клипборда не успя.", "settings_gpxExportContactsSubtitle": "Експортира спътници с местоположение в GPX файл.", "settings_gpxExportRepeatersSubtitle": "Изпраща повторители / roomserver с местоположение в GPX файл.", "settings_gpxExportAll": "Експортирай всички контакти в GPX", @@ -1591,6 +1590,10 @@ "settings_gpxExportAllContacts": "Местоположения на всички контакти", "settings_gpxExportShareText": "Картинни данни изнесени от meshcore-open", "settings_gpxExportShareSubject": "meshcore-open износ на данни за карта в формат GPX", - "pathTrace_someHopsNoLocation": "Един или повече от хмелите липсва местоположение!" - + "pathTrace_someHopsNoLocation": "Един или повече от хмелите липсва местоположение!", + "map_pathTraceCancelled": "Отменен е следването на пътя.", + "pathTrace_clearTooltip": "Изчисти пътя", + "map_removeLast": "Премахни Последно", + "map_runTrace": "Изпълни Път на Следване", + "map_tapToAdd": "Натиснете върху възлите, за да ги добавите към пътя." } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 66bc049..2cece8a 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1569,34 +1569,40 @@ "contacts_zeroHopContactAdvertSent": "Kontakt über Anzeige gesendet", "contacts_contactAdvertCopied": "Anzeige in die Zwischenablage kopiert.", "contacts_contactAdvertCopyFailed": "Kopieren der Ankündigung in die Zwischenablage fehlgeschlagen.", - "notification_activityTitle": "MeshCore Aktivität", "notification_messagesCount": "{count} {count, plural, =1{Nachricht} other{Nachrichten}}", "@notification_messagesCount": { "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "notification_channelMessagesCount": "{count} {count, plural, =1{Kanalnachricht} other{Kanalnachrichten}}", "@notification_channelMessagesCount": { "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "notification_newNodesCount": "{count} {count, plural, =1{neuer Knoten} other{neue Knoten}}", "@notification_newNodesCount": { "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "notification_newTypeDiscovered": "Neuer {contactType} entdeckt", "@notification_newTypeDiscovered": { "placeholders": { - "contactType": {"type": "String"} + "contactType": { + "type": "String" + } } }, "notification_receivedNewMessage": "Neue Nachricht empfangen", - "contacts_contactAdvertCopyFailed": "Kopieren der Ankündigung in die Zwischenablage fehlgeschlagen.", "settings_gpxExportAll": "Alle Knoten als GPX exportieren", "settings_gpxExportAllSubtitle": "Exportiert alle Knoten mit einem Standort in eine GPX-Datei.", "settings_gpxExportRepeaters": "Repeater und Raumserver als GPX exportieren", @@ -1612,6 +1618,10 @@ "settings_gpxExportAllContacts": "Alle Kontaktstandorte", "settings_gpxExportShareSubject": "GPX-Kartendaten aus meshcore-open exportieren", "settings_gpxExportShareText": "GPX-Kartendaten aus meshcore-open exportiert", - "pathTrace_someHopsNoLocation": "Bei einer oder mehreren Knoten fehlt der Standort!" - + "pathTrace_someHopsNoLocation": "Bei einer oder mehreren Knoten fehlt der Standort!", + "map_removeLast": "Letztes Entfernen", + "map_tapToAdd": "Tippen Sie auf Knoten, um sie zum Pfad hinzuzufügen.", + "map_runTrace": "Pfadverlauf ausführen", + "pathTrace_clearTooltip": "Pfad löschen", + "map_pathTraceCancelled": "Pfadverfolgung abgebrochen." } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 668f72e..9f24f46 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -621,6 +621,10 @@ "map_sharedPin": "Shared pin", "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_removeLast": "Remove Last", + "map_pathTraceCancelled": "Path trace cancelled.", "mapCache_title": "Offline Map Cache", "mapCache_selectAreaFirst": "Select an area to cache first", "mapCache_noTilesToDownload": "No tiles to download for this area", @@ -1319,6 +1323,7 @@ "pathTrace_notAvailable": "Path trace not available.", "pathTrace_refreshTooltip": "Refresh Path Trace.", "pathTrace_someHopsNoLocation": "One or more of the hops is missing a location!", + "pathTrace_clearTooltip": "Clear path.", "contacts_pathTrace": "Path Trace", "contacts_ping": "Ping", "contacts_repeaterPathTrace": "Path trace to repeater", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 3d5ab63..f1a0651 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1569,34 +1569,40 @@ "contacts_zeroHopContactAdvertSent": "Envió contacto por anuncio.", "contacts_contactAdvertCopied": "Anuncio copiado al Portapapeles.", "contacts_contactAdvertCopyFailed": "Copiar anuncio al Portapapeles ha fallado.", - "notification_activityTitle": "Actividad de MeshCore", "notification_messagesCount": "{count} {count, plural, =1{mensaje} other{mensajes}}", "@notification_messagesCount": { "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "notification_channelMessagesCount": "{count} {count, plural, =1{mensaje de canal} other{mensajes de canal}}", "@notification_channelMessagesCount": { "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "notification_newNodesCount": "{count} {count, plural, =1{nuevo nodo} other{nuevos nodos}}", "@notification_newNodesCount": { "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "notification_newTypeDiscovered": "Nuevo {contactType} descubierto", "@notification_newTypeDiscovered": { "placeholders": { - "contactType": {"type": "String"} + "contactType": { + "type": "String" + } } }, "notification_receivedNewMessage": "Nuevo mensaje recibido", - "contacts_contactAdvertCopyFailed": "Copiar anuncio al Portapapeles ha fallado.", "settings_gpxExportContactsSubtitle": "Exporta compañeros con una ubicación a archivo GPX.", "settings_gpxExportRepeaters": "Exportar repetidores / servidor de sala a GPX", "settings_gpxExportSuccess": "Archivo GPX exportado con éxito.", @@ -1612,6 +1618,10 @@ "settings_gpxExportAllContacts": "Todas las ubicaciones de contactos", "settings_gpxExportShareText": "Datos del mapa exportados desde meshcore-open", "settings_gpxExportShareSubject": "meshcore-open exportación de datos de mapa GPX", - "pathTrace_someHopsNoLocation": "Uno o más de los lúpulos carecen de una ubicación" - + "pathTrace_someHopsNoLocation": "Uno o más de los lúpulos carecen de una ubicación", + "pathTrace_clearTooltip": "Borrar ruta", + "map_runTrace": "Ejecutar Rastreo de Ruta", + "map_tapToAdd": "Pulse en los nodos para agregarlos al camino.", + "map_removeLast": "Eliminar último", + "map_pathTraceCancelled": "Rastreo de ruta cancelado." } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 0827576..39d7176 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1590,5 +1590,10 @@ "settings_gpxExportAllContacts": "Tous les emplacements des contacts", "settings_gpxExportShareText": "Données de carte exportées à partir de meshcore-open", "settings_gpxExportShareSubject": "meshcore-open exporter les données de carte GPX", - "pathTrace_someHopsNoLocation": "Un ou plusieurs des sauts manquent d'une localisation !" + "pathTrace_someHopsNoLocation": "Un ou plusieurs des sauts manquent d'une localisation !", + "map_tapToAdd": "Appuyez sur les nœuds pour les ajouter au chemin.", + "pathTrace_clearTooltip": "Effacer le chemin", + "map_pathTraceCancelled": "Traçage de chemin annulé", + "map_removeLast": "Supprimer le dernier", + "map_runTrace": "Exécuter la traçage de chemin" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index dd9c373..99f11f2 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{nuovo nodo} other{nuovi nodi}}", "notification_newTypeDiscovered": "Nuovo {contactType} scoperto", "notification_receivedNewMessage": "Nuovo messaggio ricevuto", - "contacts_contactAdvertCopied": "Annuncio copiato negli Appunti.", "settings_gpxExportRepeaters": "Esporta ripetitori / server di stanza in GPX", "settings_gpxExportContacts": "Esporta compagni in GPX", "settings_gpxExportSuccess": "Esportazione del file GPX completata con successo.", @@ -1591,6 +1590,10 @@ "settings_gpxExportAllContacts": "Tutte le posizioni dei contatti", "settings_gpxExportShareText": "Dati mappa esportati da meshcore-open", "settings_gpxExportShareSubject": "meshcore-open esportazione dati mappa GPX", - "pathTrace_someHopsNoLocation": "Uno o più dei luppoli mancano di una posizione!" - + "pathTrace_someHopsNoLocation": "Uno o più dei luppoli mancano di una posizione!", + "map_removeLast": "Rimuovi ultimo", + "map_pathTraceCancelled": "Tracciamento del percorso annullato.", + "pathTrace_clearTooltip": "Pulisci percorso", + "map_runTrace": "Esegui Path Trace", + "map_tapToAdd": "Tocca i nodi per aggiungerli al percorso." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8f4d693..20ac664 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2518,6 +2518,30 @@ abstract class AppLocalizations { /// **'Manage Repeater'** String get map_manageRepeater; + /// No description provided for @map_tapToAdd. + /// + /// In en, this message translates to: + /// **'Tap on nodes to add them to the path.'** + String get map_tapToAdd; + + /// No description provided for @map_runTrace. + /// + /// In en, this message translates to: + /// **'Run Path Trace'** + String get map_runTrace; + + /// No description provided for @map_removeLast. + /// + /// In en, this message translates to: + /// **'Remove Last'** + String get map_removeLast; + + /// No description provided for @map_pathTraceCancelled. + /// + /// In en, this message translates to: + /// **'Path trace cancelled.'** + String get map_pathTraceCancelled; + /// No description provided for @mapCache_title. /// /// In en, this message translates to: @@ -4730,6 +4754,12 @@ abstract class AppLocalizations { /// **'One or more of the hops is missing a location!'** String get pathTrace_someHopsNoLocation; + /// No description provided for @pathTrace_clearTooltip. + /// + /// In en, this message translates to: + /// **'Clear path.'** + String get pathTrace_clearTooltip; + /// No description provided for @contacts_pathTrace. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 68e821e..30a7ca7 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1363,6 +1363,19 @@ class AppLocalizationsBg extends AppLocalizations { @override String get map_manageRepeater => 'Управление на Повтарящ се Елемент'; + @override + String get map_tapToAdd => + 'Натиснете върху възлите, за да ги добавите към пътя.'; + + @override + String get map_runTrace => 'Изпълни Път на Следване'; + + @override + String get map_removeLast => 'Премахни Последно'; + + @override + String get map_pathTraceCancelled => 'Отменен е следването на пътя.'; + @override String get mapCache_title => 'Кеш на офлайн карти'; @@ -2699,6 +2712,9 @@ class AppLocalizationsBg extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Един или повече от хмелите липсва местоположение!'; + @override + String get pathTrace_clearTooltip => 'Изчисти пътя'; + @override String get contacts_pathTrace => 'Пътен проследяване'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 0967227..4c3245d 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1362,6 +1362,19 @@ class AppLocalizationsDe extends AppLocalizations { @override String get map_manageRepeater => 'Repeater verwalten'; + @override + String get map_tapToAdd => + 'Tippen Sie auf Knoten, um sie zum Pfad hinzuzufügen.'; + + @override + String get map_runTrace => 'Pfadverlauf ausführen'; + + @override + String get map_removeLast => 'Letztes Entfernen'; + + @override + String get map_pathTraceCancelled => 'Pfadverfolgung abgebrochen.'; + @override String get mapCache_title => 'Offline-Karten-Cache'; @@ -2704,6 +2717,9 @@ class AppLocalizationsDe extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Bei einer oder mehreren Knoten fehlt der Standort!'; + @override + String get pathTrace_clearTooltip => 'Pfad löschen'; + @override String get contacts_pathTrace => 'Pfadverfolgung'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 8eb76e8..120d242 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1342,6 +1342,18 @@ class AppLocalizationsEn extends AppLocalizations { @override String get map_manageRepeater => 'Manage Repeater'; + @override + String get map_tapToAdd => 'Tap on nodes to add them to the path.'; + + @override + String get map_runTrace => 'Run Path Trace'; + + @override + String get map_removeLast => 'Remove Last'; + + @override + String get map_pathTraceCancelled => 'Path trace cancelled.'; + @override String get mapCache_title => 'Offline Map Cache'; @@ -2659,6 +2671,9 @@ class AppLocalizationsEn extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'One or more of the hops is missing a location!'; + @override + String get pathTrace_clearTooltip => 'Clear path.'; + @override String get contacts_pathTrace => 'Path Trace'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index cc9bff7..069e3c7 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1360,6 +1360,18 @@ class AppLocalizationsEs extends AppLocalizations { @override String get map_manageRepeater => 'Gestionar Repetidor'; + @override + String get map_tapToAdd => 'Pulse en los nodos para agregarlos al camino.'; + + @override + String get map_runTrace => 'Ejecutar Rastreo de Ruta'; + + @override + String get map_removeLast => 'Eliminar último'; + + @override + String get map_pathTraceCancelled => 'Rastreo de ruta cancelado.'; + @override String get mapCache_title => 'Caché de Mapa Offline'; @@ -2698,6 +2710,9 @@ class AppLocalizationsEs extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Uno o más de los lúpulos carecen de una ubicación'; + @override + String get pathTrace_clearTooltip => 'Borrar ruta'; + @override String get contacts_pathTrace => 'Rastreo de caminos'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index eb6d50f..44b70af 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1367,6 +1367,19 @@ class AppLocalizationsFr extends AppLocalizations { @override String get map_manageRepeater => 'Gérer le répéteur'; + @override + String get map_tapToAdd => + 'Appuyez sur les nœuds pour les ajouter au chemin.'; + + @override + String get map_runTrace => 'Exécuter la traçage de chemin'; + + @override + String get map_removeLast => 'Supprimer le dernier'; + + @override + String get map_pathTraceCancelled => 'Traçage de chemin annulé'; + @override String get mapCache_title => 'Cache de Carte Hors Ligne'; @@ -2714,6 +2727,9 @@ class AppLocalizationsFr extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Un ou plusieurs des sauts manquent d\'une localisation !'; + @override + String get pathTrace_clearTooltip => 'Effacer le chemin'; + @override String get contacts_pathTrace => 'Traçage de chemin'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 68465dd..0bce5b4 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1359,6 +1359,18 @@ class AppLocalizationsIt extends AppLocalizations { @override String get map_manageRepeater => 'Gestisci Ripetitore'; + @override + String get map_tapToAdd => 'Tocca i nodi per aggiungerli al percorso.'; + + @override + String get map_runTrace => 'Esegui Path Trace'; + + @override + String get map_removeLast => 'Rimuovi ultimo'; + + @override + String get map_pathTraceCancelled => 'Tracciamento del percorso annullato.'; + @override String get mapCache_title => 'Cache Mappa Offline'; @@ -2699,6 +2711,9 @@ class AppLocalizationsIt extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Uno o più dei luppoli mancano di una posizione!'; + @override + String get pathTrace_clearTooltip => 'Pulisci percorso'; + @override String get contacts_pathTrace => 'Traccia Percorso'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index a093a35..432de6c 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1355,6 +1355,19 @@ class AppLocalizationsNl extends AppLocalizations { @override String get map_manageRepeater => 'Beheer Repeater'; + @override + String get map_tapToAdd => + 'Tik op knooppunten om ze toe te voegen aan het pad'; + + @override + String get map_runTrace => 'Padeshulp traceren'; + + @override + String get map_removeLast => 'Verwijder Laatste'; + + @override + String get map_pathTraceCancelled => 'Pad traceren geannuleerd'; + @override String get mapCache_title => 'Offline Kaarten Cache'; @@ -2689,6 +2702,9 @@ class AppLocalizationsNl extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Een of meer van de hops ontbreken een locatie!'; + @override + String get pathTrace_clearTooltip => 'Weg wissen'; + @override String get contacts_pathTrace => 'Pad Traceren'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 895e3c7..fb53893 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1361,6 +1361,18 @@ class AppLocalizationsPl extends AppLocalizations { @override String get map_manageRepeater => 'Zarządzaj Powtórzami'; + @override + String get map_tapToAdd => 'Kliknij na węzły, aby dodać je do ścieżki.'; + + @override + String get map_runTrace => 'Uruchom ślad ścieżki'; + + @override + String get map_removeLast => 'Usuń ostatni'; + + @override + String get map_pathTraceCancelled => 'Śledzenie ścieżki anulowano.'; + @override String get mapCache_title => 'Bufor Map Offline'; @@ -2697,6 +2709,9 @@ class AppLocalizationsPl extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Jeden lub więcej z chmieli nie ma określonej lokalizacji!'; + @override + String get pathTrace_clearTooltip => 'Wyczyść ścieżkę'; + @override String get contacts_pathTrace => 'Śledzenie Ścieżek'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index ce8d07c..f9d8415 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1361,6 +1361,18 @@ class AppLocalizationsPt extends AppLocalizations { @override String get map_manageRepeater => 'Gerenciar Repetidor'; + @override + String get map_tapToAdd => 'Toque nos nós para adicioná-los ao caminho.'; + + @override + String get map_runTrace => 'Executar Traçado de Caminho'; + + @override + String get map_removeLast => 'Remover Último'; + + @override + String get map_pathTraceCancelled => 'Rastreamento de caminho cancelado.'; + @override String get mapCache_title => 'Cache de Mapa Offline'; @@ -2700,6 +2712,9 @@ class AppLocalizationsPt extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Um ou mais dos lúpulos estão sem localização!'; + @override + String get pathTrace_clearTooltip => 'Limpar caminho'; + @override String get contacts_pathTrace => 'Traçado de Caminho'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 9e4cd95..f8a90ad 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1362,6 +1362,18 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_manageRepeater => 'Управление репитером'; + @override + String get map_tapToAdd => 'Нажимайте на узлы, чтобы добавить их в путь.'; + + @override + String get map_runTrace => 'Запустить трассировку пути'; + + @override + String get map_removeLast => 'Удалить последний'; + + @override + String get map_pathTraceCancelled => 'Отмена трассировки пути'; + @override String get mapCache_title => 'Кэш офлайн-карты'; @@ -2702,6 +2714,9 @@ class AppLocalizationsRu extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Одному или нескольким хмелям не указано местоположение!'; + @override + String get pathTrace_clearTooltip => 'Очистить путь'; + @override String get contacts_pathTrace => 'Трассировка пути'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index ed66f97..7e61cc2 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1356,6 +1356,18 @@ class AppLocalizationsSk extends AppLocalizations { @override String get map_manageRepeater => 'Spravovať Opakovanie'; + @override + String get map_tapToAdd => 'Kliknite na uzly, aby ste ich pridali k ceste.'; + + @override + String get map_runTrace => 'Spustiť trasovaním cesty'; + + @override + String get map_removeLast => 'Odstrániť posledný'; + + @override + String get map_pathTraceCancelled => 'Zrušenie stopáže cesty bolo zrušené.'; + @override String get mapCache_title => 'Offline Mapa Pamäť'; @@ -2685,6 +2697,9 @@ class AppLocalizationsSk extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Jedna alebo viac chmeľov chýba lokalita!'; + @override + String get pathTrace_clearTooltip => 'Zmazať cestu'; + @override String get contacts_pathTrace => 'Sledovanie lúčov'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 3307547..53da59a 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1352,6 +1352,18 @@ class AppLocalizationsSl extends AppLocalizations { @override String get map_manageRepeater => 'Upravljajte Ponovitve'; + @override + String get map_tapToAdd => 'Pritisnite na vozlišča, da jih dodate poti.'; + + @override + String get map_runTrace => 'Zaženi sledenje poti'; + + @override + String get map_removeLast => 'Odstrani Zadnji'; + + @override + String get map_pathTraceCancelled => 'Spremljanje poti je prekinjeno.'; + @override String get mapCache_title => 'Omrezni predpomnilnik zemljeških zemljejevskih slik'; @@ -2688,6 +2700,9 @@ class AppLocalizationsSl extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Ena ali več hmelju manjka lokacija!'; + @override + String get pathTrace_clearTooltip => 'Počisti pot'; + @override String get contacts_pathTrace => 'Sledenje poti'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 5239b06..d125979 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1348,6 +1348,18 @@ class AppLocalizationsSv extends AppLocalizations { @override String get map_manageRepeater => 'Hantera Upprepare'; + @override + String get map_tapToAdd => 'Tryck på noder för att lägga till dem i banan.'; + + @override + String get map_runTrace => 'Kör spårsökning'; + + @override + String get map_removeLast => 'Ta bort sista'; + + @override + String get map_pathTraceCancelled => 'Sökvägsspårning avbruten.'; + @override String get mapCache_title => 'Offline Kartcache'; @@ -2673,6 +2685,9 @@ class AppLocalizationsSv extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'En eller flera av humlen saknar en plats!'; + @override + String get pathTrace_clearTooltip => 'Rensa väg'; + @override String get contacts_pathTrace => 'Path Trace'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index b6ff8ce..de68840 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1361,6 +1361,18 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_manageRepeater => 'Керувати ретранслятором'; + @override + String get map_tapToAdd => 'Натисніть на вузли, щоб додати їх до шляху'; + + @override + String get map_runTrace => 'Виконати трасування шляху'; + + @override + String get map_removeLast => 'Видалити останній'; + + @override + String get map_pathTraceCancelled => 'Відмінується трасування шляху'; + @override String get mapCache_title => 'Офлайн-кеш карти'; @@ -2709,6 +2721,9 @@ class AppLocalizationsUk extends AppLocalizations { String get pathTrace_someHopsNoLocation => 'Одне або більше хмелів відсутнє місце розташування!'; + @override + String get pathTrace_clearTooltip => 'Очистити шлях'; + @override String get contacts_pathTrace => 'Трасування шляхів'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index a529a1b..040d4ef 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1301,6 +1301,18 @@ class AppLocalizationsZh extends AppLocalizations { @override String get map_manageRepeater => '管理重复器'; + @override + String get map_tapToAdd => '点击节点将其添加到路径中'; + + @override + String get map_runTrace => '运行路径跟踪'; + + @override + String get map_removeLast => '删除最后一个'; + + @override + String get map_pathTraceCancelled => '路径跟踪已取消'; + @override String get mapCache_title => '离线地图缓存'; @@ -2557,6 +2569,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get pathTrace_someHopsNoLocation => '其中一个或多个啤酒花缺少位置!'; + @override + String get pathTrace_clearTooltip => '清除路径'; + @override String get contacts_pathTrace => '路径追踪'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 91163ac..033b424 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{nieuw knooppunt} other{nieuwe knooppunten}}", "notification_newTypeDiscovered": "Nieuw {contactType} ontdekt", "notification_receivedNewMessage": "Nieuw bericht ontvangen", - "contacts_zeroHopContactAdvertFailed": "Mislukt om contact te verzenden", "settings_gpxExportRepeatersSubtitle": "Exporteert repeaters / roomserver met een locatie naar GPX-bestand.", "settings_gpxExportRepeaters": "Exporteer repeaters / roomserver naar GPX", "settings_gpxExportSuccess": "Succesvol GPX-bestand geëxporteerd.", @@ -1591,6 +1590,10 @@ "settings_gpxExportAllContacts": "Alle contactlocaties", "settings_gpxExportShareText": "Kaartgegevens geëxporteerd uit meshcore-open", "settings_gpxExportShareSubject": "meshcore-open GPX kaartgegevens exporteren", - "pathTrace_someHopsNoLocation": "Een of meer van de hops ontbreken een locatie!" - + "pathTrace_someHopsNoLocation": "Een of meer van de hops ontbreken een locatie!", + "map_removeLast": "Verwijder Laatste", + "pathTrace_clearTooltip": "Weg wissen", + "map_pathTraceCancelled": "Pad traceren geannuleerd", + "map_tapToAdd": "Tik op knooppunten om ze toe te voegen aan het pad", + "map_runTrace": "Padeshulp traceren" } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 3c2a96f..e0a08dd 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{nowy węzeł} few{nowe węzły} many{nowych węzłów} other{nowych węzłów}}", "notification_newTypeDiscovered": "Nowy {contactType} wykryty", "notification_receivedNewMessage": "Otrzymano nową wiadomość", - "contacts_zeroHopContactAdvertFailed": "Nie udało się wysłać kontaktu.", "settings_gpxExportContacts": "Eksportuj towarzyszy do GPX", "settings_gpxExportRepeaters": "Eksportuj powtórki / serwer pokojowy do GPX", "settings_gpxExportRepeatersSubtitle": "Eksportuje powtarzacze / roomserver z lokalizacją do pliku GPX.", @@ -1591,6 +1590,10 @@ "settings_gpxExportChat": "Lokalizacje towarzyszy", "settings_gpxExportShareText": "Dane mapy wyeksportowane z meshcore-open", "settings_gpxExportShareSubject": "Eksport danych mapy GPX meshcore-open", - "pathTrace_someHopsNoLocation": "Jeden lub więcej z chmieli nie ma określonej lokalizacji!" - + "pathTrace_someHopsNoLocation": "Jeden lub więcej z chmieli nie ma określonej lokalizacji!", + "map_pathTraceCancelled": "Śledzenie ścieżki anulowano.", + "map_runTrace": "Uruchom ślad ścieżki", + "pathTrace_clearTooltip": "Wyczyść ścieżkę", + "map_removeLast": "Usuń ostatni", + "map_tapToAdd": "Kliknij na węzły, aby dodać je do ścieżki." } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index dc38c11..20eee88 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{novo nó} other{novos nós}}", "notification_newTypeDiscovered": "Novo {contactType} descoberto", "notification_receivedNewMessage": "Nova mensagem recebida", - "contacts_zeroHopContactAdvertFailed": "Falha ao enviar contato.", "settings_gpxExportRepeaters": "Exportar repetidores / servidor de sala para GPX", "settings_gpxExportRepeatersSubtitle": "Exporta repetidores / roomserver com localização para arquivo GPX.", "settings_gpxExportSuccess": "Arquivo GPX exportado com sucesso.", @@ -1591,6 +1590,10 @@ "settings_gpxExportAllContacts": "Todos os locais de contatos", "settings_gpxExportShareText": "Dados do mapa exportados do meshcore-open", "settings_gpxExportShareSubject": "meshcore-open exportação de dados de mapa GPX", - "pathTrace_someHopsNoLocation": "Um ou mais dos lúpulos estão sem localização!" - + "pathTrace_someHopsNoLocation": "Um ou mais dos lúpulos estão sem localização!", + "map_runTrace": "Executar Traçado de Caminho", + "map_pathTraceCancelled": "Rastreamento de caminho cancelado.", + "pathTrace_clearTooltip": "Limpar caminho", + "map_removeLast": "Remover Último", + "map_tapToAdd": "Toque nos nós para adicioná-los ao caminho." } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index ddbbe79..165ccbb 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -815,7 +815,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{новый узел} few{новых узла} many{новых узлов} other{новых узлов}}", "notification_newTypeDiscovered": "Обнаружен новый {contactType}", "notification_receivedNewMessage": "Получено новое сообщение", - "contacts_zeroHopContactAdvertSent": "Отправлено сообщение по объявлению.", "settings_gpxExportRepeaters": "Экспортировать рипитеры / сервер комнаты в GPX", "settings_gpxExportRepeatersSubtitle": "Экспортирует ретрансляторы / сервер комнат с местоположением в файл GPX.", "settings_gpxExportContacts": "Экспортировать спутников в GPX", @@ -831,6 +830,10 @@ "settings_gpxExportNoContacts": "Нет контактов для экспорта.", "settings_gpxExportShareText": "Данные карты экспортированы из meshcore-open", "settings_gpxExportShareSubject": "meshcore-open экспорт данных карты GPX", - "pathTrace_someHopsNoLocation": "Одному или нескольким хмелям не указано местоположение!" - + "pathTrace_someHopsNoLocation": "Одному или нескольким хмелям не указано местоположение!", + "map_tapToAdd": "Нажимайте на узлы, чтобы добавить их в путь.", + "map_removeLast": "Удалить последний", + "map_pathTraceCancelled": "Отмена трассировки пути", + "pathTrace_clearTooltip": "Очистить путь", + "map_runTrace": "Запустить трассировку пути" } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index c09502a..08439da 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{nový uzol} few{nové uzly} other{nových uzlov}}", "notification_newTypeDiscovered": "Nový {contactType} objavený", "notification_receivedNewMessage": "Prijatá nová správa", - "contacts_ShareContact": "Kopírovať kontakt do schránky", "settings_gpxExportRepeatersSubtitle": "Exportuje repeater / roomserver s lokalitou do súboru GPX.", "settings_gpxExportContacts": "Export sprievodcov do GPX", "settings_gpxExportSuccess": "Úspešne exportovaný súbor GPX.", @@ -1591,6 +1590,10 @@ "settings_gpxExportChat": "Lokácie sprievodcov", "settings_gpxExportShareText": "Mapové údaje exportované z meshcore-open", "settings_gpxExportShareSubject": "meshcore-open export dát GPX mapových údajov", - "pathTrace_someHopsNoLocation": "Jedna alebo viac chmeľov chýba lokalita!" - + "pathTrace_someHopsNoLocation": "Jedna alebo viac chmeľov chýba lokalita!", + "pathTrace_clearTooltip": "Zmazať cestu", + "map_tapToAdd": "Kliknite na uzly, aby ste ich pridali k ceste.", + "map_removeLast": "Odstrániť posledný", + "map_runTrace": "Spustiť trasovaním cesty", + "map_pathTraceCancelled": "Zrušenie stopáže cesty bolo zrušené." } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 97a396a..d321fd1 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{novo vozlišče} =2{novi vozlišči} few{nova vozlišča} other{novih vozlišč}}", "notification_newTypeDiscovered": "Odkrito novo {contactType}", "notification_receivedNewMessage": "Prejeto novo sporočilo", - "contacts_ShareContact": "Kopiraj stik v Odložišče", "settings_gpxExportAll": "Izvozi vse kontakte v GPX", "settings_gpxExportContacts": "Izvoz spremljevalcev v GPX", "settings_gpxExportRepeatersSubtitle": "Izvozi ponovljene oddajnike / strežnik sobe z lokacijo v datoteko GPX.", @@ -1591,6 +1590,10 @@ "settings_gpxExportNoContacts": "Ni stikov za izvoz.", "settings_gpxExportNotAvailable": "Ni podprto na vašem napravi/operacijskem sistemu", "settings_gpxExportShareSubject": "meshcore-open izvoz podatkov GPX karte", - "pathTrace_someHopsNoLocation": "Ena ali več hmelju manjka lokacija!" - + "pathTrace_someHopsNoLocation": "Ena ali več hmelju manjka lokacija!", + "map_tapToAdd": "Pritisnite na vozlišča, da jih dodate poti.", + "map_removeLast": "Odstrani Zadnji", + "map_runTrace": "Zaženi sledenje poti", + "pathTrace_clearTooltip": "Počisti pot", + "map_pathTraceCancelled": "Spremljanje poti je prekinjeno." } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 6df28bd..4b01494 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{ny nod} other{nya noder}}", "notification_newTypeDiscovered": "Ny {contactType} upptäckt", "notification_receivedNewMessage": "Nytt meddelande mottaget", - "contacts_ShareContactZeroHop": "Dela kontakt via annons", "settings_gpxExportAll": "Exportera alla kontakter till GPX", "settings_gpxExportRepeatersSubtitle": "Exporterar repeater / roomserver med plats till GPX-fil.", "settings_gpxExportSuccess": "Har exporterat GPX-fil med framgång", @@ -1591,6 +1590,10 @@ "settings_gpxExportAllContacts": "Alla kontakters platser", "settings_gpxExportShareSubject": "meshcore-open export av GPX-kartdata", "settings_gpxExportShareText": "Kartdata exporterad från meshcore-open", - "pathTrace_someHopsNoLocation": "En eller flera av humlen saknar en plats!" - + "pathTrace_someHopsNoLocation": "En eller flera av humlen saknar en plats!", + "pathTrace_clearTooltip": "Rensa väg", + "map_pathTraceCancelled": "Sökvägsspårning avbruten.", + "map_runTrace": "Kör spårsökning", + "map_tapToAdd": "Tryck på noder för att lägga till dem i banan.", + "map_removeLast": "Ta bort sista" } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 9f5e64d..9ff05fb 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} {count, plural, =1{новий вузол} few{нових вузли} many{нових вузлів} other{нових вузлів}}", "notification_newTypeDiscovered": "Виявлено новий {contactType}", "notification_receivedNewMessage": "Отримано нове повідомлення", - "contacts_ShareContactZeroHop": "Поділитися контактом за оголошенням", "settings_gpxExportRepeaters": "Експортувати ретранслятори / сервер кімнати до GPX", "settings_gpxExportRepeatersSubtitle": "Експортує ретранслятори / сервер кімнати з місцезнаходженням у файл GPX.", "settings_gpxExportSuccess": "Успішно експортовано файл GPX.", @@ -1591,6 +1590,10 @@ "settings_gpxExportShareText": "Дані карти експортовані з meshcore-open", "settings_gpxExportAllContacts": "Усі місця контактів", "settings_gpxExportShareSubject": "експорт даних карти meshcore-open у форматі GPX", - "pathTrace_someHopsNoLocation": "Одне або більше хмелів відсутнє місце розташування!" - + "pathTrace_someHopsNoLocation": "Одне або більше хмелів відсутнє місце розташування!", + "map_tapToAdd": "Натисніть на вузли, щоб додати їх до шляху", + "map_runTrace": "Виконати трасування шляху", + "pathTrace_clearTooltip": "Очистити шлях", + "map_removeLast": "Видалити останній", + "map_pathTraceCancelled": "Відмінується трасування шляху" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 8c65510..0f8c079 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1575,7 +1575,6 @@ "notification_newNodesCount": "{count} 个新节点", "notification_newTypeDiscovered": "发现新 {contactType}", "notification_receivedNewMessage": "收到新消息", - "contacts_contactAdvertCopyFailed": "将广告复制到剪贴板操作失败。", "settings_gpxExportRepeaters": "导出重复器/房间服务器到GPX", "settings_gpxExportRepeatersSubtitle": "导出带有位置的重复器/房间服务器到GPX文件。", "settings_gpxExportContactsSubtitle": "导出带有位置的伙伴到GPX文件。", @@ -1591,6 +1590,10 @@ "settings_gpxExportNoContacts": "没有联系人可导出", "settings_gpxExportShareText": "来自meshcore-open的导出地图数据", "settings_gpxExportShareSubject": "meshcore-open GPX 地图数据导出", - "pathTrace_someHopsNoLocation": "其中一个或多个啤酒花缺少位置!" - + "pathTrace_someHopsNoLocation": "其中一个或多个啤酒花缺少位置!", + "map_tapToAdd": "点击节点将其添加到路径中", + "pathTrace_clearTooltip": "清除路径", + "map_pathTraceCancelled": "路径跟踪已取消", + "map_removeLast": "删除最后一个", + "map_runTrace": "运行路径跟踪" } diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index 135babd..4e31733 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -384,6 +384,7 @@ class AppSettingsScreen extends StatelessWidget { ); } + // Fixed rendering issues Widget _buildBatteryCard( BuildContext context, AppSettingsService settingsService, @@ -399,6 +400,7 @@ class AppSettingsScreen extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 4), Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Text( @@ -406,6 +408,8 @@ class AppSettingsScreen extends StatelessWidget { style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), + + // Main tile (icon + text only) ListTile( leading: const Icon(Icons.battery_full), title: Text(context.l10n.appSettings_batteryChemistry), @@ -416,8 +420,19 @@ class AppSettingsScreen extends StatelessWidget { ) : context.l10n.appSettings_batteryChemistryConnectFirst, ), - trailing: DropdownButton( - value: selection, + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + ), + + // Dropdown (separate full-width row) + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: DropdownButtonFormField( + initialValue: selection, + isExpanded: true, + decoration: const InputDecoration( + border: UnderlineInputBorder(), + isDense: true, + ), onChanged: isConnected ? (value) { if (value != null) { diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index f522407..552f579 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1,8 +1,10 @@ import 'dart:math'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; +import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; @@ -48,9 +50,14 @@ class _MapScreenState extends State { final MapMarkerService _markerService = MapMarkerService(); final Set _hiddenMarkerIds = {}; Set _removedMarkerIds = {}; + bool _isBuildingPathTrace = false; bool _isSelectingPoi = false; bool _hasInitializedMap = false; bool _removedMarkersLoaded = false; + final List _pathTrace = []; + final List _points = []; + final List _polylines = []; + bool _legendExpanded = false; @override void initState() { @@ -147,6 +154,19 @@ class _MapScreenState extends State { .where((c) => c.hasLocation) .toList(); + _polylines.clear(); + _polylines.addAll( + _points.length > 1 + ? [ + Polyline( + points: _points, + strokeWidth: 4, + color: Colors.blueAccent, + ), + ] + : [], + ); + // Calculate center and zoom of all nodes, or default to (0, 0) LatLng center = const LatLng(0, 0); double initialZoom = 10.0; @@ -247,6 +267,12 @@ class _MapScreenState extends State { centerTitle: true, automaticallyImplyLeading: false, actions: [ + if (!_isBuildingPathTrace) + IconButton( + icon: const Icon(Icons.radar), + onPressed: () => _startPath(), + tooltip: context.l10n.contacts_pathTrace, + ), PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem( @@ -334,6 +360,8 @@ class _MapScreenState extends State { MapTileCacheService.userAgentPackageName, maxZoom: 19, ), + if (_polylines.isNotEmpty && _isBuildingPathTrace) + PolylineLayer(polylines: _polylines), MarkerLayer( markers: [ if (highlightPosition != null) @@ -390,7 +418,12 @@ class _MapScreenState extends State { ), ], ), - _buildLegend(contactsWithLocation.length, sharedMarkers.length), + if (!_isBuildingPathTrace) + _buildLegend( + contactsWithLocation.length, + sharedMarkers.length, + ), + if (_isBuildingPathTrace) _buildPathTraceOverlay(), ], ), bottomNavigationBar: SafeArea( @@ -434,7 +467,11 @@ class _MapScreenState extends State { width: 35, height: 35, child: GestureDetector( - onTap: () => _showNodeInfo(context, contact), + onLongPress: () => + _isBuildingPathTrace ? _showNodeInfo(context, contact) : null, + onTap: () => _isBuildingPathTrace + ? _addToPath(context, contact) + : _showNodeInfo(context, contact), child: Column( children: [ Container( @@ -503,60 +540,102 @@ class _MapScreenState extends State { top: 16, right: 16, child: Card( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - context.l10n.map_nodesCount(nodeCount), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + setState(() { + _legendExpanded = !_legendExpanded; + }); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 10, 12, 10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.map_nodesCount(nodeCount), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + Text( + context.l10n.map_pinsCount(markerCount), + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 12, + ), + ), + ], + ), + const SizedBox(width: 8), + AnimatedRotation( + turns: _legendExpanded ? 0.5 : 0, + duration: const Duration(milliseconds: 200), + child: const Icon(Icons.expand_more, size: 20), + ), + ], ), ), - Text( - context.l10n.map_pinsCount(markerCount), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12, + ), + AnimatedCrossFade( + firstChild: const SizedBox.shrink(), + secondChild: Padding( + padding: const EdgeInsets.fromLTRB(12, 0, 12, 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 6), + _buildLegendItem( + Icons.person, + context.l10n.map_chat, + Colors.blue, + ), + _buildLegendItem( + Icons.router, + context.l10n.map_repeater, + Colors.green, + ), + _buildLegendItem( + Icons.meeting_room, + context.l10n.map_room, + Colors.purple, + ), + _buildLegendItem( + Icons.sensors, + context.l10n.map_sensor, + Colors.orange, + ), + _buildLegendItem( + Icons.flag, + context.l10n.map_pinDm, + Colors.blue, + ), + _buildLegendItem( + Icons.flag, + context.l10n.map_pinPrivate, + Colors.purple, + ), + _buildLegendItem( + Icons.flag, + context.l10n.map_pinPublic, + Colors.orange, + ), + ], ), ), - const SizedBox(height: 8), - _buildLegendItem( - Icons.person, - context.l10n.map_chat, - Colors.blue, - ), - _buildLegendItem( - Icons.router, - context.l10n.map_repeater, - Colors.green, - ), - _buildLegendItem( - Icons.meeting_room, - context.l10n.map_room, - Colors.purple, - ), - _buildLegendItem( - Icons.sensors, - context.l10n.map_sensor, - Colors.orange, - ), - _buildLegendItem(Icons.flag, context.l10n.map_pinDm, Colors.blue), - _buildLegendItem( - Icons.flag, - context.l10n.map_pinPrivate, - Colors.purple, - ), - _buildLegendItem( - Icons.flag, - context.l10n.map_pinPublic, - Colors.orange, - ), - ], - ), + crossFadeState: _legendExpanded + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + duration: const Duration(milliseconds: 200), + ), + ], ), ), ); @@ -564,7 +643,7 @@ class _MapScreenState extends State { Widget _buildLegendItem(IconData icon, String label, Color color) { return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), + padding: const EdgeInsets.symmetric(vertical: 1.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -1412,6 +1491,114 @@ class _MapScreenState extends State { return context.l10n.time_allTime; } } + + void _addToPath(BuildContext context, Contact contact) { + setState(() { + _pathTrace.add( + contact.publicKey[0], + ); // Add first 16 bytes of public key to path trace + _points.add(LatLng(contact.latitude!, contact.longitude!)); + }); + } + + void _startPath() { + setState(() { + _isBuildingPathTrace = true; + _pathTrace.clear(); + _points.clear(); + _polylines.clear(); + }); + } + + void _removePath() { + setState(() { + _pathTrace.removeLast(); // Remove last node from path trace + _points.removeLast(); // Remove last point from points list + _polylines.clear(); // Clear polylines + }); + } + + Widget _buildPathTraceOverlay() { + final l10n = context.l10n; + return Positioned( + top: 16, + left: 16, + right: 16, + child: Card( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.contacts_pathTrace, + style: TextStyle(fontWeight: FontWeight.bold), + ), + if (_pathTrace.isEmpty) const SizedBox(height: 8), + if (_pathTrace.isEmpty) + Text(l10n.map_tapToAdd, style: TextStyle(fontSize: 12)), + const SizedBox(height: 6), + if (_pathTrace.isNotEmpty) + Text( + "${l10n.path_currentPathLabel} ${formatDistance(getPathDistanceMeters(_points))}", + style: TextStyle(fontSize: 12, color: Colors.grey[700]), + ), + SelectableText( + _pathTrace + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join(','), + style: TextStyle(fontSize: 18), + ), + const SizedBox(height: 6), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (_pathTrace.isNotEmpty) + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PathTraceMapScreen( + title: l10n.contacts_pathTrace, + path: Uint8List.fromList(_pathTrace), + ), + ), + ); + setState(() { + _isBuildingPathTrace = false; + }); + }, + child: Text(l10n.map_runTrace), + ), + if (_pathTrace.isNotEmpty) + ElevatedButton( + onPressed: _removePath, + child: Text(l10n.map_removeLast), + ), + if (_pathTrace.isEmpty) + ElevatedButton( + onPressed: () { + setState(() { + _isBuildingPathTrace = false; + _pathTrace.clear(); + _points.clear(); + _polylines.clear(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.map_pathTraceCancelled)), + ); + }, + child: Text(l10n.common_cancel), + ), + ], + ), + ], + ), + ), + ), + ); + } } class _MarkerPayload { diff --git a/lib/screens/path_trace_map.dart b/lib/screens/path_trace_map.dart index 39de31e..5b19c30 100644 --- a/lib/screens/path_trace_map.dart +++ b/lib/screens/path_trace_map.dart @@ -13,6 +13,23 @@ import 'package:meshcore_open/services/map_tile_cache_service.dart'; import 'package:meshcore_open/widgets/snr_indicator.dart'; import 'package:provider/provider.dart'; +double getPathDistanceMeters(List points) { + if (points.length <= 1) return 0.0; + + double distanceMeters = 0.0; + final distanceCalculator = Distance(); + + for (int i = 0; i < points.length - 1; i++) { + distanceMeters += distanceCalculator(points[i], points[i + 1]); + } + + return distanceMeters; +} + +String formatDistance(double distanceMeters) { + return '(${(distanceMeters / 1609.34).toStringAsFixed(2)} Miles / ${(distanceMeters / 1000).toStringAsFixed(2)} Km)'; +} + class PathTraceData { final Uint8List pathData; final Uint8List snrData; @@ -50,7 +67,6 @@ class _PathTraceMapScreenState extends State { bool _isLoading = false; bool _failed2Loaded = false; bool _hasData = false; - bool _noLocationErr = false; PathTraceData? _traceData; List _points = []; List _polylines = []; @@ -58,7 +74,7 @@ class _PathTraceMapScreenState extends State { double _initialZoom = 2.0; LatLngBounds? _bounds; ValueKey _mapKey = const ValueKey('initial'); - double _pathDistance = 0.0; + double _pathDistanceMeters = 0.0; String _formatPathPrefixes(Uint8List pathBytes) { return pathBytes @@ -93,23 +109,11 @@ class _PathTraceMapScreenState extends State { return traceBytes; } - double getPathDistance() { - double totalDistance = 0.0; - final distanceCalculator = Distance(); - - for (int i = 0; i < _points.length - 1; i++) { - totalDistance += distanceCalculator(_points[i], _points[i + 1]); - } - - return totalDistance; - } - Future _doPathTrace() async { if (mounted) { setState(() { _isLoading = true; _failed2Loaded = false; - _noLocationErr = false; }); } @@ -160,6 +164,14 @@ class _PathTraceMapScreenState extends State { }); } + if (code == respCodeErr) { + _timeoutTimer?.cancel(); + if (!mounted) return; + setState(() { + _isLoading = false; + _failed2Loaded = true; + }); + } // Check if it's a binary response if (frame.length > 8 && code == pushCodeTraceData && @@ -215,8 +227,6 @@ class _PathTraceMapScreenState extends State { contact.latitude != null && contact.longitude != null) { _points.add(LatLng(contact.latitude!, contact.longitude!)); - } else { - _noLocationErr = true; } } _polylines = _points.length > 1 @@ -235,7 +245,7 @@ class _PathTraceMapScreenState extends State { _mapKey = ValueKey( '${context.l10n.pathTrace_you},${_formatPathPrefixes(_traceData!.pathData)}', ); - _pathDistance = getPathDistance(); + _pathDistanceMeters = getPathDistanceMeters(_points); }); } @@ -279,20 +289,7 @@ class _PathTraceMapScreenState extends State { top: false, child: Stack( children: [ - if (_noLocationErr) - Center( - child: Card( - color: Colors.red, - child: Padding( - padding: EdgeInsets.all(12), - child: Text( - context.l10n.pathTrace_someHopsNoLocation, - style: TextStyle(color: Colors.white), - ), - ), - ), - ), - if (!_hasData && !_noLocationErr) + if (!_hasData) Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -304,43 +301,11 @@ class _PathTraceMapScreenState extends State { ], ), ), - if (_hasData && !_noLocationErr) - FlutterMap( - key: _mapKey, - options: MapOptions( - initialCenter: _initialCenter!, - initialZoom: _initialZoom, - initialCameraFit: _bounds == null - ? null - : CameraFit.bounds( - bounds: _bounds!, - padding: const EdgeInsets.all(64), - maxZoom: 16, - ), - minZoom: 2.0, - maxZoom: 18.0, - ), - children: [ - TileLayer( - urlTemplate: kMapTileUrlTemplate, - tileProvider: tileCache.tileProvider, - userAgentPackageName: - MapTileCacheService.userAgentPackageName, - maxZoom: 19, - ), - if (_polylines.isNotEmpty) - PolylineLayer(polylines: _polylines), - if (_traceData!.pathData.isNotEmpty) - MarkerLayer( - markers: _buildHopMarkers(_traceData!.pathData), - ), - ], - ), + if (_hasData) _buildMapPathTrace(context, tileCache), if (_points.isEmpty && !_hasData && !_isLoading && - !_failed2Loaded && - !_noLocationErr) + !_failed2Loaded) Center( child: Card( color: Colors.white.withValues(alpha: 0.9), @@ -352,8 +317,7 @@ class _PathTraceMapScreenState extends State { ), ), ), - if (_hasData && !_noLocationErr) - _buildLegendCard(context, _traceData!), + if (_hasData) _buildLegendCard(context, _traceData!), ], ), ), @@ -365,7 +329,8 @@ class _PathTraceMapScreenState extends State { List _buildHopMarkers(List pathData) { return [ for (final hop in pathData) - if (_traceData!.pathContacts[hop]!.hasLocation) + if (_traceData!.pathContacts[hop] != null && + _traceData!.pathContacts[hop]!.hasLocation) Marker( point: LatLng( _traceData!.pathContacts[hop]!.latitude!, @@ -453,7 +418,9 @@ class _PathTraceMapScreenState extends State { .toRadixString(16) .padLeft(2, '0') .toUpperCase(); - return contactName != null ? "$hex: $contactName" : hex; + return contactName != null + ? "$hex: $contactName" + : "$hex: ${context.l10n.channelPath_unknownRepeater}"; } } else { final contactName = @@ -462,7 +429,9 @@ class _PathTraceMapScreenState extends State { .toRadixString(16) .padLeft(2, '0') .toUpperCase(); - return contactName != null ? "$hex: $contactName" : hex; + return contactName != null + ? "$hex: $contactName" + : "$hex: ${context.l10n.channelPath_unknownRepeater}"; } } @@ -475,7 +444,9 @@ class _PathTraceMapScreenState extends State { .toRadixString(16) .padLeft(2, '0') .toUpperCase(); - return contactName != null ? "$hex: $contactName" : hex; + return contactName != null + ? "$hex: $contactName" + : "$hex: ${context.l10n.channelPath_unknownRepeater}"; } else { return context.l10n.pathTrace_you; } @@ -486,10 +457,46 @@ class _PathTraceMapScreenState extends State { .toRadixString(16) .padLeft(2, '0') .toUpperCase(); - return contactName != null ? "$hex: $contactName" : hex; + return contactName != null + ? "$hex: $contactName" + : "$hex: ${context.l10n.channelPath_unknownRepeater}"; } } + Widget _buildMapPathTrace( + BuildContext context, + MapTileCacheService tileCache, + ) { + return FlutterMap( + key: _mapKey, + options: MapOptions( + interactionOptions: InteractionOptions(flags: ~InteractiveFlag.rotate), + initialCenter: _initialCenter!, + initialZoom: _initialZoom, + initialCameraFit: _bounds == null + ? null + : CameraFit.bounds( + bounds: _bounds!, + padding: const EdgeInsets.all(64), + maxZoom: 16, + ), + minZoom: 2.0, + maxZoom: 18.0, + ), + children: [ + TileLayer( + urlTemplate: kMapTileUrlTemplate, + tileProvider: tileCache.tileProvider, + userAgentPackageName: MapTileCacheService.userAgentPackageName, + maxZoom: 19, + ), + if (_polylines.isNotEmpty) PolylineLayer(polylines: _polylines), + if (_traceData!.pathData.isNotEmpty) + MarkerLayer(markers: _buildHopMarkers(_traceData!.pathData)), + ], + ); + } + Widget _buildLegendCard(BuildContext context, PathTraceData pathTraceData) { final l10n = context.l10n; final maxHeight = MediaQuery.of(context).size.height * 0.35; @@ -509,7 +516,7 @@ class _PathTraceMapScreenState extends State { Padding( padding: const EdgeInsets.all(12), child: Text( - '${l10n.channelPath_repeaterHops} (${(_pathDistance / 1609.34).toStringAsFixed(2)} Miles / ${(_pathDistance / 1000).toStringAsFixed(2)} Km)', + '${l10n.channelPath_repeaterHops} ${formatDistance(_pathDistanceMeters)}', style: const TextStyle(fontWeight: FontWeight.w600), ), ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 2212b8d..4943284 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -21,6 +21,7 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State { bool _showBatteryVoltage = false; + bool _deviceInfoExpanded = false; String _appVersion = ''; @override @@ -74,43 +75,84 @@ class _SettingsScreenState extends State { MeshCoreConnector connector, ) { final l10n = context.l10n; + return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.settings_deviceInfo, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 16), - _buildInfoRow(l10n.settings_infoName, connector.deviceDisplayName), - _buildInfoRow(l10n.settings_infoId, connector.deviceIdLabel), - _buildInfoRow( - l10n.settings_infoStatus, - connector.isConnected - ? l10n.common_connected - : l10n.common_disconnected, - ), - _buildBatteryInfoRow(context, connector), - if (connector.selfName != null) - _buildInfoRow(l10n.settings_nodeName, connector.selfName!), - if (connector.selfPublicKey != null) - _buildInfoRow( - l10n.settings_infoPublicKey, - '${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + setState(() { + _deviceInfoExpanded = !_deviceInfoExpanded; + }); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 16), + child: Row( + children: [ + Expanded( + child: Text( + l10n.settings_deviceInfo, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + AnimatedRotation( + turns: _deviceInfoExpanded ? 0.5 : 0, + duration: const Duration(milliseconds: 200), + child: const Icon(Icons.expand_more), + ), + ], ), - _buildInfoRow( - l10n.settings_infoContactsCount, - '${connector.contacts.length}', ), - _buildInfoRow( - l10n.settings_infoChannelCount, - '${connector.channels.length}', + ), + + AnimatedCrossFade( + firstChild: const SizedBox.shrink(), + secondChild: Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow( + l10n.settings_infoName, + connector.deviceDisplayName, + ), + _buildInfoRow(l10n.settings_infoId, connector.deviceIdLabel), + _buildInfoRow( + l10n.settings_infoStatus, + connector.isConnected + ? l10n.common_connected + : l10n.common_disconnected, + ), + _buildBatteryInfoRow(context, connector), + if (connector.selfName != null) + _buildInfoRow(l10n.settings_nodeName, connector.selfName!), + if (connector.selfPublicKey != null) + _buildInfoRow( + l10n.settings_infoPublicKey, + '${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...', + ), + _buildInfoRow( + l10n.settings_infoContactsCount, + '${connector.contacts.length}', + ), + _buildInfoRow( + l10n.settings_infoChannelCount, + '${connector.channels.length}', + ), + ], + ), ), - ], - ), + crossFadeState: _deviceInfoExpanded + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + duration: const Duration(milliseconds: 200), + ), + ], ), ); } @@ -355,22 +397,33 @@ class _SettingsScreenState extends State { Color? valueColor, VoidCallback? onTap, }) { + final theme = Theme.of(context); + final row = Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + padding: const EdgeInsets.symmetric(vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ if (leading != null) ...[leading, const SizedBox(width: 8)], - Text(label, style: TextStyle(color: Colors.grey[600])), + Expanded( + child: Text( + label, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w500, + ), + ), + ), ], ), - Flexible( - child: Text( - value, - style: TextStyle(fontWeight: FontWeight.w500, color: valueColor), - overflow: TextOverflow.ellipsis, + const SizedBox(height: 4), + Text( + value, + style: theme.textTheme.bodyLarge?.copyWith( + color: valueColor, + fontWeight: FontWeight.w500, ), ), ], @@ -379,11 +432,12 @@ class _SettingsScreenState extends State { if (onTap != null) { return InkWell( + borderRadius: BorderRadius.circular(6), onTap: onTap, - borderRadius: BorderRadius.circular(4), child: row, ); } + return row; } diff --git a/pubspec.yaml b/pubspec.yaml index e1ae023..fa3bd63 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 6.0.0+1 +version: 6.0.0+7 environment: sdk: ^3.9.2