From 7c768994aabfc1d454d50b9e166bd0f2935b7758 Mon Sep 17 00:00:00 2001 From: ericz Date: Fri, 10 Apr 2026 22:58:57 +0200 Subject: [PATCH] import export function for discovered contacts. --- lib/connector/meshcore_connector.dart | 20 +++ lib/l10n/app_bg.arb | 35 +++++ lib/l10n/app_de.arb | 35 +++++ lib/l10n/app_en.arb | 36 ++++- lib/l10n/app_es.arb | 35 +++++ lib/l10n/app_fr.arb | 35 +++++ lib/l10n/app_hu.arb | 35 +++++ lib/l10n/app_it.arb | 35 +++++ lib/l10n/app_ja.arb | 35 +++++ lib/l10n/app_ko.arb | 35 +++++ lib/l10n/app_localizations.dart | 42 +++++ lib/l10n/app_localizations_bg.dart | 30 ++++ lib/l10n/app_localizations_de.dart | 30 ++++ lib/l10n/app_localizations_en.dart | 30 ++++ lib/l10n/app_localizations_es.dart | 30 ++++ lib/l10n/app_localizations_fr.dart | 30 ++++ lib/l10n/app_localizations_hu.dart | 30 ++++ lib/l10n/app_localizations_it.dart | 30 ++++ lib/l10n/app_localizations_ja.dart | 29 ++++ lib/l10n/app_localizations_ko.dart | 29 ++++ lib/l10n/app_localizations_nl.dart | 30 ++++ lib/l10n/app_localizations_pl.dart | 30 ++++ lib/l10n/app_localizations_pt.dart | 30 ++++ lib/l10n/app_localizations_ru.dart | 31 ++++ lib/l10n/app_localizations_sk.dart | 30 ++++ lib/l10n/app_localizations_sl.dart | 30 ++++ lib/l10n/app_localizations_sv.dart | 30 ++++ lib/l10n/app_localizations_uk.dart | 30 ++++ lib/l10n/app_localizations_zh.dart | 29 ++++ lib/l10n/app_nl.arb | 35 +++++ lib/l10n/app_pl.arb | 41 ++++- lib/l10n/app_pt.arb | 35 +++++ lib/l10n/app_ru.arb | 41 ++++- lib/l10n/app_sk.arb | 35 +++++ lib/l10n/app_sl.arb | 35 +++++ lib/l10n/app_sv.arb | 41 ++++- lib/l10n/app_uk.arb | 41 ++++- lib/l10n/app_zh.arb | 35 +++++ lib/models/contact.dart | 2 - lib/screens/discovery_screen.dart | 187 ++++++++++++++++++++++- lib/storage/contact_discovery_store.dart | 50 ++++++ pubspec.yaml | 1 + 42 files changed, 1462 insertions(+), 33 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index b432277..9d1578f 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -356,6 +356,26 @@ class MeshCoreConnector extends ChangeNotifier { return List.unmodifiable(_discoveredContacts); } + String exportDiscoveredContactsJson() { + return _discoveryContactStore.exportContactsJson(_discoveredContacts); + } + + Future importDiscoveredContactsJson(String json) async { + final newCount = _discoveryContactStore.importContactsJson( + json: json, + existingContacts: _discoveredContacts, + knownContactKeys: _knownContactKeys, + ); + + if (newCount == 0 && _discoveredContacts.isEmpty) { + return 0; + } + + await _persistDiscoveredContacts(); + notifyListeners(); + return newCount; + } + List get channels => List.unmodifiable(_channels); bool get isConnected => _state == MeshCoreConnectionState.connected; bool get isLoadingContacts => _isLoadingContacts; diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7ac5417..7d6c6f1 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1824,6 +1824,41 @@ "discoveredContacts_copyContact": "Копирай контакт в клипборда", "discoveredContacts_deleteContact": "Изтрий контакт", "discoveredContacts_addContact": "Добави контакт", + "discoveredContacts_export": "Експортирай откритите контакти", + "discoveredContacts_import": "Импортирай откритите контакти", + "discoveredContacts_exported": "Откритите контакти са експортирани в {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Неуспешно експортиране на откритите контакти: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "Импортирани са {count} открити контакта.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "Във файла за импортиране не са намерени контакти.", + "discoveredContacts_importFailed": "Неуспешно импортиране на откритите контакти: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "Когато списъкът с контакти е пълен, най-старият неключов контакт ще бъде заменен.", "discoveredContacts_deleteContactAll": "Изтриване на Всички Открити Контакти", "discoveredContacts_deleteContactAllContent": "Сигурни ли сте, че искате да изтриете всички открити контакти?", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 4695505..ee34b3d 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1856,6 +1856,41 @@ "common_deleteAll": "Alles löschen", "discoveredContacts_deleteContactAllContent": "Sind Sie sicher, dass Sie alle gefundenen Kontakte löschen möchten?", "discoveredContacts_deleteContactAll": "Alle entdeckten Kontakte löschen", + "discoveredContacts_export": "Entdeckte Kontakte exportieren", + "discoveredContacts_import": "Entdeckte Kontakte importieren", + "discoveredContacts_exported": "Entdeckte Kontakte exportiert nach {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Export von entdeckten Kontakten fehlgeschlagen: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "{count} entdeckte Kontakte importiert.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "Keine Kontakte in Importdatei gefunden.", + "discoveredContacts_importFailed": "Import von entdeckten Kontakten fehlgeschlagen: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "map_showGuessedLocations": "Zeige die vermuteten Knotenpositionen", "map_guessedLocation": "Geschätzter Ort", "usbScreenSubtitle": "Wählen Sie ein erkannten serielles Gerät aus und verbinden Sie es direkt mit Ihrem MeshCore-Knoten.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8ad6bf3..0dc9f82 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1922,6 +1922,41 @@ "contacts_invalidAdvertFormat": "Invalid contact data", "contacts_contactImported": "Contact has been imported.", "contacts_contactImportFailed": "Failed to import contact.", + "discoveredContacts_export": "Export discovered contacts", + "discoveredContacts_import": "Import discovered contacts", + "discoveredContacts_exported": "Exported discovered contacts to {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Failed to export discovered contacts: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "Imported {count} discovered contacts.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "No contacts found in import file.", + "discoveredContacts_importFailed": "Failed to import discovered contacts: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contacts_zeroHopAdvert": "Zero Hop Advert", "contacts_floodAdvert": "Flood Advert", "contacts_copyAdvertToClipboard": "Copy Advert to Clipboard", @@ -2068,7 +2103,6 @@ "radioStats_stripWaiting": "Fetching radio stats…", "radioStats_settingsTile": "Radio stats", "radioStats_settingsSubtitle": "Noise floor, RSSI, SNR, and airtime", - "translation_title": "Translation", "translation_enableTitle": "Enable translation", "translation_enableSubtitle": "Translate incoming messages and allow pre-send translation.", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ac9527b..d6540bd 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1856,6 +1856,41 @@ "common_deleteAll": "Eliminar todo", "discoveredContacts_deleteContactAll": "Eliminar Todos los Contactos Descubiertos", "discoveredContacts_deleteContactAllContent": "¿Está seguro de que desea eliminar todos los contactos descubiertos!", + "discoveredContacts_export": "Exportar contactos descubiertos", + "discoveredContacts_import": "Importar contactos descubiertos", + "discoveredContacts_exported": "Contactos descubiertos exportados a {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Error al exportar contactos descubiertos: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "{count} contactos descubiertos importados.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "No se encontraron contactos en el archivo de importación.", + "discoveredContacts_importFailed": "Error al importar contactos descubiertos: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "map_guessedLocation": "Ubicación estimada", "map_showGuessedLocations": "Mostrar las ubicaciones estimadas de los nodos.", "usbScreenTitle": "Conecte mediante USB", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a942aa2..9f43c78 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1828,6 +1828,41 @@ "common_deleteAll": "Supprimer tout", "discoveredContacts_deleteContactAll": "Supprimer tous les contacts découverts", "discoveredContacts_deleteContactAllContent": "Êtes-vous sûr de vouloir supprimer tous les contacts découverts ?", + "discoveredContacts_export": "Exporter les contacts découverts", + "discoveredContacts_import": "Importer les contacts découverts", + "discoveredContacts_exported": "Contacts découverts exportés vers {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Impossible d'exporter les contacts découverts: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "{count} contacts découverts importés.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "Aucun contact trouvé dans le fichier d'importation.", + "discoveredContacts_importFailed": "Impossible d'importer les contacts découverts: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "map_showGuessedLocations": "Afficher les emplacements des nœuds estimés", "map_guessedLocation": "Lieu deviné", "connectionChoiceUsbLabel": "USB", diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 6f43463..dbad19c 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -1949,6 +1949,41 @@ "discoveredContacts_deleteContact": "Törölj a feltalált kapcsolatot", "discoveredContacts_deleteContactAll": "Törölj minden megtalált kapcsolatot", "discoveredContacts_deleteContactAllContent": "Biztos, hogy szeretné törölni az összes eddig megtalált kapcsolatot?", + "discoveredContacts_export": "Felfedezett kapcsolatok exportálása", + "discoveredContacts_import": "Felfedezett kapcsolatok importálása", + "discoveredContacts_exported": "A felfedezett kapcsolatok exportálva ide: {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "A felfedezett kapcsolatok exportálása nem sikerült: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "{count} felfedezett kapcsolat importálva.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "Az importfájlban nem találhatók kapcsolatok.", + "discoveredContacts_importFailed": "A felfedezett kapcsolatok importálása nem sikerült: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "chat_sendCooldown": "Kérjük, várjon egy pillanatot, mielőtt újra elküldené.", "appSettings_jumpToOldestUnread": "Jelentkezzen az legörebb, olvasatlan üzenetre", "appSettings_jumpToOldestUnreadSubtitle": "Amikor egy új csevet indítunk, amelyben vannak olvashatatlan üzenetek, görgessük a listát, hogy a legelső, olvashatatlan üzenet megjelenjen, nem pedig az utolsó.", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 387c8cf..bb49546 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1824,6 +1824,41 @@ "discoveredContacts_contactAdded": "Contatto aggiunto", "discoveredContacts_deleteContact": "Elimina Contatto", "discoveredContacts_copyContact": "Copia contatto negli appunti", + "discoveredContacts_export": "Esporta contatti scoperti", + "discoveredContacts_import": "Importa contatti scoperti", + "discoveredContacts_exported": "Contatti scoperti esportati in {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Esportazione dei contatti scoperti non riuscita: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "Importati {count} contatti scoperti.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "Nessun contatto trovato nel file di importazione.", + "discoveredContacts_importFailed": "Importazione dei contatti scoperti non riuscita: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "Quando l'elenco dei contatti è pieno, il contatto più vecchio non tra i preferiti verrà sostituito.", "common_deleteAll": "Elimina tutto", "discoveredContacts_deleteContactAllContent": "Sei sicuro di voler eliminare tutti i contatti scoperti?", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index b63f146..4f66f2a 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -1949,6 +1949,41 @@ "discoveredContacts_deleteContact": "発見された連絡先を削除", "discoveredContacts_deleteContactAll": "発見されたすべての連絡先を削除", "discoveredContacts_deleteContactAllContent": "本当に、見つけたすべての連絡先を削除してもよろしいですか?", + "discoveredContacts_export": "発見済みの連絡先をエクスポート", + "discoveredContacts_import": "発見済みの連絡先をインポート", + "discoveredContacts_exported": "発見済みの連絡先を {path} にエクスポートしました。", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "発見済みの連絡先のエクスポートに失敗しました: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "{count} 件の発見済み連絡先をインポートしました。", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "インポートファイルに連絡先が見つかりませんでした。", + "discoveredContacts_importFailed": "発見済みの連絡先のインポートに失敗しました: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "chat_sendCooldown": "再度送信する前に、しばらくお待ちください。", "appSettings_jumpToOldestUnread": "最も古い未読のメッセージへ移動", "appSettings_jumpToOldestUnreadSubtitle": "未読メッセージがあるチャットを開く際、「最新のメッセージ」ではなく、最初に未読のメッセージまでスクロールしてください。", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 40721fe..2be500b 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -1949,6 +1949,41 @@ "discoveredContacts_deleteContact": "발견된 연락처 삭제", "discoveredContacts_deleteContactAll": "발견된 모든 연락처 삭제", "discoveredContacts_deleteContactAllContent": "정말로 모든 검색된 연락처를 삭제하시겠습니까?", + "discoveredContacts_export": "발견된 연락처 내보내기", + "discoveredContacts_import": "발견된 연락처 가져오기", + "discoveredContacts_exported": "발견된 연락처를 {path}(으)로 내보냈습니다.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "발견된 연락처 내보내기에 실패했습니다: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "발견된 연락처 {count}개를 가져왔습니다.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "가져오기 파일에서 연락처를 찾을 수 없습니다.", + "discoveredContacts_importFailed": "발견된 연락처 가져오기에 실패했습니다: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "chat_sendCooldown": "다시 보내기 전에 잠시 기다려 주시기 바랍니다.", "appSettings_jumpToOldestUnread": "가장 오래된, 아직 읽지 않은 항목으로 이동", "appSettings_jumpToOldestUnreadSubtitle": "새로운 메시지가 없는 채팅을 열 때, 최신 메시지가 아닌 첫 번째 읽지 않은 메시지로 스크롤하세요.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 2c1342d..e7673e2 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -5741,6 +5741,48 @@ abstract class AppLocalizations { /// **'Failed to import contact.'** String get contacts_contactImportFailed; + /// No description provided for @discoveredContacts_export. + /// + /// In en, this message translates to: + /// **'Export discovered contacts'** + String get discoveredContacts_export; + + /// No description provided for @discoveredContacts_import. + /// + /// In en, this message translates to: + /// **'Import discovered contacts'** + String get discoveredContacts_import; + + /// No description provided for @discoveredContacts_exported. + /// + /// In en, this message translates to: + /// **'Exported discovered contacts to {path}.'** + String discoveredContacts_exported(String path); + + /// No description provided for @discoveredContacts_exportFailed. + /// + /// In en, this message translates to: + /// **'Failed to export discovered contacts: {error}'** + String discoveredContacts_exportFailed(String error); + + /// No description provided for @discoveredContacts_imported. + /// + /// In en, this message translates to: + /// **'Imported {count} discovered contacts.'** + String discoveredContacts_imported(int count); + + /// No description provided for @discoveredContacts_importNoContacts. + /// + /// In en, this message translates to: + /// **'No contacts found in import file.'** + String get discoveredContacts_importNoContacts; + + /// No description provided for @discoveredContacts_importFailed. + /// + /// In en, this message translates to: + /// **'Failed to import discovered contacts: {error}'** + String discoveredContacts_importFailed(String error); + /// No description provided for @contacts_zeroHopAdvert. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index b3e1279..b9d8188 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -3296,6 +3296,36 @@ class AppLocalizationsBg extends AppLocalizations { String get contacts_contactImportFailed => 'Контактът не е успешно импортиран.'; + @override + String get discoveredContacts_export => 'Експортирай откритите контакти'; + + @override + String get discoveredContacts_import => 'Импортирай откритите контакти'; + + @override + String discoveredContacts_exported(String path) { + return 'Откритите контакти са експортирани в $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Неуспешно експортиране на откритите контакти: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return 'Импортирани са $count открити контакта.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'Във файла за импортиране не са намерени контакти.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Неуспешно импортиране на откритите контакти: $error'; + } + @override String get contacts_zeroHopAdvert => 'Реклама без скок'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d7c1691..2b70988 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -3301,6 +3301,36 @@ class AppLocalizationsDe extends AppLocalizations { String get contacts_contactImportFailed => 'Kontakt konnte nicht importiert werden'; + @override + String get discoveredContacts_export => 'Entdeckte Kontakte exportieren'; + + @override + String get discoveredContacts_import => 'Entdeckte Kontakte importieren'; + + @override + String discoveredContacts_exported(String path) { + return 'Entdeckte Kontakte exportiert nach $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Export von entdeckten Kontakten fehlgeschlagen: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return '$count entdeckte Kontakte importiert.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'Keine Kontakte in Importdatei gefunden.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Import von entdeckten Kontakten fehlgeschlagen: $error'; + } + @override String get contacts_zeroHopAdvert => 'Zero-Hop-Ankündigung'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a2a88b0..c1fe017 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3239,6 +3239,36 @@ class AppLocalizationsEn extends AppLocalizations { @override String get contacts_contactImportFailed => 'Failed to import contact.'; + @override + String get discoveredContacts_export => 'Export discovered contacts'; + + @override + String get discoveredContacts_import => 'Import discovered contacts'; + + @override + String discoveredContacts_exported(String path) { + return 'Exported discovered contacts to $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Failed to export discovered contacts: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return 'Imported $count discovered contacts.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'No contacts found in import file.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Failed to import discovered contacts: $error'; + } + @override String get contacts_zeroHopAdvert => 'Zero Hop Advert'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index a127012..b3e668a 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -3296,6 +3296,36 @@ class AppLocalizationsEs extends AppLocalizations { String get contacts_contactImportFailed => 'Contacto no se importó correctamente.'; + @override + String get discoveredContacts_export => 'Exportar contactos descubiertos'; + + @override + String get discoveredContacts_import => 'Importar contactos descubiertos'; + + @override + String discoveredContacts_exported(String path) { + return 'Contactos descubiertos exportados a $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Error al exportar contactos descubiertos: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return '$count contactos descubiertos importados.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'No se encontraron contactos en el archivo de importación.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Error al importar contactos descubiertos: $error'; + } + @override String get contacts_zeroHopAdvert => 'Anuncio de Zero Hop'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a006391..3bf68f6 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3313,6 +3313,36 @@ class AppLocalizationsFr extends AppLocalizations { String get contacts_contactImportFailed => 'Échec de l\'importation du contact.'; + @override + String get discoveredContacts_export => 'Exporter les contacts découverts'; + + @override + String get discoveredContacts_import => 'Importer les contacts découverts'; + + @override + String discoveredContacts_exported(String path) { + return 'Contacts découverts exportés vers $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Impossible d\'exporter les contacts découverts: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return '$count contacts découverts importés.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'Aucun contact trouvé dans le fichier d\'importation.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Impossible d\'importer les contacts découverts: $error'; + } + @override String get contacts_zeroHopAdvert => 'Annonce Zero saut'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 1ad8558..38caf80 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -3310,6 +3310,36 @@ class AppLocalizationsHu extends AppLocalizations { String get contacts_contactImportFailed => 'Nem sikerült a kapcsolatot importálni.'; + @override + String get discoveredContacts_export => 'Felfedezett kapcsolatok exportálása'; + + @override + String get discoveredContacts_import => 'Felfedezett kapcsolatok importálása'; + + @override + String discoveredContacts_exported(String path) { + return 'A felfedezett kapcsolatok exportálva ide: $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'A felfedezett kapcsolatok exportálása nem sikerült: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return '$count felfedezett kapcsolat importálva.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'Az importfájlban nem találhatók kapcsolatok.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'A felfedezett kapcsolatok importálása nem sikerült: $error'; + } + @override String get contacts_zeroHopAdvert => 'Zero Hop reklám'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 3a55559..31e616e 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -3299,6 +3299,36 @@ class AppLocalizationsIt extends AppLocalizations { String get contacts_contactImportFailed => 'Contatto non importato con successo.'; + @override + String get discoveredContacts_export => 'Esporta contatti scoperti'; + + @override + String get discoveredContacts_import => 'Importa contatti scoperti'; + + @override + String discoveredContacts_exported(String path) { + return 'Contatti scoperti esportati in $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Esportazione dei contatti scoperti non riuscita: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return 'Importati $count contatti scoperti.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'Nessun contatto trovato nel file di importazione.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Importazione dei contatti scoperti non riuscita: $error'; + } + @override String get contacts_zeroHopAdvert => 'Annuncio Zero Hop'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index afb8c29..e810fd9 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -3149,6 +3149,35 @@ class AppLocalizationsJa extends AppLocalizations { @override String get contacts_contactImportFailed => '連絡先のインポートに失敗しました。'; + @override + String get discoveredContacts_export => '発見済みの連絡先をエクスポート'; + + @override + String get discoveredContacts_import => '発見済みの連絡先をインポート'; + + @override + String discoveredContacts_exported(String path) { + return '発見済みの連絡先を $path にエクスポートしました。'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return '発見済みの連絡先のエクスポートに失敗しました: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return '$count 件の発見済み連絡先をインポートしました。'; + } + + @override + String get discoveredContacts_importNoContacts => 'インポートファイルに連絡先が見つかりませんでした。'; + + @override + String discoveredContacts_importFailed(String error) { + return '発見済みの連絡先のインポートに失敗しました: $error'; + } + @override String get contacts_zeroHopAdvert => 'ゼロホップ広告'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index ff4bd26..3b9046a 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -3149,6 +3149,35 @@ class AppLocalizationsKo extends AppLocalizations { @override String get contacts_contactImportFailed => '연락처를 가져오지 못했습니다.'; + @override + String get discoveredContacts_export => '발견된 연락처 내보내기'; + + @override + String get discoveredContacts_import => '발견된 연락처 가져오기'; + + @override + String discoveredContacts_exported(String path) { + return '발견된 연락처를 $path(으)로 내보냈습니다.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return '발견된 연락처 내보내기에 실패했습니다: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return '발견된 연락처 $count개를 가져왔습니다.'; + } + + @override + String get discoveredContacts_importNoContacts => '가져오기 파일에서 연락처를 찾을 수 없습니다.'; + + @override + String discoveredContacts_importFailed(String error) { + return '발견된 연락처 가져오기에 실패했습니다: $error'; + } + @override String get contacts_zeroHopAdvert => '제로 홉 광고'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index dd770e1..f499fba 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -3280,6 +3280,36 @@ class AppLocalizationsNl extends AppLocalizations { String get contacts_contactImportFailed => 'Contact kon niet geïmporteerd worden.'; + @override + String get discoveredContacts_export => 'Ontdekte contacten exporteren'; + + @override + String get discoveredContacts_import => 'Ontdekte contacten importeren'; + + @override + String discoveredContacts_exported(String path) { + return 'Ontdekte contacten geëxporteerd naar $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Exporteren van ontdekte contacten mislukt: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return '$count ontdekte contacten geïmporteerd.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'Geen contacten gevonden in het importbestand.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Importeren van ontdekte contacten mislukt: $error'; + } + @override String get contacts_zeroHopAdvert => 'Zero Hop Reclame'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 357dd7e..f1ce8ca 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -3306,6 +3306,36 @@ class AppLocalizationsPl extends AppLocalizations { String get contacts_contactImportFailed => 'Kontakt nie został zaimportowany.'; + @override + String get discoveredContacts_export => 'Eksportuj odkryte kontakty'; + + @override + String get discoveredContacts_import => 'Importuj odkryte kontakty'; + + @override + String discoveredContacts_exported(String path) { + return 'Wyeksportowano odkryte kontakty do $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Nie udało się wyeksportować odkrytych kontaktów: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return 'Zaimportowano $count odkrytych kontaktów.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'Nie znaleziono kontaktów w pliku importu.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Nie udało się zaimportować odkrytych kontaktów: $error'; + } + @override String get contacts_zeroHopAdvert => 'Rozgłoszenie zero-hop'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 2dfcd8b..81bd78e 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -3293,6 +3293,36 @@ class AppLocalizationsPt extends AppLocalizations { @override String get contacts_contactImportFailed => 'Contato falhou ao ser importado.'; + @override + String get discoveredContacts_export => 'Exportar contatos descobertos'; + + @override + String get discoveredContacts_import => 'Importar contatos descobertos'; + + @override + String discoveredContacts_exported(String path) { + return 'Contatos descobertos exportados para $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Falha ao exportar contatos descobertos: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return '$count contatos descobertos importados.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'Nenhum contato encontrado no arquivo de importação.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Falha ao importar contatos descobertos: $error'; + } + @override String get contacts_zeroHopAdvert => 'Anúncio Zero Hop'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4fac42c..c0f537d 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -3299,6 +3299,37 @@ class AppLocalizationsRu extends AppLocalizations { @override String get contacts_contactImportFailed => 'Контакт не удалось импортировать'; + @override + String get discoveredContacts_export => + 'Экспортировать обнаруженные контакты'; + + @override + String get discoveredContacts_import => 'Импортировать обнаруженные контакты'; + + @override + String discoveredContacts_exported(String path) { + return 'Обнаруженные контакты экспортированы в $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Не удалось экспортировать обнаруженные контакты: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return 'Импортировано $count обнаруженных контактов.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'В файле импорта не найдены контакты.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Не удалось импортировать обнаруженные контакты: $error'; + } + @override String get contacts_zeroHopAdvert => 'Реклама Zero Hop'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index c42e024..7e10337 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -3274,6 +3274,36 @@ class AppLocalizationsSk extends AppLocalizations { String get contacts_contactImportFailed => 'Kontakt sa nepodarilo importovať.'; + @override + String get discoveredContacts_export => 'Exportovať objavené kontakty'; + + @override + String get discoveredContacts_import => 'Importovať objavené kontakty'; + + @override + String discoveredContacts_exported(String path) { + return 'Objavené kontakty boli exportované do $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Export objavených kontaktov zlyhal: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return 'Bolo importovaných $count objavených kontaktov.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'V importovanom súbore sa nenašli žiadne kontakty.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Import objavených kontaktov zlyhal: $error'; + } + @override String get contacts_zeroHopAdvert => 'Inzerát Zero Hop'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 2d89aa4..657d87d 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -3274,6 +3274,36 @@ class AppLocalizationsSl extends AppLocalizations { @override String get contacts_contactImportFailed => 'Kontakt ni bil uspešno uvožen.'; + @override + String get discoveredContacts_export => 'Izvozi odkrite stike'; + + @override + String get discoveredContacts_import => 'Uvozi odkrite stike'; + + @override + String discoveredContacts_exported(String path) { + return 'Odkriti stiki so izvoženi v $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Izvoz odkritih stikov ni uspel: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return 'Uvoženih je bilo $count odkritih stikov.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'V uvozni datoteki ni bilo najdenih stikov.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Uvoz odkritih stikov ni uspel: $error'; + } + @override String get contacts_zeroHopAdvert => 'Reklama brez posrednikov'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 38e0893..be28bf1 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -3256,6 +3256,36 @@ class AppLocalizationsSv extends AppLocalizations { @override String get contacts_contactImportFailed => 'Kontakt kunde inte importeras.'; + @override + String get discoveredContacts_export => 'Exportera upptäckta kontakter'; + + @override + String get discoveredContacts_import => 'Importera upptäckta kontakter'; + + @override + String discoveredContacts_exported(String path) { + return 'Upptäckta kontakter exporterades till $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Det gick inte att exportera upptäckta kontakter: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return 'Importerade $count upptäckta kontakter.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'Inga kontakter hittades i importfilen.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Det gick inte att importera upptäckta kontakter: $error'; + } + @override String get contacts_zeroHopAdvert => 'Reklam med nollhopp'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 8ed4b9f..8bb1c7e 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -3301,6 +3301,36 @@ class AppLocalizationsUk extends AppLocalizations { @override String get contacts_contactImportFailed => 'Контакт не вдалося імпортувати'; + @override + String get discoveredContacts_export => 'Експортувати виявлені контакти'; + + @override + String get discoveredContacts_import => 'Імпортувати виявлені контакти'; + + @override + String discoveredContacts_exported(String path) { + return 'Виявлені контакти експортовано до $path.'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return 'Не вдалося експортувати виявлені контакти: $error'; + } + + @override + String discoveredContacts_imported(int count) { + return 'Імпортовано $count виявлених контактів.'; + } + + @override + String get discoveredContacts_importNoContacts => + 'У файлі імпорту не знайдено контактів.'; + + @override + String discoveredContacts_importFailed(String error) { + return 'Не вдалося імпортувати виявлені контакти: $error'; + } + @override String get contacts_zeroHopAdvert => 'Реклама без перехоплення'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 4f38c64..80f9491 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -3073,6 +3073,35 @@ class AppLocalizationsZh extends AppLocalizations { @override String get contacts_contactImportFailed => '导入联系人失败。'; + @override + String get discoveredContacts_export => '导出已发现的联系人'; + + @override + String get discoveredContacts_import => '导入已发现的联系人'; + + @override + String discoveredContacts_exported(String path) { + return '已将已发现的联系人导出到$path。'; + } + + @override + String discoveredContacts_exportFailed(String error) { + return '导出已发现的联系人失败:$error'; + } + + @override + String discoveredContacts_imported(int count) { + return '已导入$count个已发现的联系人。'; + } + + @override + String get discoveredContacts_importNoContacts => '在导入文件中未找到联系人。'; + + @override + String discoveredContacts_importFailed(String error) { + return '导入已发现的联系人失败:$error'; + } + @override String get contacts_zeroHopAdvert => '发送零跳广播'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 96bdb84..a793dee 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1824,6 +1824,41 @@ "discoveredContacts_Title": "Ontdekte contacten", "discoveredContacts_contactAdded": "Contact toegevoegd", "discoveredContacts_searchHint": "Ontdekte contacten zoeken", + "discoveredContacts_export": "Ontdekte contacten exporteren", + "discoveredContacts_import": "Ontdekte contacten importeren", + "discoveredContacts_exported": "Ontdekte contacten geëxporteerd naar {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Exporteren van ontdekte contacten mislukt: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "{count} ontdekte contacten geïmporteerd.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "Geen contacten gevonden in het importbestand.", + "discoveredContacts_importFailed": "Importeren van ontdekte contacten mislukt: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "Wanneer de contactenlijst vol is, wordt de oudste niet-favoriete contactpersoon vervangen.", "common_deleteAll": "Alles verwijderen", "discoveredContacts_deleteContactAll": "Verwijder alle ontdekte contacten", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b62a78a..ad59ab4 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1862,6 +1862,41 @@ "contactsSettings_autoAddSensorsSubtitle": "Zezwól towarzyszowi na automatyczne dodawanie wykrytych czujników.", "discoveredContacts_noMatching": "Brak pasujących kontaktów", "discoveredContacts_deleteContact": "Usuń kontakt", + "discoveredContacts_export": "Eksportuj odkryte kontakty", + "discoveredContacts_import": "Importuj odkryte kontakty", + "discoveredContacts_exported": "Wyeksportowano odkryte kontakty do {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Nie udało się wyeksportować odkrytych kontaktów: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "Zaimportowano {count} odkrytych kontaktów.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "Nie znaleziono kontaktów w pliku importu.", + "discoveredContacts_importFailed": "Nie udało się zaimportować odkrytych kontaktów: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "Gdy lista kontaktów jest pełna, najstarszy nieulubiony kontakt zostanie zastąpiony.", "common_deleteAll": "Usuń wszystko", "discoveredContacts_deleteContactAllContent": "Czy na pewno chcesz usunąć wszystkie znalezione kontakty?", @@ -2100,12 +2135,6 @@ "scanner_linuxPairingPinTitle": "Kod PIN parowania Bluetooth", "repeater_cliQuickClockSync": "Synchronizacja zegara", "repeater_cliQuickDiscovery": "Odkryj Sąsiadów", - "@repeater_clockSyncAfterLogin": { - "description": "Repeater setting: auto sync device clock after successful login" - }, - "@repeater_clockSyncAfterLoginSubtitle": { - "description": "Repeater setting subtitle: describes the clock sync after login behavior" - }, "repeater_clockSyncAfterLogin": "Synchronizacja zegara po zalogowaniu", "repeater_clockSyncAfterLoginSubtitle": "Automatycznie wysyłaj powiadomienie \"synchronizacja zegara\" po pomyślnym zalogowaniu.", "chat_sendMessage": "Wyślij wiadomość", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index bf3e893..ac67ca9 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1824,6 +1824,41 @@ "discoveredContacts_deleteContact": "Excluir Contato", "discoveredContacts_contactAdded": "Contato adicionado", "discoveredContacts_addContact": "Adicionar Contato", + "discoveredContacts_export": "Exportar contatos descobertos", + "discoveredContacts_import": "Importar contatos descobertos", + "discoveredContacts_exported": "Contatos descobertos exportados para {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Falha ao exportar contatos descobertos: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "{count} contatos descobertos importados.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "Nenhum contato encontrado no arquivo de importação.", + "discoveredContacts_importFailed": "Falha ao importar contatos descobertos: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "Quando a lista de contatos estiver cheia, o contato mais antigo não favoritado será substituído.", "common_deleteAll": "Excluir Tudo", "discoveredContacts_deleteContactAll": "Excluir Todos os Contatos Descobertos", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index a83d139..c0d5158 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1064,6 +1064,41 @@ "discoveredContacts_addContact": "Добавить контакт", "discoveredContacts_Title": "Обнаруженные контакты", "discoveredContacts_deleteContact": "Удалить контакт", + "discoveredContacts_export": "Экспортировать обнаруженные контакты", + "discoveredContacts_import": "Импортировать обнаруженные контакты", + "discoveredContacts_exported": "Обнаруженные контакты экспортированы в {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Не удалось экспортировать обнаруженные контакты: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "Импортировано {count} обнаруженных контактов.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "В файле импорта не найдены контакты.", + "discoveredContacts_importFailed": "Не удалось импортировать обнаруженные контакты: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "Когда список контактов заполнен, будет заменен самый старый контакт, который не находится в избранном.", "common_deleteAll": "Удалить все", "discoveredContacts_deleteContactAllContent": "Вы уверены, что хотите удалить все обнаруженные контакты?", @@ -1302,12 +1337,6 @@ "scanner_linuxPairingPinTitle": "PIN‑код сопряжения Bluetooth", "repeater_cliQuickDiscovery": "Обнаружить Соседей", "repeater_cliQuickClockSync": "Синхронизация часов", - "@repeater_clockSyncAfterLogin": { - "description": "Repeater setting: auto sync device clock after successful login" - }, - "@repeater_clockSyncAfterLoginSubtitle": { - "description": "Repeater setting subtitle: describes the clock sync after login behavior" - }, "repeater_clockSyncAfterLogin": "Синхронизация часов после входа в систему", "repeater_clockSyncAfterLoginSubtitle": "Автоматически отправлять сообщение \"синхронизация времени\" после успешной авторизации.", "chat_sendMessage": "Отправить сообщение", diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index e4466c3..426409a 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1824,6 +1824,41 @@ "discoveredContacts_Title": "Objavené kontakty", "contactsSettings_overwriteOldestTitle": "Prepísať najstaršie", "discoveredContacts_addContact": "Pridať kontakt", + "discoveredContacts_export": "Exportovať objavené kontakty", + "discoveredContacts_import": "Importovať objavené kontakty", + "discoveredContacts_exported": "Objavené kontakty boli exportované do {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Export objavených kontaktov zlyhal: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "Bolo importovaných {count} objavených kontaktov.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "V importovanom súbore sa nenašli žiadne kontakty.", + "discoveredContacts_importFailed": "Import objavených kontaktov zlyhal: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "Keď je zoznam kontaktov plný, bude nahradený najstarší neoznačený kontakt.", "discoveredContacts_deleteContactAll": "Zmazať všetky objavené kontakty", "common_deleteAll": "Zmazať všetko", diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index f6a317e..58fba5e 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1824,6 +1824,41 @@ "discoveredContacts_Title": "Odkriti stiki", "discoveredContacts_searchHint": "Najdeni stiki po iskanju", "discoveredContacts_deleteContact": "Izbriši stik", + "discoveredContacts_export": "Izvozi odkrite stike", + "discoveredContacts_import": "Uvozi odkrite stike", + "discoveredContacts_exported": "Odkriti stiki so izvoženi v {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Izvoz odkritih stikov ni uspel: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "Uvoženih je bilo {count} odkritih stikov.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "V uvozni datoteki ni bilo najdenih stikov.", + "discoveredContacts_importFailed": "Uvoz odkritih stikov ni uspel: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "Ko je seznam stikov poln, bo najstarejši nestarševski stik zamenjan.", "common_deleteAll": "Izbriši vse", "discoveredContacts_deleteContactAllContent": "Ste prepričani, da želite izbrisati vse odkrite kontakte?", diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index eab348c..e3cea7d 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1824,6 +1824,41 @@ "discoveredContacts_contactAdded": "Kontakt tillagd", "discoveredContacts_addContact": "Lägg till kontakt", "discoveredContacts_copyContact": "Kopiera kontakt till urklipp", + "discoveredContacts_export": "Exportera upptäckta kontakter", + "discoveredContacts_import": "Importera upptäckta kontakter", + "discoveredContacts_exported": "Upptäckta kontakter exporterades till {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Det gick inte att exportera upptäckta kontakter: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "Importerade {count} upptäckta kontakter.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "Inga kontakter hittades i importfilen.", + "discoveredContacts_importFailed": "Det gick inte att importera upptäckta kontakter: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "När kontaktlistan är full ersätts den äldsta icke-favoriterade kontakten.", "common_deleteAll": "Ta bort alla", "discoveredContacts_deleteContactAllContent": "Är du säker på att du vill ta bort alla upptäckta kontakter?", @@ -2062,12 +2097,6 @@ "scanner_linuxPairingHidePin": "Dölj PIN", "repeater_cliQuickDiscovery": "Upptäck grannar", "repeater_cliQuickClockSync": "Synkronisera klocka", - "@repeater_clockSyncAfterLogin": { - "description": "Repeater setting: auto sync device clock after successful login" - }, - "@repeater_clockSyncAfterLoginSubtitle": { - "description": "Repeater setting subtitle: describes the clock sync after login behavior" - }, "repeater_clockSyncAfterLoginSubtitle": "Automatiskt skicka \"klocksynkronisering\" efter en lyckad inloggning.", "repeater_clockSyncAfterLogin": "Synkronisera klockan efter inloggning", "repeater_guest": "Information om repetorer", diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index a005b36..0605b8c 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1824,6 +1824,41 @@ "discoveredContacts_deleteContact": "Видалити контакт", "discoveredContacts_copyContact": "Копіювати контакт у буфер обміну", "discoveredContacts_addContact": "Додати контакт", + "discoveredContacts_export": "Експортувати виявлені контакти", + "discoveredContacts_import": "Імпортувати виявлені контакти", + "discoveredContacts_exported": "Виявлені контакти експортовано до {path}.", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "Не вдалося експортувати виявлені контакти: {error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "Імпортовано {count} виявлених контактів.", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "У файлі імпорту не знайдено контактів.", + "discoveredContacts_importFailed": "Не вдалося імпортувати виявлені контакти: {error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "Коли список контактів заповнений, найстарший контакт без позначки улюбленого буде замінений.", "common_deleteAll": "Видалити все", "discoveredContacts_deleteContactAll": "Видалити всі виявлені контакти", @@ -2062,12 +2097,6 @@ "scanner_linuxPairingHidePin": "Приховати PIN", "repeater_cliQuickClockSync": "Синхронізація годинника", "repeater_cliQuickDiscovery": "Відкрити сусідів", - "@repeater_clockSyncAfterLogin": { - "description": "Repeater setting: auto sync device clock after successful login" - }, - "@repeater_clockSyncAfterLoginSubtitle": { - "description": "Repeater setting subtitle: describes the clock sync after login behavior" - }, "repeater_clockSyncAfterLoginSubtitle": "Автоматично надсилати повідомлення \"синхронізація годин\" після успішного входу.", "repeater_clockSyncAfterLogin": "Синхронізація годин після входу", "repeater_guestTools": "Інструменти для гостей", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9dc2325..a05a63b 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1829,6 +1829,41 @@ "discoveredContacts_noMatching": "没有匹配的联系人", "discoveredContacts_Title": "已发现的联系人", "discoveredContacts_copyContact": "复制联系人到剪贴板", + "discoveredContacts_export": "导出已发现的联系人", + "discoveredContacts_import": "导入已发现的联系人", + "discoveredContacts_exported": "已将已发现的联系人导出到{path}。", + "@discoveredContacts_exported": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "discoveredContacts_exportFailed": "导出已发现的联系人失败:{error}", + "@discoveredContacts_exportFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "discoveredContacts_imported": "已导入{count}个已发现的联系人。", + "@discoveredContacts_imported": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discoveredContacts_importNoContacts": "在导入文件中未找到联系人。", + "discoveredContacts_importFailed": "导入已发现的联系人失败:{error}", + "@discoveredContacts_importFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "contactsSettings_overwriteOldestSubtitle": "当联系人列表已满时,将替换最老的非收藏联系人。", "common_deleteAll": "删除全部", "discoveredContacts_deleteContactAllContent": "您确定要删除所有发现的联系人吗?", diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 2699f93..da29f82 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -18,7 +18,6 @@ class Contact { final DateTime lastSeen; final DateTime lastMessageAt; final bool isActive; - final bool wasPulled; final Uint8List? rawPacket; Contact({ @@ -35,7 +34,6 @@ class Contact { required this.lastSeen, DateTime? lastMessageAt, this.isActive = true, - this.wasPulled = false, this.rawPacket, }) : lastMessageAt = lastMessageAt ?? lastSeen; diff --git a/lib/screens/discovery_screen.dart b/lib/screens/discovery_screen.dart index f9f0e07..5859f60 100644 --- a/lib/screens/discovery_screen.dart +++ b/lib/screens/discovery_screen.dart @@ -1,8 +1,13 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; +import 'package:share_plus/share_plus.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; @@ -66,9 +71,44 @@ class _DiscoveryScreenState extends State { ), centerTitle: true, actions: [ - PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( + PopupMenuButton( + onSelected: (value) { + switch (value) { + case 'export': + unawaited(_exportDiscoveredContacts(context, connector)); + break; + case 'import': + unawaited(_importDiscoveredContacts(context, connector)); + break; + case 'delete_all': + _deleteContacts(context, connector); + break; + } + }, + itemBuilder: (context) => >[ + PopupMenuItem( + value: 'export', + child: Row( + children: [ + const Icon(Icons.upload_file), + const SizedBox(width: 8), + Text(l10n.discoveredContacts_export), + ], + ), + ), + PopupMenuItem( + value: 'import', + child: Row( + children: [ + const Icon(Icons.download), + const SizedBox(width: 8), + Text(l10n.discoveredContacts_import), + ], + ), + ), + const PopupMenuDivider(), + PopupMenuItem( + value: 'delete_all', child: Row( children: [ const Icon(Icons.delete, color: Colors.red), @@ -76,9 +116,6 @@ class _DiscoveryScreenState extends State { Text(context.l10n.discoveredContacts_deleteContactAll), ], ), - onTap: () { - _deleteContacts(context, connector); - }, ), ], icon: const Icon(Icons.more_vert), @@ -270,6 +307,144 @@ class _DiscoveryScreenState extends State { ); } + Future _exportDiscoveredContacts( + BuildContext context, + MeshCoreConnector connector, + ) async { + final l10n = context.l10n; + final messenger = ScaffoldMessenger.of(context); + final json = connector.exportDiscoveredContactsJson(); + + try { + const filename = 'meshcore_discovered_contacts.json'; + final bytes = Uint8List.fromList(utf8.encode(json)); + + if (PlatformInfo.isDesktop) { + final location = await getSaveLocation( + suggestedName: filename, + acceptedTypeGroups: [ + const XTypeGroup(label: 'JSON', extensions: ['json']), + ], + ); + if (!mounted) return; + if (location == null) return; + final exportFile = XFile.fromData( + bytes, + mimeType: 'application/json', + name: filename, + ); + await exportFile.saveTo(location.path); + if (!mounted) return; + messenger.showSnackBar( + SnackBar( + content: Text(l10n.discoveredContacts_exported(location.path)), + ), + ); + return; + } + + final tempDir = await getTemporaryDirectory(); + final exportPath = '${tempDir.path}/$filename'; + final exportFile = File(exportPath); + await exportFile.writeAsBytes(bytes, flush: true); + + try { + final result = await SharePlus.instance.share( + ShareParams(subject: filename, files: [XFile(exportPath)]), + ); + + if (!mounted) return; + if (result.status == ShareResultStatus.success) { + messenger.showSnackBar( + SnackBar(content: Text(l10n.discoveredContacts_exported(filename))), + ); + } + } finally { + if (await exportFile.exists()) { + await exportFile.delete(); + } + } + } catch (e) { + if (!mounted) return; + messenger.showSnackBar( + SnackBar( + content: Text(l10n.discoveredContacts_exportFailed(e.toString())), + ), + ); + } + } + + Future _importDiscoveredContacts( + BuildContext context, + MeshCoreConnector connector, + ) async { + final l10n = context.l10n; + final messenger = ScaffoldMessenger.of(context); + try { + final json = await _resolveImportJson(context); + if (json == null) { + // User cancelled the file picker — nothing to do. + return; + } + final foundCount = _countContactsInImportJson(json); + if (foundCount == 0) { + if (!mounted) return; + messenger.showSnackBar( + SnackBar(content: Text(l10n.discoveredContacts_importNoContacts)), + ); + return; + } + + final importedCount = await connector.importDiscoveredContactsJson(json); + if (!mounted) return; + messenger.showSnackBar( + SnackBar( + content: Text(l10n.discoveredContacts_imported(importedCount)), + ), + ); + } catch (e) { + if (!mounted) return; + messenger.showSnackBar( + SnackBar( + content: Text(l10n.discoveredContacts_importFailed(e.toString())), + ), + ); + } + } + + Future _resolveImportJson(BuildContext context) async { + final file = await openFile( + acceptedTypeGroups: [ + const XTypeGroup(label: 'JSON', extensions: ['json']), + ], + ); + if (file == null) return null; + final bytes = await file.readAsBytes(); + + if (bytes.isEmpty) return ''; + + // Try UTF-8 first (handles BOM automatically) + try { + final text = utf8.decode(bytes, allowMalformed: true); + // Remove BOM if present + return text.startsWith('\ufeff') ? text.substring(1) : text; + } catch (_) { + return ''; + } + } + + int _countContactsInImportJson(String json) { + try { + final decoded = jsonDecode(json); + if (decoded is List) { + return decoded.length; + } + return 0; + } catch (_) { + return 0; + } + } + Widget _buildFilters( List filteredAndSorted, MeshCoreConnector connector, diff --git a/lib/storage/contact_discovery_store.dart b/lib/storage/contact_discovery_store.dart index 89ca027..cf3316a 100644 --- a/lib/storage/contact_discovery_store.dart +++ b/lib/storage/contact_discovery_store.dart @@ -28,6 +28,55 @@ class ContactDiscoveryStore { await prefs.setString(_keyPrefix, jsonEncode(jsonList)); } + String exportContactsJson(List contacts) { + final jsonList = contacts.map(_toJson).toList(); + return jsonEncode(jsonList); + } + + int importContactsJson({ + required String json, + required List existingContacts, + required Set knownContactKeys, + }) { + try { + final jsonList = jsonDecode(json) as List; + final importedContacts = jsonList + .map((entry) => _fromJson(entry as Map)) + .toList(); + + int newCount = 0; + + // Create a set of existing discovered contact keys for deduplication + final existingKeySet = {}; + for (final contact in existingContacts) { + existingKeySet.add(contact.publicKeyHex); + } + + // Process imported contacts + for (final imported in importedContacts) { + final keyHex = imported.publicKeyHex; + + // Skip if already in device's contact list + if (knownContactKeys.contains(keyHex)) { + continue; + } + + // Skip if already in discovered contacts (existing is always fresher) + if (existingKeySet.contains(keyHex)) { + continue; + } + + // Add as new discovered contact + existingContacts.add(imported); + newCount++; + } + + return newCount; + } catch (_) { + return 0; + } + } + Map _toJson(Contact contact) { return { 'publicKey': base64Encode(contact.publicKey), @@ -44,6 +93,7 @@ class ContactDiscoveryStore { 'longitude': contact.longitude, 'lastSeen': contact.lastSeen.millisecondsSinceEpoch, 'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch, + 'isActive': contact.isActive, 'rawPacket': contact.rawPacket != null ? base64Encode(contact.rawPacket!) : null, diff --git a/pubspec.yaml b/pubspec.yaml index af07196..1046607 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: gpx: ^2.3.0 path_provider: ^2.1.5 share_plus: ^12.0.1 + file_selector: ^1.0.3 build_pipe: ^0.3.1 material_symbols_icons: ^4.2906.0 web: ^1.1.1