From 07bef59eb6baf6591f17384ba939c2efe196d588 Mon Sep 17 00:00:00 2001 From: Strycher Date: Thu, 24 Sep 2026 03:41:57 -0400 Subject: [PATCH] fix(#697): stop swallowing an unconfirmed BLE disconnect; retry + warn disconnect() now caps the platform-confirm wait at 10 s (the vendored Android plugin's backstop resolves within ~5 s), retries once after 500 ms, and when both attempts fail sets bleReleaseUnconfirmed instead of pretending the release worked. The scanner screen shows a persistent, dismissible banner naming the recovery (toggle Bluetooth / force-stop), per the error-visibility standard. Cleared on the next connect or disconnect attempt. Co-Authored-By: Claude Fable 5 --- lib/connector/meshcore_connector.dart | 44 +++++++++-- lib/l10n/app_en.arb | 8 ++ lib/l10n/app_localizations.dart | 12 +++ lib/l10n/app_localizations_bg.dart | 8 ++ lib/l10n/app_localizations_de.dart | 8 ++ lib/l10n/app_localizations_en.dart | 8 ++ lib/l10n/app_localizations_es.dart | 8 ++ lib/l10n/app_localizations_fr.dart | 8 ++ lib/l10n/app_localizations_hu.dart | 8 ++ lib/l10n/app_localizations_it.dart | 8 ++ lib/l10n/app_localizations_ja.dart | 8 ++ lib/l10n/app_localizations_ko.dart | 8 ++ lib/l10n/app_localizations_nl.dart | 8 ++ lib/l10n/app_localizations_pl.dart | 8 ++ lib/l10n/app_localizations_pt.dart | 8 ++ lib/l10n/app_localizations_ru.dart | 8 ++ lib/l10n/app_localizations_sk.dart | 8 ++ lib/l10n/app_localizations_sl.dart | 8 ++ lib/l10n/app_localizations_sv.dart | 8 ++ lib/l10n/app_localizations_uk.dart | 8 ++ lib/l10n/app_localizations_zh.dart | 8 ++ lib/screens/scanner_screen.dart | 49 +++++++++++++ untranslated.json | 102 +++++++++----------------- 23 files changed, 286 insertions(+), 73 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 77cf2ef..7868162 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -255,6 +255,18 @@ class MeshCoreConnector extends ChangeNotifier { String? _lastDeviceId; String? _lastDeviceDisplayName; bool _manualDisconnect = false; + + // Set when a manual BLE disconnect was never confirmed by the platform, so + // the OS may still hold the radio and it stops advertising (#689/#697). + // Cleared on the next connect, the next disconnect attempt, or UI dismiss. + bool _bleReleaseUnconfirmed = false; + bool get bleReleaseUnconfirmed => _bleReleaseUnconfirmed; + void clearBleReleaseWarning() { + if (!_bleReleaseUnconfirmed) return; + _bleReleaseUnconfirmed = false; + notifyListeners(); + } + final MeshCoreUsbManager _usbManager = MeshCoreUsbManager(); final LinuxBlePairingService _linuxBlePairingService = LinuxBlePairingService(); @@ -2364,6 +2376,7 @@ class MeshCoreConnector extends ChangeNotifier { _lastDeviceId = _deviceId; _lastDeviceDisplayName = _deviceDisplayName; _manualDisconnect = false; + _bleReleaseUnconfirmed = false; _cancelReconnectTimer(); _bleInitialSyncStarted = false; if (PlatformInfo.isWeb) { @@ -3130,11 +3143,32 @@ class MeshCoreConnector extends ChangeNotifier { await _translationService?.releaseModel(); if (!skipBleDeviceDisconnect) { - try { - // Skip queued BLE operations so disconnect doesn't get stuck behind them. - await _device?.disconnect(queue: false); - } catch (e) { - _appDebugLogService?.warn('Disconnect error: $e', tag: 'BLE Connect'); + _bleReleaseUnconfirmed = false; + var released = _device == null; + for (var attempt = 1; attempt <= 2 && !released; attempt++) { + try { + // Skip queued BLE operations so disconnect doesn't get stuck behind + // them. Cap the platform-confirm wait well below FBP's 35 s default; + // the vendored Android plugin force-closes and confirms within ~5 s + // when the OS never reports the disconnect (#696). + await _device?.disconnect(queue: false, timeout: 10); + released = true; + } catch (e) { + _appDebugLogService?.warn( + 'Disconnect attempt $attempt failed: $e', + tag: 'BLE Connect', + ); + if (attempt == 1) { + await Future.delayed(const Duration(milliseconds: 500)); + } + } + } + if (!released && + transportAtDisconnect == MeshCoreTransportType.bluetooth) { + // Both attempts failed: the OS never confirmed the release, so this + // device may still hold the radio and keep it from advertising + // (#689). Surfaced as a persistent warning, never swallowed. + _bleReleaseUnconfirmed = true; } } else { _appDebugLogService?.info( diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index bd63924..d12e873 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -134,6 +134,14 @@ "scanner_scan": "Scan", "scanner_bluetoothOff": "Bluetooth is off", "scanner_bluetoothOffMessage": "Please turn on Bluetooth to scan for devices", + "scanner_bleReleaseWarning": "Radio may still be held by this device", + "@scanner_bleReleaseWarning": { + "description": "Persistent banner title when a BLE disconnect was never confirmed by the OS, so the radio may stay latched and stop advertising (#697)." + }, + "scanner_bleReleaseWarningMessage": "The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.", + "@scanner_bleReleaseWarningMessage": { + "description": "Persistent banner body naming the recovery for an unconfirmed BLE release (#697)." + }, "scanner_chromeRequired": "Chrome Browser Required", "scanner_chromeRequiredMessage": "This web application requires Google Chrome or a Chromium-based browser for Bluetooth support.", "scanner_enableBluetooth": "Enable Bluetooth", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 28c3710..730d182 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -640,6 +640,18 @@ abstract class AppLocalizations { /// **'Please turn on Bluetooth to scan for devices'** String get scanner_bluetoothOffMessage; + /// Persistent banner title when a BLE disconnect was never confirmed by the OS, so the radio may stay latched and stop advertising (#697). + /// + /// In en, this message translates to: + /// **'Radio may still be held by this device'** + String get scanner_bleReleaseWarning; + + /// Persistent banner body naming the recovery for an unconfirmed BLE release (#697). + /// + /// In en, this message translates to: + /// **'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'** + String get scanner_bleReleaseWarningMessage; + /// No description provided for @scanner_chromeRequired. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 504792a..a891bec 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -290,6 +290,14 @@ class AppLocalizationsBg extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Моля, активирайте Bluetooth, за да сканирате за устройства.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Изисква се браузър Chrome'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 03c6cc9..597e4fc 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -293,6 +293,14 @@ class AppLocalizationsDe extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Bitte aktivieren Sie Bluetooth, um nach Geräten zu suchen.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Chrome Browser erforderlich'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 001e8c1..a048fec 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -287,6 +287,14 @@ class AppLocalizationsEn extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Please turn on Bluetooth to scan for devices'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Chrome Browser Required'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 4541d3c..3e89c4d 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -291,6 +291,14 @@ class AppLocalizationsEs extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Por favor, active el Bluetooth para escanear dispositivos.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Navegador Chrome requerido'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index c9d5193..6fe1270 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -293,6 +293,14 @@ class AppLocalizationsFr extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Veuillez activer le Bluetooth pour rechercher des appareils.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Navigateur Chrome requis'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 14d7607..910e832 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -290,6 +290,14 @@ class AppLocalizationsHu extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Kérjük, kapcsolja be a Bluetooth-ot, hogy eszközök keresése lehessen.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Chrome böngésző szükséges'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index f78808e..fc49cd6 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -293,6 +293,14 @@ class AppLocalizationsIt extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Si prega di attivare il Bluetooth per effettuare la scansione dei dispositivi.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Browser Chrome richiesto'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 3bba046..dff3789 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -280,6 +280,14 @@ class AppLocalizationsJa extends AppLocalizations { @override String get scanner_bluetoothOffMessage => 'Bluetoothを有効にして、デバイスを検索してください。'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Chrome ブラウザが必須です'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 1a1e29c..0cc0a70 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -279,6 +279,14 @@ class AppLocalizationsKo extends AppLocalizations { @override String get scanner_bluetoothOffMessage => '블루투스를 켜서 장치를 검색해주세요.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => '크롬 브라우저 필요'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index f88ceac..1aa66db 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -289,6 +289,14 @@ class AppLocalizationsNl extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Zorg ervoor dat Bluetooth is ingeschakeld om naar apparaten te zoeken.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Chrome-browser vereist'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 1d348db..d6db094 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -294,6 +294,14 @@ class AppLocalizationsPl extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Prosimy włączyć Bluetooth, aby przeskanować urządzenia.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Wymagana przeglądarka Chrome'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 38f706e..b885e11 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -292,6 +292,14 @@ class AppLocalizationsPt extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Por favor, ative o Bluetooth para escanear por dispositivos.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Navegador Chrome necessário'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 8f42e3c..61e266b 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -292,6 +292,14 @@ class AppLocalizationsRu extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Пожалуйста, включите Bluetooth, чтобы найти устройства.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Требуется браузер Chrome'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 414894e..fcbc28c 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -291,6 +291,14 @@ class AppLocalizationsSk extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Prosím, zapnite Bluetooth, aby ste mohli skenovať pre zariadenia.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Vyžaduje sa prehliadač Chrome'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 559fa3f..8193aa4 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -289,6 +289,14 @@ class AppLocalizationsSl extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Prosimo, vklopite Bluetooth, da lahko poiščete naprave.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Zahtevan brskalnik Chrome'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index a0e1a3e..8f344a2 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -288,6 +288,14 @@ class AppLocalizationsSv extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Vänligen aktivera Bluetooth för att söka efter enheter.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Chrome-webbläsare krävs'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 3b7ab78..099f2a8 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -291,6 +291,14 @@ class AppLocalizationsUk extends AppLocalizations { String get scanner_bluetoothOffMessage => 'Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => 'Потрібен браузер Chrome'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index ddfeb99..a38da41 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -277,6 +277,14 @@ class AppLocalizationsZh extends AppLocalizations { @override String get scanner_bluetoothOffMessage => '请开启蓝牙以搜索设备'; + @override + String get scanner_bleReleaseWarning => + 'Radio may still be held by this device'; + + @override + String get scanner_bleReleaseWarningMessage => + 'The disconnect was not confirmed. If the radio does not reappear, toggle Bluetooth off and on, or force-stop the app.'; + @override String get scanner_chromeRequired => '需要 Chrome 浏览器'; diff --git a/lib/screens/scanner_screen.dart b/lib/screens/scanner_screen.dart index 1ad1dc0..fa38279 100644 --- a/lib/screens/scanner_screen.dart +++ b/lib/screens/scanner_screen.dart @@ -133,6 +133,13 @@ class _ScannerScreenState extends State { if (_bluetoothState == BluetoothAdapterState.off) _bluetoothOffWarning(context), + // Unconfirmed BLE release: the OS never confirmed the last + // disconnect, so this device may still hold the radio and + // keep it from advertising (#689). Persistent until + // dismissed, per the error-visibility standard. + if (connector.bleReleaseUnconfirmed) + _bleReleaseWarning(context, connector), + // Status bar _buildStatusBar(context, connector), @@ -435,6 +442,48 @@ class _ScannerScreenState extends State { return pin; } + Widget _bleReleaseWarning(BuildContext context, MeshCoreConnector connector) { + final errorColor = Theme.of(context).colorScheme.error; + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + color: errorColor.withValues(alpha: 0.15), + child: Row( + children: [ + Icon(Icons.bluetooth_connected, size: 24, color: errorColor), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.scanner_bleReleaseWarning, + style: TextStyle( + color: errorColor, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + const SizedBox(height: 4), + Text( + context.l10n.scanner_bleReleaseWarningMessage, + style: TextStyle( + color: errorColor.withValues(alpha: 0.85), + fontSize: 12, + ), + ), + ], + ), + ), + TextButton( + onPressed: connector.clearBleReleaseWarning, + child: Text(context.l10n.common_ok), + ), + ], + ), + ); + } + Widget _bluetoothOffWarning(BuildContext context) { final errorColor = Theme.of(context).colorScheme.error; return Container( diff --git a/untranslated.json b/untranslated.json index 47ad178..4bb1eba 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1,5 +1,7 @@ { "bg": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -72,10 +74,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -205,6 +203,8 @@ ], "de": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -277,10 +277,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -410,6 +406,8 @@ ], "es": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -482,10 +480,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -615,6 +609,8 @@ ], "fr": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -687,10 +683,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -820,6 +812,8 @@ ], "hu": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -892,10 +886,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1025,6 +1015,8 @@ ], "it": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -1097,10 +1089,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1230,6 +1218,8 @@ ], "ja": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -1302,10 +1292,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1435,6 +1421,8 @@ ], "ko": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -1507,10 +1495,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1640,6 +1624,8 @@ ], "nl": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -1712,10 +1698,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1845,6 +1827,8 @@ ], "pl": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -1917,10 +1901,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -2050,6 +2030,8 @@ ], "pt": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -2122,10 +2104,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -2255,6 +2233,8 @@ ], "ru": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -2327,10 +2307,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -2460,6 +2436,8 @@ ], "sk": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -2532,10 +2510,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -2665,6 +2639,8 @@ ], "sl": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -2737,10 +2713,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -2870,6 +2842,8 @@ ], "sv": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -2942,10 +2916,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -3075,6 +3045,8 @@ ], "uk": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -3147,10 +3119,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -3280,6 +3248,8 @@ ], "zh": [ + "scanner_bleReleaseWarning", + "scanner_bleReleaseWarningMessage", "settings_messageSettings", "settings_messageSettingsSubtitle", "settings_gpsStatusTitle", @@ -3352,10 +3322,6 @@ "path_suggestedRoute", "path_searchRepeaters", "repeater_cliCommandTimeout", - "repeater_cliLateResponse", - "repeater_cliUnmatchedResponse", - "repeater_lateResponseDiscarded", - "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel",