Added GPS enable and interval settings

This commit is contained in:
Winston Lowe 2026-01-18 01:05:46 -08:00
parent 2e1a5e0fbf
commit 714aecd7e6
28 changed files with 495 additions and 148 deletions

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Входът не беше успешен. Или паролата е грешна, или повторителят е недостъпен.",
"common_reload": "Презареди",
"common_clear": "Изчисти",
"path_currentPath": "Текущ път: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Сканирайте QR код",
"channels_scanQrCodeComingSoon": "Ще излезе скоро",
"channels_enterHashtag": "Въведете хаштаг",
"channels_hashtagHint": "напр. #отбор"
"channels_hashtagHint": "напр. #отбор",
"settings_locationIntervalSec": "Интервал (Секунди)",
"settings_locationGPSEnableSubtitle": "Активирайте GPS, за автоматично изпращане на данни за местоположението (ако е поддържано).",
"settings_locationIntervalInvalid": "Интервалът трябва да бъде поне 60 секунди и по-малко от 86400 секунди.",
"settings_locationGPSEnable": "Активиране на GPS"
}

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Anmeldung fehlgeschlagen. Entweder ist das Passwort falsch oder der Repeater ist nicht erreichbar.",
"common_reload": "Neu laden",
"common_clear": "Löschen",
"path_currentPath": "Aktiver Pfad: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Scannen Sie einen QR-Code",
"channels_scanQrCodeComingSoon": "Bald verfügbar",
"channels_enterHashtag": "Gib Hashtag ein",
"channels_hashtagHint": "z.B. #team"
"channels_hashtagHint": "z.B. #team",
"settings_locationGPSEnable": "GPS aktivieren",
"settings_locationGPSEnableSubtitle": "Aktivieren Sie GPS, um Standortdaten automatisch zu senden (falls unterstützt).",
"settings_locationIntervalSec": "Zeitintervall (Sekunden)",
"settings_locationIntervalInvalid": "Der Zeitraum muss mindestens 60 Sekunden betragen und weniger als 86400 Sekunden sein."
}

View file

@ -86,6 +86,11 @@
"settings_locationUpdated": "Location updated",
"settings_locationBothRequired": "Enter both latitude and longitude.",
"settings_locationInvalid": "Invalid latitude or longitude.",
"settings_locationGPSEnable": "GPS Enable",
"settings_locationGPSEnableSubtitle": "Enable GPS to automatically send location data (if supported)",
"settings_locationIntervalSec": "Interval (Seconds)",
"settings_locationIntervalInvalid": "Interval must be at least 60 seconds, and less than 86400 seconds.",
"settings_locationUpdated": "GPS settings updated.",
"settings_latitude": "Latitude",
"settings_longitude": "Longitude",
"settings_privacyMode": "Privacy Mode",

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Inicio fallido. La contraseña es incorrecta o el repetidor no está disponible.",
"common_reload": "Recargar",
"common_clear": "Borrar",
"path_currentPath": "Ruta actual: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Escanear un Código QR",
"channels_scanQrCodeComingSoon": "Próximamente",
"channels_enterHashtag": "Introducir hashtag",
"channels_hashtagHint": "ej. #equipo"
"channels_hashtagHint": "ej. #equipo",
"settings_locationGPSEnableSubtitle": "Habilitar GPS para enviar automáticamente datos de ubicación (si está disponible).",
"settings_locationGPSEnable": "Habilitar GPS",
"settings_locationIntervalSec": "Intervalo (Segundos)",
"settings_locationIntervalInvalid": "El intervalo debe ser de al menos 60 segundos y menor que 86400 segundos."
}

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Connexion échouée. Soit le mot de passe est incorrect, soit le relais est injoignable.",
"common_reload": "Recharger",
"common_clear": "Effacer",
"path_currentPath": "Chemin actuel : {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Scanner un code QR",
"channels_scanQrCodeComingSoon": "Bientôt disponible",
"channels_enterHashtag": "Entrez le hashtag",
"channels_hashtagHint": "ex. #équipe"
"channels_hashtagHint": "ex. #équipe",
"settings_locationGPSEnable": "Activer le GPS",
"settings_locationGPSEnableSubtitle": "Activer le GPS pour envoyer automatiquement les données de localisation (si pris en charge).",
"settings_locationIntervalSec": "Intervalle (Secondes)",
"settings_locationIntervalInvalid": "L'intervalle doit être d'au moins 60 secondes et inférieur à 86400 secondes."
}

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Accesso fallito. La password non è corretta oppure il ripetitore non è raggiungibile.",
"common_reload": "Ricaricare",
"common_clear": "Cancella",
"path_currentPath": "Percorso corrente: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Scansiona un codice QR",
"channels_scanQrCodeComingSoon": "Arriverà presto",
"channels_enterHashtag": "Inserisci hashtag",
"channels_hashtagHint": "es. #team"
"channels_hashtagHint": "es. #team",
"settings_locationGPSEnableSubtitle": "Abilita il GPS per inviare automaticamente i dati di posizione (se supportato).",
"settings_locationGPSEnable": "Abilita GPS",
"settings_locationIntervalSec": "Intervallo (Secondi)",
"settings_locationIntervalInvalid": "L'intervallo deve essere di almeno 60 secondi e inferiore a 86400 secondi."
}

View file

@ -465,7 +465,7 @@ abstract class AppLocalizations {
/// No description provided for @settings_locationUpdated.
///
/// In en, this message translates to:
/// **'Location updated'**
/// **'GPS settings updated.'**
String get settings_locationUpdated;
/// No description provided for @settings_locationBothRequired.
@ -480,6 +480,30 @@ abstract class AppLocalizations {
/// **'Invalid latitude or longitude.'**
String get settings_locationInvalid;
/// No description provided for @settings_locationGPSEnable.
///
/// In en, this message translates to:
/// **'GPS Enable'**
String get settings_locationGPSEnable;
/// No description provided for @settings_locationGPSEnableSubtitle.
///
/// In en, this message translates to:
/// **'Enable GPS to automatically send location data (if supported)'**
String get settings_locationGPSEnableSubtitle;
/// No description provided for @settings_locationIntervalSec.
///
/// In en, this message translates to:
/// **'Interval (Seconds)'**
String get settings_locationIntervalSec;
/// No description provided for @settings_locationIntervalInvalid.
///
/// In en, this message translates to:
/// **'Interval must be at least 60 seconds, and less than 86400 seconds.'**
String get settings_locationIntervalInvalid;
/// No description provided for @settings_latitude.
///
/// In en, this message translates to:

View file

@ -201,6 +201,20 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get settings_locationInvalid => 'Невалидна ширина или дължина.';
@override
String get settings_locationGPSEnable => 'Активиране на GPS';
@override
String get settings_locationGPSEnableSubtitle =>
'Активирайте GPS, за автоматично изпращане на данни за местоположението (ако е поддържано).';
@override
String get settings_locationIntervalSec => 'Интервал (Секунди)';
@override
String get settings_locationIntervalInvalid =>
'Интервалът трябва да бъде поне 60 секунди и по-малко от 86400 секунди.';
@override
String get settings_latitude => 'Широчина';

View file

@ -200,6 +200,20 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get settings_locationInvalid => 'Ungültige Breiten- oder Längengrade.';
@override
String get settings_locationGPSEnable => 'GPS aktivieren';
@override
String get settings_locationGPSEnableSubtitle =>
'Aktivieren Sie GPS, um Standortdaten automatisch zu senden (falls unterstützt).';
@override
String get settings_locationIntervalSec => 'Zeitintervall (Sekunden)';
@override
String get settings_locationIntervalInvalid =>
'Der Zeitraum muss mindestens 60 Sekunden betragen und weniger als 86400 Sekunden sein.';
@override
String get settings_latitude => 'Breitengrad';

View file

@ -190,7 +190,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get settings_locationSubtitle => 'GPS coordinates';
@override
String get settings_locationUpdated => 'Location updated';
String get settings_locationUpdated => 'GPS settings updated.';
@override
String get settings_locationBothRequired =>
@ -199,6 +199,20 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get settings_locationInvalid => 'Invalid latitude or longitude.';
@override
String get settings_locationGPSEnable => 'GPS Enable';
@override
String get settings_locationGPSEnableSubtitle =>
'Enable GPS to automatically send location data (if supported)';
@override
String get settings_locationIntervalSec => 'Interval (Seconds)';
@override
String get settings_locationIntervalInvalid =>
'Interval must be at least 60 seconds, and less than 86400 seconds.';
@override
String get settings_latitude => 'Latitude';

View file

@ -200,6 +200,20 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get settings_locationInvalid => 'Latitud o longitud inválidos.';
@override
String get settings_locationGPSEnable => 'Habilitar GPS';
@override
String get settings_locationGPSEnableSubtitle =>
'Habilitar GPS para enviar automáticamente datos de ubicación (si está disponible).';
@override
String get settings_locationIntervalSec => 'Intervalo (Segundos)';
@override
String get settings_locationIntervalInvalid =>
'El intervalo debe ser de al menos 60 segundos y menor que 86400 segundos.';
@override
String get settings_latitude => 'Latitud';

View file

@ -200,6 +200,20 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get settings_locationInvalid => 'Latitude ou longitude invalide.';
@override
String get settings_locationGPSEnable => 'Activer le GPS';
@override
String get settings_locationGPSEnableSubtitle =>
'Activer le GPS pour envoyer automatiquement les données de localisation (si pris en charge).';
@override
String get settings_locationIntervalSec => 'Intervalle (Secondes)';
@override
String get settings_locationIntervalInvalid =>
'L\'intervalle doit être d\'au moins 60 secondes et inférieur à 86400 secondes.';
@override
String get settings_latitude => 'Latitude';

View file

@ -200,6 +200,20 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get settings_locationInvalid => 'Latitudine o longitudine non valida.';
@override
String get settings_locationGPSEnable => 'Abilita GPS';
@override
String get settings_locationGPSEnableSubtitle =>
'Abilita il GPS per inviare automaticamente i dati di posizione (se supportato).';
@override
String get settings_locationIntervalSec => 'Intervallo (Secondi)';
@override
String get settings_locationIntervalInvalid =>
'L\'intervallo deve essere di almeno 60 secondi e inferiore a 86400 secondi.';
@override
String get settings_latitude => 'Latitudine';

View file

@ -200,6 +200,20 @@ class AppLocalizationsNl extends AppLocalizations {
String get settings_locationInvalid =>
'Ongeldige breedtegraad of lengtegraad.';
@override
String get settings_locationGPSEnable => 'GPS inschakelen';
@override
String get settings_locationGPSEnableSubtitle =>
'Zijze GPS inschakelen om locatiegegevens automatisch te verzenden (indien ondersteund).';
@override
String get settings_locationIntervalSec => 'Interval (Seconden)';
@override
String get settings_locationIntervalInvalid =>
'De intervallen moeten minstens 60 seconden zijn en minder dan 86400 seconden.';
@override
String get settings_latitude => 'Breedtegraad';

View file

@ -202,6 +202,20 @@ class AppLocalizationsPl extends AppLocalizations {
String get settings_locationInvalid =>
'Nieprawidłowa szerokość geograficzna lub długość geograficzna.';
@override
String get settings_locationGPSEnable => 'Włącz GPS';
@override
String get settings_locationGPSEnableSubtitle =>
'Włącz GPS, aby automatycznie wysyłać dane o lokalizacji (jeśli jest obsługiwane).';
@override
String get settings_locationIntervalSec => 'Interwał (Sekundy)';
@override
String get settings_locationIntervalInvalid =>
'Interwał musi wynosić co najmniej 60 sekund i mniej niż 86400 sekund.';
@override
String get settings_latitude => 'Szerokość';

View file

@ -201,6 +201,20 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get settings_locationInvalid => 'Latitude ou longitude inválidos.';
@override
String get settings_locationGPSEnable => 'Ativar GPS';
@override
String get settings_locationGPSEnableSubtitle =>
'Habilite o GPS para enviar dados de localização automaticamente (se suportado).';
@override
String get settings_locationIntervalSec => 'Intervalo (Segundos)';
@override
String get settings_locationIntervalInvalid =>
'O intervalo deve ser de pelo menos 60 segundos e inferior a 86400 segundos.';
@override
String get settings_latitude => 'Latitude';

View file

@ -200,6 +200,20 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get settings_locationInvalid => 'Neplatná šírka alebo dĺžka.';
@override
String get settings_locationGPSEnable => 'Aktivovať GPS';
@override
String get settings_locationGPSEnableSubtitle =>
'Zapnite GPS na automatické posielanie dát o polohe (ak je podporované).';
@override
String get settings_locationIntervalSec => 'Interval (Sekundy)';
@override
String get settings_locationIntervalInvalid =>
'Interval musí byť aspoň 60 sekúnd a menej ako 86400 sekúnd.';
@override
String get settings_latitude => 'Súradnica';

View file

@ -200,6 +200,20 @@ class AppLocalizationsSl extends AppLocalizations {
String get settings_locationInvalid =>
'Neveljna zemeljska širina ali dolžina.';
@override
String get settings_locationGPSEnable => 'Omogoči GPS';
@override
String get settings_locationGPSEnableSubtitle =>
'Omogoči GPS za samodejno pošiljanje podatkov o lokaciji (če je podprto).';
@override
String get settings_locationIntervalSec => 'Interval (Sekunde)';
@override
String get settings_locationIntervalInvalid =>
'Intervallo mora biti vsaj 60 sekund in manj kot 86400 sekund.';
@override
String get settings_latitude => 'Širina';

View file

@ -199,6 +199,20 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get settings_locationInvalid => 'Ogiltig latitud eller longitud.';
@override
String get settings_locationGPSEnable => 'Aktivera GPS';
@override
String get settings_locationGPSEnableSubtitle =>
'Aktivera GPS för att automatiskt skicka platsdata (om det stöds).';
@override
String get settings_locationIntervalSec => 'Tidsintervall (Sekunder)';
@override
String get settings_locationIntervalInvalid =>
'Intervalet måste vara minst 60 sekunder och mindre än 86400 sekunder.';
@override
String get settings_latitude => 'Latitud';

View file

@ -196,6 +196,18 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get settings_locationInvalid => '无效的纬度或经度。';
@override
String get settings_locationGPSEnable => '启用GPS';
@override
String get settings_locationGPSEnableSubtitle => '启用GPS自动发送位置数据如果支持';
@override
String get settings_locationIntervalSec => '时间间隔(秒)';
@override
String get settings_locationIntervalInvalid => '时间间隔必须至少为60秒且小于86400秒。';
@override
String get settings_latitude => '纬度';

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Inloggen mislukt. Het wachtwoord is onjuist of de repeater is niet bereikbaar.",
"common_reload": "Opnieuw laden",
"common_clear": "Schoonmaken",
"path_currentPath": "Huidige pad: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Scan een QR-code",
"channels_scanQrCodeComingSoon": "Komt later",
"channels_enterHashtag": "Voer hashtag in",
"channels_hashtagHint": "bijv. #team"
"channels_hashtagHint": "bijv. #team",
"settings_locationGPSEnable": "GPS inschakelen",
"settings_locationGPSEnableSubtitle": "Zijze GPS inschakelen om locatiegegevens automatisch te verzenden (indien ondersteund).",
"settings_locationIntervalInvalid": "De intervallen moeten minstens 60 seconden zijn en minder dan 86400 seconden.",
"settings_locationIntervalSec": "Interval (Seconden)"
}

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.",
"common_reload": "Ponownie załadować",
"common_clear": "Wyczyść",
"path_currentPath": "Aktualny ścieżka: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Skanuj kod QR",
"channels_scanQrCodeComingSoon": "Wkrótce",
"channels_enterHashtag": "Wprowadź hashtag",
"channels_hashtagHint": "np. #zespół"
"channels_hashtagHint": "np. #zespół",
"settings_locationGPSEnable": "Włącz GPS",
"settings_locationGPSEnableSubtitle": "Włącz GPS, aby automatycznie wysyłać dane o lokalizacji (jeśli jest obsługiwane).",
"settings_locationIntervalSec": "Interwał (Sekundy)",
"settings_locationIntervalInvalid": "Interwał musi wynosić co najmniej 60 sekund i mniej niż 86400 sekund."
}

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Falha no login. A senha está incorreta ou o repetidor está inacessível.",
"common_reload": "Recarregar",
"common_clear": "Limpar",
"path_currentPath": "Caminho atual: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Digitalizar um Código QR",
"channels_scanQrCodeComingSoon": "Em breve",
"channels_enterHashtag": "Insira hashtag",
"channels_hashtagHint": "ex. #equipe"
"channels_hashtagHint": "ex. #equipe",
"settings_locationGPSEnable": "Ativar GPS",
"settings_locationGPSEnableSubtitle": "Habilite o GPS para enviar dados de localização automaticamente (se suportado).",
"settings_locationIntervalSec": "Intervalo (Segundos)",
"settings_locationIntervalInvalid": "O intervalo deve ser de pelo menos 60 segundos e inferior a 86400 segundos."
}

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Prihlásenie zlyhalo. Heslo je nesprávne alebo je opakovač nedostupný.",
"common_reload": "Načítať",
"common_clear": "Zmazať",
"path_currentPath": "Aktívna cesta: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Skenujte QR kód",
"channels_scanQrCodeComingSoon": "Čoskoro",
"channels_enterHashtag": "Zadajte hashtag",
"channels_hashtagHint": "napr. #tím"
"channels_hashtagHint": "napr. #tím",
"settings_locationGPSEnable": "Aktivovať GPS",
"settings_locationGPSEnableSubtitle": "Zapnite GPS na automatické posielanie dát o polohe (ak je podporované).",
"settings_locationIntervalSec": "Interval (Sekundy)",
"settings_locationIntervalInvalid": "Interval musí byť aspoň 60 sekúnd a menej ako 86400 sekúnd."
}

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Prijava je bila neuspešna. Geslo je napačno ali pa je repetitor nedosegljiv.",
"common_reload": "Ponovno naloži",
"common_clear": "Ponoviti",
"path_currentPath": "Trenutna pot: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Skeniraj QR kodo",
"channels_scanQrCodeComingSoon": "Prihajajoča",
"channels_enterHashtag": "Vnesite hashtag",
"channels_hashtagHint": "npr. #ekipa"
"channels_hashtagHint": "npr. #ekipa",
"settings_locationGPSEnable": "Omogoči GPS",
"settings_locationGPSEnableSubtitle": "Omogoči GPS za samodejno pošiljanje podatkov o lokaciji (če je podprto).",
"settings_locationIntervalSec": "Interval (Sekunde)",
"settings_locationIntervalInvalid": "Intervallo mora biti vsaj 60 sekund in manj kot 86400 sekund."
}

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "Inloggning misslyckades. Antingen är lösenordet fel eller så går det inte att nå repeatern.",
"common_reload": "Ladda om",
"common_clear": "Rensa",
"path_currentPath": "Nuvarande sökväg: {path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "Skanna en QR-kod",
"channels_scanQrCodeComingSoon": "Kommer snart",
"channels_enterHashtag": "Ange hashtag",
"channels_hashtagHint": "t.ex. #team"
"channels_hashtagHint": "t.ex. #team",
"settings_locationGPSEnable": "Aktivera GPS",
"settings_locationIntervalSec": "Tidsintervall (Sekunder)",
"settings_locationGPSEnableSubtitle": "Aktivera GPS för att automatiskt skicka platsdata (om det stöds).",
"settings_locationIntervalInvalid": "Intervalet måste vara minst 60 sekunder och mindre än 86400 sekunder."
}

