mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
Added a basic neighbours screen for repeaters
This commit is contained in:
parent
dba639abdc
commit
04a713bb76
30 changed files with 1075 additions and 13 deletions
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Повторители",
|
||||
"listFilter_roomServers": "Сървъри на стая",
|
||||
"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": "Няма налични данни за съседи."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Wiederholer",
|
||||
"listFilter_roomServers": "Raumserver",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -753,6 +753,8 @@
|
|||
"repeater_telemetrySubtitle": "View telemetry of sensors and system stats",
|
||||
"repeater_cli": "CLI",
|
||||
"repeater_cliSubtitle": "Send commands to the repeater",
|
||||
"repeater_neighbours": "Neighbors",
|
||||
"repeater_neighboursSubtitle": "View zero hop neighbors.",
|
||||
"repeater_settings": "Settings",
|
||||
"repeater_settingsSubtitle": "Configure repeater parameters",
|
||||
|
||||
|
|
@ -1055,6 +1057,18 @@
|
|||
"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.",
|
||||
|
||||
"channelPath_title": "Packet Path",
|
||||
"channelPath_viewMap": "View map",
|
||||
"channelPath_otherObservedPaths": "Other Observed Paths",
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Repetidores",
|
||||
"listFilter_roomServers": "Servidores de la sala",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Répéteurs",
|
||||
"listFilter_roomServers": "Serveurs de pièce",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Ripetitori",
|
||||
"listFilter_roomServers": "Server della stanza",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2831,6 +2831,18 @@ abstract class AppLocalizations {
|
|||
/// **'Send commands to the repeater'**
|
||||
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.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3970,6 +3982,36 @@ abstract class AppLocalizations {
|
|||
/// **'{celsius}°C / {fahrenheit}°F'**
|
||||
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 @channelPath_title.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -1567,6 +1567,12 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Изпрати команди към ретранслатора';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Настройки';
|
||||
|
||||
|
|
@ -2252,6 +2258,23 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
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 get channelPath_title => 'Пътеки пъзел';
|
||||
|
||||
|
|
|
|||
|
|
@ -1565,6 +1565,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Sende Befehle an den Repeater';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Einstellungen';
|
||||
|
||||
|
|
@ -2254,6 +2260,23 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
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 get channelPath_title => 'Paketpfad';
|
||||
|
||||
|
|
|
|||
|
|
@ -1543,6 +1543,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
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
|
||||
String get repeater_settings => 'Settings';
|
||||
|
||||
|
|
@ -2216,6 +2222,23 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
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 get channelPath_title => 'Packet Path';
|
||||
|
||||
|
|
|
|||
|
|
@ -1564,6 +1564,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Enviar comandos al repetidor';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Configuración';
|
||||
|
||||
|
|
@ -2248,6 +2254,23 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
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 get channelPath_title => 'Ruta del Paquete';
|
||||
|
||||
|
|
|
|||
|
|
@ -1570,6 +1570,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Envoyer des commandes au répétiteur';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Paramètres';
|
||||
|
||||
|
|
@ -2261,6 +2267,23 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
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 get channelPath_title => 'Chemin de paquet';
|
||||
|
||||
|
|
|
|||
|
|
@ -1562,6 +1562,12 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Invia comandi al ripetitore';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Impostazioni';
|
||||
|
||||
|
|
@ -2248,6 +2254,23 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
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 get channelPath_title => 'Percorso Pacchetto';
|
||||
|
||||
|
|
|
|||
|
|
@ -1558,6 +1558,12 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Verzend commando\'s naar de herhaaldere';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Instellingen';
|
||||
|
||||
|
|
@ -2241,6 +2247,23 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
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 get channelPath_title => 'Pakketpad';
|
||||
|
||||
|
|
|
|||
|
|
@ -1566,6 +1566,12 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Wyślij polecenia do powielacza';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Ustawienia';
|
||||
|
||||
|
|
@ -2246,6 +2252,23 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
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 get channelPath_title => 'Ścieżka pakietu';
|
||||
|
||||
|
|
|
|||
|
|
@ -1564,6 +1564,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Enviar comandos ao repetidor';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Configurações';
|
||||
|
||||
|
|
@ -2248,6 +2254,23 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
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 get channelPath_title => 'Rótulo de Caminho de Pacote';
|
||||
|
||||
|
|
|
|||
|
|
@ -1560,6 +1560,12 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Pošlite príkazy opakovaču';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Nastavenia';
|
||||
|
||||
|
|
@ -2237,6 +2243,23 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
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 get channelPath_title => 'Cesta balíka';
|
||||
|
||||
|
|
|
|||
|
|
@ -1561,6 +1561,12 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
String get repeater_cliSubtitle =>
|
||||
'Pošlji ukazne povelje na ponovitveno enoto.';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Nastavitve';
|
||||
|
||||
|
|
@ -2242,6 +2248,23 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
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 get channelPath_title => 'Pot do paketa';
|
||||
|
||||
|
|
|
|||
|
|
@ -1548,6 +1548,12 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => 'Skicka kommandon till repetitorn';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Inställningar';
|
||||
|
||||
|
|
@ -2225,6 +2231,23 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
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 get channelPath_title => 'Paketväg';
|
||||
|
||||
|
|
|
|||
|
|
@ -1495,6 +1495,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliSubtitle => '发送命令到重复器';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => '设置';
|
||||
|
||||
|
|
@ -2125,6 +2131,23 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
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 get channelPath_title => '数据包路径';
|
||||
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Herhalingen",
|
||||
"listFilter_roomServers": "Kamervirtualisatie",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Powtarzacze",
|
||||
"listFilter_roomServers": "Serwery pokoju",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Repetidores",
|
||||
"listFilter_roomServers": "Servidores de sala",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Opakovadlá",
|
||||
"listFilter_roomServers": "Servéry miestnosti",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Ponovitve",
|
||||
"listFilter_roomServers": "Smeti za prostore",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "Upprepare",
|
||||
"listFilter_roomServers": "Rumservrar",
|
||||
"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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,5 +1335,19 @@
|
|||
"listFilter_repeaters": "重复器",
|
||||
"listFilter_roomServers": "房间服务器",
|
||||
"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": "没有可用的邻居数据。"
|
||||
}
|
||||
|
|
|
|||
456
lib/screens/neighbours_screen.dart
Normal file
456
lib/screens/neighbours_screen.dart
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
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 _timeEstment = 0;
|
||||
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(context, 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(
|
||||
BuildContext context,
|
||||
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,
|
||||
) {
|
||||
parsedNeighbours.forEach((neighbourData) {
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
: 'Unknown (${pubKeyToHex(entry.value['publicKey'])})',
|
||||
'Heard: ${fmtDuration(entry.value['lastHeard'] + 0.0)} ago',
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import 'repeater_status_screen.dart';
|
|||
import 'repeater_cli_screen.dart';
|
||||
import 'repeater_settings_screen.dart';
|
||||
import 'telemetry_screen.dart';
|
||||
import 'neighbours_screen.dart';
|
||||
|
||||
class RepeaterHubScreen extends StatelessWidget {
|
||||
final Contact repeater;
|
||||
|
|
@ -145,12 +146,32 @@ class RepeaterHubScreen extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 12),
|
||||
// Settings 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
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.settings,
|
||||
title: l10n.repeater_settings,
|
||||
subtitle: l10n.repeater_settingsSubtitle,
|
||||
color: Colors.orange,
|
||||
color: Colors.deepOrange,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
|
|
|
|||
62
lib/widgets/snr_indicator.dart
Normal file
62
lib/widgets/snr_indicator.dart
Normal 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)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue