From 567745db189caa24689a7ef8f11447d52bd50ee1 Mon Sep 17 00:00:00 2001 From: Strycher Date: Sun, 9 Aug 2026 22:53:21 -0400 Subject: [PATCH] feat(#509): hidden 7-tap Experimental unlock + settings section 7 taps on the About version row (rolling ~3s window) reveal a neutral 'Experimental' settings category; persisted on AppSettings, survives restart, re-hidden from a switch inside the section. Empty of toggles for now (Fast Sync #118, CoreScope #524 land into it later). Countdown snackbars after tap 4; already-unlocked feedback. Version-text tap is absorbed so it counts without opening the About dialog. Agent: QuietSnow (session 31eaba02) --- lib/l10n/app_en.arb | 15 + lib/l10n/app_localizations.dart | 48 +++ lib/l10n/app_localizations_bg.dart | 30 ++ lib/l10n/app_localizations_de.dart | 30 ++ lib/l10n/app_localizations_en.dart | 30 ++ lib/l10n/app_localizations_es.dart | 30 ++ lib/l10n/app_localizations_fr.dart | 30 ++ lib/l10n/app_localizations_hu.dart | 30 ++ lib/l10n/app_localizations_it.dart | 30 ++ lib/l10n/app_localizations_ja.dart | 30 ++ lib/l10n/app_localizations_ko.dart | 30 ++ lib/l10n/app_localizations_nl.dart | 30 ++ lib/l10n/app_localizations_pl.dart | 30 ++ lib/l10n/app_localizations_pt.dart | 30 ++ lib/l10n/app_localizations_ru.dart | 30 ++ lib/l10n/app_localizations_sk.dart | 30 ++ lib/l10n/app_localizations_sl.dart | 30 ++ lib/l10n/app_localizations_sv.dart | 30 ++ lib/l10n/app_localizations_uk.dart | 30 ++ lib/l10n/app_localizations_zh.dart | 30 ++ lib/models/app_settings.dart | 10 + lib/screens/settings_screen.dart | 108 ++++- lib/services/app_settings_service.dart | 4 + test/models/model_changes_test.dart | 14 + untranslated.json | 527 ++++++++++++++++++++++++- 25 files changed, 1246 insertions(+), 20 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 83b522a..b85e26b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -215,6 +215,21 @@ "settings_rebootDeviceSubtitle": "Restart the MeshCore device", "settings_rebootDeviceConfirm": "Are you sure you want to reboot the device? You will be disconnected.", "settings_debug": "Debug", + "settings_experimental": "Experimental", + "settings_experimentalSubtitle": "Advanced and in-development features", + "settings_experimentalDescription": "These features are experimental and may change or be removed at any time. They default to off.", + "settings_experimentalHide": "Hide this section", + "settings_experimentalHideSubtitle": "Re-hide Experimental. Unlock again with 7 taps on the version row.", + "settings_experimentalUnlocked": "Experimental features unlocked", + "settings_experimentalAlreadyUnlocked": "Experimental features are already unlocked", + "settings_experimentalCountdown": "{count} more taps to unlock experimental features", + "@settings_experimentalCountdown": { + "placeholders": { + "count": { + "type": "int" + } + } + }, "settings_companionDebugLog": "Companion Debug Log", "settings_companionDebugLogSubtitle": "BLE/TCP/USB commands, responses, and raw data", "settings_appDebugLog": "App Debug Log", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 06b1299..3473b08 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1090,6 +1090,54 @@ abstract class AppLocalizations { /// **'Debug'** String get settings_debug; + /// No description provided for @settings_experimental. + /// + /// In en, this message translates to: + /// **'Experimental'** + String get settings_experimental; + + /// No description provided for @settings_experimentalSubtitle. + /// + /// In en, this message translates to: + /// **'Advanced and in-development features'** + String get settings_experimentalSubtitle; + + /// No description provided for @settings_experimentalDescription. + /// + /// In en, this message translates to: + /// **'These features are experimental and may change or be removed at any time. They default to off.'** + String get settings_experimentalDescription; + + /// No description provided for @settings_experimentalHide. + /// + /// In en, this message translates to: + /// **'Hide this section'** + String get settings_experimentalHide; + + /// No description provided for @settings_experimentalHideSubtitle. + /// + /// In en, this message translates to: + /// **'Re-hide Experimental. Unlock again with 7 taps on the version row.'** + String get settings_experimentalHideSubtitle; + + /// No description provided for @settings_experimentalUnlocked. + /// + /// In en, this message translates to: + /// **'Experimental features unlocked'** + String get settings_experimentalUnlocked; + + /// No description provided for @settings_experimentalAlreadyUnlocked. + /// + /// In en, this message translates to: + /// **'Experimental features are already unlocked'** + String get settings_experimentalAlreadyUnlocked; + + /// No description provided for @settings_experimentalCountdown. + /// + /// In en, this message translates to: + /// **'{count} more taps to unlock experimental features'** + String settings_experimentalCountdown(int count); + /// No description provided for @settings_companionDebugLog. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 7e27684..fb52b66 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -540,6 +540,36 @@ class AppLocalizationsBg extends AppLocalizations { @override String get settings_debug => 'Отстрани'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Лог за отстраняване на грешки (за съпътстваща програма)'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index ae9c265..48ac2e2 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -537,6 +537,36 @@ class AppLocalizationsDe extends AppLocalizations { @override String get settings_debug => 'Fehlerbehebung'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Debug-Protokoll für die Begleitsoftware'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 33597a4..df11879 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -528,6 +528,36 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settings_debug => 'Debug'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Companion Debug Log'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index a3d5252..4c265a5 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -538,6 +538,36 @@ class AppLocalizationsEs extends AppLocalizations { @override String get settings_debug => 'Depurar'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Registro de depuración asociado'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index eba4521..6b892db 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -542,6 +542,36 @@ class AppLocalizationsFr extends AppLocalizations { @override String get settings_debug => 'Déboguer'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Journal de débogage associé'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index fb42803..575cc96 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -540,6 +540,36 @@ class AppLocalizationsHu extends AppLocalizations { @override String get settings_debug => 'Hibakeresés'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Párhuzamos hibakeresési napló'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 5cd6761..17e4454 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -540,6 +540,36 @@ class AppLocalizationsIt extends AppLocalizations { @override String get settings_debug => 'Risoluzione dei problemi'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Registro di debug per il supporto'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 4dd94a1..1b0bb8e 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -514,6 +514,36 @@ class AppLocalizationsJa extends AppLocalizations { @override String get settings_debug => 'デバッグ'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => '同伴デバッグログ'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index f9a1fec..e966ca5 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -514,6 +514,36 @@ class AppLocalizationsKo extends AppLocalizations { @override String get settings_debug => '디버깅'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => '동반 디버깅 로그'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 0dc6d77..e347694 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -534,6 +534,36 @@ class AppLocalizationsNl extends AppLocalizations { @override String get settings_debug => 'Foutopsporing'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Debuglog voor aanvullende informatie'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 15d3358..8df92f5 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -542,6 +542,36 @@ class AppLocalizationsPl extends AppLocalizations { @override String get settings_debug => 'Naprawianie błędów'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Log debugowania (dla pomocy w rozwiązywaniu problemów)'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index ae4d686..9b16dd6 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -540,6 +540,36 @@ class AppLocalizationsPt extends AppLocalizations { @override String get settings_debug => 'Depurar'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Registro de depuração auxiliar'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index fd7434e..b4f6dfc 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -539,6 +539,36 @@ class AppLocalizationsRu extends AppLocalizations { @override String get settings_debug => 'Отладка'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Журнал отладки (для сопутствующего приложения)'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 3e2938a..c128c5f 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -534,6 +534,36 @@ class AppLocalizationsSk extends AppLocalizations { @override String get settings_debug => 'Ladenie'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Logovanie pre ladenie (sprievodný log)'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index f46ad30..13707b1 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -532,6 +532,36 @@ class AppLocalizationsSl extends AppLocalizations { @override String get settings_debug => 'Odpravljanje napak'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Log zapis za odpravljanje napak'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 232c592..e42ccf0 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -529,6 +529,36 @@ class AppLocalizationsSv extends AppLocalizations { @override String get settings_debug => 'Felsök'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Följande felsökningslogg'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index a3f4b37..a149a4d 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -536,6 +536,36 @@ class AppLocalizationsUk extends AppLocalizations { @override String get settings_debug => 'Налагодження'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => 'Журнал відлачування (для супутника)'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index ab4a053..8f3dbaf 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -507,6 +507,36 @@ class AppLocalizationsZh extends AppLocalizations { @override String get settings_debug => '调试'; + @override + String get settings_experimental => 'Experimental'; + + @override + String get settings_experimentalSubtitle => + 'Advanced and in-development features'; + + @override + String get settings_experimentalDescription => + 'These features are experimental and may change or be removed at any time. They default to off.'; + + @override + String get settings_experimentalHide => 'Hide this section'; + + @override + String get settings_experimentalHideSubtitle => + 'Re-hide Experimental. Unlock again with 7 taps on the version row.'; + + @override + String get settings_experimentalUnlocked => 'Experimental features unlocked'; + + @override + String get settings_experimentalAlreadyUnlocked => + 'Experimental features are already unlocked'; + + @override + String settings_experimentalCountdown(int count) { + return '$count more taps to unlock experimental features'; + } + @override String get settings_companionDebugLog => '调试日志'; diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart index 4997403..5fa1438 100644 --- a/lib/models/app_settings.dart +++ b/lib/models/app_settings.dart @@ -139,6 +139,11 @@ class AppSettings { final String themeMode; final String? languageOverride; // null = system default final bool appDebugLogEnabled; + + /// Owner-only Experimental settings section, revealed by a 7-tap gesture on + /// the version row (#509). Not a security boundary — just keeps advanced/ + /// me-centric toggles out of the default UI. Persists across restarts. + final bool experimentalUnlocked; final Map batteryChemistryByDeviceId; final Map batteryChemistryByRepeaterId; final UnitSystem unitSystem; @@ -217,6 +222,7 @@ class AppSettings { this.themeMode = 'system', this.languageOverride, this.appDebugLogEnabled = false, + this.experimentalUnlocked = false, Map? batteryChemistryByDeviceId, Map? batteryChemistryByRepeaterId, this.unitSystem = UnitSystem.metric, @@ -283,6 +289,7 @@ class AppSettings { 'theme_mode': themeMode, 'language_override': languageOverride, 'app_debug_log_enabled': appDebugLogEnabled, + 'experimental_unlocked': experimentalUnlocked, 'battery_chemistry_by_device_id': batteryChemistryByDeviceId, 'battery_chemistry_by_repeater_id': batteryChemistryByRepeaterId, 'unit_system': unitSystem.value, @@ -380,6 +387,7 @@ class AppSettings { themeMode: json['theme_mode'] as String? ?? 'system', languageOverride: json['language_override'] as String?, appDebugLogEnabled: json['app_debug_log_enabled'] as bool? ?? false, + experimentalUnlocked: json['experimental_unlocked'] as bool? ?? false, batteryChemistryByDeviceId: (json['battery_chemistry_by_device_id'] as Map?)?.map( (key, value) => MapEntry(key.toString(), value.toString()), @@ -497,6 +505,7 @@ class AppSettings { String? themeMode, Object? languageOverride = _unset, bool? appDebugLogEnabled, + bool? experimentalUnlocked, Map? batteryChemistryByDeviceId, Map? batteryChemistryByRepeaterId, UnitSystem? unitSystem, @@ -556,6 +565,7 @@ class AppSettings { ? this.languageOverride : languageOverride as String?, appDebugLogEnabled: appDebugLogEnabled ?? this.appDebugLogEnabled, + experimentalUnlocked: experimentalUnlocked ?? this.experimentalUnlocked, batteryChemistryByDeviceId: batteryChemistryByDeviceId ?? this.batteryChemistryByDeviceId, batteryChemistryByRepeaterId: diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 0165466..b24e4ac 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -55,6 +55,13 @@ class _SettingsScreenState extends State { bool _showBatteryVoltage = false; String _appVersion = ''; + // #509 hidden unlock: 7 taps on the version row within a rolling window + // reveals the Experimental settings section. + static const int _experimentalUnlockTaps = 7; + static const Duration _experimentalTapWindow = Duration(seconds: 3); + int _versionTapCount = 0; + DateTime? _lastVersionTap; + @override void initState() { super.initState(); @@ -69,6 +76,50 @@ class _SettingsScreenState extends State { }); } + void _handleVersionTap() { + final l10n = context.l10n; + final settingsService = context.read(); + + if (settingsService.settings.experimentalUnlocked) { + _versionTapCount = 0; + _showVersionSnack(l10n.settings_experimentalAlreadyUnlocked); + return; + } + + final now = DateTime.now(); + if (_lastVersionTap == null || + now.difference(_lastVersionTap!) > _experimentalTapWindow) { + _versionTapCount = 0; + } + _lastVersionTap = now; + _versionTapCount++; + + final remaining = _experimentalUnlockTaps - _versionTapCount; + if (remaining <= 0) { + _versionTapCount = 0; + settingsService.setExperimentalUnlocked(true); + _showVersionSnack(l10n.settings_experimentalUnlocked); + } else if (remaining <= 3) { + _showVersionSnack( + l10n.settings_experimentalCountdown(remaining), + brief: true, + ); + } + } + + void _showVersionSnack(String message, {bool brief = false}) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text(message), + duration: brief + ? const Duration(milliseconds: 900) + : const Duration(seconds: 2), + ), + ); + } + @override Widget build(BuildContext context) { final l10n = context.l10n; @@ -90,6 +141,12 @@ class _SettingsScreenState extends State { offbandCaps: c.offbandCaps ?? 0, ), ); + // Owner-only Experimental section (#509), revealed by the 7-tap gesture on + // the About version row. Selects narrowly so the category list only rebuilds + // when the flag flips, not on every connector update (same reason as above). + final experimentalUnlocked = context.select( + (s) => s.settings.experimentalUnlocked, + ); return [ SettingsCategory( icon: Icons.badge_outlined, @@ -155,6 +212,13 @@ class _SettingsScreenState extends State { title: l10n.settings_actions, builder: _actionsPane, ), + if (experimentalUnlocked) + SettingsCategory( + icon: Icons.science_outlined, + title: l10n.settings_experimental, + subtitle: l10n.settings_experimentalSubtitle, + builder: _experimentalPane, + ), SettingsCategory( icon: Icons.build_outlined, title: l10n.settings_debug, @@ -163,6 +227,38 @@ class _SettingsScreenState extends State { ]; } + Widget _experimentalPane(BuildContext context) { + final l10n = context.l10n; + final settingsService = context.watch(); + return ListView( + padding: const EdgeInsets.all(16), + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text(l10n.settings_experimentalDescription), + ), + ), + const SizedBox(height: 16), + // Future me-centric/experimental toggles land here (e.g. Fast Sync + // #118, CoreScope repeats #524). Empty of features until they arrive. + Card( + child: SwitchListTile( + secondary: const Icon(Icons.visibility_off_outlined), + title: Text(l10n.settings_experimentalHide), + subtitle: Text(l10n.settings_experimentalHideSubtitle), + value: settingsService.settings.experimentalUnlocked, + onChanged: (value) { + if (!value) { + settingsService.setExperimentalUnlocked(false); + } + }, + ), + ), + ], + ); + } + Widget _observerPane(BuildContext context) => const ObserverSettingsView(); Widget _blockedPane(BuildContext context) => const BlockedView(); @@ -637,9 +733,15 @@ class _SettingsScreenState extends State { child: ListTile( leading: const Icon(Icons.info_outline), title: Text(l10n.settings_about), - subtitle: Text( - l10n.settings_aboutVersion( - _appVersion.isEmpty ? l10n.common_loading : _appVersion, + // Tapping the version number counts toward the #509 unlock and is + // absorbed here; tapping elsewhere on the row still opens the dialog. + subtitle: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: _handleVersionTap, + child: Text( + l10n.settings_aboutVersion( + _appVersion.isEmpty ? l10n.common_loading : _appVersion, + ), ), ), onTap: () => _showAbout(context), diff --git a/lib/services/app_settings_service.dart b/lib/services/app_settings_service.dart index 4352f41..f666cfe 100644 --- a/lib/services/app_settings_service.dart +++ b/lib/services/app_settings_service.dart @@ -182,6 +182,10 @@ class AppSettingsService extends ChangeNotifier { appLogger.setEnabled(value); } + Future setExperimentalUnlocked(bool value) async { + await updateSettings(_settings.copyWith(experimentalUnlocked: value)); + } + Future setMapShowDiscoveryContacts(bool value) async { await updateSettings(_settings.copyWith(mapShowDiscoveryContacts: value)); } diff --git a/test/models/model_changes_test.dart b/test/models/model_changes_test.dart index 9402a65..5f3d76a 100644 --- a/test/models/model_changes_test.dart +++ b/test/models/model_changes_test.dart @@ -278,6 +278,20 @@ void main() { expect(settings.maxMessageRetries, equals(5)); }); + test('experimentalUnlocked defaults off and round-trips (#509)', () { + expect(AppSettings().experimentalUnlocked, isFalse); + + final unlocked = AppSettings().copyWith(experimentalUnlocked: true); + expect(unlocked.experimentalUnlocked, isTrue); + expect(unlocked.toJson()['experimental_unlocked'], isTrue); + + final restored = AppSettings.fromJson(unlocked.toJson()); + expect(restored.experimentalUnlocked, isTrue); + + // Missing key (older persisted settings) defaults to locked. + expect(AppSettings.fromJson({}).experimentalUnlocked, isFalse); + }); + test('toJson includes all new fields', () { final settings = AppSettings(); final json = settings.toJson(); diff --git a/untranslated.json b/untranslated.json index 7784f58..7e7793a 100644 --- a/untranslated.json +++ b/untranslated.json @@ -12,6 +12,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -30,11 +38,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -63,7 +85,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "de": [ @@ -79,6 +108,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -97,11 +134,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -130,7 +181,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "es": [ @@ -146,6 +204,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -164,11 +230,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -197,7 +277,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "fr": [ @@ -213,6 +300,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -231,11 +326,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -264,7 +373,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "hu": [ @@ -280,6 +396,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -298,11 +422,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -331,7 +469,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "it": [ @@ -347,6 +492,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -365,11 +518,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -398,7 +565,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "ja": [ @@ -414,6 +588,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -432,11 +614,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -465,7 +661,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "ko": [ @@ -481,6 +684,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -499,11 +710,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -532,7 +757,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "nl": [ @@ -548,6 +780,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -566,11 +806,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -599,7 +853,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "pl": [ @@ -615,6 +876,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -633,11 +902,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -666,7 +949,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "pt": [ @@ -682,6 +972,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -700,11 +998,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -733,7 +1045,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "ru": [ @@ -749,6 +1068,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -767,11 +1094,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -800,7 +1141,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "sk": [ @@ -816,6 +1164,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -834,11 +1190,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -867,7 +1237,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "sl": [ @@ -883,6 +1260,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -901,11 +1286,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -934,7 +1333,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "sv": [ @@ -950,6 +1356,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -968,11 +1382,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -1001,7 +1429,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "uk": [ @@ -1017,6 +1452,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -1035,11 +1478,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -1068,7 +1525,14 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ], "zh": [ @@ -1084,6 +1548,14 @@ "settings_gpsStatusTime", "settings_gpsStatusTapRefresh", "settings_gpsStatusNoResponse", + "settings_experimental", + "settings_experimentalSubtitle", + "settings_experimentalDescription", + "settings_experimentalHide", + "settings_experimentalHideSubtitle", + "settings_experimentalUnlocked", + "settings_experimentalAlreadyUnlocked", + "settings_experimentalCountdown", "settings_infoFirmware", "settings_infoModel", "settings_infoOffbandCaps", @@ -1102,11 +1574,25 @@ "channels_notifyAll", "channels_notifyMentionsOnly", "channels_notifyOff", + "chat_sendingAgain", + "message_sendAgain", "chat_replyWithRoute", + "debugLog_shareLog", + "debugLog_saveLog", + "debugLog_downloadLog", + "debugLog_shareSubject", + "debugLog_logSaved", + "debugLog_logUnavailable", "channel_heardTooltip", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "channelPath_routeDirect", + "channelPath_routeFlood", + "channelPath_snrLabel", + "channelPath_snrValue", + "channelPath_rssiLabel", + "channelPath_rssiValue", "channels_shareQr", "channels_shareQrTitle", "channels_shareQrInstructions", @@ -1135,6 +1621,13 @@ "block_namesHint", "block_offloadActive", "block_offloadStoreFull", - "block_composerNotice" + "block_composerNotice", + "storageUnavailableTitle", + "storageUnavailableBody", + "batteryOptimizationTitle", + "batteryOptimizationBody", + "batteryOptimizationOpenSettings", + "batteryOptimizationDismiss", + "batteryOptimizationOpenFailed" ] }