Merge pull request #42 from wel97459/dev-neighbours

Added Neighbors to the repeater hub and a screen to display the Neighbors
This commit is contained in:
zjs81
2026-01-19 19:17:00 -07:00
committed by GitHub
33 changed files with 1436 additions and 9 deletions
+1
View File
@@ -3,3 +3,4 @@ template-arb-file: app_en.arb
output-localization-file: app_localizations.dart output-localization-file: app_localizations.dart
output-class: AppLocalizations output-class: AppLocalizations
nullable-getter: false nullable-getter: false
untranslated-messages-file: untranslated.json
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Сървъри на стая", "listFilter_roomServers": "Сървъри на стая",
"listFilter_unreadOnly": "Само непрочетените", "listFilter_unreadOnly": "Само непрочетените",
"listFilter_newGroup": "Нова група", "listFilter_newGroup": "Нова група",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighboursSubtitle": "Преглед на съседни възли с нулев скок.",
"repeater_neighbours": "Съседи",
"neighbors_receivedData": "Получени данни за съседи",
"neighbors_requestTimedOut": "Съседите поискат изтичане на време.",
"neighbors_errorLoading": "Грешка при зареждане на съседи: {error}",
"neighbors_repeatersNeighbours": "Повторители Съседи",
"neighbors_noData": "Няма налични данни за съседи.",
"channels_createPrivateChannel": "Създай Частен Канал", "channels_createPrivateChannel": "Създай Частен Канал",
"channels_joinPrivateChannel": "Присъедини се към Частен Канал", "channels_joinPrivateChannel": "Присъедини се към Частен Канал",
"channels_createPrivateChannelDesc": "Защитено с таен ключ.", "channels_createPrivateChannelDesc": "Защитено с таен ключ.",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Ще излезе скоро", "channels_scanQrCodeComingSoon": "Ще излезе скоро",
"channels_enterHashtag": "Въведете хаштаг", "channels_enterHashtag": "Въведете хаштаг",
"channels_hashtagHint": "напр. #отбор", "channels_hashtagHint": "напр. #отбор",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_heardAgo": "Слушано преди {time}.",
"neighbors_unknownContact": "Неизвестна {pubkey}",
"settings_locationIntervalSec": "Интервал за GPS (Секунди)", "settings_locationIntervalSec": "Интервал за GPS (Секунди)",
"settings_locationGPSEnable": "Активиране на GPS", "settings_locationGPSEnable": "Активиране на GPS",
"settings_locationGPSEnableSubtitle": "Активирайте автоматичното актуализиране на местоположението чрез GPS.", "settings_locationGPSEnableSubtitle": "Активирайте автоматичното актуализиране на местоположението чрез GPS.",
+37 -1
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Raumserver", "listFilter_roomServers": "Raumserver",
"listFilter_unreadOnly": "Nur nicht gelesen", "listFilter_unreadOnly": "Nur nicht gelesen",
"listFilter_newGroup": "Neue Gruppe", "listFilter_newGroup": "Neue Gruppe",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighbours": "Nachbarn",
"repeater_neighboursSubtitle": "Anzahl der Hop-Nachbarn anzeigen.",
"neighbors_receivedData": "Empfangene Nachbarendaten",
"neighbors_requestTimedOut": "Nachbarn melden zeitweise Ausfall.",
"neighbors_errorLoading": "Fehler beim Laden der Nachbarn: {error}",
"neighbors_repeatersNeighbours": "Wiederholer Nachbarn",
"neighbors_noData": "Keine Nachbardaten verfügbar.",
"channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei", "channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei",
"channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.", "channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.",
"channels_createPrivateChannel": "Erstelle einen privaten Kanal", "channels_createPrivateChannel": "Erstelle einen privaten Kanal",
@@ -1348,5 +1362,27 @@
"channels_scanQrCode": "Scannen Sie einen QR-Code", "channels_scanQrCode": "Scannen Sie einen QR-Code",
"channels_scanQrCodeComingSoon": "Bald verfügbar", "channels_scanQrCodeComingSoon": "Bald verfügbar",
"channels_enterHashtag": "Gib Hashtag ein", "channels_enterHashtag": "Gib Hashtag ein",
"channels_hashtagHint": "z.B. #team" "channels_hashtagHint": "z.B. #team",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_heardAgo": "Hörte: {time} vor her.",
"neighbors_unknownContact": "Unbekannte {pubkey}",
"settings_locationGPSEnable": "GPS aktivieren",
"settings_locationGPSEnableSubtitle": "Aktiviert GPS zur automatischen Aktualisierung des Standorts.",
"settings_locationIntervalSec": "Intervall für GPS (Sekunden)",
"settings_locationIntervalInvalid": "Das Intervall muss mindestens 60 Sekunden und weniger als 86400 Sekunden betragen.",
"contacts_manageRoom": "Raum-Server verwalten",
"room_management": "Raum-Server-Verwaltung"
} }
+25
View File
@@ -773,6 +773,8 @@
"repeater_telemetrySubtitle": "View telemetry of sensors and system stats", "repeater_telemetrySubtitle": "View telemetry of sensors and system stats",
"repeater_cli": "CLI", "repeater_cli": "CLI",
"repeater_cliSubtitle": "Send commands to the repeater", "repeater_cliSubtitle": "Send commands to the repeater",
"repeater_neighbours": "Neighbors",
"repeater_neighboursSubtitle": "View zero hop neighbors.",
"repeater_settings": "Settings", "repeater_settings": "Settings",
"repeater_settingsSubtitle": "Configure repeater parameters", "repeater_settingsSubtitle": "Configure repeater parameters",
@@ -1075,6 +1077,29 @@
"fahrenheit": {"type": "String"} "fahrenheit": {"type": "String"}
} }
}, },
"neighbors_receivedData": "Received Neighbours Data",
"neighbors_requestTimedOut": "Neighbours request timed out.",
"neighbors_errorLoading": "Error loading neighbors: {error}",
"@neighbors_errorLoading": {
"placeholders": {
"error": {"type": "String"}
}
},
"neighbors_repeatersNeighbours": "Repeaters Neighbours",
"neighbors_noData": "No neighbours data available.",
"neighbors_unknownContact": "Unknown {pubkey}",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {"type": "String"}
}
},
"neighbors_heardAgo": "Heard: {time} ago",
"@neighbors_heardAgo": {
"placeholders": {
"time": {"type": "String"}
}
},
"channelPath_title": "Packet Path", "channelPath_title": "Packet Path",
"channelPath_viewMap": "View map", "channelPath_viewMap": "View map",
"channelPath_otherObservedPaths": "Other Observed Paths", "channelPath_otherObservedPaths": "Other Observed Paths",
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Servidores de la sala", "listFilter_roomServers": "Servidores de la sala",
"listFilter_unreadOnly": "Solo sin leer", "listFilter_unreadOnly": "Solo sin leer",
"listFilter_newGroup": "Nuevo grupo", "listFilter_newGroup": "Nuevo grupo",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighbours": "Vecinos",
"repeater_neighboursSubtitle": "Ver vecinos de salto cero.",
"neighbors_receivedData": "Recibidas Datos de Vecinos",
"neighbors_requestTimedOut": "Los vecinos solicitan que se desconecte.",
"neighbors_errorLoading": "Error al cargar vecinos: {error}",
"neighbors_repeatersNeighbours": "Repetidores Vecinos",
"neighbors_noData": "No hay datos de vecinos disponibles.",
"channels_joinPrivateChannel": "Únete a un Canal Privado", "channels_joinPrivateChannel": "Únete a un Canal Privado",
"channels_createPrivateChannel": "Crear un Canal Privado", "channels_createPrivateChannel": "Crear un Canal Privado",
"channels_createPrivateChannelDesc": "Cifrado con una clave secreta.", "channels_createPrivateChannelDesc": "Cifrado con una clave secreta.",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Próximamente", "channels_scanQrCodeComingSoon": "Próximamente",
"channels_enterHashtag": "Introducir hashtag", "channels_enterHashtag": "Introducir hashtag",
"channels_hashtagHint": "ej. #equipo", "channels_hashtagHint": "ej. #equipo",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_unknownContact": "Clave pública desconocida {pubkey}",
"neighbors_heardAgo": "Escuchado: {time} hace atrás",
"settings_locationGPSEnable": "Habilitar GPS", "settings_locationGPSEnable": "Habilitar GPS",
"settings_locationGPSEnableSubtitle": "Habilita la actualización automática de la ubicación mediante GPS.", "settings_locationGPSEnableSubtitle": "Habilita la actualización automática de la ubicación mediante GPS.",
"settings_locationIntervalSec": "Intervalo para GPS (Segundos)", "settings_locationIntervalSec": "Intervalo para GPS (Segundos)",
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Serveurs de pièce", "listFilter_roomServers": "Serveurs de pièce",
"listFilter_unreadOnly": "Messages non lus seulement", "listFilter_unreadOnly": "Messages non lus seulement",
"listFilter_newGroup": "Nouvelle groupe", "listFilter_newGroup": "Nouvelle groupe",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighbours": "Voisins",
"repeater_neighboursSubtitle": "Afficher les voisins de saut nuls.",
"neighbors_receivedData": "Données des voisins reçues",
"neighbors_requestTimedOut": "Les voisins demandent un délai.",
"neighbors_errorLoading": "Erreur lors du chargement des voisins : {error}",
"neighbors_repeatersNeighbours": "Répéteurs Voisins",
"neighbors_noData": "Aucune donnée concernant les voisins disponible.",
"channels_createPrivateChannelDesc": "Sécurisé avec une clé secrète.", "channels_createPrivateChannelDesc": "Sécurisé avec une clé secrète.",
"channels_joinPrivateChannel": "Rejoindre un Canal Privé", "channels_joinPrivateChannel": "Rejoindre un Canal Privé",
"channels_createPrivateChannel": "Créer un Canal Privé", "channels_createPrivateChannel": "Créer un Canal Privé",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Bientôt disponible", "channels_scanQrCodeComingSoon": "Bientôt disponible",
"channels_enterHashtag": "Entrez le hashtag", "channels_enterHashtag": "Entrez le hashtag",
"channels_hashtagHint": "ex. #équipe", "channels_hashtagHint": "ex. #équipe",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_unknownContact": "Clé publique inconnue {pubkey}",
"neighbors_heardAgo": "Écouté : {time} auparavant",
"settings_locationGPSEnable": "Habilita GPS", "settings_locationGPSEnable": "Habilita GPS",
"settings_locationGPSEnableSubtitle": "Habilita la actualización automática de la ubicación mediante GPS.", "settings_locationGPSEnableSubtitle": "Habilita la actualización automática de la ubicación mediante GPS.",
"settings_locationIntervalSec": "Intervalo pour GPS (Segundos)", "settings_locationIntervalSec": "Intervalo pour GPS (Segundos)",
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Server della stanza", "listFilter_roomServers": "Server della stanza",
"listFilter_unreadOnly": "Solo non letto", "listFilter_unreadOnly": "Solo non letto",
"listFilter_newGroup": "Nuovo gruppo", "listFilter_newGroup": "Nuovo gruppo",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighbours": "Vicini",
"repeater_neighboursSubtitle": "Visualizza vicini di salto pari a zero.",
"neighbors_receivedData": "Ricevute dati vicini",
"neighbors_requestTimedOut": "I vicini richiedono un timeout.",
"neighbors_errorLoading": "Errore nel caricamento dei vicini: {error}",
"neighbors_repeatersNeighbours": "Ripetitori Vicini",
"neighbors_noData": "Nessun dato sugli vicini disponibile.",
"channels_createPrivateChannel": "Crea un Canale Privato", "channels_createPrivateChannel": "Crea un Canale Privato",
"channels_createPrivateChannelDesc": "Protetta con una chiave segreta.", "channels_createPrivateChannelDesc": "Protetta con una chiave segreta.",
"channels_joinPrivateChannel": "Unisciti a un Canale Privato", "channels_joinPrivateChannel": "Unisciti a un Canale Privato",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Arriverà presto", "channels_scanQrCodeComingSoon": "Arriverà presto",
"channels_enterHashtag": "Inserisci hashtag", "channels_enterHashtag": "Inserisci hashtag",
"channels_hashtagHint": "es. #team", "channels_hashtagHint": "es. #team",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_heardAgo": "Sentito: {time} fa",
"neighbors_unknownContact": "Chiave pubblica sconosciuta {pubkey}",
"settings_locationGPSEnable": "Abilita GPS", "settings_locationGPSEnable": "Abilita GPS",
"settings_locationGPSEnableSubtitle": "Abilita l'aggiornamento automatico della posizione tramite GPS.", "settings_locationGPSEnableSubtitle": "Abilita l'aggiornamento automatico della posizione tramite GPS.",
"settings_locationIntervalSec": "Intervallo GPS (Secondi)", "settings_locationIntervalSec": "Intervallo GPS (Secondi)",
+54
View File
@@ -2945,6 +2945,18 @@ abstract class AppLocalizations {
/// **'Send commands to the repeater'** /// **'Send commands to the repeater'**
String get repeater_cliSubtitle; String get repeater_cliSubtitle;
/// No description provided for @repeater_neighbours.
///
/// In en, this message translates to:
/// **'Neighbors'**
String get repeater_neighbours;
/// No description provided for @repeater_neighboursSubtitle.
///
/// In en, this message translates to:
/// **'View zero hop neighbors.'**
String get repeater_neighboursSubtitle;
/// No description provided for @repeater_settings. /// No description provided for @repeater_settings.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -4084,6 +4096,48 @@ abstract class AppLocalizations {
/// **'{celsius}°C / {fahrenheit}°F'** /// **'{celsius}°C / {fahrenheit}°F'**
String telemetry_temperatureValue(String celsius, String fahrenheit); String telemetry_temperatureValue(String celsius, String fahrenheit);
/// No description provided for @neighbors_receivedData.
///
/// In en, this message translates to:
/// **'Received Neighbours Data'**
String get neighbors_receivedData;
/// No description provided for @neighbors_requestTimedOut.
///
/// In en, this message translates to:
/// **'Neighbours request timed out.'**
String get neighbors_requestTimedOut;
/// No description provided for @neighbors_errorLoading.
///
/// In en, this message translates to:
/// **'Error loading neighbors: {error}'**
String neighbors_errorLoading(String error);
/// No description provided for @neighbors_repeatersNeighbours.
///
/// In en, this message translates to:
/// **'Repeaters Neighbours'**
String get neighbors_repeatersNeighbours;
/// No description provided for @neighbors_noData.
///
/// In en, this message translates to:
/// **'No neighbours data available.'**
String get neighbors_noData;
/// No description provided for @neighbors_unknownContact.
///
/// In en, this message translates to:
/// **'Unknown {pubkey}'**
String neighbors_unknownContact(String pubkey);
/// No description provided for @neighbors_heardAgo.
///
/// In en, this message translates to:
/// **'Heard: {time} ago'**
String neighbors_heardAgo(String time);
/// No description provided for @channelPath_title. /// No description provided for @channelPath_title.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
+34
View File
@@ -1630,6 +1630,13 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Изпрати команди към ретранслатора'; String get repeater_cliSubtitle => 'Изпрати команди към ретранслатора';
@override
String get repeater_neighbours => 'Съседи';
@override
String get repeater_neighboursSubtitle =>
'Преглед на съседни възли с нулев скок.';
@override @override
String get repeater_settings => 'Настройки'; String get repeater_settings => 'Настройки';
@@ -2315,6 +2322,33 @@ class AppLocalizationsBg extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Получени данни за съседи';
@override
String get neighbors_requestTimedOut => 'Съседите поискат изтичане на време.';
@override
String neighbors_errorLoading(String error) {
return 'Грешка при зареждане на съседи: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Повторители Съседи';
@override
String get neighbors_noData => 'Няма налични данни за съседи.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Неизвестна $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Слушано преди $time.';
}
@override @override
String get channelPath_title => 'Пътеки пъзел'; String get channelPath_title => 'Пътеки пъзел';
+39 -6
View File
@@ -201,18 +201,18 @@ class AppLocalizationsDe extends AppLocalizations {
String get settings_locationInvalid => 'Ungültige Breiten- oder Längengrade.'; String get settings_locationInvalid => 'Ungültige Breiten- oder Längengrade.';
@override @override
String get settings_locationGPSEnable => 'GPS Enable'; String get settings_locationGPSEnable => 'GPS aktivieren';
@override @override
String get settings_locationGPSEnableSubtitle => String get settings_locationGPSEnableSubtitle =>
'Enables GPS to automatically update location.'; 'Aktiviert GPS zur automatischen Aktualisierung des Standorts.';
@override @override
String get settings_locationIntervalSec => 'Interval for GPS (Seconds)'; String get settings_locationIntervalSec => 'Intervall für GPS (Sekunden)';
@override @override
String get settings_locationIntervalInvalid => String get settings_locationIntervalInvalid =>
'Interval must be at least 60 seconds, and less than 86400 seconds.'; 'Das Intervall muss mindestens 60 Sekunden und weniger als 86400 Sekunden betragen.';
@override @override
String get settings_latitude => 'Breitengrad'; String get settings_latitude => 'Breitengrad';
@@ -662,7 +662,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get contacts_manageRepeater => 'Wiederholungen verwalten'; String get contacts_manageRepeater => 'Wiederholungen verwalten';
@override @override
String get contacts_manageRoom => 'Manage Room Server'; String get contacts_manageRoom => 'Raum-Server verwalten';
@override @override
String get contacts_roomLogin => 'Raum-Login'; String get contacts_roomLogin => 'Raum-Login';
@@ -1604,7 +1604,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get repeater_management => 'Repeater-Verwaltung'; String get repeater_management => 'Repeater-Verwaltung';
@override @override
String get room_management => 'Room Server Management'; String get room_management => 'Raum-Server-Verwaltung';
@override @override
String get repeater_managementTools => 'Verwaltungs-Tools'; String get repeater_managementTools => 'Verwaltungs-Tools';
@@ -1629,6 +1629,12 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Sende Befehle an den Repeater'; String get repeater_cliSubtitle => 'Sende Befehle an den Repeater';
@override
String get repeater_neighbours => 'Nachbarn';
@override
String get repeater_neighboursSubtitle => 'Anzahl der Hop-Nachbarn anzeigen.';
@override @override
String get repeater_settings => 'Einstellungen'; String get repeater_settings => 'Einstellungen';
@@ -2318,6 +2324,33 @@ class AppLocalizationsDe extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Empfangene Nachbarendaten';
@override
String get neighbors_requestTimedOut => 'Nachbarn melden zeitweise Ausfall.';
@override
String neighbors_errorLoading(String error) {
return 'Fehler beim Laden der Nachbarn: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Wiederholer Nachbarn';
@override
String get neighbors_noData => 'Keine Nachbardaten verfügbar.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Unbekannte $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Hörte: $time vor her.';
}
@override @override
String get channelPath_title => 'Paketpfad'; String get channelPath_title => 'Paketpfad';
+33
View File
@@ -1604,6 +1604,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Send commands to the repeater'; String get repeater_cliSubtitle => 'Send commands to the repeater';
@override
String get repeater_neighbours => 'Neighbors';
@override
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
@override @override
String get repeater_settings => 'Settings'; String get repeater_settings => 'Settings';
@@ -2277,6 +2283,33 @@ class AppLocalizationsEn extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Received Neighbours Data';
@override
String get neighbors_requestTimedOut => 'Neighbours request timed out.';
@override
String neighbors_errorLoading(String error) {
return 'Error loading neighbors: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Repeaters Neighbours';
@override
String get neighbors_noData => 'No neighbours data available.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Unknown $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Heard: $time ago';
}
@override @override
String get channelPath_title => 'Packet Path'; String get channelPath_title => 'Packet Path';
+34
View File
@@ -1628,6 +1628,12 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Enviar comandos al repetidor'; String get repeater_cliSubtitle => 'Enviar comandos al repetidor';
@override
String get repeater_neighbours => 'Vecinos';
@override
String get repeater_neighboursSubtitle => 'Ver vecinos de salto cero.';
@override @override
String get repeater_settings => 'Configuración'; String get repeater_settings => 'Configuración';
@@ -2312,6 +2318,34 @@ class AppLocalizationsEs extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Recibidas Datos de Vecinos';
@override
String get neighbors_requestTimedOut =>
'Los vecinos solicitan que se desconecte.';
@override
String neighbors_errorLoading(String error) {
return 'Error al cargar vecinos: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Repetidores Vecinos';
@override
String get neighbors_noData => 'No hay datos de vecinos disponibles.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Clave pública desconocida $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Escuchado: $time hace atrás';
}
@override @override
String get channelPath_title => 'Ruta del Paquete'; String get channelPath_title => 'Ruta del Paquete';
+35
View File
@@ -1634,6 +1634,13 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Envoyer des commandes au répétiteur'; String get repeater_cliSubtitle => 'Envoyer des commandes au répétiteur';
@override
String get repeater_neighbours => 'Voisins';
@override
String get repeater_neighboursSubtitle =>
'Afficher les voisins de saut nuls.';
@override @override
String get repeater_settings => 'Paramètres'; String get repeater_settings => 'Paramètres';
@@ -2326,6 +2333,34 @@ class AppLocalizationsFr extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Données des voisins reçues';
@override
String get neighbors_requestTimedOut => 'Les voisins demandent un délai.';
@override
String neighbors_errorLoading(String error) {
return 'Erreur lors du chargement des voisins : $error';
}
@override
String get neighbors_repeatersNeighbours => 'Répéteurs Voisins';
@override
String get neighbors_noData =>
'Aucune donnée concernant les voisins disponible.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Clé publique inconnue $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Écouté : $time auparavant';
}
@override @override
String get channelPath_title => 'Chemin de paquet'; String get channelPath_title => 'Chemin de paquet';
+34
View File
@@ -1626,6 +1626,13 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Invia comandi al ripetitore'; String get repeater_cliSubtitle => 'Invia comandi al ripetitore';
@override
String get repeater_neighbours => 'Vicini';
@override
String get repeater_neighboursSubtitle =>
'Visualizza vicini di salto pari a zero.';
@override @override
String get repeater_settings => 'Impostazioni'; String get repeater_settings => 'Impostazioni';
@@ -2312,6 +2319,33 @@ class AppLocalizationsIt extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Ricevute dati vicini';
@override
String get neighbors_requestTimedOut => 'I vicini richiedono un timeout.';
@override
String neighbors_errorLoading(String error) {
return 'Errore nel caricamento dei vicini: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Ripetitori Vicini';
@override
String get neighbors_noData => 'Nessun dato sugli vicini disponibile.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Chiave pubblica sconosciuta $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Sentito: $time fa';
}
@override @override
String get channelPath_title => 'Percorso Pacchetto'; String get channelPath_title => 'Percorso Pacchetto';
+34
View File
@@ -1621,6 +1621,12 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Verzend commando\'s naar de repeater'; String get repeater_cliSubtitle => 'Verzend commando\'s naar de repeater';
@override
String get repeater_neighbours => 'Buren';
@override
String get repeater_neighboursSubtitle => 'Bekijk nul hops buren.';
@override @override
String get repeater_settings => 'Instellingen'; String get repeater_settings => 'Instellingen';
@@ -2302,6 +2308,34 @@ class AppLocalizationsNl extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Ontvangen Buurdata';
@override
String get neighbors_requestTimedOut =>
'Buren vragen om tijdelijk uitgeschakeld.';
@override
String neighbors_errorLoading(String error) {
return 'Fout bij het laden van buren: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Herhalingen Buren';
@override
String get neighbors_noData => 'Geen gegevens van buren beschikbaar.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Onbekende $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Horen: $time geleden';
}
@override @override
String get channelPath_title => 'Pakketpad'; String get channelPath_title => 'Pakketpad';
+35
View File
@@ -1630,6 +1630,13 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Wyślij polecenia do powielacza'; String get repeater_cliSubtitle => 'Wyślij polecenia do powielacza';
@override
String get repeater_neighbours => 'Sąsiedzi';
@override
String get repeater_neighboursSubtitle =>
'Wyświetl sąsiedztwo zerowych hopów.';
@override @override
String get repeater_settings => 'Ustawienia'; String get repeater_settings => 'Ustawienia';
@@ -2310,6 +2317,34 @@ class AppLocalizationsPl extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Otrzymano dane sąsiedztwa';
@override
String get neighbors_requestTimedOut =>
'Sąsiedzi proszą o wyłączenie timingu.';
@override
String neighbors_errorLoading(String error) {
return 'Błąd podczas ładowania sąsiadów: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Powtarzacze Sąsiedzi';
@override
String get neighbors_noData => 'Brak danych dotyczących sąsiadów.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Nieznana $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Usłyszano: $time temu';
}
@override @override
String get channelPath_title => 'Ścieżka pakietu'; String get channelPath_title => 'Ścieżka pakietu';
+35
View File
@@ -1628,6 +1628,13 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Enviar comandos ao repetidor'; String get repeater_cliSubtitle => 'Enviar comandos ao repetidor';
@override
String get repeater_neighbours => 'Vizinhos';
@override
String get repeater_neighboursSubtitle =>
'Visualizar vizinhos de salto zero.';
@override @override
String get repeater_settings => 'Configurações'; String get repeater_settings => 'Configurações';
@@ -2312,6 +2319,34 @@ class AppLocalizationsPt extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Dados dos Vizinhos Recebidos';
@override
String get neighbors_requestTimedOut =>
'Vizinhos solicitam tempo limite esgotado.';
@override
String neighbors_errorLoading(String error) {
return 'Erro ao carregar vizinhos: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Repetidores Vizinhos';
@override
String get neighbors_noData => 'Não estão disponíveis dados de vizinhos.';
@override
String neighbors_unknownContact(String pubkey) {
return '$pubkey Desconhecido';
}
@override
String neighbors_heardAgo(String time) {
return 'Ouvido: $time atrás';
}
@override @override
String get channelPath_title => 'Rótulo de Caminho de Pacote'; String get channelPath_title => 'Rótulo de Caminho de Pacote';
+34
View File
@@ -1623,6 +1623,12 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Pošlite príkazy opakovaču'; String get repeater_cliSubtitle => 'Pošlite príkazy opakovaču';
@override
String get repeater_neighbours => 'Súsezný';
@override
String get repeater_neighboursSubtitle => 'Zobraziť susedné body bez skokov.';
@override @override
String get repeater_settings => 'Nastavenia'; String get repeater_settings => 'Nastavenia';
@@ -2300,6 +2306,34 @@ class AppLocalizationsSk extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Obdielo dáta suseda';
@override
String get neighbors_requestTimedOut => 'Súďia žiadajú o časové ukončenie.';
@override
String neighbors_errorLoading(String error) {
return 'Chyba pri načítaní susedov: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Opakovadlá Súsezná';
@override
String get neighbors_noData =>
'Nie je dostupná žiadna informácia o susedoch.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Neznáma $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Počuli sme to: $time dozadu';
}
@override @override
String get channelPath_title => 'Cesta balíka'; String get channelPath_title => 'Cesta balíka';
+34
View File
@@ -1624,6 +1624,12 @@ class AppLocalizationsSl extends AppLocalizations {
String get repeater_cliSubtitle => String get repeater_cliSubtitle =>
'Pošlji ukazne povelje na ponovitveno enoto.'; 'Pošlji ukazne povelje na ponovitveno enoto.';
@override
String get repeater_neighbours => 'Sosedi';
@override
String get repeater_neighboursSubtitle => 'Pogledati nič sosednjih hopjev.';
@override @override
String get repeater_settings => 'Nastavitve'; String get repeater_settings => 'Nastavitve';
@@ -2305,6 +2311,34 @@ class AppLocalizationsSl extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Prejeto podatke o sosedih';
@override
String get neighbors_requestTimedOut =>
'Sosedi zahtevajo izklop po dogovoru.';
@override
String neighbors_errorLoading(String error) {
return 'Napaka pri obnašanju sosedov: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Ponovitve Sosedi';
@override
String get neighbors_noData => 'Niso na voljo podatki o sosedih.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Nepoznano $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Udeleženec je prejel sporočilo $time nazaj.';
}
@override @override
String get channelPath_title => 'Pot do paketa'; String get channelPath_title => 'Pot do paketa';
+33
View File
@@ -1612,6 +1612,12 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => 'Skicka kommandon till repetitorn'; String get repeater_cliSubtitle => 'Skicka kommandon till repetitorn';
@override
String get repeater_neighbours => 'Grannar';
@override
String get repeater_neighboursSubtitle => 'Visa noll hoppgrannar.';
@override @override
String get repeater_settings => 'Inställningar'; String get repeater_settings => 'Inställningar';
@@ -2289,6 +2295,33 @@ class AppLocalizationsSv extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => 'Mottagna grannars data';
@override
String get neighbors_requestTimedOut => 'Grannar begär tidsinställd utskick.';
@override
String neighbors_errorLoading(String error) {
return 'Fel vid inläsning av grannar: $error';
}
@override
String get neighbors_repeatersNeighbours => 'Upprepar grannar';
@override
String get neighbors_noData => 'Inga grannuppgifter finns tillgängliga.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Okänd $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Hördes: $time sedan';
}
@override @override
String get channelPath_title => 'Paketväg'; String get channelPath_title => 'Paketväg';
+33
View File
@@ -1552,6 +1552,12 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get repeater_cliSubtitle => '发送命令到重复器'; String get repeater_cliSubtitle => '发送命令到重复器';
@override
String get repeater_neighbours => '邻居';
@override
String get repeater_neighboursSubtitle => '查看零跳邻居。';
@override @override
String get repeater_settings => '设置'; String get repeater_settings => '设置';
@@ -2182,6 +2188,33 @@ class AppLocalizationsZh extends AppLocalizations {
return '$celsius°C / $fahrenheit°F'; return '$celsius°C / $fahrenheit°F';
} }
@override
String get neighbors_receivedData => '收到邻居数据';
@override
String get neighbors_requestTimedOut => '邻居请求超时处理。';
@override
String neighbors_errorLoading(String error) {
return '加载邻居时出错:$error';
}
@override
String get neighbors_repeatersNeighbours => '重复器邻居';
@override
String get neighbors_noData => '没有可用的邻居数据。';
@override
String neighbors_unknownContact(String pubkey) {
return '未知$pubkey';
}
@override
String neighbors_heardAgo(String time) {
return '听到的时间:$time前';
}
@override @override
String get channelPath_title => '数据包路径'; String get channelPath_title => '数据包路径';
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Roomservers", "listFilter_roomServers": "Roomservers",
"listFilter_unreadOnly": "Alleen ongelezen", "listFilter_unreadOnly": "Alleen ongelezen",
"listFilter_newGroup": "Nieuwe groep", "listFilter_newGroup": "Nieuwe groep",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighbours": "Buren",
"repeater_neighboursSubtitle": "Bekijk nul hops buren.",
"neighbors_receivedData": "Ontvangen Buurdata",
"neighbors_requestTimedOut": "Buren vragen om tijdelijk uitgeschakeld.",
"neighbors_errorLoading": "Fout bij het laden van buren: {error}",
"neighbors_repeatersNeighbours": "Herhalingen Buren",
"neighbors_noData": "Geen gegevens van buren beschikbaar.",
"channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.", "channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.",
"channels_createPrivateChannel": "Maak een Privé Kanaal", "channels_createPrivateChannel": "Maak een Privé Kanaal",
"channels_joinPrivateChannel": "Sluit een Privé Kanaal aan", "channels_joinPrivateChannel": "Sluit een Privé Kanaal aan",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Komt later", "channels_scanQrCodeComingSoon": "Komt later",
"channels_enterHashtag": "Voer hashtag in", "channels_enterHashtag": "Voer hashtag in",
"channels_hashtagHint": "bijv. #team", "channels_hashtagHint": "bijv. #team",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_unknownContact": "Onbekende {pubkey}",
"neighbors_heardAgo": "Horen: {time} geleden",
"settings_locationGPSEnable": "GPS inschakelen", "settings_locationGPSEnable": "GPS inschakelen",
"settings_locationGPSEnableSubtitle": "Activeer automatisch locatieupdates via GPS.", "settings_locationGPSEnableSubtitle": "Activeer automatisch locatieupdates via GPS.",
"settings_locationIntervalSec": "Interval voor GPS (Seconden)", "settings_locationIntervalSec": "Interval voor GPS (Seconden)",
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Serwery pokoju", "listFilter_roomServers": "Serwery pokoju",
"listFilter_unreadOnly": "Tylko nieprzeczytane", "listFilter_unreadOnly": "Tylko nieprzeczytane",
"listFilter_newGroup": "Nowa grupa", "listFilter_newGroup": "Nowa grupa",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighbours": "Sąsiedzi",
"repeater_neighboursSubtitle": "Wyświetl sąsiedztwo zerowych hopów.",
"neighbors_receivedData": "Otrzymano dane sąsiedztwa",
"neighbors_requestTimedOut": "Sąsiedzi proszą o wyłączenie timingu.",
"neighbors_errorLoading": "Błąd podczas ładowania sąsiadów: {error}",
"neighbors_repeatersNeighbours": "Powtarzacze Sąsiedzi",
"neighbors_noData": "Brak danych dotyczących sąsiadów.",
"channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.", "channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.",
"channels_createPrivateChannel": "Utwórz Prywatny Kanał", "channels_createPrivateChannel": "Utwórz Prywatny Kanał",
"channels_createPrivateChannelDesc": "Zabezpieczone kluczem szyfrowym.", "channels_createPrivateChannelDesc": "Zabezpieczone kluczem szyfrowym.",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Wkrótce", "channels_scanQrCodeComingSoon": "Wkrótce",
"channels_enterHashtag": "Wprowadź hashtag", "channels_enterHashtag": "Wprowadź hashtag",
"channels_hashtagHint": "np. #zespół", "channels_hashtagHint": "np. #zespół",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_heardAgo": "Usłyszano: {time} temu",
"neighbors_unknownContact": "Nieznana {pubkey}",
"settings_locationGPSEnable": "Włącz GPS", "settings_locationGPSEnable": "Włącz GPS",
"settings_locationGPSEnableSubtitle": "Włącza automatyczne aktualizowanie pozycji za pomocą GPS.", "settings_locationGPSEnableSubtitle": "Włącza automatyczne aktualizowanie pozycji za pomocą GPS.",
"settings_locationIntervalSec": "Interwał dla GPS (Sekundy)", "settings_locationIntervalSec": "Interwał dla GPS (Sekundy)",
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Servidores de sala", "listFilter_roomServers": "Servidores de sala",
"listFilter_unreadOnly": "Apenas não lido", "listFilter_unreadOnly": "Apenas não lido",
"listFilter_newGroup": "Novo grupo", "listFilter_newGroup": "Novo grupo",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighbours": "Vizinhos",
"neighbors_receivedData": "Dados dos Vizinhos Recebidos",
"repeater_neighboursSubtitle": "Visualizar vizinhos de salto zero.",
"neighbors_requestTimedOut": "Vizinhos solicitam tempo limite esgotado.",
"neighbors_errorLoading": "Erro ao carregar vizinhos: {error}",
"neighbors_repeatersNeighbours": "Repetidores Vizinhos",
"neighbors_noData": "Não estão disponíveis dados de vizinhos.",
"channels_createPrivateChannelDesc": "Protegido com uma chave secreta.", "channels_createPrivateChannelDesc": "Protegido com uma chave secreta.",
"channels_joinPrivateChannelDesc": "Inserir uma chave secreta manualmente.", "channels_joinPrivateChannelDesc": "Inserir uma chave secreta manualmente.",
"channels_createPrivateChannel": "Criar um Canal Privado", "channels_createPrivateChannel": "Criar um Canal Privado",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Em breve", "channels_scanQrCodeComingSoon": "Em breve",
"channels_enterHashtag": "Insira hashtag", "channels_enterHashtag": "Insira hashtag",
"channels_hashtagHint": "ex. #equipe", "channels_hashtagHint": "ex. #equipe",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_heardAgo": "Ouvido: {time} atrás",
"neighbors_unknownContact": "{pubkey} Desconhecido",
"settings_locationGPSEnable": "Ativar GPS", "settings_locationGPSEnable": "Ativar GPS",
"settings_locationGPSEnableSubtitle": "Habilita a atualização automática da localização via GPS.", "settings_locationGPSEnableSubtitle": "Habilita a atualização automática da localização via GPS.",
"settings_locationIntervalInvalid": "O intervalo deve ser de pelo menos 60 segundos e inferior a 86400 segundos.", "settings_locationIntervalInvalid": "O intervalo deve ser de pelo menos 60 segundos e inferior a 86400 segundos.",
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Servéry miestnosti", "listFilter_roomServers": "Servéry miestnosti",
"listFilter_unreadOnly": "Nezaregistrované len", "listFilter_unreadOnly": "Nezaregistrované len",
"listFilter_newGroup": "Nová skupina", "listFilter_newGroup": "Nová skupina",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighboursSubtitle": "Zobraziť susedné body bez skokov.",
"neighbors_requestTimedOut": "Súďia žiadajú o časové ukončenie.",
"neighbors_receivedData": "Obdielo dáta suseda",
"repeater_neighbours": "Súsezný",
"neighbors_errorLoading": "Chyba pri načítaní susedov: {error}",
"neighbors_repeatersNeighbours": "Opakovadlá Súsezná",
"neighbors_noData": "Nie je dostupná žiadna informácia o susedoch.",
"channels_createPrivateChannel": "Vytvorte súkromný kanál", "channels_createPrivateChannel": "Vytvorte súkromný kanál",
"channels_joinPrivateChannel": "Pripojiť sa k súkromnému kanálu", "channels_joinPrivateChannel": "Pripojiť sa k súkromnému kanálu",
"channels_joinPrivateChannelDesc": "Ručne zadajte tajný kľúč.", "channels_joinPrivateChannelDesc": "Ručne zadajte tajný kľúč.",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Čoskoro", "channels_scanQrCodeComingSoon": "Čoskoro",
"channels_enterHashtag": "Zadajte hashtag", "channels_enterHashtag": "Zadajte hashtag",
"channels_hashtagHint": "napr. #tím", "channels_hashtagHint": "napr. #tím",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_heardAgo": "Počuli sme to: {time} dozadu",
"neighbors_unknownContact": "Neznáma {pubkey}",
"settings_locationGPSEnable": "Aktivovať GPS", "settings_locationGPSEnable": "Aktivovať GPS",
"settings_locationGPSEnableSubtitle": "Povolí automatické aktualizovanie polohy pomocou GPS.", "settings_locationGPSEnableSubtitle": "Povolí automatické aktualizovanie polohy pomocou GPS.",
"settings_locationIntervalSec": "Interval pre GPS (Sekundy)", "settings_locationIntervalSec": "Interval pre GPS (Sekundy)",
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Smeti za prostore", "listFilter_roomServers": "Smeti za prostore",
"listFilter_unreadOnly": "Nezbrani samo", "listFilter_unreadOnly": "Nezbrani samo",
"listFilter_newGroup": "Nova skupina", "listFilter_newGroup": "Nova skupina",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighboursSubtitle": "Pogledati nič sosednjih hopjev.",
"repeater_neighbours": "Sosedi",
"neighbors_receivedData": "Prejeto podatke o sosedih",
"neighbors_requestTimedOut": "Sosedi zahtevajo izklop po dogovoru.",
"neighbors_errorLoading": "Napaka pri obnašanju sosedov: {error}",
"neighbors_repeatersNeighbours": "Ponovitve Sosedi",
"neighbors_noData": "Niso na voljo podatki o sosedih.",
"channels_joinPrivateChannel": "Pridružite se zasebni skupini", "channels_joinPrivateChannel": "Pridružite se zasebni skupini",
"channels_createPrivateChannelDesc": "Varno zaklenjeno s skrivnim ključem.", "channels_createPrivateChannelDesc": "Varno zaklenjeno s skrivnim ključem.",
"channels_joinPrivateChannelDesc": "Ročno vnesite zaporni ključ.", "channels_joinPrivateChannelDesc": "Ročno vnesite zaporni ključ.",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Prihajajoča", "channels_scanQrCodeComingSoon": "Prihajajoča",
"channels_enterHashtag": "Vnesite hashtag", "channels_enterHashtag": "Vnesite hashtag",
"channels_hashtagHint": "npr. #ekipa", "channels_hashtagHint": "npr. #ekipa",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_unknownContact": "Nepoznano {pubkey}",
"neighbors_heardAgo": "Udeleženec je prejel sporočilo {time} nazaj.",
"settings_locationGPSEnable": "Omogoči GPS", "settings_locationGPSEnable": "Omogoči GPS",
"settings_locationGPSEnableSubtitle": "Omogoči samodejno posodabljanje lokacije z GPS-jem.", "settings_locationGPSEnableSubtitle": "Omogoči samodejno posodabljanje lokacije z GPS-jem.",
"settings_locationIntervalSec": "Interval za GPS (Sekunde)", "settings_locationIntervalSec": "Interval za GPS (Sekunde)",
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "Rumservrar", "listFilter_roomServers": "Rumservrar",
"listFilter_unreadOnly": "Endast oinlästa", "listFilter_unreadOnly": "Endast oinlästa",
"listFilter_newGroup": "Ny grupp", "listFilter_newGroup": "Ny grupp",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighbours": "Grannar",
"repeater_neighboursSubtitle": "Visa noll hoppgrannar.",
"neighbors_receivedData": "Mottagna grannars data",
"neighbors_requestTimedOut": "Grannar begär tidsinställd utskick.",
"neighbors_errorLoading": "Fel vid inläsning av grannar: {error}",
"neighbors_repeatersNeighbours": "Upprepar grannar",
"neighbors_noData": "Inga grannuppgifter finns tillgängliga.",
"channels_createPrivateChannel": "Skapa en privat kanal", "channels_createPrivateChannel": "Skapa en privat kanal",
"channels_joinPrivateChannel": "Gå med i en Privat Kanal", "channels_joinPrivateChannel": "Gå med i en Privat Kanal",
"channels_joinPrivateChannelDesc": "Ange en hemlig nyckel manuellt.", "channels_joinPrivateChannelDesc": "Ange en hemlig nyckel manuellt.",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "Kommer snart", "channels_scanQrCodeComingSoon": "Kommer snart",
"channels_enterHashtag": "Ange hashtag", "channels_enterHashtag": "Ange hashtag",
"channels_hashtagHint": "t.ex. #team", "channels_hashtagHint": "t.ex. #team",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_heardAgo": "Hördes: {time} sedan",
"neighbors_unknownContact": "Okänd {pubkey}",
"settings_locationGPSEnable": "Aktivera GPS", "settings_locationGPSEnable": "Aktivera GPS",
"settings_locationGPSEnableSubtitle": "Aktivera automatiska uppdateringar av platsen med hjälp av GPS.", "settings_locationGPSEnableSubtitle": "Aktivera automatiska uppdateringar av platsen med hjälp av GPS.",
"settings_locationIntervalSec": "Interval för GPS (Sekunder)", "settings_locationIntervalSec": "Interval för GPS (Sekunder)",
+30
View File
@@ -1337,6 +1337,20 @@
"listFilter_roomServers": "房间服务器", "listFilter_roomServers": "房间服务器",
"listFilter_unreadOnly": "未读消息", "listFilter_unreadOnly": "未读消息",
"listFilter_newGroup": "新组", "listFilter_newGroup": "新组",
"@neighbors_errorLoading": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"repeater_neighboursSubtitle": "查看零跳邻居。",
"repeater_neighbours": "邻居",
"neighbors_receivedData": "收到邻居数据",
"neighbors_requestTimedOut": "邻居请求超时处理。",
"neighbors_errorLoading": "加载邻居时出错:{error}",
"neighbors_repeatersNeighbours": "重复器邻居",
"neighbors_noData": "没有可用的邻居数据。",
"channels_joinPrivateChannel": "加入私密频道", "channels_joinPrivateChannel": "加入私密频道",
"channels_createPrivateChannelDesc": "使用密钥保护。", "channels_createPrivateChannelDesc": "使用密钥保护。",
"channels_joinPrivateChannelDesc": "手动输入密钥。", "channels_joinPrivateChannelDesc": "手动输入密钥。",
@@ -1349,6 +1363,22 @@
"channels_scanQrCodeComingSoon": "即将到来", "channels_scanQrCodeComingSoon": "即将到来",
"channels_enterHashtag": "输入标签", "channels_enterHashtag": "输入标签",
"channels_hashtagHint": "例如 #团队", "channels_hashtagHint": "例如 #团队",
"@neighbors_unknownContact": {
"placeholders": {
"pubkey": {
"type": "String"
}
}
},
"@neighbors_heardAgo": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"neighbors_heardAgo": "听到的时间:{time}前",
"neighbors_unknownContact": "未知{pubkey}",
"settings_locationGPSEnable": "启用GPS", "settings_locationGPSEnable": "启用GPS",
"settings_locationGPSEnableSubtitle": "启用GPS自动更新位置。", "settings_locationGPSEnableSubtitle": "启用GPS自动更新位置。",
"settings_locationIntervalSec": "GPS 间隔(秒)", "settings_locationIntervalSec": "GPS 间隔(秒)",
+456
View File
@@ -0,0 +1,456 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../models/path_selection.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
import '../services/repeater_command_service.dart';
import '../widgets/path_management_dialog.dart';
import '../widgets/snr_indicator.dart';
class NeighboursScreen extends StatefulWidget {
final Contact repeater;
final String password;
const NeighboursScreen({
super.key,
required this.repeater,
required this.password,
});
@override
State<NeighboursScreen> createState() => _NeighboursScreenState();
}
class _NeighboursScreenState extends State<NeighboursScreen> {
static const int _reqNeighboursKeyLen = 4;
static const int _statusPayloadOffset = 8;
static const int _statusStatsSize = 52;
static const int _statusResponseBytes =
_statusPayloadOffset + _statusStatsSize;
Uint8List _tagData = Uint8List(4);
int _neighbourCount = 0;
bool _isLoading = false;
bool _isLoaded = false;
bool _hasData = false;
Timer? _statusTimeout;
StreamSubscription<Uint8List>? _frameSubscription;
RepeaterCommandService? _commandService;
PathSelection? _pendingStatusSelection;
List<Map<String, dynamic>>? _parsedNeighbours;
@override
void initState() {
super.initState();
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
_commandService = RepeaterCommandService(connector);
_setupMessageListener();
_loadNeighbours();
_hasData = false;
}
void _setupMessageListener() {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
// Listen for incoming text messages from the repeater
_frameSubscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty) return;
if (frame[0] == respCodeSent) {
_tagData = frame.sublist(2, 6);
//_timeEstment = frame.buffer.asByteData().getUint32(6, Endian.little);
}
// Check if it's a binary response
if (frame[0] == pushCodeBinaryResponse &&
listEquals(frame.sublist(2, 6), _tagData)) {
_handleNeighboursResponse(connector, frame.sublist(6));
}
});
}
String fmtDuration(double seconds) {
if (seconds < 60) {
return '${seconds.toStringAsFixed(1)}s';
}
final int m = (seconds ~/ 60).toInt();
final double s = seconds - (60 * m);
if (m < 60) {
return '${m}m ${s.toStringAsFixed(0)}s';
}
final int h = m ~/ 60;
final int m2 = m % 60;
return '${h}h ${m2}m';
}
static List<Map<String, dynamic>> parseNeighboursData(
BufferReader buffer,
int resultsCount,
) {
final Map<int, Map<String, dynamic>> neighbours = {};
for (var i = 0; i < resultsCount; i++) {
final neighbourData = neighbours.putIfAbsent(
i,
() => {
'contact': null,
'publicKey': <Uint8List>{},
'lastHeard': <int>{},
'snr': <double>{},
},
);
neighbourData['publicKey'] = buffer.readBytes(_reqNeighboursKeyLen);
neighbourData['lastHeard'] = buffer.readUInt32LE();
neighbourData['snr'] = buffer.readInt8() / 4.0;
}
return neighbours.values.toList();
}
void _handleNeighboursResponse(MeshCoreConnector connector, Uint8List frame) {
final buffer = BufferReader(frame);
final neighbourCount = buffer.readUInt16LE();
final parsedNeighbours = parseNeighboursData(buffer, buffer.readUInt16LE());
connector.contacts.where((c) => c.type == advTypeRepeater).forEach((
repeater,
) {
for (var neighbourData in parsedNeighbours) {
final publicKey = neighbourData['publicKey'];
if (listEquals(
repeater.publicKey.sublist(0, _reqNeighboursKeyLen),
publicKey,
)) {
neighbourData['contact'] = repeater;
}
}
});
setState(() {
_parsedNeighbours = parsedNeighbours;
_neighbourCount = neighbourCount;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.neighbors_receivedData),
backgroundColor: Colors.green,
),
);
_statusTimeout?.cancel();
if (!mounted) return;
setState(() {
_isLoading = false;
_isLoaded = true;
_hasData = true;
});
}
Contact _resolveRepeater(MeshCoreConnector connector) {
return connector.contacts.firstWhere(
(c) => c.publicKeyHex == widget.repeater.publicKeyHex,
orElse: () => widget.repeater,
);
}
Future<void> _loadNeighbours() async {
if (_commandService == null) return;
setState(() {
_isLoading = true;
_isLoaded = false;
});
try {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final repeater = _resolveRepeater(connector);
final selection = await connector.preparePathForContactSend(repeater);
_pendingStatusSelection = selection;
//[version][number of requested neighbours][offset_16bit][order by][len of public key]
final frame = buildSendBinaryReq(
repeater.publicKey,
payload: Uint8List.fromList([
reqTypeGetNeighbours,
0x00,
0x0F,
0x00,
0x00,
0x00,
_reqNeighboursKeyLen,
]),
);
await connector.sendFrame(frame);
final pathLengthValue = selection.useFlood ? -1 : selection.hopCount;
final messageBytes = frame.length >= _statusResponseBytes
? frame.length
: _statusResponseBytes;
final timeoutMs = connector.calculateTimeout(
pathLength: pathLengthValue,
messageBytes: messageBytes,
);
_statusTimeout?.cancel();
_statusTimeout = Timer(Duration(milliseconds: timeoutMs), () {
if (!mounted) return;
setState(() {
_isLoading = false;
_isLoaded = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.neighbors_requestTimedOut),
backgroundColor: Colors.red,
),
);
_recordStatusResult(false);
});
} catch (e) {
if (mounted) {
setState(() {
_isLoading = false;
_isLoaded = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.neighbors_errorLoading(e.toString())),
backgroundColor: Colors.red,
),
);
}
}
}
void _recordStatusResult(bool success) {
final selection = _pendingStatusSelection;
if (selection == null) return;
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final repeater = _resolveRepeater(connector);
connector.recordRepeaterPathResult(repeater, selection, success, null);
_pendingStatusSelection = null;
}
@override
void dispose() {
_frameSubscription?.cancel();
_commandService?.dispose();
_statusTimeout?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final connector = context.watch<MeshCoreConnector>();
final repeater = _resolveRepeater(connector);
final isFloodMode = repeater.pathOverride == -1;
return Scaffold(
appBar: AppBar(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.neighbors_repeatersNeighbours,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(
repeater.name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
],
),
centerTitle: false,
actions: [
PopupMenuButton<String>(
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
tooltip: l10n.repeater_routingMode,
onSelected: (mode) async {
if (mode == 'flood') {
await connector.setPathOverride(repeater, pathLen: -1);
} else {
await connector.setPathOverride(repeater, pathLen: null);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'auto',
child: Row(
children: [
Icon(
Icons.auto_mode,
size: 20,
color: !isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_autoUseSavedPath,
style: TextStyle(
fontWeight: !isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
PopupMenuItem(
value: 'flood',
child: Row(
children: [
Icon(
Icons.waves,
size: 20,
color: isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_forceFloodMode,
style: TextStyle(
fontWeight: isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
],
),
IconButton(
icon: const Icon(Icons.timeline),
tooltip: l10n.repeater_pathManagement,
onPressed: () =>
PathManagementDialog.show(context, contact: repeater),
),
IconButton(
icon: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.refresh),
onPressed: _isLoading ? null : _loadNeighbours,
tooltip: l10n.repeater_refresh,
),
],
),
body: SafeArea(
top: false,
child: RefreshIndicator(
onRefresh: _loadNeighbours,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
if (!_isLoaded &&
!_hasData &&
(_parsedNeighbours == null || _parsedNeighbours!.isEmpty))
Center(
child: Text(
l10n.neighbors_noData,
style: TextStyle(fontSize: 16, color: Colors.grey),
),
),
if (_isLoaded ||
_hasData &&
!(_parsedNeighbours == null ||
_parsedNeighbours!.isEmpty))
_buildNeighboursInfoCard(
"${l10n.repeater_neighbours} - $_neighbourCount",
),
],
),
),
),
);
}
Widget _buildNeighboursInfoCard(String title) {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.info_outline,
color: Theme.of(context).textTheme.headlineSmall?.color,
),
const SizedBox(width: 8),
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const Divider(),
for (final entry in _parsedNeighbours!.asMap().entries)
_buildInfoRow(
entry.value['contact'] != null
? entry.value['contact'].name
: context.l10n.neighbors_unknownContact(
"<${pubKeyToHex(entry.value['publicKey'])}>",
),
context.l10n.neighbors_heardAgo(
fmtDuration(entry.value['lastHeard'] + 0.0),
),
entry.value['snr'],
connector.currentSf!,
),
],
),
),
);
}
Widget _buildInfoRow(
String label,
String value,
double snr,
int spreadingFactor,
) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
label,
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text(value),
trailing: SNRIcon(
snr: snr,
snrLevels: getSNRfromSF(spreadingFactor),
),
),
),
],
),
);
}
}
+22 -1
View File
@@ -6,6 +6,7 @@ import 'repeater_status_screen.dart';
import 'repeater_cli_screen.dart'; import 'repeater_cli_screen.dart';
import 'repeater_settings_screen.dart'; import 'repeater_settings_screen.dart';
import 'telemetry_screen.dart'; import 'telemetry_screen.dart';
import 'neighbours_screen.dart';
class RepeaterHubScreen extends StatelessWidget { class RepeaterHubScreen extends StatelessWidget {
final Contact repeater; final Contact repeater;
@@ -169,13 +170,33 @@ class RepeaterHubScreen extends StatelessWidget {
}, },
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// Neighbors button
_buildManagementCard(
context,
icon: Icons.group,
title: l10n.repeater_neighbours,
subtitle: l10n.repeater_neighboursSubtitle,
color: Colors.orange,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NeighboursScreen(
repeater: repeater,
password: password,
),
),
);
},
),
const SizedBox(height: 12),
// Settings button // Settings button
_buildManagementCard( _buildManagementCard(
context, context,
icon: Icons.settings, icon: Icons.settings,
title: l10n.repeater_settings, title: l10n.repeater_settings,
subtitle: l10n.repeater_settingsSubtitle, subtitle: l10n.repeater_settingsSubtitle,
color: Colors.orange, color: Colors.deepOrange,
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
+62
View File
@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
List<double> getSNRfromSF(int spreadingFactor) {
switch (spreadingFactor) {
case 7:
return [4.0, -2.0, -4.0, -6.0];
case 8:
return [4.0, -4.0, -6.0, -8.0];
case 9:
return [4.0, -6.0, -8.0, -10.0];
case 10:
return [4.0, -8.0, -10.0, -13.0];
case 11:
return [4.0, -10.0, -12.5, -15.0];
case 12:
return [4.0, -12.5, -15.0, -18.0];
default:
return []; // Or throw Exception('Invalid SF: $spreadingFactor');
}
}
class SNRIcon extends StatelessWidget {
final double snr;
final List<double> snrLevels;
const SNRIcon({
super.key,
required this.snr,
this.snrLevels = const [4.0, -2.0, -4.0, -6.0],
});
@override
Widget build(BuildContext context) {
IconData icon;
Color color;
if (snr >= snrLevels[0]) {
icon = Icons.signal_cellular_alt;
color = Colors.green;
} else if (snr >= snrLevels[1]) {
icon = Icons.signal_cellular_alt;
color = Colors.lightGreen;
} else if (snr >= snrLevels[2]) {
icon = Icons.signal_cellular_alt;
color = Colors.yellow;
} else if (snr >= snrLevels[3]) {
icon = Icons.signal_cellular_alt_2_bar;
color = Colors.orange;
} else {
icon = Icons.signal_cellular_alt_1_bar;
color = Colors.red;
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
Text('$snr dB', style: TextStyle(fontSize: 10, color: color)),
],
);
}
}
+1 -1
View File
@@ -891,4 +891,4 @@ def translate_locale(
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())
+1
View File
@@ -0,0 +1 @@
{}