diff --git a/.github/workflows/flutter_dart.yml b/.github/workflows/flutter_dart.yml index 5b68956b..117eb4f0 100644 --- a/.github/workflows/flutter_dart.yml +++ b/.github/workflows/flutter_dart.yml @@ -1,4 +1,4 @@ -name: Flutter and Dart Analysis +name: Flutter and Dart on: pull_request: @@ -7,7 +7,7 @@ on: - main jobs: - analyze_and_format: + analyze: runs-on: ubuntu-latest steps: - name: Checkout @@ -26,3 +26,6 @@ jobs: - name: Verify formatting run: dart format --output=none --set-exit-if-changed . + + - name: Run tests + run: flutter test -r github diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 460bc9d1..01afb0cb 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1568,5 +1568,29 @@ "contacts_contactAdvertCopied": "Рекламата е копирана в клипборда.", "contacts_zeroHopContactAdvertFailed": "Неуспешно изпращане на контакт.", "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": "Един или повече от хмелите липсва местоположение!" + } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 0a72559c..3dcd0ca1 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1568,5 +1568,50 @@ "contacts_zeroHopContactAdvertFailed": "Kontakt konnte nicht gesendet werden.", "contacts_zeroHopContactAdvertSent": "Kontakt über Anzeige gesendet", "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!" + } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c91345d7..e602719a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1317,6 +1317,7 @@ "pathTrace_failed": "Path trace failed.", "pathTrace_notAvailable": "Path trace not available.", "pathTrace_refreshTooltip": "Refresh Path Trace.", + "pathTrace_someHopsNoLocation": "One or more of the hops is missing a location!", "contacts_pathTrace": "Path Trace", "contacts_ping": "Ping", "contacts_repeaterPathTrace": "Path trace to repeater", @@ -1344,5 +1345,48 @@ "contacts_zeroHopContactAdvertSent": "Sent contact by advert.", "contacts_zeroHopContactAdvertFailed": "Failed to send contact.", "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" } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index c6dad1ff..3d5ab639 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1568,5 +1568,50 @@ "contacts_zeroHopContactAdvertFailed": "No se pudo enviar el contacto.", "contacts_zeroHopContactAdvertSent": "Envió contacto por anuncio.", "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" + } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index c1157ed3..044b8060 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1568,5 +1568,29 @@ "contacts_contactAdvertCopied": "Annonce copiée dans le presse-papiers.", "contacts_contactAdvertCopyFailed": "La copie de l'annonce vers le presse-papiers a échoué.", "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 !" + } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index c32e8638..dd9c3730 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1568,5 +1568,29 @@ "contacts_contactAdvertCopyFailed": "Copia dell'annuncio nella Clipboard non riuscita.", "contacts_ShareContactZeroHop": "Condividi contatto tramite annuncio", "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!" + } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 33aa4032..cd742e50 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4730,6 +4730,12 @@ abstract class AppLocalizations { /// **'Refresh Path Trace.'** 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. /// /// In en, this message translates to: @@ -4861,6 +4867,132 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Copying advert to Clipboard failed.'** 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 diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index e373a156..0ffd2452 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -2698,6 +2698,10 @@ class AppLocalizationsBg extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Обнови Path Trace.'; + @override + String get pathTrace_someHopsNoLocation => + 'Един или повече от хмелите липсва местоположение!'; + @override String get contacts_pathTrace => 'Пътен проследяване'; @@ -2769,4 +2773,102 @@ class AppLocalizationsBg extends AppLocalizations { @override 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'; } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 779a5344..0877e474 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2702,6 +2702,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Path Trace aktualisieren.'; + @override + String get pathTrace_someHopsNoLocation => + 'Eine oder mehrere der Hopfen fehlen einen Standort!'; + @override String get contacts_pathTrace => 'Pfadverfolgung'; @@ -2776,4 +2780,102 @@ class AppLocalizationsDe extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 29f83919..d780de42 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2658,6 +2658,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Refresh Path Trace.'; + @override + String get pathTrace_someHopsNoLocation => + 'One or more of the hops is missing a location!'; + @override String get contacts_pathTrace => 'Path Trace'; @@ -2726,4 +2730,101 @@ class AppLocalizationsEn extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 7f61ee25..4b9e62e5 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -2697,6 +2697,10 @@ class AppLocalizationsEs extends AppLocalizations { @override 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 String get contacts_pathTrace => 'Rastreo de caminos'; @@ -2769,4 +2773,102 @@ class AppLocalizationsEs extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index d858934e..a276cac5 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2714,6 +2714,10 @@ class AppLocalizationsFr extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Actualiser Path Trace'; + @override + String get pathTrace_someHopsNoLocation => + 'Une ou plusieurs des houblons manquent d\'une localisation !'; + @override String get contacts_pathTrace => 'Traçage de chemin'; @@ -2790,4 +2794,105 @@ class AppLocalizationsFr extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 9786262d..72c37b76 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -2698,6 +2698,10 @@ class AppLocalizationsIt extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Aggiorna Path Trace.'; + @override + String get pathTrace_someHopsNoLocation => + 'Uno o più dei luppoli mancano di una posizione!'; + @override String get contacts_pathTrace => 'Traccia Percorso'; @@ -2772,4 +2776,103 @@ class AppLocalizationsIt extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 74f2abae..82b650e0 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -2688,6 +2688,10 @@ class AppLocalizationsNl extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Path Trace vernieuwen.'; + @override + String get pathTrace_someHopsNoLocation => + 'Een of meer van de hops ontbreken een locatie!'; + @override String get contacts_pathTrace => 'Pad Traceren'; @@ -2761,4 +2765,101 @@ class AppLocalizationsNl extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 13bb1bf4..eaf9e7a1 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -2696,6 +2696,10 @@ class AppLocalizationsPl extends AppLocalizations { @override 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 String get contacts_pathTrace => 'Śledzenie Ścieżek'; @@ -2769,4 +2773,108 @@ class AppLocalizationsPl extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index c555be3b..8eddae30 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -2699,6 +2699,10 @@ class AppLocalizationsPt extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Atualizar Path Trace.'; + @override + String get pathTrace_someHopsNoLocation => + 'Um ou mais dos lúpulos estão sem localização!'; + @override String get contacts_pathTrace => 'Traçado de Caminho'; @@ -2771,4 +2775,101 @@ class AppLocalizationsPt extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 6e3a9d7c..cd96a6dd 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -2701,6 +2701,10 @@ class AppLocalizationsRu extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Обновить Path Trace'; + @override + String get pathTrace_someHopsNoLocation => + 'Одному или нескольким хмелям не указано местоположение!'; + @override String get contacts_pathTrace => 'Трассировка пути'; @@ -2776,4 +2780,107 @@ class AppLocalizationsRu extends AppLocalizations { @override 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'; } diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 65e41fcd..12ff841e 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -2684,6 +2684,10 @@ class AppLocalizationsSk extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Obnoviť Path Trace.'; + @override + String get pathTrace_someHopsNoLocation => + 'Jedna alebo viac chmeľov chýba lokalita!'; + @override String get contacts_pathTrace => 'Sledovanie lúčov'; @@ -2755,4 +2759,104 @@ class AppLocalizationsSk extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index c3c46aed..8dd78ff3 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -2687,6 +2687,10 @@ class AppLocalizationsSl extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Osveži Path Trace.'; + @override + String get pathTrace_someHopsNoLocation => + 'Ena ali več hmelju manjka lokacija!'; + @override String get contacts_pathTrace => 'Sledenje poti'; @@ -2757,4 +2761,107 @@ class AppLocalizationsSl extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index f8652ae2..55fbe06c 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -2672,6 +2672,10 @@ class AppLocalizationsSv extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Uppdatera Path Trace'; + @override + String get pathTrace_someHopsNoLocation => + 'En eller flera av humlen saknar en plats!'; + @override String get contacts_pathTrace => 'Path Trace'; @@ -2742,4 +2746,102 @@ class AppLocalizationsSv extends AppLocalizations { @override String get contacts_contactAdvertCopyFailed => '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'; } diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index cfb88d58..706f50de 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -2708,6 +2708,10 @@ class AppLocalizationsUk extends AppLocalizations { @override String get pathTrace_refreshTooltip => 'Оновити трасування шляху'; + @override + String get pathTrace_someHopsNoLocation => + 'Одне або більше хмелів відсутнє місце розташування!'; + @override String get contacts_pathTrace => 'Трасування шляхів'; @@ -2782,4 +2786,107 @@ class AppLocalizationsUk extends AppLocalizations { @override 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'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index d6dc2ff4..2d5b1c74 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -2557,6 +2557,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get pathTrace_refreshTooltip => '重新绘制路径。'; + @override + String get pathTrace_someHopsNoLocation => '其中一个或多个啤酒花缺少位置!'; + @override String get contacts_pathTrace => '路径追踪'; @@ -2624,4 +2627,75 @@ class AppLocalizationsZh extends AppLocalizations { @override 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 地图数据导出'; } diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index e94deb37..91163ac0 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1568,5 +1568,29 @@ "contacts_contactAdvertCopyFailed": "Kopiëren van advertentie naar Clipboard is mislukt.", "contacts_ShareContact": "Kontakt naar Klembord kopiëren", "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!" + } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 44552c3c..3c2a96fc 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1568,5 +1568,29 @@ "contacts_contactAdvertCopyFailed": "Kopiowanie ogłoszenia do schowka nie powiodło się.", "contacts_ShareContactZeroHop": "Udostępnij kontakt przez ogłoszenie", "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!" + } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 56a7f2b2..dc38c115 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1568,5 +1568,29 @@ "contacts_floodAdvert": "Anúncio de Inundação", "contacts_contactAdvertCopyFailed": "Cópia do anúncio para a Área de Transferência falhou.", "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!" + } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 0bca5ef0..ddbbe79d 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -808,5 +808,29 @@ "contacts_contactAdvertCopyFailed": "Копирование рекламы в буфер обмена не удалось.", "contacts_addContactFromClipboard": "Добавить контакт из буфера обмена", "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": "Одному или нескольким хмелям не указано местоположение!" + } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index d61cca61..c09502a3 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1568,5 +1568,29 @@ "contacts_contactAdvertCopyFailed": "Kopírovanie inzerátu do schránky zlyhalo.", "contacts_zeroHopContactAdvertFailed": "Zlyhalo odoslanie kontaktu.", "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!" + } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index cbc4e3f6..97a396a1 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1568,5 +1568,29 @@ "contacts_contactAdvertCopied": "Oglas je bil kopiran v odložišče.", "contacts_contactAdvertCopyFailed": "Kopiranje oglasa v odložišče je spodletelo.", "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!" + } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 05f77cb8..6df28bdf 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1568,5 +1568,29 @@ "contacts_contactAdvertCopyFailed": "Kopiering av annons till Urklipp misslyckades.", "contacts_ShareContact": "Kopiera kontakt till Urklipp", "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!" + } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 35edeee3..0e63785e 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1569,5 +1569,29 @@ "contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало", "contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням", "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": "Одне або більше хмелів відсутнє місце розташування!" + } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index c941461a..8c65510a 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1568,5 +1568,29 @@ "contacts_zeroHopContactAdvertSent": "通过广告获取联系方式。", "contacts_zeroHopContactAdvertFailed": "发送联系方式失败。", "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": "其中一个或多个啤酒花缺少位置!" + } diff --git a/lib/main.dart b/lib/main.dart index 96a853dd..8ee0ca47 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -150,6 +150,12 @@ class MeshCoreApp extends StatelessWidget { themeMode: _themeModeFromSetting( 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(), ); }, diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index 970c152a..8dea4758 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; +import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; @@ -41,6 +42,21 @@ class ChannelMessagePathScreen extends StatelessWidget { appBar: AppBar( title: Text(l10n.channelPath_title), 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( icon: const Icon(Icons.map_outlined), tooltip: l10n.channelPath_viewMap, @@ -263,6 +279,7 @@ class ChannelMessagePathMapScreen extends StatefulWidget { class _ChannelMessagePathMapScreenState extends State { Uint8List? _selectedPath; + double _pathDistance = 0.0; @override void initState() { @@ -282,6 +299,17 @@ class _ChannelMessagePathMapScreenState } } + double _getPathDistance(List 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 Widget build(BuildContext context) { return Consumer( @@ -306,10 +334,15 @@ class _ChannelMessagePathMapScreenState connector.contacts, context.l10n, ); - final points = hops - .where((hop) => hop.hasLocation) - .map((hop) => hop.position!) - .toList(); + + final points = []; + for (final hop in hops) { + if (hop.hasLocation) { + points.add(hop.position!); + } + } + points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!)); + final polylines = points.length > 1 ? [ Polyline( @@ -327,7 +360,10 @@ class _ChannelMessagePathMapScreenState final bounds = points.length > 1 ? LatLngBounds.fromPoints(points) : null; - final mapKey = ValueKey(_formatPathPrefixes(selectedPath)); + final mapKey = ValueKey( + '${_formatPathPrefixes(selectedPath)},${context.l10n.pathTrace_you}', + ); + _pathDistance = _getPathDistance(points); return Scaffold( appBar: AppBar(title: Text(context.l10n.channelPath_mapTitle)), @@ -349,6 +385,9 @@ class _ChannelMessagePathMapScreenState ), minZoom: 2.0, maxZoom: 18.0, + interactionOptions: InteractionOptions( + flags: ~InteractiveFlag.rotate, + ), ), children: [ TileLayer( @@ -461,8 +500,8 @@ class _ChannelMessagePathMapScreenState if (hop.hasLocation) Marker( point: hop.position!, - width: 40, - height: 40, + width: 35, + height: 35, child: Container( decoration: BoxDecoration( color: Colors.green, @@ -487,6 +526,39 @@ class _ChannelMessagePathMapScreenState ), ), ), + if (context.read().selfLatitude != null && + context.read().selfLongitude != null) + Marker( + point: LatLng( + context.read().selfLatitude!, + context.read().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: const EdgeInsets.all(12), 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), ), ), diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 3477361c..f00f242a 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; +import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:provider/provider.dart'; import 'package:latlong2/latlong.dart'; @@ -701,6 +702,19 @@ class _ChatScreenState extends State { title: Text(context.l10n.chat_fullPath), content: SelectableText(formattedPath), 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( onPressed: () => Navigator.pop(context), child: Text(context.l10n.common_close), diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index ac157356..394970f9 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:meshcore_open/widgets/path_trace_dialog.dart'; import 'package:flutter/services.dart'; +import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; @@ -1009,16 +1009,16 @@ class _ContactsScreenState extends State ? Text(context.l10n.contacts_pathTrace) : Text(context.l10n.contacts_ping), onTap: () { - showDialog( - context: context, - builder: (context) { - return PathTraceDialog( + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PathTraceMapScreen( title: contact.pathLength > 0 ? context.l10n.contacts_repeaterPathTrace : context.l10n.contacts_repeaterPing, path: contact.traceRouteBytes ?? Uint8List(0), - ); - }, + ), + ), ); }, ), @@ -1037,16 +1037,16 @@ class _ContactsScreenState extends State ? Text(context.l10n.contacts_pathTrace) : Text(context.l10n.contacts_ping), onTap: () { - showDialog( - context: context, - builder: (context) { - return PathTraceDialog( + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PathTraceMapScreen( title: contact.pathLength > 0 ? context.l10n.contacts_roomPathTrace : context.l10n.contacts_roomPing, path: contact.traceRouteBytes ?? Uint8List(0), - ); - }, + ), + ), ); }, ), @@ -1079,16 +1079,16 @@ class _ContactsScreenState extends State leading: const Icon(Icons.radar, color: Colors.green), title: Text(context.l10n.contacts_chatTraceRoute), onTap: () { - showDialog( - context: context, - builder: (context) { - return PathTraceDialog( + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PathTraceMapScreen( title: context.l10n.contacts_pathTraceTo( contact.name, ), path: contact.traceRouteBytes ?? Uint8List(0), - ); - }, + ), + ), ); }, ), diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 4253391f..2e51047b 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -349,6 +349,43 @@ class _MapScreenState extends State { ), ..._buildMarkers(contactsWithLocation, settings), ...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 { final marker = Marker( point: LatLng(contact.latitude!, contact.longitude!), - width: 80, - height: 80, + width: 35, + height: 35, child: GestureDetector( onTap: () => _showNodeInfo(context, contact), child: Column( children: [ Container( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: _getNodeColor(contact.type), shape: BoxShape.circle, @@ -419,7 +456,7 @@ class _MapScreenState extends State { child: Icon( _getNodeIcon(contact.type), color: Colors.white, - size: 24, + size: 20, ), ), ], diff --git a/lib/screens/path_trace_map.dart b/lib/screens/path_trace_map.dart new file mode 100644 index 00000000..39de31e9 --- /dev/null +++ b/lib/screens/path_trace_map.dart @@ -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 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 createState() => _PathTraceMapScreenState(); +} + +class _PathTraceMapScreenState extends State { + StreamSubscription? _frameSubscription; + Timer? _timeoutTimer; + + bool _isLoading = false; + bool _failed2Loaded = false; + bool _hasData = false; + bool _noLocationErr = false; + PathTraceData? _traceData; + List _points = []; + List _polylines = []; + LatLng? _initialCenter = LatLng(0, 0); + double _initialZoom = 2.0; + LatLngBounds? _bounds; + ValueKey _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 _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(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(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 _handleTraceResponse(Uint8List frame) async { + final connector = Provider.of(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 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 = []; + _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, + ), + ] + : []; + + _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( + builder: (context, connector, _) { + final tileCache = context.read(); + + 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 _buildHopMarkers(List 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().selfLatitude != null && + context.read().selfLongitude != null) + Marker( + point: LatLng( + context.read().selfLatitude!, + context.read().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 + }, + ), + ], + ); + }, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 415d5081..2212b8db 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:meshcore_open/utils/gpx_export.dart'; import 'package:meshcore_open/widgets/elements_ui.dart'; import 'package:provider/provider.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -57,6 +58,8 @@ class _SettingsScreenState extends State { const SizedBox(height: 16), _buildDebugCard(context), const SizedBox(height: 16), + _buildExportCard(connector), + const SizedBox(height: 16), _buildAboutCard(context), ], ); @@ -684,6 +687,110 @@ class _SettingsScreenState extends State { ], ); } + + _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 { diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index ea7f031d..57331aa2 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -1,6 +1,10 @@ +import 'dart:ui'; + import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter/foundation.dart'; +import '../l10n/app_localizations.dart'; + class NotificationService { static final NotificationService _instance = NotificationService._internal(); factory NotificationService() => _instance; @@ -10,6 +14,34 @@ class NotificationService { FlutterLocalNotificationsPlugin(); 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 initialize() async { if (_isInitialized) return; @@ -76,7 +108,7 @@ class NotificationService { return true; } - Future showMessageNotification({ + Future _showMessageNotificationImpl({ required String contactName, required String message, String? contactId, @@ -118,14 +150,14 @@ class NotificationService { await _notifications.show( contactId?.hashCode ?? 0, - 'New message from $contactName', + contactName, message, notificationDetails, payload: 'message:$contactId', ); } - Future showAdvertNotification({ + Future _showAdvertNotificationImpl({ required String contactName, required String contactType, String? contactId, @@ -163,14 +195,14 @@ class NotificationService { await _notifications.show( contactId?.hashCode ?? DateTime.now().millisecondsSinceEpoch, - 'New $contactType discovered', + _l10n.notification_newTypeDiscovered(contactType), contactName, notificationDetails, payload: 'advert:$contactId', ); } - Future showChannelMessageNotification({ + Future _showChannelMessageNotificationImpl({ required String channelName, required String message, int? channelIndex, @@ -211,7 +243,9 @@ class NotificationService { ); final preview = message.trim(); - final body = preview.isEmpty ? 'Received new message' : preview; + final body = preview.isEmpty + ? _l10n.notification_receivedNewMessage + : preview; await _notifications.show( 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) { final payload = response.payload; if (payload != null) { @@ -238,4 +287,212 @@ class NotificationService { Future cancel(int id) async { await _notifications.cancel(id); } + + // ───────────────────────────────────────────────────────────────── + // Public notification methods (rate limiting is enforced automatically) + // ───────────────────────────────────────────────────────────────── + + Future 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 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 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 _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 _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 _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 = []; + 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, + }); } diff --git a/lib/utils/gpx_export.dart b/lib/utils/gpx_export.dart new file mode 100644 index 00000000..595479c1 --- /dev/null +++ b/lib/utils/gpx_export.dart @@ -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 _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 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; + } +} diff --git a/lib/widgets/path_management_dialog.dart b/lib/widgets/path_management_dialog.dart index f47b017d..483697f1 100644 --- a/lib/widgets/path_management_dialog.dart +++ b/lib/widgets/path_management_dialog.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; @@ -61,6 +62,19 @@ class _PathManagementDialog extends StatelessWidget { title: Text(l10n.chat_fullPath), content: SelectableText(formattedPath), 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( onPressed: () => Navigator.pop(context), child: Text(l10n.common_close), diff --git a/lib/widgets/path_trace_dialog.dart b/lib/widgets/path_trace_dialog.dart deleted file mode 100644 index 7294c86d..00000000 --- a/lib/widgets/path_trace_dialog.dart +++ /dev/null @@ -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 createState() => _PathTraceDialogState(); -} - -class _PathTraceDialogState extends State { - StreamSubscription? _frameSubscription; - Timer? _timeoutTimer; - - bool _isLoading = false; - bool _failed2Loaded = false; - bool _hasData = false; - Uint8List _pathData = Uint8List(0); - Uint8List _snrData = Uint8List(0); - Map _pathContacts = {}; - - @override - void initState() { - super.initState(); - _setupFrameListener(); - _doPathTrace(); - } - - @override - void dispose() { - _frameSubscription?.cancel(); - _timeoutTimer?.cancel(); - super.dispose(); - } - - Future _doPathTrace() async { - if (mounted) { - setState(() { - _isLoading = true; - _failed2Loaded = false; - }); - } - - final connector = Provider.of(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(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 _handleTraceResponse(Uint8List frame) async { - final connector = Provider.of(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 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), - ), - ], - ); - } -} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b4a41dd1..d2ea57e9 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,6 +9,7 @@ import flutter_blue_plus_darwin import flutter_local_notifications import mobile_scanner import package_info_plus +import share_plus import shared_preferences_foundation import sqflite_darwin import url_launcher_macos @@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 1e275d4f..fc116566 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -121,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: "direct main" description: @@ -341,6 +349,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + gpx: + dependency: "direct main" + description: + name: gpx + sha256: f5b12b86402c639079243600ee2b3afd85cd08d26117fc8885cf48efce471d8e + url: "https://pub.dev" + source: hosted + version: "2.3.0" hooks: dependency: transitive description: @@ -501,6 +517,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" mobile_scanner: dependency: "direct main" description: @@ -566,7 +590,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" @@ -701,6 +725,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" rxdart: dependency: transitive description: @@ -709,6 +741,22 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8b1415f6..6474c5f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 5.0.0+5 +version: 5.0.0+6 environment: sdk: ^3.9.2 @@ -57,6 +57,9 @@ dependencies: qr_flutter: ^4.1.0 # QR code generation url_launcher: ^6.3.0 # Launch URLs in system browser 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: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index eeb548fa..cd4fc19b 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,11 +7,14 @@ #include "generated_plugin_registrant.h" #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterBluePlusPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterBluePlusPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 68825d8b..571addb8 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_blue_plus_winrt + share_plus url_launcher_windows )