mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-08-04 15:03:00 +10:00
Merge branch 'main' into unread-peoplefirst
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
name: Flutter and Dart Analysis
|
name: Flutter and Dart
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze_and_format:
|
analyze:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -26,3 +26,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Verify formatting
|
- name: Verify formatting
|
||||||
run: dart format --output=none --set-exit-if-changed .
|
run: dart format --output=none --set-exit-if-changed .
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: flutter test -r github
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_contactAdvertCopied": "Рекламата е копирана в клипборда.",
|
"contacts_contactAdvertCopied": "Рекламата е копирана в клипборда.",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Неуспешно изпращане на контакт.",
|
"contacts_zeroHopContactAdvertFailed": "Неуспешно изпращане на контакт.",
|
||||||
"contacts_zeroHopContactAdvertSent": "Изпратен контакт по обява.",
|
"contacts_zeroHopContactAdvertSent": "Изпратен контакт по обява.",
|
||||||
"contacts_contactAdvertCopyFailed": "Копирането на обявата в клипборда не успя."
|
"contacts_contactAdvertCopyFailed": "Копирането на обявата в клипборда не успя.",
|
||||||
|
"notification_activityTitle": "Активност на MeshCore",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{съобщение} other{съобщения}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{съобщение в канал} other{съобщения в канали}}",
|
||||||
|
"notification_newNodesCount": "{count} {count, plural, =1{нов възел} other{нови възли}}",
|
||||||
|
"notification_newTypeDiscovered": "Открит нов {contactType}",
|
||||||
|
"notification_receivedNewMessage": "Получено ново съобщение",
|
||||||
|
"contacts_contactAdvertCopyFailed": "Копирането на обявата в клипборда не успя.",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Експортира спътници с местоположение в GPX файл.",
|
||||||
|
"settings_gpxExportRepeatersSubtitle": "Изпраща повторители / roomserver с местоположение в GPX файл.",
|
||||||
|
"settings_gpxExportAll": "Експортирай всички контакти в GPX",
|
||||||
|
"settings_gpxExportAllSubtitle": "Експортира всички контакти с местоположение в файл GPX.",
|
||||||
|
"settings_gpxExportRepeaters": "Експортиране на повтарящи се устройства / сървър на стаята до GPX",
|
||||||
|
"settings_gpxExportContacts": "Експортирай спътници към GPX",
|
||||||
|
"settings_gpxExportSuccess": "Успешно изlexport на файл GPX.",
|
||||||
|
"settings_gpxExportNoContacts": "Няма контакти за изlexport.",
|
||||||
|
"settings_gpxExportChat": "Местоположения на спътници",
|
||||||
|
"settings_gpxExportError": "Възникна грешка при изнасяне.",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Местоположения на повторител и сървър на стаята",
|
||||||
|
"settings_gpxExportNotAvailable": "Не е поддържан на вашето устройство/ОС",
|
||||||
|
"settings_gpxExportAllContacts": "Местоположения на всички контакти",
|
||||||
|
"settings_gpxExportShareText": "Картинни данни изнесени от meshcore-open",
|
||||||
|
"settings_gpxExportShareSubject": "meshcore-open износ на данни за карта в формат GPX",
|
||||||
|
"pathTrace_someHopsNoLocation": "Един или повече от хмелите липсва местоположение!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-1
@@ -1568,5 +1568,50 @@
|
|||||||
"contacts_zeroHopContactAdvertFailed": "Kontakt konnte nicht gesendet werden.",
|
"contacts_zeroHopContactAdvertFailed": "Kontakt konnte nicht gesendet werden.",
|
||||||
"contacts_zeroHopContactAdvertSent": "Kontakt über Anzeige gesendet",
|
"contacts_zeroHopContactAdvertSent": "Kontakt über Anzeige gesendet",
|
||||||
"contacts_contactAdvertCopied": "Anzeige in die Zwischenablage kopiert.",
|
"contacts_contactAdvertCopied": "Anzeige in die Zwischenablage kopiert.",
|
||||||
"contacts_contactAdvertCopyFailed": "Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen."
|
"contacts_contactAdvertCopyFailed": "Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen.",
|
||||||
|
|
||||||
|
"notification_activityTitle": "MeshCore Aktivität",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{Nachricht} other{Nachrichten}}",
|
||||||
|
"@notification_messagesCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {"type": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{Kanalnachricht} other{Kanalnachrichten}}",
|
||||||
|
"@notification_channelMessagesCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {"type": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_newNodesCount": "{count} {count, plural, =1{neuer Knoten} other{neue Knoten}}",
|
||||||
|
"@notification_newNodesCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {"type": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_newTypeDiscovered": "Neuer {contactType} entdeckt",
|
||||||
|
"@notification_newTypeDiscovered": {
|
||||||
|
"placeholders": {
|
||||||
|
"contactType": {"type": "String"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_receivedNewMessage": "Neue Nachricht empfangen",
|
||||||
|
"contacts_contactAdvertCopyFailed": "Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen.",
|
||||||
|
"settings_gpxExportAll": "Alle Kontakte nach GPX exportieren",
|
||||||
|
"settings_gpxExportAllSubtitle": "Exportiert alle Kontakte mit einem Standort in eine GPX-Datei.",
|
||||||
|
"settings_gpxExportRepeaters": "Repeater und Raumserver nach GPX exportieren",
|
||||||
|
"settings_gpxExportContacts": "Begleiter nach GPX exportieren",
|
||||||
|
"settings_gpxExportRepeatersSubtitle": "Exportiert Repeater und Raumserver mit einem Standort in eine GPX-Datei.",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Exportiert Begleiter mit einem Ort in eine GPX-Datei.",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Repeater- und Raumserver-Standorte",
|
||||||
|
"settings_gpxExportChat": "Begleiterstandorte",
|
||||||
|
"settings_gpxExportNoContacts": "Keine Kontakte zum Exportieren.",
|
||||||
|
"settings_gpxExportError": "Beim Export ist ein Fehler aufgetreten.",
|
||||||
|
"settings_gpxExportNotAvailable": "Nicht auf Ihrem Gerät/Betriebssystem unterstützt",
|
||||||
|
"settings_gpxExportSuccess": "Erfolgreich GPX-Datei exportiert.",
|
||||||
|
"settings_gpxExportAllContacts": "Alle Kontaktstandorte",
|
||||||
|
"settings_gpxExportShareSubject": "meshcore-open GPX-Kartendaten exportieren",
|
||||||
|
"settings_gpxExportShareText": "Kartendaten aus meshcore-open exportiert",
|
||||||
|
"pathTrace_someHopsNoLocation": "Eine oder mehrere der Hopfen fehlen einen Standort!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-1
@@ -1317,6 +1317,7 @@
|
|||||||
"pathTrace_failed": "Path trace failed.",
|
"pathTrace_failed": "Path trace failed.",
|
||||||
"pathTrace_notAvailable": "Path trace not available.",
|
"pathTrace_notAvailable": "Path trace not available.",
|
||||||
"pathTrace_refreshTooltip": "Refresh Path Trace.",
|
"pathTrace_refreshTooltip": "Refresh Path Trace.",
|
||||||
|
"pathTrace_someHopsNoLocation": "One or more of the hops is missing a location!",
|
||||||
"contacts_pathTrace": "Path Trace",
|
"contacts_pathTrace": "Path Trace",
|
||||||
"contacts_ping": "Ping",
|
"contacts_ping": "Ping",
|
||||||
"contacts_repeaterPathTrace": "Path trace to repeater",
|
"contacts_repeaterPathTrace": "Path trace to repeater",
|
||||||
@@ -1344,5 +1345,48 @@
|
|||||||
"contacts_zeroHopContactAdvertSent": "Sent contact by advert.",
|
"contacts_zeroHopContactAdvertSent": "Sent contact by advert.",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Failed to send contact.",
|
"contacts_zeroHopContactAdvertFailed": "Failed to send contact.",
|
||||||
"contacts_contactAdvertCopied": "Advert copied to Clipboard.",
|
"contacts_contactAdvertCopied": "Advert copied to Clipboard.",
|
||||||
"contacts_contactAdvertCopyFailed": "Copying advert to Clipboard failed."
|
"contacts_contactAdvertCopyFailed": "Copying advert to Clipboard failed.",
|
||||||
|
|
||||||
|
"notification_activityTitle": "MeshCore Activity",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{message} other{messages}}",
|
||||||
|
"@notification_messagesCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {"type": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{channel message} other{channel messages}}",
|
||||||
|
"@notification_channelMessagesCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {"type": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_newNodesCount": "{count} {count, plural, =1{new node} other{new nodes}}",
|
||||||
|
"@notification_newNodesCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {"type": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_newTypeDiscovered": "New {contactType} discovered",
|
||||||
|
"@notification_newTypeDiscovered": {
|
||||||
|
"placeholders": {
|
||||||
|
"contactType": {"type": "String"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_receivedNewMessage": "Received new message",
|
||||||
|
|
||||||
|
"settings_gpxExportRepeaters": "Export repeaters / room server to GPX",
|
||||||
|
"settings_gpxExportRepeatersSubtitle": "Exports repeaters / roomserver with a location to GPX file.",
|
||||||
|
"settings_gpxExportContacts": "Export companions to GPX",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Exports companions with a location to GPX file.",
|
||||||
|
"settings_gpxExportAll": "Export all contacts to GPX",
|
||||||
|
"settings_gpxExportAllSubtitle": "Exports all contacts with a location to GPX file.",
|
||||||
|
"settings_gpxExportSuccess": "Successfully exported GPX file.",
|
||||||
|
"settings_gpxExportNoContacts": "No contacts to export.",
|
||||||
|
"settings_gpxExportNotAvailable": "Not supported on your device/OS",
|
||||||
|
"settings_gpxExportError": "There was an error when exporting.",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Repeater & room server locations",
|
||||||
|
"settings_gpxExportChat": "Companion locations",
|
||||||
|
"settings_gpxExportAllContacts": "All contacts locations",
|
||||||
|
"settings_gpxExportShareText": "Map data exported from meshcore-open",
|
||||||
|
"settings_gpxExportShareSubject": "meshcore-open GPX map data export"
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-1
@@ -1568,5 +1568,50 @@
|
|||||||
"contacts_zeroHopContactAdvertFailed": "No se pudo enviar el contacto.",
|
"contacts_zeroHopContactAdvertFailed": "No se pudo enviar el contacto.",
|
||||||
"contacts_zeroHopContactAdvertSent": "Envió contacto por anuncio.",
|
"contacts_zeroHopContactAdvertSent": "Envió contacto por anuncio.",
|
||||||
"contacts_contactAdvertCopied": "Anuncio copiado al Portapapeles.",
|
"contacts_contactAdvertCopied": "Anuncio copiado al Portapapeles.",
|
||||||
"contacts_contactAdvertCopyFailed": "Copiar anuncio al Portapapeles ha fallado."
|
"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"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{mensaje de canal} other{mensajes de canal}}",
|
||||||
|
"@notification_channelMessagesCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {"type": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_newNodesCount": "{count} {count, plural, =1{nuevo nodo} other{nuevos nodos}}",
|
||||||
|
"@notification_newNodesCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {"type": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_newTypeDiscovered": "Nuevo {contactType} descubierto",
|
||||||
|
"@notification_newTypeDiscovered": {
|
||||||
|
"placeholders": {
|
||||||
|
"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.",
|
||||||
|
"settings_gpxExportNoContacts": "No hay contactos para exportar.",
|
||||||
|
"settings_gpxExportNotAvailable": "No compatible con tu dispositivo/SO",
|
||||||
|
"settings_gpxExportError": "Hubo un error al exportar.",
|
||||||
|
"settings_gpxExportRepeatersSubtitle": "Exporta repetidores o roomserver con una ubicación a un archivo GPX.",
|
||||||
|
"settings_gpxExportAllSubtitle": "Exporta todos los contactos con una ubicación a un archivo GPX.",
|
||||||
|
"settings_gpxExportAll": "Exportar todos los contactos a GPX",
|
||||||
|
"settings_gpxExportContacts": "Exportar compañeros a GPX",
|
||||||
|
"settings_gpxExportChat": "Ubicaciones de compañero",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Ubicaciones del servidor de repetidor y sala",
|
||||||
|
"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"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_contactAdvertCopied": "Annonce copiée dans le presse-papiers.",
|
"contacts_contactAdvertCopied": "Annonce copiée dans le presse-papiers.",
|
||||||
"contacts_contactAdvertCopyFailed": "La copie de l'annonce vers le presse-papiers a échoué.",
|
"contacts_contactAdvertCopyFailed": "La copie de l'annonce vers le presse-papiers a échoué.",
|
||||||
"contacts_zeroHopContactAdvertSent": "Envoyer un contact par annonce.",
|
"contacts_zeroHopContactAdvertSent": "Envoyer un contact par annonce.",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Échec de l'envoi du contact."
|
"contacts_zeroHopContactAdvertFailed": "Échec de l'envoi du contact.",
|
||||||
|
"notification_activityTitle": "Activité MeshCore",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{message} other{messages}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{message de canal} other{messages de canal}}",
|
||||||
|
"notification_newNodesCount": "{count} {count, plural, =1{nouveau nœud} other{nouveaux nœuds}}",
|
||||||
|
"notification_newTypeDiscovered": "Nouveau {contactType} découvert",
|
||||||
|
"notification_receivedNewMessage": "Nouveau message reçu",
|
||||||
|
"contacts_zeroHopContactAdvertFailed": "Échec de l'envoi du contact.",
|
||||||
|
"settings_gpxExportRepeaters": "Exporter les répéteurs / serveur de salle au format GPX",
|
||||||
|
"settings_gpxExportRepeatersSubtitle": "Exporte les répéteurs / roomserver avec une localisation vers un fichier GPX.",
|
||||||
|
"settings_gpxExportNoContacts": "Aucun contact à exporter.",
|
||||||
|
"settings_gpxExportNotAvailable": "Non pris en charge sur votre appareil/Système d'exploitation",
|
||||||
|
"settings_gpxExportError": "Une erreur s'est produite lors de l'exportation.",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Emplacements des serveurs de répéteur et de salle",
|
||||||
|
"settings_gpxExportContacts": "Exporter les compagnons au format GPX",
|
||||||
|
"settings_gpxExportAll": "Exporter tous les contacts au format GPX",
|
||||||
|
"settings_gpxExportAllSubtitle": "Exporte tous les contacts avec une localisation vers un fichier GPX.",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Exporte les compagnons avec un emplacement vers un fichier GPX.",
|
||||||
|
"settings_gpxExportChat": "Emplacements des compagnons",
|
||||||
|
"settings_gpxExportSuccess": "Fichier GPX exporté avec succès.",
|
||||||
|
"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": "Une ou plusieurs des houblons manquent d'une localisation !"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_contactAdvertCopyFailed": "Copia dell'annuncio nella Clipboard non riuscita.",
|
"contacts_contactAdvertCopyFailed": "Copia dell'annuncio nella Clipboard non riuscita.",
|
||||||
"contacts_ShareContactZeroHop": "Condividi contatto tramite annuncio",
|
"contacts_ShareContactZeroHop": "Condividi contatto tramite annuncio",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Invio del contatto non riuscito.",
|
"contacts_zeroHopContactAdvertFailed": "Invio del contatto non riuscito.",
|
||||||
"contacts_contactAdvertCopied": "Annuncio copiato negli Appunti."
|
"contacts_contactAdvertCopied": "Annuncio copiato negli Appunti.",
|
||||||
|
"notification_activityTitle": "Attività MeshCore",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{messaggio} other{messaggi}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{messaggio del canale} other{messaggi del canale}}",
|
||||||
|
"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.",
|
||||||
|
"settings_gpxExportNoContacts": "Nessun contatto da esportare.",
|
||||||
|
"settings_gpxExportNotAvailable": "Non supportato sul tuo dispositivo/Sistema Operativo",
|
||||||
|
"settings_gpxExportError": "Si è verificato un errore durante l'esportazione.",
|
||||||
|
"settings_gpxExportRepeatersSubtitle": "Esporta ripetitori / roomserver con una posizione in un file GPX.",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Esporta i compagni con una posizione in un file GPX.",
|
||||||
|
"settings_gpxExportAll": "Esporta tutti i contatti in GPX",
|
||||||
|
"settings_gpxExportAllSubtitle": "Esporta tutti i contatti con una posizione in un file GPX.",
|
||||||
|
"settings_gpxExportChat": "Posizioni dei compagni",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Posizioni del server ripetitore e della stanza",
|
||||||
|
"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!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4730,6 +4730,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Refresh Path Trace.'**
|
/// **'Refresh Path Trace.'**
|
||||||
String get pathTrace_refreshTooltip;
|
String get pathTrace_refreshTooltip;
|
||||||
|
|
||||||
|
/// No description provided for @pathTrace_someHopsNoLocation.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'One or more of the hops is missing a location!'**
|
||||||
|
String get pathTrace_someHopsNoLocation;
|
||||||
|
|
||||||
/// No description provided for @contacts_pathTrace.
|
/// No description provided for @contacts_pathTrace.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -4861,6 +4867,132 @@ abstract class AppLocalizations {
|
|||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Copying advert to Clipboard failed.'**
|
/// **'Copying advert to Clipboard failed.'**
|
||||||
String get contacts_contactAdvertCopyFailed;
|
String get contacts_contactAdvertCopyFailed;
|
||||||
|
|
||||||
|
/// No description provided for @notification_activityTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'MeshCore Activity'**
|
||||||
|
String get notification_activityTitle;
|
||||||
|
|
||||||
|
/// No description provided for @notification_messagesCount.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count} {count, plural, =1{message} other{messages}}'**
|
||||||
|
String notification_messagesCount(int count);
|
||||||
|
|
||||||
|
/// No description provided for @notification_channelMessagesCount.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count} {count, plural, =1{channel message} other{channel messages}}'**
|
||||||
|
String notification_channelMessagesCount(int count);
|
||||||
|
|
||||||
|
/// No description provided for @notification_newNodesCount.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count} {count, plural, =1{new node} other{new nodes}}'**
|
||||||
|
String notification_newNodesCount(int count);
|
||||||
|
|
||||||
|
/// No description provided for @notification_newTypeDiscovered.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'New {contactType} discovered'**
|
||||||
|
String notification_newTypeDiscovered(String contactType);
|
||||||
|
|
||||||
|
/// No description provided for @notification_receivedNewMessage.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Received new message'**
|
||||||
|
String get notification_receivedNewMessage;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportRepeaters.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Export repeaters / room server to GPX'**
|
||||||
|
String get settings_gpxExportRepeaters;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportRepeatersSubtitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Exports repeaters / roomserver with a location to GPX file.'**
|
||||||
|
String get settings_gpxExportRepeatersSubtitle;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportContacts.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Export companions to GPX'**
|
||||||
|
String get settings_gpxExportContacts;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportContactsSubtitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Exports companions with a location to GPX file.'**
|
||||||
|
String get settings_gpxExportContactsSubtitle;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportAll.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Export all contacts to GPX'**
|
||||||
|
String get settings_gpxExportAll;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportAllSubtitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Exports all contacts with a location to GPX file.'**
|
||||||
|
String get settings_gpxExportAllSubtitle;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportSuccess.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Successfully exported GPX file.'**
|
||||||
|
String get settings_gpxExportSuccess;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportNoContacts.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No contacts to export.'**
|
||||||
|
String get settings_gpxExportNoContacts;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportNotAvailable.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Not supported on your device/OS'**
|
||||||
|
String get settings_gpxExportNotAvailable;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportError.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'There was an error when exporting.'**
|
||||||
|
String get settings_gpxExportError;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportRepeatersRoom.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Repeater & room server locations'**
|
||||||
|
String get settings_gpxExportRepeatersRoom;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportChat.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Companion locations'**
|
||||||
|
String get settings_gpxExportChat;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportAllContacts.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'All contacts locations'**
|
||||||
|
String get settings_gpxExportAllContacts;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportShareText.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Map data exported from meshcore-open'**
|
||||||
|
String get settings_gpxExportShareText;
|
||||||
|
|
||||||
|
/// No description provided for @settings_gpxExportShareSubject.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'meshcore-open GPX map data export'**
|
||||||
|
String get settings_gpxExportShareSubject;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -2698,6 +2698,10 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Обнови Path Trace.';
|
String get pathTrace_refreshTooltip => 'Обнови Path Trace.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Един или повече от хмелите липсва местоположение!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Пътен проследяване';
|
String get contacts_pathTrace => 'Пътен проследяване';
|
||||||
|
|
||||||
@@ -2769,4 +2773,102 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Копирането на обявата в клипборда не успя.';
|
'Копирането на обявата в клипборда не успя.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Активност на MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'съобщения',
|
||||||
|
one: 'съобщение',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'съобщения в канали',
|
||||||
|
one: 'съобщение в канал',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'нови възли',
|
||||||
|
one: 'нов възел',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Открит нов $contactType';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Получено ново съобщение';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Експортиране на повтарящи се устройства / сървър на стаята до GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Изпраща повторители / roomserver с местоположение в GPX файл.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Експортирай спътници към GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Експортира спътници с местоположение в GPX файл.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Експортирай всички контакти в GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Експортира всички контакти с местоположение в файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Успешно изlexport на файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Няма контакти за изlexport.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Не е поддържан на вашето устройство/ОС';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'Възникна грешка при изнасяне.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Местоположения на повторител и сървър на стаята';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Местоположения на спътници';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts =>
|
||||||
|
'Местоположения на всички контакти';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Картинни данни изнесени от meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open износ на данни за карта в формат GPX';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2702,6 +2702,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Path Trace aktualisieren.';
|
String get pathTrace_refreshTooltip => 'Path Trace aktualisieren.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Eine oder mehrere der Hopfen fehlen einen Standort!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Pfadverfolgung';
|
String get contacts_pathTrace => 'Pfadverfolgung';
|
||||||
|
|
||||||
@@ -2776,4 +2780,102 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen.';
|
'Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'MeshCore Aktivität';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'Nachrichten',
|
||||||
|
one: 'Nachricht',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'Kanalnachrichten',
|
||||||
|
one: 'Kanalnachricht',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'neue Knoten',
|
||||||
|
one: 'neuer Knoten',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Neuer $contactType entdeckt';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Neue Nachricht empfangen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Repeater und Raumserver nach GPX exportieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Exportiert Repeater und Raumserver mit einem Standort in eine GPX-Datei.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Begleiter nach GPX exportieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Exportiert Begleiter mit einem Ort in eine GPX-Datei.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Alle Kontakte nach GPX exportieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Exportiert alle Kontakte mit einem Standort in eine GPX-Datei.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Erfolgreich GPX-Datei exportiert.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Keine Kontakte zum Exportieren.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Nicht auf Ihrem Gerät/Betriebssystem unterstützt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError =>
|
||||||
|
'Beim Export ist ein Fehler aufgetreten.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Repeater- und Raumserver-Standorte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Begleiterstandorte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Alle Kontaktstandorte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Kartendaten aus meshcore-open exportiert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open GPX-Kartendaten exportieren';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2658,6 +2658,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Refresh Path Trace.';
|
String get pathTrace_refreshTooltip => 'Refresh Path Trace.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'One or more of the hops is missing a location!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Path Trace';
|
String get contacts_pathTrace => 'Path Trace';
|
||||||
|
|
||||||
@@ -2726,4 +2730,101 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Copying advert to Clipboard failed.';
|
'Copying advert to Clipboard failed.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'MeshCore Activity';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'messages',
|
||||||
|
one: 'message',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'channel messages',
|
||||||
|
one: 'channel message',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'new nodes',
|
||||||
|
one: 'new node',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'New $contactType discovered';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Received new message';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Export repeaters / room server to GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Exports repeaters / roomserver with a location to GPX file.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Export companions to GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Exports companions with a location to GPX file.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Export all contacts to GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Exports all contacts with a location to GPX file.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Successfully exported GPX file.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'No contacts to export.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Not supported on your device/OS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'There was an error when exporting.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Repeater & room server locations';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Companion locations';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'All contacts locations';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Map data exported from meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open GPX map data export';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2697,6 +2697,10 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Actualizar Path Trace';
|
String get pathTrace_refreshTooltip => 'Actualizar Path Trace';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Uno o más de los lúpulos carecen de una ubicación';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Rastreo de caminos';
|
String get contacts_pathTrace => 'Rastreo de caminos';
|
||||||
|
|
||||||
@@ -2769,4 +2773,102 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Copiar anuncio al Portapapeles ha fallado.';
|
'Copiar anuncio al Portapapeles ha fallado.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Actividad de MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'mensajes',
|
||||||
|
one: 'mensaje',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'mensajes de canal',
|
||||||
|
one: 'mensaje de canal',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'nuevos nodos',
|
||||||
|
one: 'nuevo nodo',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Nuevo $contactType descubierto';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Nuevo mensaje recibido';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Exportar repetidores / servidor de sala a GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Exporta repetidores o roomserver con una ubicación a un archivo GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Exportar compañeros a GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Exporta compañeros con una ubicación a archivo GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Exportar todos los contactos a GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Exporta todos los contactos con una ubicación a un archivo GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Archivo GPX exportado con éxito.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'No hay contactos para exportar.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'No compatible con tu dispositivo/SO';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'Hubo un error al exportar.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Ubicaciones del servidor de repetidor y sala';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Ubicaciones de compañero';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts =>
|
||||||
|
'Todas las ubicaciones de contactos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Datos del mapa exportados desde meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open exportación de datos de mapa GPX';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2714,6 +2714,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Actualiser Path Trace';
|
String get pathTrace_refreshTooltip => 'Actualiser Path Trace';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Une ou plusieurs des houblons manquent d\'une localisation !';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Traçage de chemin';
|
String get contacts_pathTrace => 'Traçage de chemin';
|
||||||
|
|
||||||
@@ -2790,4 +2794,105 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'La copie de l\'annonce vers le presse-papiers a échoué.';
|
'La copie de l\'annonce vers le presse-papiers a échoué.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Activité MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'messages',
|
||||||
|
one: 'message',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'messages de canal',
|
||||||
|
one: 'message de canal',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'nouveaux nœuds',
|
||||||
|
one: 'nouveau nœud',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Nouveau $contactType découvert';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Nouveau message reçu';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Exporter les répéteurs / serveur de salle au format GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Exporte les répéteurs / roomserver avec une localisation vers un fichier GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts =>
|
||||||
|
'Exporter les compagnons au format GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Exporte les compagnons avec un emplacement vers un fichier GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll =>
|
||||||
|
'Exporter tous les contacts au format GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Exporte tous les contacts avec une localisation vers un fichier GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Fichier GPX exporté avec succès.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Aucun contact à exporter.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Non pris en charge sur votre appareil/Système d\'exploitation';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError =>
|
||||||
|
'Une erreur s\'est produite lors de l\'exportation.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Emplacements des serveurs de répéteur et de salle';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Emplacements des compagnons';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts =>
|
||||||
|
'Tous les emplacements des contacts';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Données de carte exportées à partir de meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open exporter les données de carte GPX';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2698,6 +2698,10 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Aggiorna Path Trace.';
|
String get pathTrace_refreshTooltip => 'Aggiorna Path Trace.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Uno o più dei luppoli mancano di una posizione!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Traccia Percorso';
|
String get contacts_pathTrace => 'Traccia Percorso';
|
||||||
|
|
||||||
@@ -2772,4 +2776,103 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Copia dell\'annuncio nella Clipboard non riuscita.';
|
'Copia dell\'annuncio nella Clipboard non riuscita.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Attività MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'messaggi',
|
||||||
|
one: 'messaggio',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'messaggi del canale',
|
||||||
|
one: 'messaggio del canale',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'nuovi nodi',
|
||||||
|
one: 'nuovo nodo',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Nuovo $contactType scoperto';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Nuovo messaggio ricevuto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Esporta ripetitori / server di stanza in GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Esporta ripetitori / roomserver con una posizione in un file GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Esporta compagni in GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Esporta i compagni con una posizione in un file GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Esporta tutti i contatti in GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Esporta tutti i contatti con una posizione in un file GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess =>
|
||||||
|
'Esportazione del file GPX completata con successo.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Nessun contatto da esportare.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Non supportato sul tuo dispositivo/Sistema Operativo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError =>
|
||||||
|
'Si è verificato un errore durante l\'esportazione.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Posizioni del server ripetitore e della stanza';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Posizioni dei compagni';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Tutte le posizioni dei contatti';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Dati mappa esportati da meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open esportazione dati mappa GPX';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2688,6 +2688,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Path Trace vernieuwen.';
|
String get pathTrace_refreshTooltip => 'Path Trace vernieuwen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Een of meer van de hops ontbreken een locatie!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Pad Traceren';
|
String get contacts_pathTrace => 'Pad Traceren';
|
||||||
|
|
||||||
@@ -2761,4 +2765,101 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Kopiëren van advertentie naar Clipboard is mislukt.';
|
'Kopiëren van advertentie naar Clipboard is mislukt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'MeshCore Activiteit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'berichten',
|
||||||
|
one: 'bericht',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'kanaalberichten',
|
||||||
|
one: 'kanaalbericht',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'nieuwe knooppunten',
|
||||||
|
one: 'nieuw knooppunt',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Nieuw $contactType ontdekt';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Nieuw bericht ontvangen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Exporteer repeaters / roomserver naar GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Exporteert repeaters / roomserver met een locatie naar GPX-bestand.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Companions exporteren naar GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Exporteert metgezellen met een locatie naar een GPX-bestand.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Alle contacten exporteren naar GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Exporteert alle contacten met een locatie naar een GPX-bestand.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Succesvol GPX-bestand geëxporteerd.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Geen contacten om te exporteren.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Niet ondersteund op uw apparaat/besturingssysteem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'Er was een fout bij het exporteren.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Repeater- en kamer servers locaties';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Locaties van metgezellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Alle contactlocaties';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Kaartgegevens geëxporteerd uit meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open GPX kaartgegevens exporteren';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2696,6 +2696,10 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Odśwież ścieżkę.';
|
String get pathTrace_refreshTooltip => 'Odśwież ścieżkę.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Jeden lub więcej z chmieli nie ma określonej lokalizacji!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Śledzenie Ścieżek';
|
String get contacts_pathTrace => 'Śledzenie Ścieżek';
|
||||||
|
|
||||||
@@ -2769,4 +2773,108 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Kopiowanie ogłoszenia do schowka nie powiodło się.';
|
'Kopiowanie ogłoszenia do schowka nie powiodło się.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Aktywność MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'wiadomości',
|
||||||
|
many: 'wiadomości',
|
||||||
|
few: 'wiadomości',
|
||||||
|
one: 'wiadomość',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'wiadomości kanału',
|
||||||
|
many: 'wiadomości kanału',
|
||||||
|
few: 'wiadomości kanału',
|
||||||
|
one: 'wiadomość kanału',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'nowych węzłów',
|
||||||
|
many: 'nowych węzłów',
|
||||||
|
few: 'nowe węzły',
|
||||||
|
one: 'nowy węzeł',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Nowy $contactType wykryty';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Otrzymano nową wiadomość';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Eksportuj powtórki / serwer pokojowy do GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Eksportuje powtarzacze / roomserver z lokalizacją do pliku GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Eksportuj towarzyszy do GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Eksportuje towarzyszy z lokalizacją do pliku GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Eksportuj wszystkie kontakty do GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Eksportuje wszystkie kontakty z lokalizacją do pliku GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Pomyślnie wyeksportowano plik GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts =>
|
||||||
|
'Brak kontaktów do wyeksportowania.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Nie obsługiwane na Twoim urządzeniu/systemie operacyjnym';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'Wystąpił błąd podczas eksportowania.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Lokalizacje serwerów powtarzających i pomieszczeń';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Lokalizacje towarzyszy';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Wszystkie lokalizacje kontaktów';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Dane mapy wyeksportowane z meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'Eksport danych mapy GPX meshcore-open';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2699,6 +2699,10 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Atualizar Path Trace.';
|
String get pathTrace_refreshTooltip => 'Atualizar Path Trace.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Um ou mais dos lúpulos estão sem localização!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Traçado de Caminho';
|
String get contacts_pathTrace => 'Traçado de Caminho';
|
||||||
|
|
||||||
@@ -2771,4 +2775,101 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Cópia do anúncio para a Área de Transferência falhou.';
|
'Cópia do anúncio para a Área de Transferência falhou.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Atividade MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'mensagens',
|
||||||
|
one: 'mensagem',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'mensagens de canal',
|
||||||
|
one: 'mensagem de canal',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'novos nós',
|
||||||
|
one: 'novo nó',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Novo $contactType descoberto';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Nova mensagem recebida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Exportar repetidores / servidor de sala para GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Exporta repetidores / roomserver com localização para arquivo GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Exportar companheiros para GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Exporta companheiros com uma localização para um arquivo GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Exportar todos os contatos para GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Exporta todos os contatos com uma localização para um arquivo GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Arquivo GPX exportado com sucesso.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Nenhum contato para exportar.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Não suportado no seu dispositivo/SO';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'Ocorreu um erro ao exportar.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Localizações do servidor de repetidor e sala';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Localizações de companheiros';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Todos os locais de contatos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Dados do mapa exportados do meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open exportação de dados de mapa GPX';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2701,6 +2701,10 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Обновить Path Trace';
|
String get pathTrace_refreshTooltip => 'Обновить Path Trace';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Одному или нескольким хмелям не указано местоположение!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Трассировка пути';
|
String get contacts_pathTrace => 'Трассировка пути';
|
||||||
|
|
||||||
@@ -2776,4 +2780,107 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Копирование рекламы в буфер обмена не удалось.';
|
'Копирование рекламы в буфер обмена не удалось.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Активность MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'сообщений',
|
||||||
|
many: 'сообщений',
|
||||||
|
few: 'сообщения',
|
||||||
|
one: 'сообщение',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'сообщений канала',
|
||||||
|
many: 'сообщений канала',
|
||||||
|
few: 'сообщения канала',
|
||||||
|
one: 'сообщение канала',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'новых узлов',
|
||||||
|
many: 'новых узлов',
|
||||||
|
few: 'новых узла',
|
||||||
|
one: 'новый узел',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Обнаружен новый $contactType';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Получено новое сообщение';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Экспортировать рипитеры / сервер комнаты в GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Экспортирует ретрансляторы / сервер комнат с местоположением в файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Экспортировать спутников в GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Экспортирует спутников с местоположением в файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Экспортировать все контакты в GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Экспортирует все контакты с местоположением в файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Успешно экспортирован файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Нет контактов для экспорта.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Не поддерживается на вашем устройстве/ОС';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'Произошла ошибка при экспорте.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Местоположения повторителей и серверов комнат';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Местоположения спутников';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Все местоположения контактов';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Данные карты экспортированы из meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open экспорт данных карты GPX';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2684,6 +2684,10 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Obnoviť Path Trace.';
|
String get pathTrace_refreshTooltip => 'Obnoviť Path Trace.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Jedna alebo viac chmeľov chýba lokalita!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Sledovanie lúčov';
|
String get contacts_pathTrace => 'Sledovanie lúčov';
|
||||||
|
|
||||||
@@ -2755,4 +2759,104 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Kopírovanie inzerátu do schránky zlyhalo.';
|
'Kopírovanie inzerátu do schránky zlyhalo.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Aktivita MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'správ',
|
||||||
|
few: 'správy',
|
||||||
|
one: 'správa',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'správ kanálu',
|
||||||
|
few: 'správy kanálu',
|
||||||
|
one: 'správa kanálu',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'nových uzlov',
|
||||||
|
few: 'nové uzly',
|
||||||
|
one: 'nový uzol',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Nový $contactType objavený';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Prijatá nová správa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Exportovať repeater / server miestnosti do GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Exportuje repeater / roomserver s lokalitou do súboru GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Export sprievodcov do GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Exportuje sprievodcov s umiestnením do súboru GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Exportovať všetky kontakty do GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Exportuje všetky kontakty s lokalitou do súboru GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Úspešne exportovaný súbor GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Žiadne kontakty na export.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Nie je podporované na vašom zariadení/operáciomnom systéme';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'Vyskytol sa chyba počas exportu.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Umiestnenia opakovačov a serverov miestností';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Lokácie sprievodcov';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Všetky kontaktné lokality';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Mapové údaje exportované z meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open export dát GPX mapových údajov';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2687,6 +2687,10 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Osveži Path Trace.';
|
String get pathTrace_refreshTooltip => 'Osveži Path Trace.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Ena ali več hmelju manjka lokacija!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Sledenje poti';
|
String get contacts_pathTrace => 'Sledenje poti';
|
||||||
|
|
||||||
@@ -2757,4 +2761,107 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Kopiranje oglasa v odložišče je spodletelo.';
|
'Kopiranje oglasa v odložišče je spodletelo.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Aktivnost MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'sporočil',
|
||||||
|
few: 'sporočila',
|
||||||
|
two: 'sporočili',
|
||||||
|
one: 'sporočilo',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'sporočil kanala',
|
||||||
|
few: 'sporočila kanala',
|
||||||
|
two: 'sporočili kanala',
|
||||||
|
one: 'sporočilo kanala',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'novih vozlišč',
|
||||||
|
few: 'nova vozlišča',
|
||||||
|
two: 'novi vozlišči',
|
||||||
|
one: 'novo vozlišče',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Odkrito novo $contactType';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Prejeto novo sporočilo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Izvoz ponoviteljev / strežnika sobe v GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Izvozi ponovljene oddajnike / strežnik sobe z lokacijo v datoteko GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Izvoz spremljevalcev v GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Izvozi spremljevalce z lokacijo v datoteko GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Izvozi vse kontakte v GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Izvozi vse kontakte z lokacijo v datoteko GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Uspešno izvoz GPX datoteke.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Ni stikov za izvoz.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Ni podprto na vašem napravi/operacijskem sistemu';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'Pri izvozu je prišlo do napake.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Lokacije ponovljivca in strežnika sobe';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Lokacije spremljevalcev';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Lokacije vseh stikov';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Podatki kart izvoženi iz meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open izvoz podatkov GPX karte';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2672,6 +2672,10 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Uppdatera Path Trace';
|
String get pathTrace_refreshTooltip => 'Uppdatera Path Trace';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'En eller flera av humlen saknar en plats!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Path Trace';
|
String get contacts_pathTrace => 'Path Trace';
|
||||||
|
|
||||||
@@ -2742,4 +2746,102 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Kopiering av annons till Urklipp misslyckades.';
|
'Kopiering av annons till Urklipp misslyckades.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'MeshCore Aktivitet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'meddelanden',
|
||||||
|
one: 'meddelande',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'kanalmeddelanden',
|
||||||
|
one: 'kanalmeddelande',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'nya noder',
|
||||||
|
one: 'ny nod',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Ny $contactType upptäckt';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Nytt meddelande mottaget';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Exportera repeater / rumsservrar till GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Exporterar repeater / roomserver med plats till GPX-fil.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Exportera följeslagare till GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Exporterar följeslagare med en plats till GPX-fil.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Exportera alla kontakter till GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Exporterar alla kontakter med en plats till GPX-fil.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Har exporterat GPX-fil med framgång';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Inga kontakter att exportera.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Stöds inte på din enhet/operativsystem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError =>
|
||||||
|
'Det uppstod ett fel när data exporterades.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Repeater- och rumsserverplatser';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Medhjälparplatser';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Alla kontakters platser';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Kartdata exporterad från meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'meshcore-open export av GPX-kartdata';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2708,6 +2708,10 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => 'Оновити трасування шляху';
|
String get pathTrace_refreshTooltip => 'Оновити трасування шляху';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation =>
|
||||||
|
'Одне або більше хмелів відсутнє місце розташування!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => 'Трасування шляхів';
|
String get contacts_pathTrace => 'Трасування шляхів';
|
||||||
|
|
||||||
@@ -2782,4 +2786,107 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed =>
|
String get contacts_contactAdvertCopyFailed =>
|
||||||
'Копіювання оголошення в буфер обміну завершилося невдало';
|
'Копіювання оголошення в буфер обміну завершилося невдало';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'Активність MeshCore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'повідомлень',
|
||||||
|
many: 'повідомлень',
|
||||||
|
few: 'повідомлення',
|
||||||
|
one: 'повідомлення',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'повідомлень каналу',
|
||||||
|
many: 'повідомлень каналу',
|
||||||
|
few: 'повідомлення каналу',
|
||||||
|
one: 'повідомлення каналу',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'нових вузлів',
|
||||||
|
many: 'нових вузлів',
|
||||||
|
few: 'нових вузли',
|
||||||
|
one: 'новий вузол',
|
||||||
|
);
|
||||||
|
return '$count $_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return 'Виявлено новий $contactType';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => 'Отримано нове повідомлення';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters =>
|
||||||
|
'Експортувати ретранслятори / сервер кімнати до GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle =>
|
||||||
|
'Експортує ретранслятори / сервер кімнати з місцезнаходженням у файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => 'Експортувати супутників до GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle =>
|
||||||
|
'Експортує супутників з місцезнаходженням у файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => 'Експортувати всі контакти до GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle =>
|
||||||
|
'Експортує всі контакти з місцем розташування у файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => 'Успішно експортовано файл GPX.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => 'Немає контактів для експорту.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable =>
|
||||||
|
'Не підтримується на вашому пристрої/операційній системі';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => 'Сталася помилка під час експорту.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom =>
|
||||||
|
'Місцезнаходження повторювача та сервера кімнати';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => 'Місця супутників';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => 'Усі місця контактів';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText =>
|
||||||
|
'Дані карти експортовані з meshcore-open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject =>
|
||||||
|
'експорт даних карти meshcore-open у форматі GPX';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2557,6 +2557,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get pathTrace_refreshTooltip => '重新绘制路径。';
|
String get pathTrace_refreshTooltip => '重新绘制路径。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pathTrace_someHopsNoLocation => '其中一个或多个啤酒花缺少位置!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_pathTrace => '路径追踪';
|
String get contacts_pathTrace => '路径追踪';
|
||||||
|
|
||||||
@@ -2624,4 +2627,75 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get contacts_contactAdvertCopyFailed => '将广告复制到剪贴板操作失败。';
|
String get contacts_contactAdvertCopyFailed => '将广告复制到剪贴板操作失败。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_activityTitle => 'MeshCore 活动';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_messagesCount(int count) {
|
||||||
|
return '$count 条消息';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_channelMessagesCount(int count) {
|
||||||
|
return '$count 条频道消息';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newNodesCount(int count) {
|
||||||
|
return '$count 个新节点';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String notification_newTypeDiscovered(String contactType) {
|
||||||
|
return '发现新 $contactType';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notification_receivedNewMessage => '收到新消息';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeaters => '导出重复器/房间服务器到GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersSubtitle => '导出带有位置的重复器/房间服务器到GPX文件。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContacts => '导出伴侣到GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportContactsSubtitle => '导出带有位置的伙伴到GPX文件。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAll => '导出所有联系人到GPX';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllSubtitle => '导出所有带有位置的联系人到GPX文件。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportSuccess => '成功导出GPX文件';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNoContacts => '没有联系人可导出';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportNotAvailable => '您的设备/操作系统不支持';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportError => '导出时发生错误';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportRepeatersRoom => '重复器和房间服务器位置';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportChat => '伴侣位置';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportAllContacts => '所有联系人位置';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareText => '来自meshcore-open的导出地图数据';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings_gpxExportShareSubject => 'meshcore-open GPX 地图数据导出';
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_contactAdvertCopyFailed": "Kopiëren van advertentie naar Clipboard is mislukt.",
|
"contacts_contactAdvertCopyFailed": "Kopiëren van advertentie naar Clipboard is mislukt.",
|
||||||
"contacts_ShareContact": "Kontakt naar Klembord kopiëren",
|
"contacts_ShareContact": "Kontakt naar Klembord kopiëren",
|
||||||
"contacts_ShareContactZeroHop": "Contact delen via advertentie",
|
"contacts_ShareContactZeroHop": "Contact delen via advertentie",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Mislukt om contact te verzenden"
|
"contacts_zeroHopContactAdvertFailed": "Mislukt om contact te verzenden",
|
||||||
|
"notification_activityTitle": "MeshCore Activiteit",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{bericht} other{berichten}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{kanaalbericht} other{kanaalberichten}}",
|
||||||
|
"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.",
|
||||||
|
"settings_gpxExportNoContacts": "Geen contacten om te exporteren.",
|
||||||
|
"settings_gpxExportNotAvailable": "Niet ondersteund op uw apparaat/besturingssysteem",
|
||||||
|
"settings_gpxExportError": "Er was een fout bij het exporteren.",
|
||||||
|
"settings_gpxExportContacts": "Companions exporteren naar GPX",
|
||||||
|
"settings_gpxExportAll": "Alle contacten exporteren naar GPX",
|
||||||
|
"settings_gpxExportAllSubtitle": "Exporteert alle contacten met een locatie naar een GPX-bestand.",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Exporteert metgezellen met een locatie naar een GPX-bestand.",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Repeater- en kamer servers locaties",
|
||||||
|
"settings_gpxExportChat": "Locaties van metgezellen",
|
||||||
|
"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!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_contactAdvertCopyFailed": "Kopiowanie ogłoszenia do schowka nie powiodło się.",
|
"contacts_contactAdvertCopyFailed": "Kopiowanie ogłoszenia do schowka nie powiodło się.",
|
||||||
"contacts_ShareContactZeroHop": "Udostępnij kontakt przez ogłoszenie",
|
"contacts_ShareContactZeroHop": "Udostępnij kontakt przez ogłoszenie",
|
||||||
"contacts_ShareContact": "Kopiuj kontakt do schowka",
|
"contacts_ShareContact": "Kopiuj kontakt do schowka",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Nie udało się wysłać kontaktu."
|
"contacts_zeroHopContactAdvertFailed": "Nie udało się wysłać kontaktu.",
|
||||||
|
"notification_activityTitle": "Aktywność MeshCore",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{wiadomość} few{wiadomości} many{wiadomości} other{wiadomości}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{wiadomość kanału} few{wiadomości kanału} many{wiadomości kanału} other{wiadomości kanału}}",
|
||||||
|
"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.",
|
||||||
|
"settings_gpxExportSuccess": "Pomyślnie wyeksportowano plik GPX.",
|
||||||
|
"settings_gpxExportNotAvailable": "Nie obsługiwane na Twoim urządzeniu/systemie operacyjnym",
|
||||||
|
"settings_gpxExportError": "Wystąpił błąd podczas eksportowania.",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Lokalizacje serwerów powtarzających i pomieszczeń",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Eksportuje towarzyszy z lokalizacją do pliku GPX.",
|
||||||
|
"settings_gpxExportAll": "Eksportuj wszystkie kontakty do GPX",
|
||||||
|
"settings_gpxExportAllSubtitle": "Eksportuje wszystkie kontakty z lokalizacją do pliku GPX.",
|
||||||
|
"settings_gpxExportAllContacts": "Wszystkie lokalizacje kontaktów",
|
||||||
|
"settings_gpxExportNoContacts": "Brak kontaktów do wyeksportowania.",
|
||||||
|
"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!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_floodAdvert": "Anúncio de Inundação",
|
"contacts_floodAdvert": "Anúncio de Inundação",
|
||||||
"contacts_contactAdvertCopyFailed": "Cópia do anúncio para a Área de Transferência falhou.",
|
"contacts_contactAdvertCopyFailed": "Cópia do anúncio para a Área de Transferência falhou.",
|
||||||
"contacts_ShareContactZeroHop": "Compartilhar contato por anúncio",
|
"contacts_ShareContactZeroHop": "Compartilhar contato por anúncio",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Falha ao enviar contato."
|
"contacts_zeroHopContactAdvertFailed": "Falha ao enviar contato.",
|
||||||
|
"notification_activityTitle": "Atividade MeshCore",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{mensagem} other{mensagens}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{mensagem de canal} other{mensagens de canal}}",
|
||||||
|
"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.",
|
||||||
|
"settings_gpxExportAllSubtitle": "Exporta todos os contatos com uma localização para um arquivo GPX.",
|
||||||
|
"settings_gpxExportNotAvailable": "Não suportado no seu dispositivo/SO",
|
||||||
|
"settings_gpxExportError": "Ocorreu um erro ao exportar.",
|
||||||
|
"settings_gpxExportAll": "Exportar todos os contatos para GPX",
|
||||||
|
"settings_gpxExportContacts": "Exportar companheiros para GPX",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Exporta companheiros com uma localização para um arquivo GPX.",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Localizações do servidor de repetidor e sala",
|
||||||
|
"settings_gpxExportChat": "Localizações de companheiros",
|
||||||
|
"settings_gpxExportNoContacts": "Nenhum contato para exportar.",
|
||||||
|
"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!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -808,5 +808,29 @@
|
|||||||
"contacts_contactAdvertCopyFailed": "Копирование рекламы в буфер обмена не удалось.",
|
"contacts_contactAdvertCopyFailed": "Копирование рекламы в буфер обмена не удалось.",
|
||||||
"contacts_addContactFromClipboard": "Добавить контакт из буфера обмена",
|
"contacts_addContactFromClipboard": "Добавить контакт из буфера обмена",
|
||||||
"contacts_ShareContactZeroHop": "Поделиться контактом по объявлению",
|
"contacts_ShareContactZeroHop": "Поделиться контактом по объявлению",
|
||||||
"contacts_zeroHopContactAdvertSent": "Отправлено сообщение по объявлению."
|
"contacts_zeroHopContactAdvertSent": "Отправлено сообщение по объявлению.",
|
||||||
|
"notification_activityTitle": "Активность MeshCore",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{сообщение} few{сообщения} many{сообщений} other{сообщений}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{сообщение канала} few{сообщения канала} many{сообщений канала} other{сообщений канала}}",
|
||||||
|
"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",
|
||||||
|
"settings_gpxExportNotAvailable": "Не поддерживается на вашем устройстве/ОС",
|
||||||
|
"settings_gpxExportError": "Произошла ошибка при экспорте.",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Местоположения повторителей и серверов комнат",
|
||||||
|
"settings_gpxExportChat": "Местоположения спутников",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Экспортирует спутников с местоположением в файл GPX.",
|
||||||
|
"settings_gpxExportAll": "Экспортировать все контакты в GPX",
|
||||||
|
"settings_gpxExportAllSubtitle": "Экспортирует все контакты с местоположением в файл GPX.",
|
||||||
|
"settings_gpxExportAllContacts": "Все местоположения контактов",
|
||||||
|
"settings_gpxExportSuccess": "Успешно экспортирован файл GPX.",
|
||||||
|
"settings_gpxExportNoContacts": "Нет контактов для экспорта.",
|
||||||
|
"settings_gpxExportShareText": "Данные карты экспортированы из meshcore-open",
|
||||||
|
"settings_gpxExportShareSubject": "meshcore-open экспорт данных карты GPX",
|
||||||
|
"pathTrace_someHopsNoLocation": "Одному или нескольким хмелям не указано местоположение!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_contactAdvertCopyFailed": "Kopírovanie inzerátu do schránky zlyhalo.",
|
"contacts_contactAdvertCopyFailed": "Kopírovanie inzerátu do schránky zlyhalo.",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Zlyhalo odoslanie kontaktu.",
|
"contacts_zeroHopContactAdvertFailed": "Zlyhalo odoslanie kontaktu.",
|
||||||
"contacts_ShareContactZeroHop": "Zdieľať kontakt cez inzerát",
|
"contacts_ShareContactZeroHop": "Zdieľať kontakt cez inzerát",
|
||||||
"contacts_ShareContact": "Kopírovať kontakt do schránky"
|
"contacts_ShareContact": "Kopírovať kontakt do schránky",
|
||||||
|
"notification_activityTitle": "Aktivita MeshCore",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{správa} few{správy} other{správ}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{správa kanálu} few{správy kanálu} other{správ kanálu}}",
|
||||||
|
"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.",
|
||||||
|
"settings_gpxExportNoContacts": "Žiadne kontakty na export.",
|
||||||
|
"settings_gpxExportNotAvailable": "Nie je podporované na vašom zariadení/operáciomnom systéme",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Umiestnenia opakovačov a serverov miestností",
|
||||||
|
"settings_gpxExportError": "Vyskytol sa chyba počas exportu.",
|
||||||
|
"settings_gpxExportAllSubtitle": "Exportuje všetky kontakty s lokalitou do súboru GPX.",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Exportuje sprievodcov s umiestnením do súboru GPX.",
|
||||||
|
"settings_gpxExportRepeaters": "Exportovať repeater / server miestnosti do GPX",
|
||||||
|
"settings_gpxExportAll": "Exportovať všetky kontakty do GPX",
|
||||||
|
"settings_gpxExportAllContacts": "Všetky kontaktné lokality",
|
||||||
|
"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!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_contactAdvertCopied": "Oglas je bil kopiran v odložišče.",
|
"contacts_contactAdvertCopied": "Oglas je bil kopiran v odložišče.",
|
||||||
"contacts_contactAdvertCopyFailed": "Kopiranje oglasa v odložišče je spodletelo.",
|
"contacts_contactAdvertCopyFailed": "Kopiranje oglasa v odložišče je spodletelo.",
|
||||||
"contacts_ShareContactZeroHop": "Deliti kontakt prek oglasa",
|
"contacts_ShareContactZeroHop": "Deliti kontakt prek oglasa",
|
||||||
"contacts_ShareContact": "Kopiraj stik v Odložišče"
|
"contacts_ShareContact": "Kopiraj stik v Odložišče",
|
||||||
|
"notification_activityTitle": "Aktivnost MeshCore",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{sporočilo} =2{sporočili} few{sporočila} other{sporočil}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{sporočilo kanala} =2{sporočili kanala} few{sporočila kanala} other{sporočil kanala}}",
|
||||||
|
"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.",
|
||||||
|
"settings_gpxExportRepeaters": "Izvoz ponoviteljev / strežnika sobe v GPX",
|
||||||
|
"settings_gpxExportError": "Pri izvozu je prišlo do napake.",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Lokacije ponovljivca in strežnika sobe",
|
||||||
|
"settings_gpxExportChat": "Lokacije spremljevalcev",
|
||||||
|
"settings_gpxExportAllContacts": "Lokacije vseh stikov",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Izvozi spremljevalce z lokacijo v datoteko GPX.",
|
||||||
|
"settings_gpxExportAllSubtitle": "Izvozi vse kontakte z lokacijo v datoteko GPX.",
|
||||||
|
"settings_gpxExportSuccess": "Uspešno izvoz GPX datoteke.",
|
||||||
|
"settings_gpxExportShareText": "Podatki kart izvoženi iz meshcore-open",
|
||||||
|
"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!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_contactAdvertCopyFailed": "Kopiering av annons till Urklipp misslyckades.",
|
"contacts_contactAdvertCopyFailed": "Kopiering av annons till Urklipp misslyckades.",
|
||||||
"contacts_ShareContact": "Kopiera kontakt till Urklipp",
|
"contacts_ShareContact": "Kopiera kontakt till Urklipp",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Misslyckades med att skicka kontakt.",
|
"contacts_zeroHopContactAdvertFailed": "Misslyckades med att skicka kontakt.",
|
||||||
"contacts_ShareContactZeroHop": "Dela kontakt via annons"
|
"contacts_ShareContactZeroHop": "Dela kontakt via annons",
|
||||||
|
"notification_activityTitle": "MeshCore Aktivitet",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{meddelande} other{meddelanden}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{kanalmeddelande} other{kanalmeddelanden}}",
|
||||||
|
"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",
|
||||||
|
"settings_gpxExportNoContacts": "Inga kontakter att exportera.",
|
||||||
|
"settings_gpxExportNotAvailable": "Stöds inte på din enhet/operativsystem",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Repeater- och rumsserverplatser",
|
||||||
|
"settings_gpxExportRepeaters": "Exportera repeater / rumsservrar till GPX",
|
||||||
|
"settings_gpxExportAllSubtitle": "Exporterar alla kontakter med en plats till GPX-fil.",
|
||||||
|
"settings_gpxExportContacts": "Exportera följeslagare till GPX",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Exporterar följeslagare med en plats till GPX-fil.",
|
||||||
|
"settings_gpxExportChat": "Medhjälparplatser",
|
||||||
|
"settings_gpxExportError": "Det uppstod ett fel när data exporterades.",
|
||||||
|
"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!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1569,5 +1569,29 @@
|
|||||||
"contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало",
|
"contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало",
|
||||||
"contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням",
|
"contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням",
|
||||||
"contacts_addContactFromClipboard": "Додати контакт з буфера обміну",
|
"contacts_addContactFromClipboard": "Додати контакт з буфера обміну",
|
||||||
"contacts_ShareContactZeroHop": "Поділитися контактом за оголошенням"
|
"contacts_ShareContactZeroHop": "Поділитися контактом за оголошенням",
|
||||||
|
"notification_activityTitle": "Активність MeshCore",
|
||||||
|
"notification_messagesCount": "{count} {count, plural, =1{повідомлення} few{повідомлення} many{повідомлень} other{повідомлень}}",
|
||||||
|
"notification_channelMessagesCount": "{count} {count, plural, =1{повідомлення каналу} few{повідомлення каналу} many{повідомлень каналу} other{повідомлень каналу}}",
|
||||||
|
"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.",
|
||||||
|
"settings_gpxExportNoContacts": "Немає контактів для експорту.",
|
||||||
|
"settings_gpxExportNotAvailable": "Не підтримується на вашому пристрої/операційній системі",
|
||||||
|
"settings_gpxExportError": "Сталася помилка під час експорту.",
|
||||||
|
"settings_gpxExportAllSubtitle": "Експортує всі контакти з місцем розташування у файл GPX.",
|
||||||
|
"settings_gpxExportAll": "Експортувати всі контакти до GPX",
|
||||||
|
"settings_gpxExportContactsSubtitle": "Експортує супутників з місцезнаходженням у файл GPX.",
|
||||||
|
"settings_gpxExportContacts": "Експортувати супутників до GPX",
|
||||||
|
"settings_gpxExportRepeatersRoom": "Місцезнаходження повторювача та сервера кімнати",
|
||||||
|
"settings_gpxExportChat": "Місця супутників",
|
||||||
|
"settings_gpxExportShareText": "Дані карти експортовані з meshcore-open",
|
||||||
|
"settings_gpxExportAllContacts": "Усі місця контактів",
|
||||||
|
"settings_gpxExportShareSubject": "експорт даних карти meshcore-open у форматі GPX",
|
||||||
|
"pathTrace_someHopsNoLocation": "Одне або більше хмелів відсутнє місце розташування!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1568,5 +1568,29 @@
|
|||||||
"contacts_zeroHopContactAdvertSent": "通过广告获取联系方式。",
|
"contacts_zeroHopContactAdvertSent": "通过广告获取联系方式。",
|
||||||
"contacts_zeroHopContactAdvertFailed": "发送联系方式失败。",
|
"contacts_zeroHopContactAdvertFailed": "发送联系方式失败。",
|
||||||
"contacts_contactAdvertCopied": "广告内容已复制到剪贴板。",
|
"contacts_contactAdvertCopied": "广告内容已复制到剪贴板。",
|
||||||
"contacts_contactAdvertCopyFailed": "将广告复制到剪贴板操作失败。"
|
"contacts_contactAdvertCopyFailed": "将广告复制到剪贴板操作失败。",
|
||||||
|
"notification_activityTitle": "MeshCore 活动",
|
||||||
|
"notification_messagesCount": "{count} 条消息",
|
||||||
|
"notification_channelMessagesCount": "{count} 条频道消息",
|
||||||
|
"notification_newNodesCount": "{count} 个新节点",
|
||||||
|
"notification_newTypeDiscovered": "发现新 {contactType}",
|
||||||
|
"notification_receivedNewMessage": "收到新消息",
|
||||||
|
"contacts_contactAdvertCopyFailed": "将广告复制到剪贴板操作失败。",
|
||||||
|
"settings_gpxExportRepeaters": "导出重复器/房间服务器到GPX",
|
||||||
|
"settings_gpxExportRepeatersSubtitle": "导出带有位置的重复器/房间服务器到GPX文件。",
|
||||||
|
"settings_gpxExportContactsSubtitle": "导出带有位置的伙伴到GPX文件。",
|
||||||
|
"settings_gpxExportNotAvailable": "您的设备/操作系统不支持",
|
||||||
|
"settings_gpxExportSuccess": "成功导出GPX文件",
|
||||||
|
"settings_gpxExportError": "导出时发生错误",
|
||||||
|
"settings_gpxExportRepeatersRoom": "重复器和房间服务器位置",
|
||||||
|
"settings_gpxExportChat": "伴侣位置",
|
||||||
|
"settings_gpxExportAll": "导出所有联系人到GPX",
|
||||||
|
"settings_gpxExportContacts": "导出伴侣到GPX",
|
||||||
|
"settings_gpxExportAllSubtitle": "导出所有带有位置的联系人到GPX文件。",
|
||||||
|
"settings_gpxExportAllContacts": "所有联系人位置",
|
||||||
|
"settings_gpxExportNoContacts": "没有联系人可导出",
|
||||||
|
"settings_gpxExportShareText": "来自meshcore-open的导出地图数据",
|
||||||
|
"settings_gpxExportShareSubject": "meshcore-open GPX 地图数据导出",
|
||||||
|
"pathTrace_someHopsNoLocation": "其中一个或多个啤酒花缺少位置!"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,12 @@ class MeshCoreApp extends StatelessWidget {
|
|||||||
themeMode: _themeModeFromSetting(
|
themeMode: _themeModeFromSetting(
|
||||||
settingsService.settings.themeMode,
|
settingsService.settings.themeMode,
|
||||||
),
|
),
|
||||||
|
builder: (context, child) {
|
||||||
|
// Update notification service with resolved locale
|
||||||
|
final locale = Localizations.localeOf(context);
|
||||||
|
NotificationService().setLocale(locale);
|
||||||
|
return child ?? const SizedBox.shrink();
|
||||||
|
},
|
||||||
home: const ScannerScreen(),
|
home: const ScannerScreen(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:typed_data';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:meshcore_open/screens/path_trace_map.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../connector/meshcore_connector.dart';
|
import '../connector/meshcore_connector.dart';
|
||||||
@@ -41,6 +42,21 @@ class ChannelMessagePathScreen extends StatelessWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(l10n.channelPath_title),
|
title: Text(l10n.channelPath_title),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.radar_outlined),
|
||||||
|
tooltip: l10n.channelPath_viewMap,
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => PathTraceMapScreen(
|
||||||
|
title: context.l10n.contacts_repeaterPathTrace,
|
||||||
|
path: Uint8List.fromList(primaryPath),
|
||||||
|
flipPathRound: true,
|
||||||
|
reversePathRound: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.map_outlined),
|
icon: const Icon(Icons.map_outlined),
|
||||||
tooltip: l10n.channelPath_viewMap,
|
tooltip: l10n.channelPath_viewMap,
|
||||||
@@ -263,6 +279,7 @@ class ChannelMessagePathMapScreen extends StatefulWidget {
|
|||||||
class _ChannelMessagePathMapScreenState
|
class _ChannelMessagePathMapScreenState
|
||||||
extends State<ChannelMessagePathMapScreen> {
|
extends State<ChannelMessagePathMapScreen> {
|
||||||
Uint8List? _selectedPath;
|
Uint8List? _selectedPath;
|
||||||
|
double _pathDistance = 0.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -282,6 +299,17 @@ class _ChannelMessagePathMapScreenState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _getPathDistance(List<LatLng> points) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<MeshCoreConnector>(
|
return Consumer<MeshCoreConnector>(
|
||||||
@@ -306,10 +334,15 @@ class _ChannelMessagePathMapScreenState
|
|||||||
connector.contacts,
|
connector.contacts,
|
||||||
context.l10n,
|
context.l10n,
|
||||||
);
|
);
|
||||||
final points = hops
|
|
||||||
.where((hop) => hop.hasLocation)
|
final points = <LatLng>[];
|
||||||
.map((hop) => hop.position!)
|
for (final hop in hops) {
|
||||||
.toList();
|
if (hop.hasLocation) {
|
||||||
|
points.add(hop.position!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
|
||||||
|
|
||||||
final polylines = points.length > 1
|
final polylines = points.length > 1
|
||||||
? [
|
? [
|
||||||
Polyline(
|
Polyline(
|
||||||
@@ -327,7 +360,10 @@ class _ChannelMessagePathMapScreenState
|
|||||||
final bounds = points.length > 1
|
final bounds = points.length > 1
|
||||||
? LatLngBounds.fromPoints(points)
|
? LatLngBounds.fromPoints(points)
|
||||||
: null;
|
: null;
|
||||||
final mapKey = ValueKey(_formatPathPrefixes(selectedPath));
|
final mapKey = ValueKey(
|
||||||
|
'${_formatPathPrefixes(selectedPath)},${context.l10n.pathTrace_you}',
|
||||||
|
);
|
||||||
|
_pathDistance = _getPathDistance(points);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text(context.l10n.channelPath_mapTitle)),
|
appBar: AppBar(title: Text(context.l10n.channelPath_mapTitle)),
|
||||||
@@ -349,6 +385,9 @@ class _ChannelMessagePathMapScreenState
|
|||||||
),
|
),
|
||||||
minZoom: 2.0,
|
minZoom: 2.0,
|
||||||
maxZoom: 18.0,
|
maxZoom: 18.0,
|
||||||
|
interactionOptions: InteractionOptions(
|
||||||
|
flags: ~InteractiveFlag.rotate,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TileLayer(
|
TileLayer(
|
||||||
@@ -461,8 +500,8 @@ class _ChannelMessagePathMapScreenState
|
|||||||
if (hop.hasLocation)
|
if (hop.hasLocation)
|
||||||
Marker(
|
Marker(
|
||||||
point: hop.position!,
|
point: hop.position!,
|
||||||
width: 40,
|
width: 35,
|
||||||
height: 40,
|
height: 35,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
@@ -487,6 +526,39 @@ class _ChannelMessagePathMapScreenState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (context.read<MeshCoreConnector>().selfLatitude != null &&
|
||||||
|
context.read<MeshCoreConnector>().selfLongitude != null)
|
||||||
|
Marker(
|
||||||
|
point: LatLng(
|
||||||
|
context.read<MeshCoreConnector>().selfLatitude!,
|
||||||
|
context.read<MeshCoreConnector>().selfLongitude!,
|
||||||
|
),
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.teal,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.white, width: 2),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.3),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
context.l10n.pathTrace_you,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,7 +581,7 @@ class _ChannelMessagePathMapScreenState
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Text(
|
child: Text(
|
||||||
l10n.channelPath_repeaterHops,
|
'${l10n.channelPath_repeaterHops} (${(_pathDistance / 1609.34).toStringAsFixed(2)} Miles / ${(_pathDistance / 1000).toStringAsFixed(2)} Km)',
|
||||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
|
import 'package:meshcore_open/screens/path_trace_map.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
@@ -701,6 +702,19 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
title: Text(context.l10n.chat_fullPath),
|
title: Text(context.l10n.chat_fullPath),
|
||||||
content: SelectableText(formattedPath),
|
content: SelectableText(formattedPath),
|
||||||
actions: [
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => PathTraceMapScreen(
|
||||||
|
title: context.l10n.contacts_repeaterPathTrace,
|
||||||
|
path: Uint8List.fromList(pathBytes),
|
||||||
|
flipPathRound: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(context.l10n.contacts_pathTrace),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: Text(context.l10n.common_close),
|
child: Text(context.l10n.common_close),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:meshcore_open/widgets/path_trace_dialog.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:meshcore_open/screens/path_trace_map.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../connector/meshcore_connector.dart';
|
import '../connector/meshcore_connector.dart';
|
||||||
@@ -1009,16 +1009,16 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
? Text(context.l10n.contacts_pathTrace)
|
? Text(context.l10n.contacts_pathTrace)
|
||||||
: Text(context.l10n.contacts_ping),
|
: Text(context.l10n.contacts_ping),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
Navigator.push(
|
||||||
context: context,
|
context,
|
||||||
builder: (context) {
|
MaterialPageRoute(
|
||||||
return PathTraceDialog(
|
builder: (context) => PathTraceMapScreen(
|
||||||
title: contact.pathLength > 0
|
title: contact.pathLength > 0
|
||||||
? context.l10n.contacts_repeaterPathTrace
|
? context.l10n.contacts_repeaterPathTrace
|
||||||
: context.l10n.contacts_repeaterPing,
|
: context.l10n.contacts_repeaterPing,
|
||||||
path: contact.traceRouteBytes ?? Uint8List(0),
|
path: contact.traceRouteBytes ?? Uint8List(0),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1037,16 +1037,16 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
? Text(context.l10n.contacts_pathTrace)
|
? Text(context.l10n.contacts_pathTrace)
|
||||||
: Text(context.l10n.contacts_ping),
|
: Text(context.l10n.contacts_ping),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
Navigator.push(
|
||||||
context: context,
|
context,
|
||||||
builder: (context) {
|
MaterialPageRoute(
|
||||||
return PathTraceDialog(
|
builder: (context) => PathTraceMapScreen(
|
||||||
title: contact.pathLength > 0
|
title: contact.pathLength > 0
|
||||||
? context.l10n.contacts_roomPathTrace
|
? context.l10n.contacts_roomPathTrace
|
||||||
: context.l10n.contacts_roomPing,
|
: context.l10n.contacts_roomPing,
|
||||||
path: contact.traceRouteBytes ?? Uint8List(0),
|
path: contact.traceRouteBytes ?? Uint8List(0),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1079,16 +1079,16 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
leading: const Icon(Icons.radar, color: Colors.green),
|
leading: const Icon(Icons.radar, color: Colors.green),
|
||||||
title: Text(context.l10n.contacts_chatTraceRoute),
|
title: Text(context.l10n.contacts_chatTraceRoute),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
Navigator.push(
|
||||||
context: context,
|
context,
|
||||||
builder: (context) {
|
MaterialPageRoute(
|
||||||
return PathTraceDialog(
|
builder: (context) => PathTraceMapScreen(
|
||||||
title: context.l10n.contacts_pathTraceTo(
|
title: context.l10n.contacts_pathTraceTo(
|
||||||
contact.name,
|
contact.name,
|
||||||
),
|
),
|
||||||
path: contact.traceRouteBytes ?? Uint8List(0),
|
path: contact.traceRouteBytes ?? Uint8List(0),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -349,6 +349,43 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
),
|
),
|
||||||
..._buildMarkers(contactsWithLocation, settings),
|
..._buildMarkers(contactsWithLocation, settings),
|
||||||
...sharedMarkers.map(_buildSharedMarker),
|
...sharedMarkers.map(_buildSharedMarker),
|
||||||
|
if (connector.selfLatitude != null &&
|
||||||
|
connector.selfLongitude != null)
|
||||||
|
Marker(
|
||||||
|
point: LatLng(
|
||||||
|
connector.selfLatitude!,
|
||||||
|
connector.selfLongitude!,
|
||||||
|
),
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.teal,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.white,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.3),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
context.l10n.pathTrace_you,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -396,14 +433,14 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
|
|
||||||
final marker = Marker(
|
final marker = Marker(
|
||||||
point: LatLng(contact.latitude!, contact.longitude!),
|
point: LatLng(contact.latitude!, contact.longitude!),
|
||||||
width: 80,
|
width: 35,
|
||||||
height: 80,
|
height: 35,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => _showNodeInfo(context, contact),
|
onTap: () => _showNodeInfo(context, contact),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _getNodeColor(contact.type),
|
color: _getNodeColor(contact.type),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
@@ -419,7 +456,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
child: Icon(
|
child: Icon(
|
||||||
_getNodeIcon(contact.type),
|
_getNodeIcon(contact.type),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
size: 24,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,569 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:meshcore_open/connector/meshcore_connector.dart';
|
||||||
|
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
||||||
|
import 'package:meshcore_open/l10n/l10n.dart';
|
||||||
|
import 'package:meshcore_open/models/contact.dart';
|
||||||
|
import 'package:meshcore_open/services/map_tile_cache_service.dart';
|
||||||
|
import 'package:meshcore_open/widgets/snr_indicator.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class PathTraceData {
|
||||||
|
final Uint8List pathData;
|
||||||
|
final Uint8List snrData;
|
||||||
|
final Map<int, Contact> pathContacts;
|
||||||
|
|
||||||
|
PathTraceData({
|
||||||
|
required this.pathData,
|
||||||
|
required this.snrData,
|
||||||
|
required this.pathContacts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathTraceMapScreen extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final Uint8List path;
|
||||||
|
final bool flipPathRound;
|
||||||
|
final bool reversePathRound;
|
||||||
|
|
||||||
|
const PathTraceMapScreen({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.path,
|
||||||
|
this.flipPathRound = false,
|
||||||
|
this.reversePathRound = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PathTraceMapScreen> createState() => _PathTraceMapScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
||||||
|
StreamSubscription<Uint8List>? _frameSubscription;
|
||||||
|
Timer? _timeoutTimer;
|
||||||
|
|
||||||
|
bool _isLoading = false;
|
||||||
|
bool _failed2Loaded = false;
|
||||||
|
bool _hasData = false;
|
||||||
|
bool _noLocationErr = false;
|
||||||
|
PathTraceData? _traceData;
|
||||||
|
List<LatLng> _points = <LatLng>[];
|
||||||
|
List<Polyline> _polylines = [];
|
||||||
|
LatLng? _initialCenter = LatLng(0, 0);
|
||||||
|
double _initialZoom = 2.0;
|
||||||
|
LatLngBounds? _bounds;
|
||||||
|
ValueKey<String> _mapKey = const ValueKey('initial');
|
||||||
|
double _pathDistance = 0.0;
|
||||||
|
|
||||||
|
String _formatPathPrefixes(Uint8List pathBytes) {
|
||||||
|
return pathBytes
|
||||||
|
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
|
||||||
|
.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_setupFrameListener();
|
||||||
|
_doPathTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_frameSubscription?.cancel();
|
||||||
|
_timeoutTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List addReturnpath(Uint8List pathBytes) {
|
||||||
|
Uint8List? traceBytes;
|
||||||
|
final len = (pathBytes.length + pathBytes.length - 1);
|
||||||
|
traceBytes = Uint8List(len);
|
||||||
|
for (int i = 0; i < pathBytes.length; i++) {
|
||||||
|
traceBytes[i] = pathBytes[i];
|
||||||
|
if (i < pathBytes.length - 1) {
|
||||||
|
traceBytes[len - 1 - i] = pathBytes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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<void> _doPathTrace() async {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
_failed2Loaded = false;
|
||||||
|
_noLocationErr = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final Uint8List path;
|
||||||
|
|
||||||
|
Uint8List pathTmp = widget.reversePathRound
|
||||||
|
? Uint8List.fromList(widget.path.reversed.toList())
|
||||||
|
: widget.path;
|
||||||
|
|
||||||
|
if (widget.flipPathRound) {
|
||||||
|
path = addReturnpath(pathTmp);
|
||||||
|
} else {
|
||||||
|
path = pathTmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||||
|
final frame = buildTraceReq(
|
||||||
|
DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
0, //flags
|
||||||
|
0, //auth
|
||||||
|
payload: path,
|
||||||
|
);
|
||||||
|
connector.sendFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setupFrameListener() {
|
||||||
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||||
|
Uint8List tagData = Uint8List(4);
|
||||||
|
// Listen for incoming text messages from the repeater
|
||||||
|
_frameSubscription = connector.receivedFrames.listen((frame) {
|
||||||
|
if (frame.isEmpty) return;
|
||||||
|
final frameBuffer = BufferReader(frame);
|
||||||
|
final code = frameBuffer.readUInt8();
|
||||||
|
|
||||||
|
if (code == respCodeSent) {
|
||||||
|
frameBuffer.skipBytes(1); //reserved
|
||||||
|
tagData = frameBuffer.readBytes(4);
|
||||||
|
final timeoutSeconds = frameBuffer.readUInt32LE();
|
||||||
|
|
||||||
|
// Start timeout timer for trace response
|
||||||
|
_timeoutTimer?.cancel();
|
||||||
|
_timeoutTimer = Timer(Duration(milliseconds: timeoutSeconds), () {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
_failed2Loaded = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a binary response
|
||||||
|
if (frame.length > 8 &&
|
||||||
|
code == pushCodeTraceData &&
|
||||||
|
listEquals(frame.sublist(4, 8), tagData)) {
|
||||||
|
_timeoutTimer?.cancel();
|
||||||
|
if (!mounted) return;
|
||||||
|
frameBuffer.skipBytes(3); //reserved + path length + flag
|
||||||
|
if (listEquals(frameBuffer.readBytes(4), tagData)) {
|
||||||
|
_handleTraceResponse(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleTraceResponse(Uint8List frame) async {
|
||||||
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||||
|
|
||||||
|
final buffer = BufferReader(frame);
|
||||||
|
buffer.skipBytes(2); // Skip push code and reserved byte
|
||||||
|
int pathLength = buffer.readUInt8();
|
||||||
|
buffer.skipBytes(5); // Skip Flag byte and tag data
|
||||||
|
buffer.skipBytes(4); // Skip auth code
|
||||||
|
Uint8List pathData = buffer.readBytes(pathLength);
|
||||||
|
Uint8List snrData = buffer.readRemainingBytes();
|
||||||
|
|
||||||
|
Map<int, Contact> pathContacts = {};
|
||||||
|
|
||||||
|
connector.contacts.where((c) => c.type != advTypeChat).forEach((repeater) {
|
||||||
|
for (var repeaterData in pathData) {
|
||||||
|
if (listEquals(
|
||||||
|
repeater.publicKey.sublist(0, 1),
|
||||||
|
Uint8List.fromList([repeaterData]),
|
||||||
|
)) {
|
||||||
|
pathContacts[repeaterData] = repeater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
_hasData = true;
|
||||||
|
_traceData = PathTraceData(
|
||||||
|
pathData: pathData,
|
||||||
|
snrData: snrData,
|
||||||
|
pathContacts: pathContacts,
|
||||||
|
);
|
||||||
|
_points = <LatLng>[];
|
||||||
|
_points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
|
||||||
|
for (final hop in _traceData!.pathData) {
|
||||||
|
final contact = _traceData!.pathContacts[hop];
|
||||||
|
if (contact != null &&
|
||||||
|
contact.hasLocation &&
|
||||||
|
contact.latitude != null &&
|
||||||
|
contact.longitude != null) {
|
||||||
|
_points.add(LatLng(contact.latitude!, contact.longitude!));
|
||||||
|
} else {
|
||||||
|
_noLocationErr = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_polylines = _points.length > 1
|
||||||
|
? [
|
||||||
|
Polyline(
|
||||||
|
points: _points,
|
||||||
|
strokeWidth: 4,
|
||||||
|
color: Colors.blueAccent,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: <Polyline>[];
|
||||||
|
|
||||||
|
_initialCenter = _points.isNotEmpty ? _points.first : const LatLng(0, 0);
|
||||||
|
_initialZoom = _points.isNotEmpty ? 13.0 : 2.0;
|
||||||
|
_bounds = _points.length > 1 ? LatLngBounds.fromPoints(_points) : null;
|
||||||
|
_mapKey = ValueKey(
|
||||||
|
'${context.l10n.pathTrace_you},${_formatPathPrefixes(_traceData!.pathData)}',
|
||||||
|
);
|
||||||
|
_pathDistance = getPathDistance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<MeshCoreConnector>(
|
||||||
|
builder: (context, connector, _) {
|
||||||
|
final tileCache = context.read<MapTileCacheService>();
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
widget.title,
|
||||||
|
style: const TextStyle(fontSize: 24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
centerTitle: false,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: _isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.refresh),
|
||||||
|
onPressed: _isLoading ? null : _doPathTrace,
|
||||||
|
tooltip: context.l10n.pathTrace_refreshTooltip,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
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)
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (_isLoading) const CircularProgressIndicator(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (!_isLoading && _failed2Loaded)
|
||||||
|
Text(context.l10n.pathTrace_notAvailable),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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 (_points.isEmpty &&
|
||||||
|
!_hasData &&
|
||||||
|
!_isLoading &&
|
||||||
|
!_failed2Loaded &&
|
||||||
|
!_noLocationErr)
|
||||||
|
Center(
|
||||||
|
child: Card(
|
||||||
|
color: Colors.white.withValues(alpha: 0.9),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
child: Text(
|
||||||
|
context.l10n.channelPath_noRepeaterLocations,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_hasData && !_noLocationErr)
|
||||||
|
_buildLegendCard(context, _traceData!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Marker> _buildHopMarkers(List<int> pathData) {
|
||||||
|
return [
|
||||||
|
for (final hop in pathData)
|
||||||
|
if (_traceData!.pathContacts[hop]!.hasLocation)
|
||||||
|
Marker(
|
||||||
|
point: LatLng(
|
||||||
|
_traceData!.pathContacts[hop]!.latitude!,
|
||||||
|
_traceData!.pathContacts[hop]!.longitude!,
|
||||||
|
),
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.green,
|
||||||
|
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: Text(
|
||||||
|
_traceData!.pathContacts[hop]!.publicKey
|
||||||
|
.sublist(0, 1)
|
||||||
|
.map(
|
||||||
|
(b) => b.toRadixString(16).padLeft(2, '0').toUpperCase(),
|
||||||
|
)
|
||||||
|
.join(),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (context.read<MeshCoreConnector>().selfLatitude != null &&
|
||||||
|
context.read<MeshCoreConnector>().selfLongitude != null)
|
||||||
|
Marker(
|
||||||
|
point: LatLng(
|
||||||
|
context.read<MeshCoreConnector>().selfLatitude!,
|
||||||
|
context.read<MeshCoreConnector>().selfLongitude!,
|
||||||
|
),
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
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: Text(
|
||||||
|
context.l10n.pathTrace_you,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatDirectionText(PathTraceData pathTraceData, int index) {
|
||||||
|
if (index == 0 || index == pathTraceData.snrData.length - 1) {
|
||||||
|
if (index == 0) {
|
||||||
|
return context.l10n.pathTrace_you;
|
||||||
|
} else {
|
||||||
|
final contactName = pathTraceData
|
||||||
|
.pathContacts[pathTraceData.pathData[pathTraceData.pathData.length -
|
||||||
|
1]]
|
||||||
|
?.name;
|
||||||
|
final hex = pathTraceData.pathData[pathTraceData.pathData.length - 1]
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
return contactName != null ? "$hex: $contactName" : hex;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final contactName =
|
||||||
|
pathTraceData.pathContacts[pathTraceData.pathData[index - 1]]?.name;
|
||||||
|
final hex = pathTraceData.pathData[index - 1]
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
return contactName != null ? "$hex: $contactName" : hex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatDirectionSubText(PathTraceData pathTraceData, int index) {
|
||||||
|
if (index == 0 || index == pathTraceData.snrData.length - 1) {
|
||||||
|
if (index == 0) {
|
||||||
|
final contactName =
|
||||||
|
pathTraceData.pathContacts[pathTraceData.pathData[0]]?.name;
|
||||||
|
final hex = pathTraceData.pathData[0]
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
return contactName != null ? "$hex: $contactName" : hex;
|
||||||
|
} else {
|
||||||
|
return context.l10n.pathTrace_you;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final contactName =
|
||||||
|
pathTraceData.pathContacts[pathTraceData.pathData[index]]?.name;
|
||||||
|
final hex = pathTraceData.pathData[index]
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
return contactName != null ? "$hex: $contactName" : hex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLegendCard(BuildContext context, PathTraceData pathTraceData) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
final maxHeight = MediaQuery.of(context).size.height * 0.35;
|
||||||
|
final estimatedHeight = 72.0 + (pathTraceData.pathData.length * 56.0);
|
||||||
|
final cardHeight = max(96.0, min(maxHeight, estimatedHeight));
|
||||||
|
|
||||||
|
return Positioned(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
bottom: 16,
|
||||||
|
child: SizedBox(
|
||||||
|
height: cardHeight,
|
||||||
|
child: Card(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Text(
|
||||||
|
'${l10n.channelPath_repeaterHops} (${(_pathDistance / 1609.34).toStringAsFixed(2)} Miles / ${(_pathDistance / 1000).toStringAsFixed(2)} Km)',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: pathTraceData.pathData.isEmpty
|
||||||
|
? Center(
|
||||||
|
child: Text(l10n.channelPath_noHopDetailsAvailable),
|
||||||
|
)
|
||||||
|
: Scrollbar(
|
||||||
|
child: ListView.separated(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
itemCount: pathTraceData.pathData.length + 1,
|
||||||
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading:
|
||||||
|
index >= pathTraceData.snrData.length / 2
|
||||||
|
? Icon(Icons.call_received)
|
||||||
|
: Icon(Icons.call_made),
|
||||||
|
title: Text(
|
||||||
|
formatDirectionText(pathTraceData, index),
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
formatDirectionSubText(
|
||||||
|
pathTraceData,
|
||||||
|
index,
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
trailing: SNRIcon(
|
||||||
|
snr:
|
||||||
|
pathTraceData.snrData[index].toSigned(
|
||||||
|
8,
|
||||||
|
) /
|
||||||
|
4.0,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
// Handle item tap
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:meshcore_open/utils/gpx_export.dart';
|
||||||
import 'package:meshcore_open/widgets/elements_ui.dart';
|
import 'package:meshcore_open/widgets/elements_ui.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
@@ -57,6 +58,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildDebugCard(context),
|
_buildDebugCard(context),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
_buildExportCard(connector),
|
||||||
|
const SizedBox(height: 16),
|
||||||
_buildAboutCard(context),
|
_buildAboutCard(context),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -684,6 +687,110 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_gpxExport(
|
||||||
|
GpxExport exporter,
|
||||||
|
String name,
|
||||||
|
String description,
|
||||||
|
String filename,
|
||||||
|
String shareText,
|
||||||
|
String subject,
|
||||||
|
) async {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
final result = await exporter.exportGPX(
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
filename,
|
||||||
|
shareText,
|
||||||
|
subject,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
switch (result) {
|
||||||
|
case gpxExportSuccess:
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text(l10n.settings_gpxExportSuccess)));
|
||||||
|
case gpxExportNoContacts:
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(l10n.settings_gpxExportNoContacts)),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case gpxExportNotAvailable:
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(l10n.settings_gpxExportNotAvailable)),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case gpxExportFailed:
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text(l10n.settings_gpxExportError)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildExportCard(MeshCoreConnector connector) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.download_outlined),
|
||||||
|
title: Text(l10n.settings_gpxExportRepeaters),
|
||||||
|
subtitle: Text(l10n.settings_gpxExportRepeatersSubtitle),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: () async {
|
||||||
|
final exporter = GpxExport(connector);
|
||||||
|
exporter.addRepeaters();
|
||||||
|
_gpxExport(
|
||||||
|
exporter,
|
||||||
|
l10n.map_repeater,
|
||||||
|
l10n.settings_gpxExportRepeatersRoom,
|
||||||
|
"meshcore_repeaters_",
|
||||||
|
l10n.settings_gpxExportShareText,
|
||||||
|
l10n.settings_gpxExportShareSubject,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.download_outlined),
|
||||||
|
title: Text(l10n.settings_gpxExportContacts),
|
||||||
|
subtitle: Text(l10n.settings_gpxExportContactsSubtitle),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: () async {
|
||||||
|
final exporter = GpxExport(connector);
|
||||||
|
exporter.addContacts();
|
||||||
|
_gpxExport(
|
||||||
|
exporter,
|
||||||
|
l10n.map_repeater,
|
||||||
|
l10n.settings_gpxExportChat,
|
||||||
|
"meshcore_contacts_",
|
||||||
|
l10n.settings_gpxExportShareText,
|
||||||
|
l10n.settings_gpxExportShareSubject,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.download_outlined),
|
||||||
|
title: Text(l10n.settings_gpxExportAll),
|
||||||
|
subtitle: Text(l10n.settings_gpxExportAllSubtitle),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: () async {
|
||||||
|
final exporter = GpxExport(connector);
|
||||||
|
exporter.addAll();
|
||||||
|
_gpxExport(
|
||||||
|
exporter,
|
||||||
|
l10n.map_repeater,
|
||||||
|
l10n.settings_gpxExportAllContacts,
|
||||||
|
"meshcore_all_",
|
||||||
|
l10n.settings_gpxExportShareText,
|
||||||
|
l10n.settings_gpxExportShareSubject,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RadioSettingsDialog extends StatefulWidget {
|
class _RadioSettingsDialog extends StatefulWidget {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class NotificationService {
|
class NotificationService {
|
||||||
static final NotificationService _instance = NotificationService._internal();
|
static final NotificationService _instance = NotificationService._internal();
|
||||||
factory NotificationService() => _instance;
|
factory NotificationService() => _instance;
|
||||||
@@ -10,6 +14,34 @@ class NotificationService {
|
|||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
|
|
||||||
|
// Locale for localized notification strings
|
||||||
|
Locale _locale = const Locale('en');
|
||||||
|
|
||||||
|
/// Set the locale for notification strings (call when app locale changes)
|
||||||
|
void setLocale(Locale locale) {
|
||||||
|
_locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLocalizations get _l10n => lookupAppLocalizations(_locale);
|
||||||
|
|
||||||
|
// Rate limiting to prevent notification storms
|
||||||
|
// (Added after getting notification-flooded while evaluating RF flood management. The irony.)
|
||||||
|
static const _minNotificationInterval = Duration(seconds: 3);
|
||||||
|
static const _batchWindow = Duration(seconds: 5);
|
||||||
|
|
||||||
|
DateTime? _lastNotificationTime;
|
||||||
|
final List<_PendingNotification> _pendingNotifications = [];
|
||||||
|
bool _isBatchingActive = false;
|
||||||
|
bool _suppressNotifications = false;
|
||||||
|
|
||||||
|
/// Temporarily suppress all notifications (e.g., during sync)
|
||||||
|
void suppressNotifications(bool suppress) {
|
||||||
|
_suppressNotifications = suppress;
|
||||||
|
if (suppress) {
|
||||||
|
_pendingNotifications.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
if (_isInitialized) return;
|
if (_isInitialized) return;
|
||||||
|
|
||||||
@@ -76,7 +108,7 @@ class NotificationService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showMessageNotification({
|
Future<void> _showMessageNotificationImpl({
|
||||||
required String contactName,
|
required String contactName,
|
||||||
required String message,
|
required String message,
|
||||||
String? contactId,
|
String? contactId,
|
||||||
@@ -118,14 +150,14 @@ class NotificationService {
|
|||||||
|
|
||||||
await _notifications.show(
|
await _notifications.show(
|
||||||
contactId?.hashCode ?? 0,
|
contactId?.hashCode ?? 0,
|
||||||
'New message from $contactName',
|
contactName,
|
||||||
message,
|
message,
|
||||||
notificationDetails,
|
notificationDetails,
|
||||||
payload: 'message:$contactId',
|
payload: 'message:$contactId',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showAdvertNotification({
|
Future<void> _showAdvertNotificationImpl({
|
||||||
required String contactName,
|
required String contactName,
|
||||||
required String contactType,
|
required String contactType,
|
||||||
String? contactId,
|
String? contactId,
|
||||||
@@ -163,14 +195,14 @@ class NotificationService {
|
|||||||
|
|
||||||
await _notifications.show(
|
await _notifications.show(
|
||||||
contactId?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
contactId?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||||
'New $contactType discovered',
|
_l10n.notification_newTypeDiscovered(contactType),
|
||||||
contactName,
|
contactName,
|
||||||
notificationDetails,
|
notificationDetails,
|
||||||
payload: 'advert:$contactId',
|
payload: 'advert:$contactId',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showChannelMessageNotification({
|
Future<void> _showChannelMessageNotificationImpl({
|
||||||
required String channelName,
|
required String channelName,
|
||||||
required String message,
|
required String message,
|
||||||
int? channelIndex,
|
int? channelIndex,
|
||||||
@@ -211,7 +243,9 @@ class NotificationService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final preview = message.trim();
|
final preview = message.trim();
|
||||||
final body = preview.isEmpty ? 'Received new message' : preview;
|
final body = preview.isEmpty
|
||||||
|
? _l10n.notification_receivedNewMessage
|
||||||
|
: preview;
|
||||||
|
|
||||||
await _notifications.show(
|
await _notifications.show(
|
||||||
channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||||
@@ -222,6 +256,21 @@ class NotificationService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a privacy-safe identifier for debug logging.
|
||||||
|
/// - advert: shows device name (body contains contactName)
|
||||||
|
/// - message: shows "from: sender" (avoids logging message content)
|
||||||
|
/// - channelMessage: shows "in: channel" (avoids logging message content)
|
||||||
|
String _getNotificationIdentifier(_PendingNotification n) {
|
||||||
|
switch (n.type) {
|
||||||
|
case _NotificationType.advert:
|
||||||
|
return n.body;
|
||||||
|
case _NotificationType.message:
|
||||||
|
return 'from: ${n.title}';
|
||||||
|
case _NotificationType.channelMessage:
|
||||||
|
return 'in: ${n.title}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onNotificationTapped(NotificationResponse response) {
|
void _onNotificationTapped(NotificationResponse response) {
|
||||||
final payload = response.payload;
|
final payload = response.payload;
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
@@ -238,4 +287,212 @@ class NotificationService {
|
|||||||
Future<void> cancel(int id) async {
|
Future<void> cancel(int id) async {
|
||||||
await _notifications.cancel(id);
|
await _notifications.cancel(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// Public notification methods (rate limiting is enforced automatically)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> showMessageNotification({
|
||||||
|
required String contactName,
|
||||||
|
required String message,
|
||||||
|
String? contactId,
|
||||||
|
int? badgeCount,
|
||||||
|
}) async {
|
||||||
|
if (_suppressNotifications) return;
|
||||||
|
|
||||||
|
_queueNotification(
|
||||||
|
_PendingNotification(
|
||||||
|
type: _NotificationType.message,
|
||||||
|
title: contactName,
|
||||||
|
body: message,
|
||||||
|
id: contactId,
|
||||||
|
badgeCount: badgeCount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showAdvertNotification({
|
||||||
|
required String contactName,
|
||||||
|
required String contactType,
|
||||||
|
String? contactId,
|
||||||
|
}) async {
|
||||||
|
if (_suppressNotifications) return;
|
||||||
|
|
||||||
|
_queueNotification(
|
||||||
|
_PendingNotification(
|
||||||
|
type: _NotificationType.advert,
|
||||||
|
title: contactType,
|
||||||
|
body: contactName,
|
||||||
|
id: contactId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showChannelMessageNotification({
|
||||||
|
required String channelName,
|
||||||
|
required String message,
|
||||||
|
int? channelIndex,
|
||||||
|
int? badgeCount,
|
||||||
|
}) async {
|
||||||
|
if (_suppressNotifications) return;
|
||||||
|
|
||||||
|
_queueNotification(
|
||||||
|
_PendingNotification(
|
||||||
|
type: _NotificationType.channelMessage,
|
||||||
|
title: channelName,
|
||||||
|
body: message,
|
||||||
|
id: channelIndex?.toString(),
|
||||||
|
badgeCount: badgeCount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _queueNotification(_PendingNotification notification) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
// If we recently showed a notification, start batching
|
||||||
|
if (_lastNotificationTime != null &&
|
||||||
|
now.difference(_lastNotificationTime!) < _minNotificationInterval) {
|
||||||
|
_pendingNotifications.add(notification);
|
||||||
|
debugPrint(
|
||||||
|
'[Notification] queued: ${notification.type.name} (${_getNotificationIdentifier(notification)})',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start batch timer if not already running
|
||||||
|
if (!_isBatchingActive) {
|
||||||
|
_isBatchingActive = true;
|
||||||
|
Future.delayed(_batchWindow, _processBatch);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show immediately if enough time has passed
|
||||||
|
debugPrint(
|
||||||
|
'[Notification] sent immediately: ${notification.type.name} (${_getNotificationIdentifier(notification)})',
|
||||||
|
);
|
||||||
|
_showNotificationImmediately(notification);
|
||||||
|
_lastNotificationTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _processBatch() async {
|
||||||
|
_isBatchingActive = false;
|
||||||
|
|
||||||
|
if (_pendingNotifications.isEmpty) return;
|
||||||
|
|
||||||
|
final batch = List<_PendingNotification>.from(_pendingNotifications);
|
||||||
|
_pendingNotifications.clear();
|
||||||
|
|
||||||
|
if (batch.length == 1) {
|
||||||
|
// Single notification, show normally
|
||||||
|
_showNotificationImmediately(batch.first);
|
||||||
|
} else {
|
||||||
|
// Multiple notifications, show summary
|
||||||
|
await _showBatchSummary(batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastNotificationTime = DateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showNotificationImmediately(
|
||||||
|
_PendingNotification notification,
|
||||||
|
) async {
|
||||||
|
switch (notification.type) {
|
||||||
|
case _NotificationType.message:
|
||||||
|
await _showMessageNotificationImpl(
|
||||||
|
contactName: notification.title,
|
||||||
|
message: notification.body,
|
||||||
|
contactId: notification.id,
|
||||||
|
badgeCount: notification.badgeCount,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case _NotificationType.advert:
|
||||||
|
await _showAdvertNotificationImpl(
|
||||||
|
contactName: notification.body,
|
||||||
|
contactType: notification.title,
|
||||||
|
contactId: notification.id,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case _NotificationType.channelMessage:
|
||||||
|
await _showChannelMessageNotificationImpl(
|
||||||
|
channelName: notification.title,
|
||||||
|
message: notification.body,
|
||||||
|
channelIndex: int.tryParse(notification.id ?? ''),
|
||||||
|
badgeCount: notification.badgeCount,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showBatchSummary(List<_PendingNotification> batch) async {
|
||||||
|
if (!_isInitialized) await initialize();
|
||||||
|
|
||||||
|
// Group by type
|
||||||
|
final messages = batch
|
||||||
|
.where((n) => n.type == _NotificationType.message)
|
||||||
|
.toList();
|
||||||
|
final adverts = batch
|
||||||
|
.where((n) => n.type == _NotificationType.advert)
|
||||||
|
.toList();
|
||||||
|
final channelMsgs = batch
|
||||||
|
.where((n) => n.type == _NotificationType.channelMessage)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Build summary text using localized plurals
|
||||||
|
final parts = <String>[];
|
||||||
|
if (messages.isNotEmpty) {
|
||||||
|
parts.add(_l10n.notification_messagesCount(messages.length));
|
||||||
|
}
|
||||||
|
if (channelMsgs.isNotEmpty) {
|
||||||
|
parts.add(_l10n.notification_channelMessagesCount(channelMsgs.length));
|
||||||
|
}
|
||||||
|
if (adverts.isNotEmpty) {
|
||||||
|
parts.add(_l10n.notification_newNodesCount(adverts.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.isEmpty) return;
|
||||||
|
|
||||||
|
// Show first few device names in batch summary for debugging (only if adverts exist)
|
||||||
|
final deviceInfo = adverts.isNotEmpty
|
||||||
|
? ' (${adverts.take(5).map((n) => n.body).join(', ')}${adverts.length > 5 ? ', ...' : ''})'
|
||||||
|
: '';
|
||||||
|
debugPrint('[Notification] batch summary: ${parts.join(", ")}$deviceInfo');
|
||||||
|
|
||||||
|
const androidDetails = AndroidNotificationDetails(
|
||||||
|
'batch_summary',
|
||||||
|
'Activity Summary',
|
||||||
|
channelDescription: 'Batched notification summaries',
|
||||||
|
importance: Importance.defaultImportance,
|
||||||
|
priority: Priority.defaultPriority,
|
||||||
|
icon: '@mipmap/ic_launcher',
|
||||||
|
);
|
||||||
|
|
||||||
|
const notificationDetails = NotificationDetails(android: androidDetails);
|
||||||
|
|
||||||
|
await _notifications.show(
|
||||||
|
'batch_summary'.hashCode,
|
||||||
|
_l10n.notification_activityTitle,
|
||||||
|
parts.join(', '),
|
||||||
|
notificationDetails,
|
||||||
|
payload: 'batch',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper class for pending notifications
|
||||||
|
enum _NotificationType { message, advert, channelMessage }
|
||||||
|
|
||||||
|
class _PendingNotification {
|
||||||
|
final _NotificationType type;
|
||||||
|
final String title;
|
||||||
|
final String body;
|
||||||
|
final String? id;
|
||||||
|
final int? badgeCount;
|
||||||
|
|
||||||
|
_PendingNotification({
|
||||||
|
required this.type,
|
||||||
|
required this.title,
|
||||||
|
required this.body,
|
||||||
|
this.id,
|
||||||
|
this.badgeCount,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:gpx/gpx.dart';
|
||||||
|
import 'package:meshcore_open/connector/meshcore_connector.dart';
|
||||||
|
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
|
class ContactExport {
|
||||||
|
final String name;
|
||||||
|
final double lat;
|
||||||
|
final double lon;
|
||||||
|
final String desc;
|
||||||
|
final double? ele;
|
||||||
|
|
||||||
|
ContactExport({
|
||||||
|
required this.name,
|
||||||
|
required this.lat,
|
||||||
|
required this.lon,
|
||||||
|
required this.desc,
|
||||||
|
this.ele,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const int gpxExportFailed = -1;
|
||||||
|
const int gpxExportSuccess = 1;
|
||||||
|
const int gpxExportNoContacts = 2;
|
||||||
|
const int gpxExportCancelled = 3;
|
||||||
|
const int gpxExportNotAvailable = 4;
|
||||||
|
|
||||||
|
class GpxExport {
|
||||||
|
final MeshCoreConnector _connector;
|
||||||
|
final List<ContactExport> _contacts = [];
|
||||||
|
|
||||||
|
GpxExport(this._connector);
|
||||||
|
|
||||||
|
void _addContact(
|
||||||
|
String name,
|
||||||
|
double lat,
|
||||||
|
double lon,
|
||||||
|
String desc, [
|
||||||
|
double? ele,
|
||||||
|
]) {
|
||||||
|
_contacts.add(
|
||||||
|
ContactExport(
|
||||||
|
name: name.trim(),
|
||||||
|
lat: lat,
|
||||||
|
lon: lon,
|
||||||
|
desc: desc.trim(),
|
||||||
|
ele: ele,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRepeaters() {
|
||||||
|
final contacts = _connector.contacts
|
||||||
|
.where((c) => c.type == advTypeRepeater || c.type == advTypeRoom)
|
||||||
|
.toList();
|
||||||
|
for (var contact in contacts) {
|
||||||
|
if (contact.latitude == null || contact.longitude == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_addContact(
|
||||||
|
contact.name,
|
||||||
|
contact.latitude!,
|
||||||
|
contact.longitude!,
|
||||||
|
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addContacts() {
|
||||||
|
final contacts = _connector.contacts
|
||||||
|
.where((c) => c.type == advTypeChat)
|
||||||
|
.toList();
|
||||||
|
for (var contact in contacts) {
|
||||||
|
if (contact.latitude == null || contact.longitude == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_addContact(
|
||||||
|
contact.name,
|
||||||
|
contact.latitude!,
|
||||||
|
contact.longitude!,
|
||||||
|
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addAll() {
|
||||||
|
final contacts = _connector.contacts;
|
||||||
|
for (var contact in contacts.toList()) {
|
||||||
|
if (contact.latitude == null || contact.longitude == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_addContact(
|
||||||
|
contact.name,
|
||||||
|
contact.latitude ?? 0.0,
|
||||||
|
contact.longitude ?? 0.0,
|
||||||
|
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> exportGPX(
|
||||||
|
String name,
|
||||||
|
String description,
|
||||||
|
String filename,
|
||||||
|
String shareText,
|
||||||
|
String subject,
|
||||||
|
) async {
|
||||||
|
if (_contacts.isEmpty) {
|
||||||
|
debugPrint("No repeaters to export – nothing to share.");
|
||||||
|
return gpxExportNoContacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Build GPX content (your existing logic – unchanged here)
|
||||||
|
final gpx = Gpx()
|
||||||
|
..version = '1.1'
|
||||||
|
..creator = 'meshcore-open exporter'
|
||||||
|
..metadata = Metadata(
|
||||||
|
name: name,
|
||||||
|
desc: description,
|
||||||
|
time: DateTime.now().toUtc(),
|
||||||
|
);
|
||||||
|
|
||||||
|
gpx.wpts = _contacts
|
||||||
|
.map(
|
||||||
|
(c) => Wpt(
|
||||||
|
lat: c.lat,
|
||||||
|
lon: c.lon,
|
||||||
|
ele: c.ele,
|
||||||
|
name: c.name,
|
||||||
|
desc: c.desc,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final xml = GpxWriter().asString(gpx, pretty: true);
|
||||||
|
|
||||||
|
// 2. Save to file
|
||||||
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
|
final timestamp = DateTime.now()
|
||||||
|
.toUtc()
|
||||||
|
.toIso8601String()
|
||||||
|
.replaceAll(':', '-')
|
||||||
|
.replaceAll('.', '-')
|
||||||
|
.split('T')
|
||||||
|
.join('_');
|
||||||
|
|
||||||
|
final path = '${dir.path}/$filename$timestamp.gpx';
|
||||||
|
|
||||||
|
final file = File(path);
|
||||||
|
await file.writeAsString(xml);
|
||||||
|
|
||||||
|
final result = await SharePlus.instance.share(
|
||||||
|
ShareParams(text: shareText, subject: subject, files: [XFile(path)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
await file.delete();
|
||||||
|
|
||||||
|
switch (result.status) {
|
||||||
|
case ShareResultStatus.success:
|
||||||
|
debugPrint('Share successful – user completed the action.');
|
||||||
|
return gpxExportSuccess;
|
||||||
|
case ShareResultStatus.dismissed:
|
||||||
|
debugPrint('Share sheet was dismissed / cancelled by user.');
|
||||||
|
return gpxExportCancelled;
|
||||||
|
case ShareResultStatus.unavailable:
|
||||||
|
debugPrint('Sharing is not available on this platform / context.');
|
||||||
|
return gpxExportNotAvailable;
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
debugPrint('Export or share failed: $e\n$stack');
|
||||||
|
}
|
||||||
|
return gpxExportFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:meshcore_open/screens/path_trace_map.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../connector/meshcore_connector.dart';
|
import '../connector/meshcore_connector.dart';
|
||||||
@@ -61,6 +62,19 @@ class _PathManagementDialog extends StatelessWidget {
|
|||||||
title: Text(l10n.chat_fullPath),
|
title: Text(l10n.chat_fullPath),
|
||||||
content: SelectableText(formattedPath),
|
content: SelectableText(formattedPath),
|
||||||
actions: [
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => PathTraceMapScreen(
|
||||||
|
title: context.l10n.contacts_repeaterPathTrace,
|
||||||
|
path: Uint8List.fromList(pathBytes),
|
||||||
|
flipPathRound: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(context.l10n.contacts_pathTrace),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: Text(l10n.common_close),
|
child: Text(l10n.common_close),
|
||||||
|
|||||||
@@ -1,240 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import '../connector/meshcore_connector.dart';
|
|
||||||
import '../connector/meshcore_protocol.dart';
|
|
||||||
import '../models/contact.dart';
|
|
||||||
import '../widgets/snr_indicator.dart';
|
|
||||||
import '../l10n/l10n.dart';
|
|
||||||
|
|
||||||
class PathTraceDialog extends StatefulWidget {
|
|
||||||
const PathTraceDialog({super.key, required this.title, required this.path});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
final Uint8List path;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<PathTraceDialog> createState() => _PathTraceDialogState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PathTraceDialogState extends State<PathTraceDialog> {
|
|
||||||
StreamSubscription<Uint8List>? _frameSubscription;
|
|
||||||
Timer? _timeoutTimer;
|
|
||||||
|
|
||||||
bool _isLoading = false;
|
|
||||||
bool _failed2Loaded = false;
|
|
||||||
bool _hasData = false;
|
|
||||||
Uint8List _pathData = Uint8List(0);
|
|
||||||
Uint8List _snrData = Uint8List(0);
|
|
||||||
Map<int, Contact> _pathContacts = {};
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_setupFrameListener();
|
|
||||||
_doPathTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_frameSubscription?.cancel();
|
|
||||||
_timeoutTimer?.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _doPathTrace() async {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = true;
|
|
||||||
_failed2Loaded = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
|
||||||
final frame = buildTraceReq(
|
|
||||||
DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
|
||||||
0, //flags
|
|
||||||
0, //auth
|
|
||||||
payload: widget.path,
|
|
||||||
);
|
|
||||||
connector.sendFrame(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setupFrameListener() {
|
|
||||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
|
||||||
Uint8List tagData = Uint8List(4);
|
|
||||||
// Listen for incoming text messages from the repeater
|
|
||||||
_frameSubscription = connector.receivedFrames.listen((frame) {
|
|
||||||
if (frame.isEmpty) return;
|
|
||||||
final frameBuffer = BufferReader(frame);
|
|
||||||
final code = frameBuffer.readUInt8();
|
|
||||||
|
|
||||||
if (code == respCodeSent) {
|
|
||||||
frameBuffer.skipBytes(1); //reserved
|
|
||||||
tagData = frameBuffer.readBytes(4);
|
|
||||||
final timeoutSeconds = frameBuffer.readUInt32LE();
|
|
||||||
|
|
||||||
// Start timeout timer for trace response
|
|
||||||
_timeoutTimer?.cancel();
|
|
||||||
_timeoutTimer = Timer(Duration(milliseconds: timeoutSeconds), () {
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
_failed2Loaded = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's a binary response
|
|
||||||
if (code == pushCodeTraceData &&
|
|
||||||
listEquals(frame.sublist(4, 8), tagData)) {
|
|
||||||
_timeoutTimer?.cancel();
|
|
||||||
if (!mounted) return;
|
|
||||||
frameBuffer.skipBytes(3); //reserved + path length + flag
|
|
||||||
if (listEquals(frameBuffer.readBytes(4), tagData)) {
|
|
||||||
_handleTraceResponse(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleTraceResponse(Uint8List frame) async {
|
|
||||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
|
||||||
|
|
||||||
final buffer = BufferReader(frame);
|
|
||||||
buffer.skipBytes(2); // Skip push code and reserved byte
|
|
||||||
int pathLength = buffer.readUInt8();
|
|
||||||
buffer.skipBytes(5); // Skip Flag byte and tag data
|
|
||||||
buffer.skipBytes(4); // Skip auth code
|
|
||||||
Uint8List pathData = buffer.readBytes(pathLength);
|
|
||||||
Uint8List snrData = buffer.readRemainingBytes();
|
|
||||||
|
|
||||||
Map<int, Contact> pathContacts = {};
|
|
||||||
|
|
||||||
connector.contacts.where((c) => c.type != advTypeChat).forEach((repeater) {
|
|
||||||
for (var neighbourData in pathData) {
|
|
||||||
if (listEquals(
|
|
||||||
repeater.publicKey.sublist(0, 1),
|
|
||||||
Uint8List.fromList([neighbourData]),
|
|
||||||
)) {
|
|
||||||
pathContacts[neighbourData] = repeater;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
_hasData = true;
|
|
||||||
_pathData = pathData;
|
|
||||||
_snrData = snrData;
|
|
||||||
_pathContacts = pathContacts;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatDirectionText(int index) {
|
|
||||||
if (index == 0 || index == _snrData.length - 1) {
|
|
||||||
if (index == 0) {
|
|
||||||
return context.l10n.pathTrace_you;
|
|
||||||
} else {
|
|
||||||
return _pathContacts[_pathData[_pathData.length - 1]]?.name ??
|
|
||||||
"0x${_pathData[_pathData.length - 1].toRadixString(16).toUpperCase()}";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return _pathContacts[_pathData[index - 1]]?.name ??
|
|
||||||
"0x${_pathData[index - 1].toRadixString(16).toUpperCase()}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatDirectionSubText(int index) {
|
|
||||||
if (index == 0 || index == _snrData.length - 1) {
|
|
||||||
if (index == 0) {
|
|
||||||
return _pathContacts[_pathData[0]]?.name ??
|
|
||||||
"0x${_pathData[0].toRadixString(16).toUpperCase()}";
|
|
||||||
} else {
|
|
||||||
return context.l10n.pathTrace_you;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return _pathContacts[_pathData[index]]?.name ??
|
|
||||||
"0x${_pathData[index].toRadixString(16).toUpperCase()}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l10n = context.l10n;
|
|
||||||
return AlertDialog(
|
|
||||||
title: Column(
|
|
||||||
children: [
|
|
||||||
FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
child: Text(widget.title, style: const TextStyle(fontSize: 24)),
|
|
||||||
),
|
|
||||||
if (_failed2Loaded)
|
|
||||||
Text(
|
|
||||||
l10n.pathTrace_failed,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
content: SafeArea(
|
|
||||||
child: RefreshIndicator(
|
|
||||||
onRefresh: _doPathTrace,
|
|
||||||
child: !_hasData
|
|
||||||
? Center(child: Text(l10n.pathTrace_notAvailable))
|
|
||||||
: ListView.builder(
|
|
||||||
itemCount: _snrData.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: index >= _snrData.length / 2
|
|
||||||
? Icon(Icons.call_received)
|
|
||||||
: Icon(Icons.call_made),
|
|
||||||
title: Text(
|
|
||||||
formatDirectionText(index),
|
|
||||||
style: const TextStyle(fontSize: 14),
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
formatDirectionSubText(index),
|
|
||||||
style: const TextStyle(fontSize: 14),
|
|
||||||
),
|
|
||||||
trailing: SNRIcon(
|
|
||||||
snr: _snrData[index].toSigned(8) / 4.0,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
// Handle item tap
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (index < _snrData.length - 1)
|
|
||||||
const Divider(height: 0.0),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: _isLoading
|
|
||||||
? const SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
|
||||||
)
|
|
||||||
: const Icon(Icons.refresh),
|
|
||||||
onPressed: _isLoading ? null : _doPathTrace,
|
|
||||||
tooltip: l10n.pathTrace_refreshTooltip,
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: Text(l10n.common_close),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,7 @@ import flutter_blue_plus_darwin
|
|||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
import mobile_scanner
|
import mobile_scanner
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
@@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
|||||||
+49
-1
@@ -121,6 +121,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.5+2"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -341,6 +349,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
gpx:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: gpx
|
||||||
|
sha256: f5b12b86402c639079243600ee2b3afd85cd08d26117fc8885cf48efce471d8e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
hooks:
|
hooks:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -501,6 +517,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
mobile_scanner:
|
mobile_scanner:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -566,7 +590,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
@@ -701,6 +725,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
|
quiver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: quiver
|
||||||
|
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -709,6 +741,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.28.0"
|
version: "0.28.0"
|
||||||
|
share_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: share_plus
|
||||||
|
sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "12.0.1"
|
||||||
|
share_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: share_plus_platform_interface
|
||||||
|
sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
+4
-1
@@ -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
|
# 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
|
# 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.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 5.0.0+5
|
version: 5.0.0+6
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.9.2
|
sdk: ^3.9.2
|
||||||
@@ -57,6 +57,9 @@ dependencies:
|
|||||||
qr_flutter: ^4.1.0 # QR code generation
|
qr_flutter: ^4.1.0 # QR code generation
|
||||||
url_launcher: ^6.3.0 # Launch URLs in system browser
|
url_launcher: ^6.3.0 # Launch URLs in system browser
|
||||||
flutter_linkify: ^6.0.0 # Auto-detect and linkify URLs in text
|
flutter_linkify: ^6.0.0 # Auto-detect and linkify URLs in text
|
||||||
|
gpx: ^2.3.0
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
share_plus: ^12.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -7,11 +7,14 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <flutter_blue_plus_winrt/flutter_blue_plus_plugin.h>
|
#include <flutter_blue_plus_winrt/flutter_blue_plus_plugin.h>
|
||||||
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
FlutterBluePlusPluginRegisterWithRegistrar(
|
FlutterBluePlusPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterBluePlusPlugin"));
|
registry->GetRegistrarForPlugin("FlutterBluePlusPlugin"));
|
||||||
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
flutter_blue_plus_winrt
|
flutter_blue_plus_winrt
|
||||||
|
share_plus
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user