diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c90a330..05054c3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -2493,6 +2493,23 @@ "contacts_floodAdvert": "Flood Advert", "contacts_copyAdvertToClipboard": "Copy Advert to Clipboard", "contacts_addContactFromClipboard": "Add Contact from Clipboard", + "contacts_addByKey": "Add by public key", + "contacts_addByKeyDescription": "Add someone using the public key they gave you. Works even if you have never heard their advert.", + "contacts_publicKeyLabel": "Public key", + "contacts_publicKeyHelper": "64 hex characters, or paste a meshcore:// contact link", + "contacts_publicKeyInvalid": "Enter 64 hex characters, or a meshcore:// contact link", + "contacts_nameLabel": "Name", + "contacts_typeLabel": "Type", + "contacts_addByKeyUnverifiedNote": "Not confirmed on air yet. The name is whatever you type here until this node sends an advert.", + "contacts_addByKeyAdded": "Added {name}", + "@contacts_addByKeyAdded": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "contacts_addByKeyFailed": "Could not add the contact. Check the radio connection.", "contacts_ShareContact": "Copy contact to Clipboard", "contacts_ShareContactZeroHop": "Share contact by advert", "contacts_zeroHopContactAdvertSent": "Sent contact by advert.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index d73c671..3d62feb 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -7450,6 +7450,66 @@ abstract class AppLocalizations { /// **'Add Contact from Clipboard'** String get contacts_addContactFromClipboard; + /// No description provided for @contacts_addByKey. + /// + /// In en, this message translates to: + /// **'Add by public key'** + String get contacts_addByKey; + + /// No description provided for @contacts_addByKeyDescription. + /// + /// In en, this message translates to: + /// **'Add someone using the public key they gave you. Works even if you have never heard their advert.'** + String get contacts_addByKeyDescription; + + /// No description provided for @contacts_publicKeyLabel. + /// + /// In en, this message translates to: + /// **'Public key'** + String get contacts_publicKeyLabel; + + /// No description provided for @contacts_publicKeyHelper. + /// + /// In en, this message translates to: + /// **'64 hex characters, or paste a meshcore:// contact link'** + String get contacts_publicKeyHelper; + + /// No description provided for @contacts_publicKeyInvalid. + /// + /// In en, this message translates to: + /// **'Enter 64 hex characters, or a meshcore:// contact link'** + String get contacts_publicKeyInvalid; + + /// No description provided for @contacts_nameLabel. + /// + /// In en, this message translates to: + /// **'Name'** + String get contacts_nameLabel; + + /// No description provided for @contacts_typeLabel. + /// + /// In en, this message translates to: + /// **'Type'** + String get contacts_typeLabel; + + /// No description provided for @contacts_addByKeyUnverifiedNote. + /// + /// In en, this message translates to: + /// **'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'** + String get contacts_addByKeyUnverifiedNote; + + /// No description provided for @contacts_addByKeyAdded. + /// + /// In en, this message translates to: + /// **'Added {name}'** + String contacts_addByKeyAdded(String name); + + /// No description provided for @contacts_addByKeyFailed. + /// + /// In en, this message translates to: + /// **'Could not add the contact. Check the radio connection.'** + String get contacts_addByKeyFailed; + /// No description provided for @contacts_ShareContact. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 5420409..3448993 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -4349,6 +4349,43 @@ class AppLocalizationsBg extends AppLocalizations { @override String get contacts_addContactFromClipboard => 'Добави контакт от клипборда'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Копирай контакт в клипборда'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index be54da5..6d2927d 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -4361,6 +4361,43 @@ class AppLocalizationsDe extends AppLocalizations { String get contacts_addContactFromClipboard => 'Kontakt aus Zwischenablage hinzufügen'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Kontakt in die Zwischenablage kopieren'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 7e0ab51..a0ab4d3 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -4283,6 +4283,43 @@ class AppLocalizationsEn extends AppLocalizations { @override String get contacts_addContactFromClipboard => 'Add Contact from Clipboard'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Copy contact to Clipboard'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index d9c137a..22c4a43 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -4350,6 +4350,43 @@ class AppLocalizationsEs extends AppLocalizations { String get contacts_addContactFromClipboard => 'Agregar contacto desde el portapapeles'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Copiar contacto al Portapapeles'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a5f67de..f82d58f 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -4372,6 +4372,43 @@ class AppLocalizationsFr extends AppLocalizations { String get contacts_addContactFromClipboard => 'Ajouter un contact depuis le presse-papiers'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Copier le contact dans le presse-papiers'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index e2df173..1b32e33 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -4367,6 +4367,43 @@ class AppLocalizationsHu extends AppLocalizations { String get contacts_addContactFromClipboard => 'Adjon hozzá egy kapcsolatot a kiválasztott listából'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Másolja a kapcsolatot a kiválasztóba'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 9a94d64..bab9a0f 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -4355,6 +4355,43 @@ class AppLocalizationsIt extends AppLocalizations { String get contacts_addContactFromClipboard => 'Aggiungere contatto dalla clipboard'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Copia contatto negli Appunti'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 662782a..ba56ead 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -4152,6 +4152,43 @@ class AppLocalizationsJa extends AppLocalizations { @override String get contacts_addContactFromClipboard => 'クリップボードから連絡先を追加する'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => '連絡先をクリップボードにコピー'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 181f1e2..880f524 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -4154,6 +4154,43 @@ class AppLocalizationsKo extends AppLocalizations { @override String get contacts_addContactFromClipboard => '복사본에서 연락처 추가'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => '연락처를 복사'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 6f76fea..ddbbe3f 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -4335,6 +4335,43 @@ class AppLocalizationsNl extends AppLocalizations { String get contacts_addContactFromClipboard => 'Contact uit klembord toevoegen'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Contact naar Klembord kopiëren'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index dc6b388..77e7f54 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -4365,6 +4365,43 @@ class AppLocalizationsPl extends AppLocalizations { @override String get contacts_addContactFromClipboard => 'Dodaj kontakt z schowka'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Kopiuj kontakt do schowka'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 6eb1aac..a50e6b4 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -4347,6 +4347,43 @@ class AppLocalizationsPt extends AppLocalizations { String get contacts_addContactFromClipboard => 'Adicionar Contato da Área de Transferência'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Copiar contato para Área de Transferência'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index e437aa2..cd0d8f1 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -4357,6 +4357,43 @@ class AppLocalizationsRu extends AppLocalizations { String get contacts_addContactFromClipboard => 'Добавить контакт из буфера обмена'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Копировать контакт в буфер обмена'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 5544a87..826e796 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -4331,6 +4331,43 @@ class AppLocalizationsSk extends AppLocalizations { @override String get contacts_addContactFromClipboard => 'Pridať kontakt z schránky'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Kopírovať kontakt do schránky'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 23c380b..a53562a 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -4325,6 +4325,43 @@ class AppLocalizationsSl extends AppLocalizations { @override String get contacts_addContactFromClipboard => 'Dodaj stik iz odložišča'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Kopiraj stik v Odložišče'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 4bc637a..c4ea817 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -4304,6 +4304,43 @@ class AppLocalizationsSv extends AppLocalizations { String get contacts_addContactFromClipboard => 'Lägg till kontakt från urklipp'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Kopiera kontakt till Urklipp'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 46d8b63..239dc2d 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -4356,6 +4356,43 @@ class AppLocalizationsUk extends AppLocalizations { @override String get contacts_addContactFromClipboard => 'Додати контакт з буфера'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => 'Копіювати контакт у буфер обміну'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 6955358..e83864f 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -4055,6 +4055,43 @@ class AppLocalizationsZh extends AppLocalizations { @override String get contacts_addContactFromClipboard => '从剪贴板添加联系人'; + @override + String get contacts_addByKey => 'Add by public key'; + + @override + String get contacts_addByKeyDescription => + 'Add someone using the public key they gave you. Works even if you have never heard their advert.'; + + @override + String get contacts_publicKeyLabel => 'Public key'; + + @override + String get contacts_publicKeyHelper => + '64 hex characters, or paste a meshcore:// contact link'; + + @override + String get contacts_publicKeyInvalid => + 'Enter 64 hex characters, or a meshcore:// contact link'; + + @override + String get contacts_nameLabel => 'Name'; + + @override + String get contacts_typeLabel => 'Type'; + + @override + String get contacts_addByKeyUnverifiedNote => + 'Not confirmed on air yet. The name is whatever you type here until this node sends an advert.'; + + @override + String contacts_addByKeyAdded(String name) { + return 'Added $name'; + } + + @override + String get contacts_addByKeyFailed => + 'Could not add the contact. Check the radio connection.'; + @override String get contacts_ShareContact => '复制联系人信息到剪贴板'; diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 3934973..edd078b 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -34,6 +34,7 @@ import '../widgets/path_selection_dialog.dart'; import '../widgets/repeater_login_dialog.dart'; import '../widgets/room_login_dialog.dart'; import '../widgets/sync_progress_overlay.dart'; +import '../widgets/add_contact_by_key_dialog.dart'; import '../widgets/unread_badge.dart'; import '../helpers/snack_bar_builder.dart'; import 'channels_screen.dart'; @@ -411,6 +412,19 @@ class _ContactsScreenState extends State ), ), ), + // Provisional home so key entry is reachable. The proper + // add-contact surface, split away from the advert affordance, + // is #632 under epic #623. + PopupMenuItem( + child: Row( + children: [ + const Icon(Icons.key_outlined), + const SizedBox(width: 8), + Text(context.l10n.contacts_addByKey), + ], + ), + onTap: () => showAddContactByKeyDialog(context), + ), ], icon: const Icon(Icons.more_vert), ), diff --git a/lib/widgets/add_contact_by_key_dialog.dart b/lib/widgets/add_contact_by_key_dialog.dart new file mode 100644 index 0000000..4c5ee6b --- /dev/null +++ b/lib/widgets/add_contact_by_key_dialog.dart @@ -0,0 +1,217 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../connector/meshcore_connector.dart'; +import '../connector/meshcore_protocol.dart'; +import '../l10n/l10n.dart'; +import '../helpers/snack_bar_builder.dart'; +import '../models/contact.dart'; + +/// Adds a contact from an identity alone: a public key, a name and a type. +/// +/// This is the manual half of the identity exchange (#628). It exists because +/// a public key is never present in channel traffic, so for anyone who has not +/// recently adverted there is otherwise no way to reach them at all (#620). +/// +/// The field also accepts a whole `meshcore://contact/add` link, since that is +/// what someone is most likely to paste, and a QR is only that same link +/// rendered visually. +Future showAddContactByKeyDialog(BuildContext context) { + return showDialog( + context: context, + builder: (_) => const _AddContactByKeyDialog(), + ); +} + +class _AddContactByKeyDialog extends StatefulWidget { + const _AddContactByKeyDialog(); + + @override + State<_AddContactByKeyDialog> createState() => _AddContactByKeyDialogState(); +} + +class _AddContactByKeyDialogState extends State<_AddContactByKeyDialog> { + final _keyController = TextEditingController(); + final _nameController = TextEditingController(); + int _type = advTypeChat; + String? _keyError; + bool _submitting = false; + + @override + void dispose() { + _keyController.dispose(); + _nameController.dispose(); + super.dispose(); + } + + /// Strips whitespace so a key copied across a line break still works. + String get _cleanedKey => + _keyController.text.replaceAll(RegExp(r'\s+'), '').toLowerCase(); + + bool get _keyLooksValid => Contact.isValidShareUri( + Contact.buildShareUri(publicKeyHex: _cleanedKey, name: 'x', type: _type), + ); + + /// If a full contact link was pasted, absorb every field from it rather than + /// making the user retype a name they already have. + void _onKeyChanged(String raw) { + final pasted = Contact.fromShareUri(raw); + if (pasted != null) { + setState(() { + _keyController.value = TextEditingValue( + text: pasted.publicKeyHex, + selection: TextSelection.collapsed( + offset: pasted.publicKeyHex.length, + ), + ); + if (pasted.name != 'Unknown') _nameController.text = pasted.name; + _type = pasted.type; + _keyError = null; + }); + return; + } + if (_keyError != null) setState(() => _keyError = null); + } + + Future _submit() async { + final l10n = context.l10n; + if (!_keyLooksValid) { + setState(() => _keyError = l10n.contacts_publicKeyInvalid); + return; + } + + final name = _nameController.text.trim(); + // Round-trip through the share URI so a manually typed contact and a + // scanned QR produce byte-identical state. One code path, no drift. + final stub = Contact.fromShareUri( + Contact.buildShareUri(publicKeyHex: _cleanedKey, name: name, type: _type), + ); + if (stub == null) { + setState(() => _keyError = l10n.contacts_publicKeyInvalid); + return; + } + + setState(() => _submitting = true); + final connector = Provider.of(context, listen: false); + final added = await connector.addContactByKey(stub); + if (!mounted) return; + + Navigator.of(context).pop(); + showDismissibleSnackBar( + context, + content: Text( + added + ? l10n.contacts_addByKeyAdded(stub.name) + : l10n.contacts_addByKeyFailed, + ), + ); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final theme = Theme.of(context); + + return AlertDialog( + title: Text(l10n.contacts_addByKey), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.contacts_addByKeyDescription, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 16), + TextField( + controller: _keyController, + autofocus: true, + maxLines: 2, + minLines: 1, + decoration: InputDecoration( + labelText: l10n.contacts_publicKeyLabel, + helperText: l10n.contacts_publicKeyHelper, + helperMaxLines: 2, + errorText: _keyError, + errorMaxLines: 2, + border: const OutlineInputBorder(), + ), + onChanged: _onKeyChanged, + ), + const SizedBox(height: 16), + TextField( + controller: _nameController, + decoration: InputDecoration( + labelText: l10n.contacts_nameLabel, + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 16), + DropdownButtonFormField( + initialValue: _type, + decoration: InputDecoration( + labelText: l10n.contacts_typeLabel, + border: const OutlineInputBorder(), + ), + items: [ + DropdownMenuItem( + value: advTypeChat, + child: Text(l10n.contact_typeChat), + ), + DropdownMenuItem( + value: advTypeRepeater, + child: Text(l10n.contact_typeRepeater), + ), + DropdownMenuItem( + value: advTypeRoom, + child: Text(l10n.contact_typeRoom), + ), + DropdownMenuItem( + value: advTypeSensor, + child: Text(l10n.contact_typeSensor), + ), + ], + onChanged: (v) => setState(() => _type = v ?? advTypeChat), + ), + const SizedBox(height: 16), + // Honest about what a key-only add actually gives you. Deliberately + // informational, not a warning: nothing is wrong with this contact + // (#630). + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.info_outline, + size: 16, + color: theme.colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + l10n.contacts_addByKeyUnverifiedNote, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ), + ], + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: _submitting ? null : () => Navigator.of(context).pop(), + child: Text(l10n.common_cancel), + ), + FilledButton( + onPressed: _submitting ? null : _submit, + child: Text(l10n.common_add), + ), + ], + ); + } +} diff --git a/test/widgets/add_contact_by_key_dialog_test.dart b/test/widgets/add_contact_by_key_dialog_test.dart new file mode 100644 index 0000000..1337a4b --- /dev/null +++ b/test/widgets/add_contact_by_key_dialog_test.dart @@ -0,0 +1,155 @@ +// Widget tests for manual key entry (#628). +// +// The point of this dialog is that it must produce exactly the same contact +// stub a scanned QR would, so these pin what actually reaches the connector: +// the key, the typed name, the chosen type, and the epoch lastSeen that keeps +// the firmware advert replay guard from muting the contact (#620). + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +import 'package:meshcore_open/connector/meshcore_connector.dart'; +import 'package:meshcore_open/connector/meshcore_protocol.dart'; +import 'package:meshcore_open/l10n/app_localizations.dart'; +import 'package:meshcore_open/models/contact.dart'; +import 'package:meshcore_open/widgets/add_contact_by_key_dialog.dart'; + +const _key = '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff'; + +class _CapturingConn extends MeshCoreConnector { + final List added = []; + + @override + Future addContactByKey(Contact stub) async { + added.add(stub); + return true; + } +} + +Future _open(WidgetTester tester, _CapturingConn conn) async { + await tester.pumpWidget( + ChangeNotifierProvider.value( + value: conn, + child: MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + body: Builder( + builder: (context) => ElevatedButton( + onPressed: () => showAddContactByKeyDialog(context), + child: const Text('open'), + ), + ), + ), + ), + ), + ); + await tester.tap(find.text('open')); + await tester.pumpAndSettle(); +} + +void main() { + testWidgets('a valid key produces an unverified stub with the typed name', ( + tester, + ) async { + final conn = _CapturingConn(); + await _open(tester, conn); + + await tester.enterText(find.byType(TextField).first, _key); + await tester.enterText(find.byType(TextField).last, 'Ka8sbi'); + await tester.pumpAndSettle(); + + await tester.tap(find.widgetWithText(FilledButton, 'Add')); + await tester.pumpAndSettle(); + + expect(conn.added, hasLength(1)); + final stub = conn.added.single; + expect(stub.publicKeyHex, _key); + expect(stub.name, 'Ka8sbi'); + expect(stub.type, advTypeChat); + // The reason this whole path exists. Anything else here and the contact + // goes permanently deaf to its own adverts. + expect(stub.lastSeen, DateTime.fromMillisecondsSinceEpoch(0)); + expect(stub.pathLength, -1); + }); + + testWidgets('pasting a full contact link fills in every field', ( + tester, + ) async { + final conn = _CapturingConn(); + await _open(tester, conn); + + await tester.enterText( + find.byType(TextField).first, + 'meshcore://contact/add?name=Two+Words&public_key=$_key&type=2', + ); + await tester.pumpAndSettle(); + + await tester.tap(find.widgetWithText(FilledButton, 'Add')); + await tester.pumpAndSettle(); + + expect(conn.added, hasLength(1)); + final stub = conn.added.single; + expect(stub.publicKeyHex, _key); + // Name and type came from the pasted link, not retyped by hand. + expect(stub.name, 'Two Words'); + expect(stub.type, advTypeRepeater); + }); + + testWidgets('whitespace in a pasted key is tolerated', (tester) async { + final conn = _CapturingConn(); + await _open(tester, conn); + + // A key copied out of a chat log often arrives wrapped. + await tester.enterText( + find.byType(TextField).first, + '${_key.substring(0, 32)}\n${_key.substring(32)}', + ); + await tester.enterText(find.byType(TextField).last, 'Bob'); + await tester.pumpAndSettle(); + + await tester.tap(find.widgetWithText(FilledButton, 'Add')); + await tester.pumpAndSettle(); + + expect(conn.added, hasLength(1)); + expect(conn.added.single.publicKeyHex, _key); + }); + + testWidgets('an invalid key shows an error and sends nothing', ( + tester, + ) async { + final conn = _CapturingConn(); + await _open(tester, conn); + + await tester.enterText(find.byType(TextField).first, 'not-a-key'); + await tester.pumpAndSettle(); + + await tester.tap(find.widgetWithText(FilledButton, 'Add')); + await tester.pumpAndSettle(); + + expect(conn.added, isEmpty); + // Dialog stays open so the user can correct it. + expect(find.byType(AlertDialog), findsOneWidget); + expect( + find.textContaining('64 hex characters', findRichText: true), + findsWidgets, + ); + }); + + testWidgets('a missing name falls back rather than blocking the add', ( + tester, + ) async { + final conn = _CapturingConn(); + await _open(tester, conn); + + await tester.enterText(find.byType(TextField).first, _key); + await tester.pumpAndSettle(); + + await tester.tap(find.widgetWithText(FilledButton, 'Add')); + await tester.pumpAndSettle(); + + expect(conn.added, hasLength(1)); + expect(conn.added.single.name, 'Unknown'); + }); +} diff --git a/untranslated.json b/untranslated.json index 79eeb12..da87696 100644 --- a/untranslated.json +++ b/untranslated.json @@ -60,9 +60,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -82,6 +94,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -168,9 +190,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -190,6 +224,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -276,9 +320,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -298,6 +354,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -384,9 +450,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -406,6 +484,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -492,9 +580,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -514,6 +614,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -600,9 +710,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -622,6 +744,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -708,9 +840,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -730,6 +874,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -816,9 +970,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -838,6 +1004,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -924,9 +1100,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -946,6 +1134,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -1032,9 +1230,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1054,6 +1264,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -1140,9 +1360,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1162,6 +1394,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -1248,9 +1490,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1270,6 +1524,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -1356,9 +1620,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1378,6 +1654,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -1464,9 +1750,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1486,6 +1784,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -1572,9 +1880,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1594,6 +1914,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -1680,9 +2010,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1702,6 +2044,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock", @@ -1788,9 +2140,21 @@ "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", "channel_coreScopeCounts", + "channel_senderNameUnknown", + "channel_senderNameUnknownHint", + "channel_senderAlreadyContact", + "channel_senderMultipleMatches", + "channel_senderAddNotConnected", + "channel_senderAddFailed", + "channel_senderCandidate", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1810,6 +2174,16 @@ "channels_qrExists", "listFilter_sensors", "contacts_repeaterPathTraceVia", + "contacts_addByKey", + "contacts_addByKeyDescription", + "contacts_publicKeyLabel", + "contacts_publicKeyHelper", + "contacts_publicKeyInvalid", + "contacts_nameLabel", + "contacts_typeLabel", + "contacts_addByKeyUnverifiedNote", + "contacts_addByKeyAdded", + "contacts_addByKeyFailed", "snrIndicator_noNeighbors", "block_block", "block_unblock",