View file

@ -822,7 +822,6 @@
}
},
"login_failedMessage": "登录失败。密码不正确或中继器不可达。",
"common_reload": "重新加载",
"common_clear": "清除",
"path_currentPath": "当前路径:{path}",
@ -1349,5 +1348,9 @@
"channels_scanQrCode": "扫描二维码",
"channels_scanQrCodeComingSoon": "即将到来",
"channels_enterHashtag": "输入标签",
"channels_hashtagHint": "例如 #团队"
"channels_hashtagHint": "例如 #团队",
"settings_locationGPSEnableSubtitle": "启用GPS自动发送位置数据如果支持。",
"settings_locationGPSEnable": "启用GPS",
"settings_locationIntervalSec": "时间间隔(秒)",
"settings_locationIntervalInvalid": "时间间隔必须至少为60秒且小于86400秒。"
}

View file

@ -38,10 +38,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
appBar: AppBar(
title: Text(l10n.settings_title),
centerTitle: true,
),
appBar: AppBar(title: Text(l10n.settings_title), centerTitle: true),
body: SafeArea(
top: false,
child: Consumer<MeshCoreConnector>(
@ -68,7 +65,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
);
}
Widget _buildDeviceInfoCard(BuildContext context, MeshCoreConnector connector) {
Widget _buildDeviceInfoCard(
BuildContext context,
MeshCoreConnector connector,
) {
final l10n = context.l10n;
return Card(
child: Padding(
@ -83,21 +83,38 @@ class _SettingsScreenState extends State<SettingsScreen> {
const SizedBox(height: 16),
_buildInfoRow(l10n.settings_infoName, connector.deviceDisplayName),
_buildInfoRow(l10n.settings_infoId, connector.deviceIdLabel),
_buildInfoRow(l10n.settings_infoStatus, connector.isConnected ? l10n.common_connected : l10n.common_disconnected),
_buildInfoRow(
l10n.settings_infoStatus,
connector.isConnected
? l10n.common_connected
: l10n.common_disconnected,
),
_buildBatteryInfoRow(context, connector),
if (connector.selfName != null)
_buildInfoRow(l10n.settings_nodeName, connector.selfName!),
if (connector.selfPublicKey != null)
_buildInfoRow(l10n.settings_infoPublicKey, '${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...'),
_buildInfoRow(l10n.settings_infoContactsCount, '${connector.contacts.length}'),
_buildInfoRow(l10n.settings_infoChannelCount, '${connector.channels.length}'),
_buildInfoRow(
l10n.settings_infoPublicKey,
'${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...',
),
_buildInfoRow(
l10n.settings_infoContactsCount,
'${connector.contacts.length}',
),
_buildInfoRow(
l10n.settings_infoChannelCount,
'${connector.channels.length}',
),
],
),
),
);
}
Widget _buildBatteryInfoRow(BuildContext context, MeshCoreConnector connector) {
Widget _buildBatteryInfoRow(
BuildContext context,
MeshCoreConnector connector,
) {
final l10n = context.l10n;
final percent = connector.batteryPercent;
final millivolts = connector.batteryMillivolts;
@ -167,7 +184,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
);
}
Widget _buildNodeSettingsCard(BuildContext context, MeshCoreConnector connector) {
Widget _buildNodeSettingsCard(
BuildContext context,
MeshCoreConnector connector,
) {
final l10n = context.l10n;
return Card(
child: Column(
@ -298,7 +318,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const BleDebugLogScreen()),
MaterialPageRoute(
builder: (context) => const BleDebugLogScreen(),
),
);
},
),
@ -311,7 +333,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AppDebugLogScreen()),
MaterialPageRoute(
builder: (context) => const AppDebugLogScreen(),
),
);
},
),
@ -334,20 +358,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [
Row(
children: [
if (leading != null) ...[
leading,
const SizedBox(width: 8),
],
if (leading != null) ...[leading, const SizedBox(width: 8)],
Text(label, style: TextStyle(color: Colors.grey[600])),
],
),
Flexible(
child: Text(
value,
style: TextStyle(
fontWeight: FontWeight.w500,
color: valueColor,
),
style: TextStyle(fontWeight: FontWeight.w500, color: valueColor),
overflow: TextOverflow.ellipsis,
),
),
@ -413,75 +431,152 @@ class _SettingsScreenState extends State<SettingsScreen> {
final l10n = context.l10n;
final latController = TextEditingController();
final lonController = TextEditingController();
final intervalController = TextEditingController();
intervalController.text = "900";
latController.text = connector.selfLatitude?.toStringAsFixed(6) ?? '';
lonController.text = connector.selfLongitude?.toStringAsFixed(6) ?? '';
bool isGPSEnabled = false;
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(l10n.settings_location),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: latController,
decoration: InputDecoration(
labelText: l10n.settings_latitude,
border: const OutlineInputBorder(),
builder: (dialogContext) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
title: Text(l10n.settings_location),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!isGPSEnabled) ...[
TextField(
controller: latController,
decoration: InputDecoration(
labelText: l10n.settings_latitude,
border: const OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
signed: true,
),
),
const SizedBox(height: 16),
TextField(
controller: lonController,
decoration: InputDecoration(
labelText: l10n.settings_longitude,
border: const OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
signed: true,
),
),
],
const SizedBox(height: 16),
CheckboxListTile(
value: isGPSEnabled,
enabled: true,
onChanged: (v) =>
setDialogState(() => isGPSEnabled = v ?? false),
//controlAffinity: ListTileControlAffinity.leading,
title: Text(
l10n.settings_locationGPSEnable,
style: TextStyle(fontSize: 12),
),
subtitle: Text(
l10n.settings_locationGPSEnableSubtitle,
style: TextStyle(fontSize: 10),
),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
if (isGPSEnabled) ...{
const SizedBox(height: 16),
TextField(
controller: intervalController,
decoration: InputDecoration(
labelText: l10n.settings_locationIntervalSec,
border: const OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(
decimal: false,
signed: false,
),
),
},
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.common_cancel),
),
const SizedBox(height: 16),
TextField(
controller: lonController,
decoration: InputDecoration(
labelText: l10n.settings_longitude,
border: const OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
TextButton(
onPressed: () async {
Navigator.pop(context);
if (isGPSEnabled) {
final intervalText = intervalController.text.trim();
if (intervalText.isEmpty) {
return;
}
final interval = int.tryParse(intervalText);
if (interval == null || interval < 60) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.settings_locationIntervalInvalid),
),
);
return;
}
await connector.setCustomVar("gps:1");
await connector.setCustomVar("gps_interval:$interval");
await connector.refreshDeviceInfo();
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_locationUpdated)),
);
} else {
final latText = latController.text.trim();
final lonText = lonController.text.trim();
if (latText.isEmpty && lonText.isEmpty) {
return;
}
final currentLat = connector.selfLatitude;
final currentLon = connector.selfLongitude;
final lat = latText.isNotEmpty
? double.tryParse(latText)
: currentLat;
final lon = lonText.isNotEmpty
? double.tryParse(lonText)
: currentLon;
if (lat == null || lon == null) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.settings_locationBothRequired),
),
);
return;
}
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_locationInvalid)),
);
return;
}
await connector.setCustomVar("gps:0");
await connector.setNodeLocation(lat: lat, lon: lon);
await connector.refreshDeviceInfo();
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_locationUpdated)),
);
}
},
child: Text(l10n.common_save),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.common_cancel),
),
TextButton(
onPressed: () async {
Navigator.pop(context);
final latText = latController.text.trim();
final lonText = lonController.text.trim();
if (latText.isEmpty && lonText.isEmpty) {
return;
}
final currentLat = connector.selfLatitude;
final currentLon = connector.selfLongitude;
final lat = latText.isNotEmpty ? double.tryParse(latText) : currentLat;
final lon = lonText.isNotEmpty ? double.tryParse(lonText) : currentLon;
if (lat == null || lon == null) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_locationBothRequired)),
);
return;
}
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_locationInvalid)),
);
return;
}
await connector.setNodeLocation(lat: lat, lon: lon);
await connector.refreshDeviceInfo();
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_locationUpdated)),
);
},
child: Text(l10n.common_save),
),
],
),
);
}
@ -530,17 +625,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
void _sendAdvert(BuildContext context, MeshCoreConnector connector) {
final l10n = context.l10n;
connector.sendSelfAdvert(flood: true);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_advertisementSent)),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(l10n.settings_advertisementSent)));
}
void _syncTime(BuildContext context, MeshCoreConnector connector) {
final l10n = context.l10n;
connector.syncTime();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_timeSynchronized)),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(l10n.settings_timeSynchronized)));
}
void _confirmReboot(BuildContext context, MeshCoreConnector connector) {
@ -560,7 +655,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
Navigator.pop(context);
connector.rebootDevice();
},
child: Text(l10n.common_reboot, style: const TextStyle(color: Colors.orange)),
child: Text(
l10n.common_reboot,
style: const TextStyle(color: Colors.orange),
),
),
],
),
@ -572,7 +670,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
showAboutDialog(
context: context,
applicationName: l10n.appTitle,
applicationVersion: _appVersion.isEmpty ? l10n.common_loading : _appVersion,
applicationVersion: _appVersion.isEmpty
? l10n.common_loading
: _appVersion,
applicationLegalese: l10n.settings_aboutLegalese,
children: [
const SizedBox(height: 16),
@ -604,7 +704,8 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
// Populate with current settings if available
if (widget.connector.currentFreqHz != null) {
_frequencyController.text = (widget.connector.currentFreqHz! / 1000.0).toStringAsFixed(3);
_frequencyController.text = (widget.connector.currentFreqHz! / 1000.0)
.toStringAsFixed(3);
} else {
_frequencyController.text = '915.0';
}
@ -670,26 +771,31 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
final txPower = int.tryParse(_txPowerController.text);
if (freqMHz == null || freqMHz < 300 || freqMHz > 2500) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_frequencyInvalid)),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(l10n.settings_frequencyInvalid)));
return;
}
if (txPower == null || txPower < 0 || txPower > 22) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_txPowerInvalid)),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(l10n.settings_txPowerInvalid)));
return;
}
final freqHz = (freqMHz * 1000).round();
final bwHz = _bandwidth.hz;
final sf = _spreadingFactor.value;
final cr = _toDeviceCodingRate(_codingRate.value, widget.connector.currentCr);
final cr = _toDeviceCodingRate(
_codingRate.value,
widget.connector.currentCr,
);
try {
await widget.connector.sendFrame(buildSetRadioParamsFrame(freqHz, bwHz, sf, cr));
await widget.connector.sendFrame(
buildSetRadioParamsFrame(freqHz, bwHz, sf, cr),
);
await widget.connector.sendFrame(buildSetRadioTxPowerFrame(txPower));
await widget.connector.refreshDeviceInfo();
@ -727,7 +833,10 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.settings_presets, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(
l10n.settings_presets,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
@ -762,7 +871,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
border: const OutlineInputBorder(),
helperText: l10n.settings_frequencyHelper,
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
),
const SizedBox(height: 16),
DropdownButtonFormField<LoRaBandwidth>(
@ -772,10 +883,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
border: const OutlineInputBorder(),
),
items: LoRaBandwidth.values
.map((bw) => DropdownMenuItem(
value: bw,
child: Text(bw.label),
))
.map(
(bw) => DropdownMenuItem(value: bw, child: Text(bw.label)),
)
.toList(),
onChanged: (value) {
if (value != null) setState(() => _bandwidth = value);
@ -789,10 +899,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
border: const OutlineInputBorder(),
),
items: LoRaSpreadingFactor.values
.map((sf) => DropdownMenuItem(
value: sf,
child: Text(sf.label),
))
.map(
(sf) => DropdownMenuItem(value: sf, child: Text(sf.label)),
)
.toList(),
onChanged: (value) {
if (value != null) setState(() => _spreadingFactor = value);
@ -806,10 +915,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
border: const OutlineInputBorder(),
),
items: LoRaCodingRate.values
.map((cr) => DropdownMenuItem(
value: cr,
child: Text(cr.label),
))
.map(
(cr) => DropdownMenuItem(value: cr, child: Text(cr.label)),
)
.toList(),
onChanged: (value) {
if (value != null) setState(() => _codingRate = value);
@ -833,10 +941,7 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
onPressed: () => Navigator.pop(context),
child: Text(l10n.common_cancel),
),
FilledButton(
onPressed: _saveSettings,
child: Text(l10n.common_save),
),
FilledButton(onPressed: _saveSettings, child: Text(l10n.common_save)),
],
);
}
@ -850,9 +955,6 @@ class _PresetChip extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ActionChip(
label: Text(label),
onPressed: onTap,
);
return ActionChip(label: Text(label), onPressed: onTap);
}
}