Merge pull request #51 from wel97459/dev-roomManagement

Added room server management
This commit is contained in:
zjs81
2026-01-19 18:34:51 -07:00
committed by GitHub
29 changed files with 458 additions and 236 deletions
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Входът не беше успешен. Или паролата е грешна, или повторителят е недостъпен.", "login_failedMessage": "Входът не беше успешен. Или паролата е грешна, или повторителят е недостъпен.",
"common_reload": "Презареди", "common_reload": "Презареди",
"common_clear": "Изчисти", "common_clear": "Изчисти",
"path_currentPath": "Текущ път: {path}", "path_currentPath": "Текущ път: {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Сканирайте QR код", "channels_scanQrCode": "Сканирайте QR код",
"channels_scanQrCodeComingSoon": "Ще излезе скоро", "channels_scanQrCodeComingSoon": "Ще излезе скоро",
"channels_enterHashtag": "Въведете хаштаг", "channels_enterHashtag": "Въведете хаштаг",
"channels_hashtagHint": "напр. #отбор" "channels_hashtagHint": "напр. #отбор",
"room_management": "Управление на сървъра за стая",
"contacts_manageRoom": "Управление на сървър за стая"
} }
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Anmeldung fehlgeschlagen. Entweder ist das Passwort falsch oder der Repeater ist nicht erreichbar.", "login_failedMessage": "Anmeldung fehlgeschlagen. Entweder ist das Passwort falsch oder der Repeater ist nicht erreichbar.",
"common_reload": "Neu laden", "common_reload": "Neu laden",
"common_clear": "Löschen", "common_clear": "Löschen",
"path_currentPath": "Aktiver Pfad: {path}", "path_currentPath": "Aktiver Pfad: {path}",
@@ -1349,5 +1348,7 @@
"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",
"contacts_manageRoom": "Verwalten Sie den Raumserver",
"room_management": "Raumserververwaltung"
} }
+4 -2
View File
@@ -253,7 +253,8 @@
} }
}, },
"contacts_manageRepeater": "Manage Repeater", "contacts_manageRepeater": "Manage Repeater",
"contacts_roomLogin": "Room Login", "contacts_manageRoom": "Manage Room Server",
"contacts_roomLogin": "Room Server Login",
"contacts_openChat": "Open Chat", "contacts_openChat": "Open Chat",
"contacts_editGroup": "Edit Group", "contacts_editGroup": "Edit Group",
"contacts_deleteGroup": "Delete Group", "contacts_deleteGroup": "Delete Group",
@@ -697,7 +698,7 @@
"dialog_disconnectConfirm": "Are you sure you want to disconnect from this device?", "dialog_disconnectConfirm": "Are you sure you want to disconnect from this device?",
"login_repeaterLogin": "Repeater Login", "login_repeaterLogin": "Repeater Login",
"login_roomLogin": "Room Login", "login_roomLogin": "Room Server Login",
"login_password": "Password", "login_password": "Password",
"login_enterPassword": "Enter password", "login_enterPassword": "Enter password",
"login_savePassword": "Save password", "login_savePassword": "Save password",
@@ -760,6 +761,7 @@
"path_setPath": "Set Path", "path_setPath": "Set Path",
"repeater_management": "Repeater Management", "repeater_management": "Repeater Management",
"room_management": "Room Server Management",
"repeater_managementTools": "Management Tools", "repeater_managementTools": "Management Tools",
"repeater_status": "Status", "repeater_status": "Status",
"repeater_statusSubtitle": "View repeater status, stats, and neighbors", "repeater_statusSubtitle": "View repeater status, stats, and neighbors",
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Inicio fallido. La contraseña es incorrecta o el repetidor no está disponible.", "login_failedMessage": "Inicio fallido. La contraseña es incorrecta o el repetidor no está disponible.",
"common_reload": "Recargar", "common_reload": "Recargar",
"common_clear": "Borrar", "common_clear": "Borrar",
"path_currentPath": "Ruta actual: {path}", "path_currentPath": "Ruta actual: {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Escanear un Código QR", "channels_scanQrCode": "Escanear un Código QR",
"channels_scanQrCodeComingSoon": "Próximamente", "channels_scanQrCodeComingSoon": "Próximamente",
"channels_enterHashtag": "Introducir hashtag", "channels_enterHashtag": "Introducir hashtag",
"channels_hashtagHint": "ej. #equipo" "channels_hashtagHint": "ej. #equipo",
"contacts_manageRoom": "Gestionar Servidor de Habitación",
"room_management": "Administración del Servidor de Habitación"
} }
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Connexion échouée. Soit le mot de passe est incorrect, soit le relais est injoignable.", "login_failedMessage": "Connexion échouée. Soit le mot de passe est incorrect, soit le relais est injoignable.",
"common_reload": "Recharger", "common_reload": "Recharger",
"common_clear": "Effacer", "common_clear": "Effacer",
"path_currentPath": "Chemin actuel : {path}", "path_currentPath": "Chemin actuel : {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Scanner un code QR", "channels_scanQrCode": "Scanner un code QR",
"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",
"contacts_manageRoom": "Gérer le serveur de salle",
"room_management": "Gestion du serveur de pièce"
} }
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Accesso fallito. La password non è corretta oppure il ripetitore non è raggiungibile.", "login_failedMessage": "Accesso fallito. La password non è corretta oppure il ripetitore non è raggiungibile.",
"common_reload": "Ricaricare", "common_reload": "Ricaricare",
"common_clear": "Cancella", "common_clear": "Cancella",
"path_currentPath": "Percorso corrente: {path}", "path_currentPath": "Percorso corrente: {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Scansiona un codice QR", "channels_scanQrCode": "Scansiona un codice QR",
"channels_scanQrCodeComingSoon": "Arriverà presto", "channels_scanQrCodeComingSoon": "Arriverà presto",
"channels_enterHashtag": "Inserisci hashtag", "channels_enterHashtag": "Inserisci hashtag",
"channels_hashtagHint": "es. #team" "channels_hashtagHint": "es. #team",
"room_management": "Gestione del Server di Camera",
"contacts_manageRoom": "Gestisci Server Camera"
} }
+14 -2
View File
@@ -1284,10 +1284,16 @@ abstract class AppLocalizations {
/// **'Manage Repeater'** /// **'Manage Repeater'**
String get contacts_manageRepeater; String get contacts_manageRepeater;
/// No description provided for @contacts_manageRoom.
///
/// In en, this message translates to:
/// **'Manage Room Server'**
String get contacts_manageRoom;
/// No description provided for @contacts_roomLogin. /// No description provided for @contacts_roomLogin.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Room Login'** /// **'Room Server Login'**
String get contacts_roomLogin; String get contacts_roomLogin;
/// No description provided for @contacts_openChat. /// No description provided for @contacts_openChat.
@@ -2672,7 +2678,7 @@ abstract class AppLocalizations {
/// No description provided for @login_roomLogin. /// No description provided for @login_roomLogin.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Room Login'** /// **'Room Server Login'**
String get login_roomLogin; String get login_roomLogin;
/// No description provided for @login_password. /// No description provided for @login_password.
@@ -2867,6 +2873,12 @@ abstract class AppLocalizations {
/// **'Repeater Management'** /// **'Repeater Management'**
String get repeater_management; String get repeater_management;
/// No description provided for @room_management.
///
/// In en, this message translates to:
/// **'Room Server Management'**
String get room_management;
/// No description provided for @repeater_managementTools. /// No description provided for @repeater_managementTools.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
+6
View File
@@ -650,6 +650,9 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Управление на Повтарящ се Елемент'; String get contacts_manageRepeater => 'Управление на Повтарящ се Елемент';
@override
String get contacts_manageRoom => 'Управление на сървър за стая';
@override @override
String get contacts_roomLogin => 'Вход в стаята'; String get contacts_roomLogin => 'Вход в стаята';
@@ -1587,6 +1590,9 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get repeater_management => 'Управление на повторители'; String get repeater_management => 'Управление на повторители';
@override
String get room_management => 'Управление на сървъра за стая';
@override @override
String get repeater_managementTools => 'Инструменти за управление'; String get repeater_managementTools => 'Инструменти за управление';
+6
View File
@@ -647,6 +647,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Wiederholungen verwalten'; String get contacts_manageRepeater => 'Wiederholungen verwalten';
@override
String get contacts_manageRoom => 'Verwalten Sie den Raumserver';
@override @override
String get contacts_roomLogin => 'Raum-Login'; String get contacts_roomLogin => 'Raum-Login';
@@ -1586,6 +1589,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get repeater_management => 'Repeater-Verwaltung'; String get repeater_management => 'Repeater-Verwaltung';
@override
String get room_management => 'Raumserververwaltung';
@override @override
String get repeater_managementTools => 'Verwaltungs-Tools'; String get repeater_managementTools => 'Verwaltungs-Tools';
+8 -2
View File
@@ -641,7 +641,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get contacts_manageRepeater => 'Manage Repeater'; String get contacts_manageRepeater => 'Manage Repeater';
@override @override
String get contacts_roomLogin => 'Room Login'; String get contacts_manageRoom => 'Manage Room Server';
@override
String get contacts_roomLogin => 'Room Server Login';
@override @override
String get contacts_openChat => 'Open Chat'; String get contacts_openChat => 'Open Chat';
@@ -1439,7 +1442,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get login_repeaterLogin => 'Repeater Login'; String get login_repeaterLogin => 'Repeater Login';
@override @override
String get login_roomLogin => 'Room Login'; String get login_roomLogin => 'Room Server Login';
@override @override
String get login_password => 'Password'; String get login_password => 'Password';
@@ -1561,6 +1564,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get repeater_management => 'Repeater Management'; String get repeater_management => 'Repeater Management';
@override
String get room_management => 'Room Server Management';
@override @override
String get repeater_managementTools => 'Management Tools'; String get repeater_managementTools => 'Management Tools';
+6
View File
@@ -648,6 +648,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Gestionar Repetidor'; String get contacts_manageRepeater => 'Gestionar Repetidor';
@override
String get contacts_manageRoom => 'Gestionar Servidor de Habitación';
@override @override
String get contacts_roomLogin => 'Inicio de Sala'; String get contacts_roomLogin => 'Inicio de Sala';
@@ -1585,6 +1588,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get repeater_management => 'Gestión de Repetidores'; String get repeater_management => 'Gestión de Repetidores';
@override
String get room_management => 'Administración del Servidor de Habitación';
@override @override
String get repeater_managementTools => 'Herramientas de Gestión'; String get repeater_managementTools => 'Herramientas de Gestión';
+6
View File
@@ -649,6 +649,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Gérer le répétiteur'; String get contacts_manageRepeater => 'Gérer le répétiteur';
@override
String get contacts_manageRoom => 'Gérer le serveur de salle';
@override @override
String get contacts_roomLogin => 'Connexion Salle'; String get contacts_roomLogin => 'Connexion Salle';
@@ -1591,6 +1594,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get repeater_management => 'Gestion des répétiteurs'; String get repeater_management => 'Gestion des répétiteurs';
@override
String get room_management => 'Gestion du serveur de pièce';
@override @override
String get repeater_managementTools => 'Outils de Gestion'; String get repeater_managementTools => 'Outils de Gestion';
+6
View File
@@ -646,6 +646,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Gestisci Ripetitore'; String get contacts_manageRepeater => 'Gestisci Ripetitore';
@override
String get contacts_manageRoom => 'Gestisci Server Camera';
@override @override
String get contacts_roomLogin => 'Login Camera'; String get contacts_roomLogin => 'Login Camera';
@@ -1583,6 +1586,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get repeater_management => 'Gestione Ripetitori'; String get repeater_management => 'Gestione Ripetitori';
@override
String get room_management => 'Gestione del Server di Camera';
@override @override
String get repeater_managementTools => 'Strumenti di Gestione'; String get repeater_managementTools => 'Strumenti di Gestione';
+6
View File
@@ -644,6 +644,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Beheer Repeater'; String get contacts_manageRepeater => 'Beheer Repeater';
@override
String get contacts_manageRoom => 'Beheer Ruimte Server';
@override @override
String get contacts_roomLogin => 'Ruimte Inloggen'; String get contacts_roomLogin => 'Ruimte Inloggen';
@@ -1578,6 +1581,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get repeater_management => 'Beheer Repeaters'; String get repeater_management => 'Beheer Repeaters';
@override
String get room_management => 'Beheer Server Kamer';
@override @override
String get repeater_managementTools => 'Beheerinstrumenten'; String get repeater_managementTools => 'Beheerinstrumenten';
+6
View File
@@ -649,6 +649,9 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Zarządzaj Powtórzami'; String get contacts_manageRepeater => 'Zarządzaj Powtórzami';
@override
String get contacts_manageRoom => 'Zarządzaj Serwerem Pokoju';
@override @override
String get contacts_roomLogin => 'Logowanie do pokoju'; String get contacts_roomLogin => 'Logowanie do pokoju';
@@ -1587,6 +1590,9 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get repeater_management => 'Zarządzanie Powtórzami'; String get repeater_management => 'Zarządzanie Powtórzami';
@override
String get room_management => 'Zarządzanie Serwerem Pokoju';
@override @override
String get repeater_managementTools => 'Narzędzia Zarządzania'; String get repeater_managementTools => 'Narzędzia Zarządzania';
+6
View File
@@ -649,6 +649,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Gerenciar Repetidor'; String get contacts_manageRepeater => 'Gerenciar Repetidor';
@override
String get contacts_manageRoom => 'Gerenciar Servidor de Sala';
@override @override
String get contacts_roomLogin => 'Login no Quarto'; String get contacts_roomLogin => 'Login no Quarto';
@@ -1585,6 +1588,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get repeater_management => 'Gerenciamento de Repetidor'; String get repeater_management => 'Gerenciamento de Repetidor';
@override
String get room_management => 'Gerenciamento de Servidor de Sala';
@override @override
String get repeater_managementTools => 'Ferramentas de Gerenciamento'; String get repeater_managementTools => 'Ferramentas de Gerenciamento';
+6
View File
@@ -642,6 +642,9 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Spravovať opakované zoznamy'; String get contacts_manageRepeater => 'Spravovať opakované zoznamy';
@override
String get contacts_manageRoom => 'Spravovať server miestnosti';
@override @override
String get contacts_roomLogin => 'Prihlásenie do miestnosti'; String get contacts_roomLogin => 'Prihlásenie do miestnosti';
@@ -1580,6 +1583,9 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get repeater_management => 'Správa opakérov'; String get repeater_management => 'Správa opakérov';
@override
String get room_management => 'Správa servera miestnosti';
@override @override
String get repeater_managementTools => 'Nástroje na správu'; String get repeater_managementTools => 'Nástroje na správu';
+6
View File
@@ -644,6 +644,9 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Upravljajte Ponovitve'; String get contacts_manageRepeater => 'Upravljajte Ponovitve';
@override
String get contacts_manageRoom => 'Upravljajte strežnik sobe';
@override @override
String get contacts_roomLogin => 'Vnos v sobo'; String get contacts_roomLogin => 'Vnos v sobo';
@@ -1580,6 +1583,9 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get repeater_management => 'Upravljanje ponovitve'; String get repeater_management => 'Upravljanje ponovitve';
@override
String get room_management => 'Upravljanje stremlišča';
@override @override
String get repeater_managementTools => 'Upravne orodje'; String get repeater_managementTools => 'Upravne orodje';
+6
View File
@@ -638,6 +638,9 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get contacts_manageRepeater => 'Hantera Upprepare'; String get contacts_manageRepeater => 'Hantera Upprepare';
@override
String get contacts_manageRoom => 'Hantera Rumserver';
@override @override
String get contacts_roomLogin => 'Rum Inloggning'; String get contacts_roomLogin => 'Rum Inloggning';
@@ -1569,6 +1572,9 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get repeater_management => 'Återuppspelarens Hantering'; String get repeater_management => 'Återuppspelarens Hantering';
@override
String get room_management => 'Rumserverhantering';
@override @override
String get repeater_managementTools => 'Administrationsverktyg'; String get repeater_managementTools => 'Administrationsverktyg';
+6
View File
@@ -611,6 +611,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get contacts_manageRepeater => '管理重复项'; String get contacts_manageRepeater => '管理重复项';
@override
String get contacts_manageRoom => '管理房间服务器';
@override @override
String get contacts_roomLogin => '房间登录'; String get contacts_roomLogin => '房间登录';
@@ -1513,6 +1516,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get repeater_management => '重复器管理'; String get repeater_management => '重复器管理';
@override
String get room_management => '房间服务器管理';
@override @override
String get repeater_managementTools => '管理工具'; String get repeater_managementTools => '管理工具';
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Inloggen mislukt. Het wachtwoord is onjuist of de repeater is niet bereikbaar.", "login_failedMessage": "Inloggen mislukt. Het wachtwoord is onjuist of de repeater is niet bereikbaar.",
"common_reload": "Opnieuw laden", "common_reload": "Opnieuw laden",
"common_clear": "Schoonmaken", "common_clear": "Schoonmaken",
"path_currentPath": "Huidige pad: {path}", "path_currentPath": "Huidige pad: {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Scan een QR-code", "channels_scanQrCode": "Scan een QR-code",
"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",
"room_management": "Beheer Server Kamer",
"contacts_manageRoom": "Beheer Ruimte Server"
} }
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.", "login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.",
"common_reload": "Ponownie załadować", "common_reload": "Ponownie załadować",
"common_clear": "Wyczyść", "common_clear": "Wyczyść",
"path_currentPath": "Aktualny ścieżka: {path}", "path_currentPath": "Aktualny ścieżka: {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Skanuj kod QR", "channels_scanQrCode": "Skanuj kod QR",
"channels_scanQrCodeComingSoon": "Wkrótce", "channels_scanQrCodeComingSoon": "Wkrótce",
"channels_enterHashtag": "Wprowadź hashtag", "channels_enterHashtag": "Wprowadź hashtag",
"channels_hashtagHint": "np. #zespół" "channels_hashtagHint": "np. #zespół",
"contacts_manageRoom": "Zarządzaj Serwerem Pokoju",
"room_management": "Zarządzanie Serwerem Pokoju"
} }
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Falha no login. A senha está incorreta ou o repetidor está inacessível.", "login_failedMessage": "Falha no login. A senha está incorreta ou o repetidor está inacessível.",
"common_reload": "Recarregar", "common_reload": "Recarregar",
"common_clear": "Limpar", "common_clear": "Limpar",
"path_currentPath": "Caminho atual: {path}", "path_currentPath": "Caminho atual: {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Digitalizar um Código QR", "channels_scanQrCode": "Digitalizar um Código QR",
"channels_scanQrCodeComingSoon": "Em breve", "channels_scanQrCodeComingSoon": "Em breve",
"channels_enterHashtag": "Insira hashtag", "channels_enterHashtag": "Insira hashtag",
"channels_hashtagHint": "ex. #equipe" "channels_hashtagHint": "ex. #equipe",
"contacts_manageRoom": "Gerenciar Servidor de Sala",
"room_management": "Gerenciamento de Servidor de Sala"
} }
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Prihlásenie zlyhalo. Heslo je nesprávne alebo je opakovač nedostupný.", "login_failedMessage": "Prihlásenie zlyhalo. Heslo je nesprávne alebo je opakovač nedostupný.",
"common_reload": "Načítať", "common_reload": "Načítať",
"common_clear": "Zmazať", "common_clear": "Zmazať",
"path_currentPath": "Aktívna cesta: {path}", "path_currentPath": "Aktívna cesta: {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Skenujte QR kód", "channels_scanQrCode": "Skenujte QR kód",
"channels_scanQrCodeComingSoon": "Čoskoro", "channels_scanQrCodeComingSoon": "Čoskoro",
"channels_enterHashtag": "Zadajte hashtag", "channels_enterHashtag": "Zadajte hashtag",
"channels_hashtagHint": "napr. #tím" "channels_hashtagHint": "napr. #tím",
"room_management": "Správa servera miestnosti",
"contacts_manageRoom": "Spravovať server miestnosti"
} }
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Prijava je bila neuspešna. Geslo je napačno ali pa je repetitor nedosegljiv.", "login_failedMessage": "Prijava je bila neuspešna. Geslo je napačno ali pa je repetitor nedosegljiv.",
"common_reload": "Ponovno naloži", "common_reload": "Ponovno naloži",
"common_clear": "Ponoviti", "common_clear": "Ponoviti",
"path_currentPath": "Trenutna pot: {path}", "path_currentPath": "Trenutna pot: {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Skeniraj QR kodo", "channels_scanQrCode": "Skeniraj QR kodo",
"channels_scanQrCodeComingSoon": "Prihajajoča", "channels_scanQrCodeComingSoon": "Prihajajoča",
"channels_enterHashtag": "Vnesite hashtag", "channels_enterHashtag": "Vnesite hashtag",
"channels_hashtagHint": "npr. #ekipa" "channels_hashtagHint": "npr. #ekipa",
"contacts_manageRoom": "Upravljajte strežnik sobe",
"room_management": "Upravljanje stremlišča"
} }
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "Inloggning misslyckades. Antingen är lösenordet fel eller så går det inte att nå repeatern.", "login_failedMessage": "Inloggning misslyckades. Antingen är lösenordet fel eller så går det inte att nå repeatern.",
"common_reload": "Ladda om", "common_reload": "Ladda om",
"common_clear": "Rensa", "common_clear": "Rensa",
"path_currentPath": "Nuvarande sökväg: {path}", "path_currentPath": "Nuvarande sökväg: {path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "Skanna en QR-kod", "channels_scanQrCode": "Skanna en QR-kod",
"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",
"contacts_manageRoom": "Hantera Rumserver",
"room_management": "Rumserverhantering"
} }
+3 -2
View File
@@ -822,7 +822,6 @@
} }
}, },
"login_failedMessage": "登录失败。密码不正确或中继器不可达。", "login_failedMessage": "登录失败。密码不正确或中继器不可达。",
"common_reload": "重新加载", "common_reload": "重新加载",
"common_clear": "清除", "common_clear": "清除",
"path_currentPath": "当前路径:{path}", "path_currentPath": "当前路径:{path}",
@@ -1349,5 +1348,7 @@
"channels_scanQrCode": "扫描二维码", "channels_scanQrCode": "扫描二维码",
"channels_scanQrCodeComingSoon": "即将到来", "channels_scanQrCodeComingSoon": "即将到来",
"channels_enterHashtag": "输入标签", "channels_enterHashtag": "输入标签",
"channels_hashtagHint": "例如 #团队" "channels_hashtagHint": "例如 #团队",
"contacts_manageRoom": "管理房间服务器",
"room_management": "房间服务器管理"
} }
+181 -80
View File
@@ -27,13 +27,15 @@ import 'map_screen.dart';
import 'repeater_hub_screen.dart'; import 'repeater_hub_screen.dart';
import 'settings_screen.dart'; import 'settings_screen.dart';
enum RoomLoginDestination {
chat,
management,
}
class ContactsScreen extends StatefulWidget { class ContactsScreen extends StatefulWidget {
final bool hideBackButton; final bool hideBackButton;
const ContactsScreen({ const ContactsScreen({super.key, this.hideBackButton = false});
super.key,
this.hideBackButton = false,
});
@override @override
State<ContactsScreen> createState() => _ContactsScreenState(); State<ContactsScreen> createState() => _ContactsScreenState();
@@ -114,7 +116,8 @@ class _ContactsScreenState extends State<ContactsScreen>
top: false, top: false,
child: QuickSwitchBar( child: QuickSwitchBar(
selectedIndex: 0, selectedIndex: 0,
onDestinationSelected: (index) => _handleQuickSwitch(index, context), onDestinationSelected: (index) =>
_handleQuickSwitch(index, context),
), ),
), ),
), ),
@@ -168,8 +171,9 @@ class _ContactsScreenState extends State<ContactsScreen>
} }
final filteredAndSorted = _filterAndSortContacts(contacts, connector); final filteredAndSorted = _filterAndSortContacts(contacts, connector);
final filteredGroups = final filteredGroups = _showUnreadOnly
_showUnreadOnly ? const <ContactGroup>[] : _filterAndSortGroups(_groups, contacts); ? const <ContactGroup>[]
: _filterAndSortGroups(_groups, contacts);
return Column( return Column(
children: [ children: [
@@ -199,7 +203,10 @@ class _ContactsScreenState extends State<ContactsScreen>
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
), ),
onChanged: (value) { onChanged: (value) {
_searchDebounce?.cancel(); _searchDebounce?.cancel();
@@ -238,14 +245,18 @@ class _ContactsScreenState extends State<ContactsScreen>
final group = filteredGroups[index]; final group = filteredGroups[index];
return _buildGroupTile(context, group, contacts); return _buildGroupTile(context, group, contacts);
} }
final contact = filteredAndSorted[index - filteredGroups.length]; final contact =
final unreadCount = connector.getUnreadCountForContact(contact); filteredAndSorted[index - filteredGroups.length];
final unreadCount = connector.getUnreadCountForContact(
contact,
);
return _ContactTile( return _ContactTile(
contact: contact, contact: contact,
lastSeen: _resolveLastSeen(contact), lastSeen: _resolveLastSeen(contact),
unreadCount: unreadCount, unreadCount: unreadCount,
onTap: () => _openChat(context, contact), onTap: () => _openChat(context, contact),
onLongPress: () => _showContactOptions(context, connector, contact), onLongPress: () =>
_showContactOptions(context, connector, contact),
); );
}, },
), ),
@@ -255,35 +266,48 @@ class _ContactsScreenState extends State<ContactsScreen>
); );
} }
List<ContactGroup> _filterAndSortGroups(List<ContactGroup> groups, List<Contact> contacts) { List<ContactGroup> _filterAndSortGroups(
List<ContactGroup> groups,
List<Contact> contacts,
) {
final query = _searchQuery.trim().toLowerCase(); final query = _searchQuery.trim().toLowerCase();
final contactsByKey = <String, Contact>{}; final contactsByKey = <String, Contact>{};
for (final contact in contacts) { for (final contact in contacts) {
contactsByKey[contact.publicKeyHex] = contact; contactsByKey[contact.publicKeyHex] = contact;
} }
final filtered = groups.where((group) { final filtered = groups
if (query.isEmpty) return true; .where((group) {
if (group.name.toLowerCase().contains(query)) return true; if (query.isEmpty) return true;
for (final key in group.memberKeys) { if (group.name.toLowerCase().contains(query)) return true;
final contact = contactsByKey[key]; for (final key in group.memberKeys) {
if (contact != null && matchesContactQuery(contact, query)) return true; final contact = contactsByKey[key];
} if (contact != null && matchesContactQuery(contact, query)) {
return false; return true;
}).where((group) { }
if (_typeFilter == ContactTypeFilter.all) return true; }
for (final key in group.memberKeys) { return false;
final contact = contactsByKey[key]; })
if (contact != null && _matchesTypeFilter(contact)) return true; .where((group) {
} if (_typeFilter == ContactTypeFilter.all) return true;
return false; for (final key in group.memberKeys) {
}).toList(); final contact = contactsByKey[key];
if (contact != null && _matchesTypeFilter(contact)) return true;
}
return false;
})
.toList();
filtered.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); filtered.sort(
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
);
return filtered; return filtered;
} }
List<Contact> _filterAndSortContacts(List<Contact> contacts, MeshCoreConnector connector) { List<Contact> _filterAndSortContacts(
List<Contact> contacts,
MeshCoreConnector connector,
) {
var filtered = contacts.where((contact) { var filtered = contacts.where((contact) {
if (_searchQuery.isEmpty) return true; if (_searchQuery.isEmpty) return true;
return matchesContactQuery(contact, _searchQuery); return matchesContactQuery(contact, _searchQuery);
@@ -301,19 +325,27 @@ class _ContactsScreenState extends State<ContactsScreen>
switch (_sortOption) { switch (_sortOption) {
case ContactSortOption.lastSeen: case ContactSortOption.lastSeen:
filtered.sort((a, b) => _resolveLastSeen(b).compareTo(_resolveLastSeen(a))); filtered.sort(
(a, b) => _resolveLastSeen(b).compareTo(_resolveLastSeen(a)),
);
break; break;
case ContactSortOption.recentMessages: case ContactSortOption.recentMessages:
filtered.sort((a, b) { filtered.sort((a, b) {
final aMessages = connector.getMessages(a); final aMessages = connector.getMessages(a);
final bMessages = connector.getMessages(b); final bMessages = connector.getMessages(b);
final aLastMsg = aMessages.isEmpty ? DateTime(1970) : aMessages.last.timestamp; final aLastMsg = aMessages.isEmpty
final bLastMsg = bMessages.isEmpty ? DateTime(1970) : bMessages.last.timestamp; ? DateTime(1970)
: aMessages.last.timestamp;
final bLastMsg = bMessages.isEmpty
? DateTime(1970)
: bMessages.last.timestamp;
return bLastMsg.compareTo(aLastMsg); return bLastMsg.compareTo(aLastMsg);
}); });
break; break;
case ContactSortOption.name: case ContactSortOption.name:
filtered.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); filtered.sort(
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
);
break; break;
} }
@@ -340,7 +372,11 @@ class _ContactsScreenState extends State<ContactsScreen>
: contact.lastSeen; : contact.lastSeen;
} }
Widget _buildGroupTile(BuildContext context, ContactGroup group, List<Contact> contacts) { Widget _buildGroupTile(
BuildContext context,
ContactGroup group,
List<Contact> contacts,
) {
final memberContacts = _resolveGroupContacts(group, contacts); final memberContacts = _resolveGroupContacts(group, contacts);
final subtitle = _formatGroupMembers(context, memberContacts); final subtitle = _formatGroupMembers(context, memberContacts);
return ListTile( return ListTile(
@@ -359,7 +395,10 @@ class _ContactsScreenState extends State<ContactsScreen>
); );
} }
List<Contact> _resolveGroupContacts(ContactGroup group, List<Contact> contacts) { List<Contact> _resolveGroupContacts(
ContactGroup group,
List<Contact> contacts,
) {
final byKey = <String, Contact>{}; final byKey = <String, Contact>{};
for (final contact in contacts) { for (final contact in contacts) {
byKey[contact.publicKeyHex] = contact; byKey[contact.publicKeyHex] = contact;
@@ -371,7 +410,9 @@ class _ContactsScreenState extends State<ContactsScreen>
resolved.add(contact); resolved.add(contact);
} }
} }
resolved.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); resolved.sort(
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
);
return resolved; return resolved;
} }
@@ -387,7 +428,7 @@ class _ContactsScreenState extends State<ContactsScreen>
if (contact.type == advTypeRepeater) { if (contact.type == advTypeRepeater) {
_showRepeaterLogin(context, contact); _showRepeaterLogin(context, contact);
} else if (contact.type == advTypeRoom) { } else if (contact.type == advTypeRoom) {
_showRoomLogin(context, contact); _showRoomLogin(context, contact, RoomLoginDestination.chat);
} else { } else {
context.read<MeshCoreConnector>().markContactRead(contact.publicKeyHex); context.read<MeshCoreConnector>().markContactRead(contact.publicKeyHex);
Navigator.push( Navigator.push(
@@ -403,17 +444,13 @@ class _ContactsScreenState extends State<ContactsScreen>
case 1: case 1:
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
buildQuickSwitchRoute( buildQuickSwitchRoute(const ChannelsScreen(hideBackButton: true)),
const ChannelsScreen(hideBackButton: true),
),
); );
break; break;
case 2: case 2:
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
buildQuickSwitchRoute( buildQuickSwitchRoute(const MapScreen(hideBackButton: true)),
const MapScreen(hideBackButton: true),
),
); );
break; break;
} }
@@ -429,10 +466,8 @@ class _ContactsScreenState extends State<ContactsScreen>
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => RepeaterHubScreen( builder: (context) =>
repeater: repeater, RepeaterHubScreen(repeater: repeater, password: password),
password: password,
),
), ),
); );
}, },
@@ -440,18 +475,23 @@ class _ContactsScreenState extends State<ContactsScreen>
); );
} }
void _showRoomLogin(BuildContext context, Contact room) { void _showRoomLogin(
BuildContext context,
Contact room,
RoomLoginDestination destination,
) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => RoomLoginDialog( builder: (context) => RoomLoginDialog(
room: room, room: room,
onLogin: (password) { onLogin: (password) {
// Navigate to chat screen after successful login
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex); context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChatScreen(contact: room), builder: (context) => destination == RoomLoginDestination.management
? RepeaterHubScreen(repeater: room, password: password)
: ChatScreen(contact: room),
), ),
); );
}, },
@@ -459,7 +499,11 @@ class _ContactsScreenState extends State<ContactsScreen>
); );
} }
void _showGroupOptions(BuildContext context, ContactGroup group, List<Contact> contacts) { void _showGroupOptions(
BuildContext context,
ContactGroup group,
List<Contact> contacts,
) {
final members = _resolveGroupContacts(group, contacts); final members = _resolveGroupContacts(group, contacts);
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
@@ -478,7 +522,10 @@ class _ContactsScreenState extends State<ContactsScreen>
), ),
ListTile( ListTile(
leading: const Icon(Icons.delete, color: Colors.red), leading: const Icon(Icons.delete, color: Colors.red),
title: Text(context.l10n.contacts_deleteGroup, style: const TextStyle(color: Colors.red)), title: Text(
context.l10n.contacts_deleteGroup,
style: const TextStyle(color: Colors.red),
),
onTap: () { onTap: () {
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
_confirmDeleteGroup(context, group); _confirmDeleteGroup(context, group);
@@ -522,7 +569,10 @@ class _ContactsScreenState extends State<ContactsScreen>
}); });
await _saveGroups(); await _saveGroups();
}, },
child: Text(context.l10n.common_delete, style: const TextStyle(color: Colors.red)), child: Text(
context.l10n.common_delete,
style: const TextStyle(color: Colors.red),
),
), ),
], ],
), ),
@@ -548,10 +598,16 @@ class _ContactsScreenState extends State<ContactsScreen>
final filteredContacts = filterQuery.isEmpty final filteredContacts = filterQuery.isEmpty
? sortedContacts ? sortedContacts
: sortedContacts : sortedContacts
.where((contact) => matchesContactQuery(contact, filterQuery)) .where(
.toList(); (contact) => matchesContactQuery(contact, filterQuery),
)
.toList();
return AlertDialog( return AlertDialog(
title: Text(isEditing ? context.l10n.contacts_editGroup : context.l10n.contacts_newGroup), title: Text(
isEditing
? context.l10n.contacts_editGroup
: context.l10n.contacts_newGroup,
),
content: SizedBox( content: SizedBox(
width: double.maxFinite, width: double.maxFinite,
child: Column( child: Column(
@@ -582,12 +638,18 @@ class _ContactsScreenState extends State<ContactsScreen>
SizedBox( SizedBox(
height: 240, height: 240,
child: filteredContacts.isEmpty child: filteredContacts.isEmpty
? Center(child: Text(context.l10n.contacts_noContactsMatchFilter)) ? Center(
child: Text(
context.l10n.contacts_noContactsMatchFilter,
),
)
: ListView.builder( : ListView.builder(
itemCount: filteredContacts.length, itemCount: filteredContacts.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final contact = filteredContacts[index]; final contact = filteredContacts[index];
final isSelected = selectedKeys.contains(contact.publicKeyHex); final isSelected = selectedKeys.contains(
contact.publicKeyHex,
);
return CheckboxListTile( return CheckboxListTile(
value: isSelected, value: isSelected,
title: Text(contact.name), title: Text(contact.name),
@@ -618,7 +680,9 @@ class _ContactsScreenState extends State<ContactsScreen>
final name = nameController.text.trim(); final name = nameController.text.trim();
if (name.isEmpty) { if (name.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_groupNameRequired)), SnackBar(
content: Text(context.l10n.contacts_groupNameRequired),
),
); );
return; return;
} }
@@ -628,13 +692,19 @@ class _ContactsScreenState extends State<ContactsScreen>
}); });
if (exists) { if (exists) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_groupAlreadyExists(name))), SnackBar(
content: Text(
context.l10n.contacts_groupAlreadyExists(name),
),
),
); );
return; return;
} }
setState(() { setState(() {
if (isEditing) { if (isEditing) {
final index = _groups.indexWhere((g) => g.name == group.name); final index = _groups.indexWhere(
(g) => g.name == group.name,
);
if (index != -1) { if (index != -1) {
_groups[index] = ContactGroup( _groups[index] = ContactGroup(
name: name, name: name,
@@ -642,7 +712,12 @@ class _ContactsScreenState extends State<ContactsScreen>
); );
} }
} else { } else {
_groups.add(ContactGroup(name: name, memberKeys: selectedKeys.toList())); _groups.add(
ContactGroup(
name: name,
memberKeys: selectedKeys.toList(),
),
);
} }
}); });
await _saveGroups(); await _saveGroups();
@@ -650,7 +725,11 @@ class _ContactsScreenState extends State<ContactsScreen>
Navigator.pop(dialogContext); Navigator.pop(dialogContext);
} }
}, },
child: Text(isEditing ? context.l10n.common_save : context.l10n.common_create), child: Text(
isEditing
? context.l10n.common_save
: context.l10n.common_create,
),
), ),
], ],
); );
@@ -682,16 +761,24 @@ class _ContactsScreenState extends State<ContactsScreen>
_showRepeaterLogin(context, contact); _showRepeaterLogin(context, contact);
}, },
) )
else if (isRoom) else if (isRoom) ...[
ListTile( ListTile(
leading: const Icon(Icons.room, color: Colors.blue), leading: const Icon(Icons.room, color: Colors.blue),
title: Text(context.l10n.contacts_roomLogin), title: Text(context.l10n.contacts_roomLogin),
onTap: () { onTap: () {
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
_showRoomLogin(context, contact); _showRoomLogin(context, contact, RoomLoginDestination.chat);
}, },
) ),
else ListTile(
leading: const Icon(Icons.room_preferences, color: Colors.orange),
title: Text(context.l10n.room_management),
onTap: () {
Navigator.pop(sheetContext);
_showRoomLogin(context, contact, RoomLoginDestination.management);
},
),
] else
ListTile( ListTile(
leading: const Icon(Icons.chat), leading: const Icon(Icons.chat),
title: Text(context.l10n.contacts_openChat), title: Text(context.l10n.contacts_openChat),
@@ -702,7 +789,10 @@ class _ContactsScreenState extends State<ContactsScreen>
), ),
ListTile( ListTile(
leading: const Icon(Icons.delete, color: Colors.red), leading: const Icon(Icons.delete, color: Colors.red),
title: Text(context.l10n.contacts_deleteContact, style: const TextStyle(color: Colors.red)), title: Text(
context.l10n.contacts_deleteContact,
style: const TextStyle(color: Colors.red),
),
onTap: () { onTap: () {
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
_confirmDelete(context, connector, contact); _confirmDelete(context, connector, contact);
@@ -734,7 +824,10 @@ class _ContactsScreenState extends State<ContactsScreen>
Navigator.pop(dialogContext); Navigator.pop(dialogContext);
connector.removeContact(contact); connector.removeContact(contact);
}, },
child: Text(context.l10n.common_delete, style: const TextStyle(color: Colors.red)), child: Text(
context.l10n.common_delete,
style: const TextStyle(color: Colors.red),
),
), ),
], ],
), ),
@@ -759,14 +852,17 @@ class _ContactTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final shotPublicKey = "<${contact.publicKeyHex.substring(0, 8)}...${contact.publicKeyHex.substring(contact.publicKeyHex.length - 8)}>"; final shotPublicKey =
"<${contact.publicKeyHex.substring(0, 8)}...${contact.publicKeyHex.substring(contact.publicKeyHex.length - 8)}>";
return ListTile( return ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: _getTypeColor(contact.type), backgroundColor: _getTypeColor(contact.type),
child: _buildContactAvatar(contact), child: _buildContactAvatar(contact),
), ),
title: Text(contact.name), title: Text(contact.name),
subtitle: Text('${contact.typeLabel}${contact.pathLabel} $shotPublicKey'), subtitle: Text(
'${contact.typeLabel}${contact.pathLabel} $shotPublicKey',
),
trailing: Column( trailing: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@@ -791,10 +887,7 @@ class _ContactTile extends StatelessWidget {
Widget _buildContactAvatar(Contact contact) { Widget _buildContactAvatar(Contact contact) {
final emoji = firstEmoji(contact.name); final emoji = firstEmoji(contact.name);
if (emoji != null) { if (emoji != null) {
return Text( return Text(emoji, style: const TextStyle(fontSize: 18));
emoji,
style: const TextStyle(fontSize: 18),
);
} }
return Icon(_getTypeIcon(contact.type), color: Colors.white, size: 20); return Icon(_getTypeIcon(contact.type), color: Colors.white, size: 20);
} }
@@ -833,13 +926,21 @@ class _ContactTile extends StatelessWidget {
final now = DateTime.now(); final now = DateTime.now();
final diff = now.difference(lastSeen); final diff = now.difference(lastSeen);
if (diff.isNegative || diff.inMinutes < 5) return context.l10n.contacts_lastSeenNow; if (diff.isNegative || diff.inMinutes < 5) {
if (diff.inMinutes < 60) return context.l10n.contacts_lastSeenMinsAgo(diff.inMinutes); return context.l10n.contacts_lastSeenNow;
}
if (diff.inMinutes < 60) {
return context.l10n.contacts_lastSeenMinsAgo(diff.inMinutes);
}
if (diff.inHours < 24) { if (diff.inHours < 24) {
final hours = diff.inHours; final hours = diff.inHours;
return hours == 1 ? context.l10n.contacts_lastSeenHourAgo : context.l10n.contacts_lastSeenHoursAgo(hours); return hours == 1
? context.l10n.contacts_lastSeenHourAgo
: context.l10n.contacts_lastSeenHoursAgo(hours);
} }
final days = diff.inDays; final days = diff.inDays;
return days == 1 ? context.l10n.contacts_lastSeenDayAgo : context.l10n.contacts_lastSeenDaysAgo(days); return days == 1
? context.l10n.contacts_lastSeenDayAgo
: context.l10n.contacts_lastSeenDaysAgo(days);
} }
} }
+143 -126
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meshcore_open/connector/meshcore_protocol.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
import 'repeater_status_screen.dart'; import 'repeater_status_screen.dart';
@@ -25,10 +26,17 @@ class RepeaterHubScreen extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(l10n.repeater_management), Text(
repeater.type == advTypeRepeater
? l10n.repeater_management
: l10n.room_management,
),
Text( Text(
repeater.name, repeater.name,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.normal), style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
), ),
], ],
), ),
@@ -39,135 +47,147 @@ class RepeaterHubScreen extends StatelessWidget {
child: ListView( child: ListView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
// Repeater info card // Repeater info card
Card( Card(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 40, radius: 40,
backgroundColor: Colors.orange, backgroundColor: Colors.orange,
child: const Icon(Icons.cell_tower, size: 40, color: Colors.white), child: const Icon(
Icons.cell_tower,
size: 40,
color: Colors.white,
), ),
const SizedBox(height: 16), ),
Text( const SizedBox(height: 16),
repeater.name, Text(
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), repeater.name,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
), ),
const SizedBox(height: 8), ),
Text( const SizedBox(height: 8),
'<${repeater.publicKeyHex.substring(0, 8)}...${repeater.publicKeyHex.substring(repeater.publicKeyHex.length - 8)}>', Text(
style: TextStyle(fontSize: 14, color: Colors.grey[600]), '<${repeater.publicKeyHex.substring(0, 8)}...${repeater.publicKeyHex.substring(repeater.publicKeyHex.length - 8)}>',
), style: TextStyle(fontSize: 14, color: Colors.grey[600]),
const SizedBox(height: 8), ),
Text( const SizedBox(height: 8),
repeater.pathLabel, Text(
style: TextStyle(fontSize: 14, color: Colors.grey[600]), repeater.pathLabel,
), style: TextStyle(fontSize: 14, color: Colors.grey[600]),
if (repeater.hasLocation) ...[ ),
const SizedBox(height: 4), if (repeater.hasLocation) ...[
Row( const SizedBox(height: 4),
mainAxisAlignment: MainAxisAlignment.center, Row(
children: [ mainAxisAlignment: MainAxisAlignment.center,
Icon(Icons.location_on, size: 14, color: Colors.grey[600]), children: [
const SizedBox(width: 4), Icon(
Text( Icons.location_on,
'${repeater.latitude?.toStringAsFixed(4)}, ${repeater.longitude?.toStringAsFixed(4)}', size: 14,
style: TextStyle(fontSize: 12, color: Colors.grey[600]), color: Colors.grey[600],
),
const SizedBox(width: 4),
Text(
'${repeater.latitude?.toStringAsFixed(4)}, ${repeater.longitude?.toStringAsFixed(4)}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
), ),
], ),
), ],
], ),
], ],
), ],
), ),
), ),
const SizedBox(height: 24), ),
Text( const SizedBox(height: 24),
l10n.repeater_managementTools, Text(
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), l10n.repeater_managementTools,
), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
const SizedBox(height: 16), ),
// Status button const SizedBox(height: 16),
_buildManagementCard( // Status button
context, _buildManagementCard(
icon: Icons.analytics, context,
title: l10n.repeater_status, icon: Icons.analytics,
subtitle: l10n.repeater_statusSubtitle, title: l10n.repeater_status,
color: Colors.blue, subtitle: l10n.repeater_statusSubtitle,
onTap: () { color: Colors.blue,
Navigator.push( onTap: () {
context, Navigator.push(
MaterialPageRoute( context,
builder: (context) => RepeaterStatusScreen( MaterialPageRoute(
repeater: repeater, builder: (context) => RepeaterStatusScreen(
password: password, repeater: repeater,
), password: password,
), ),
); ),
}, );
), },
const SizedBox(height: 16), ),
// Telemetry button const SizedBox(height: 16),
_buildManagementCard( // Telemetry button
context, _buildManagementCard(
icon: Icons.bar_chart_sharp, context,
title: l10n.repeater_telemetry, icon: Icons.bar_chart_sharp,
subtitle: l10n.repeater_telemetrySubtitle, title: l10n.repeater_telemetry,
color: Colors.teal, subtitle: l10n.repeater_telemetrySubtitle,
onTap: () { color: Colors.teal,
Navigator.push( onTap: () {
context, Navigator.push(
MaterialPageRoute( context,
builder: (context) => TelemetryScreen( MaterialPageRoute(
repeater: repeater, builder: (context) =>
password: password, TelemetryScreen(repeater: repeater, password: password),
), ),
);
},
),
const SizedBox(height: 12),
// CLI button
_buildManagementCard(
context,
icon: Icons.terminal,
title: l10n.repeater_cli,
subtitle: l10n.repeater_cliSubtitle,
color: Colors.green,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RepeaterCliScreen(
repeater: repeater,
password: password,
), ),
); ),
}, );
), },
const SizedBox(height: 12), ),
// CLI button const SizedBox(height: 12),
_buildManagementCard( // Settings button
context, _buildManagementCard(
icon: Icons.terminal, context,
title: l10n.repeater_cli, icon: Icons.settings,
subtitle: l10n.repeater_cliSubtitle, title: l10n.repeater_settings,
color: Colors.green, subtitle: l10n.repeater_settingsSubtitle,
onTap: () { color: Colors.orange,
Navigator.push( onTap: () {
context, Navigator.push(
MaterialPageRoute( context,
builder: (context) => RepeaterCliScreen( MaterialPageRoute(
repeater: repeater, builder: (context) => RepeaterSettingsScreen(
password: password, 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,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RepeaterSettingsScreen(
repeater: repeater,
password: password,
),
),
);
},
),
], ],
), ),
), ),
@@ -214,10 +234,7 @@ class RepeaterHubScreen extends StatelessWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
subtitle, subtitle,
style: TextStyle( style: TextStyle(fontSize: 14, color: Colors.grey[600]),
fontSize: 14,
color: Colors.grey[600],
),
), ),
], ],
), ),