From 415c6184b13dcb09451c27a9f962e2158e4e096e Mon Sep 17 00:00:00 2001 From: tajniak81 <13187254+tajniak81@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:16:04 +0200 Subject: [PATCH] Keep action rows when their key cannot be read e3917e6 hid any row whose key failed its seeding read, which also hid "Link remote controller": an action key has no value to read. Unreadable is now tracked apart from supported, and only rows that show a value hide on it. Co-Authored-By: Claude Opus 5.5 --- Fly App/lib/fly/settings/settings_spec.dart | 8 ++++++++ Fly App/lib/fly/state/key_state.dart | 17 +++++++++++------ Fly App/test/fly/settings_test.dart | 16 ++++++++++++++-- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Fly App/lib/fly/settings/settings_spec.dart b/Fly App/lib/fly/settings/settings_spec.dart index 0bf7943..69629cd 100644 --- a/Fly App/lib/fly/settings/settings_spec.dart +++ b/Fly App/lib/fly/settings/settings_spec.dart @@ -40,6 +40,9 @@ sealed class SettingSpec { /// The key the row reads, or null for app-side rows. DjiKey? get key => null; + /// Whether the row shows the key's value; an unreadable key hides it. + bool get readsValue => true; + /// Every key the row needs listened to. Iterable get watched => key == null ? const [] : [key!]; @@ -47,6 +50,7 @@ sealed class SettingSpec { if (requires != null && c.connected && !requires!.enabledFor(c.profile)) return false; final DjiKey? k = key; if (k != null && c.keys.supported(k) == false) return false; + if (k != null && readsValue && c.keys.unreadable(k)) return false; return visibleWhen?.call(c) ?? true; } @@ -264,6 +268,10 @@ class KeyActionSpec extends SettingSpec { super.groundOnly, }) : _key = key; + /// It performs the key's action; there is no value to read. + @override + bool get readsValue => false; + final DjiKey _key; final String button; final String? confirmTitle; diff --git a/Fly App/lib/fly/state/key_state.dart b/Fly App/lib/fly/state/key_state.dart index d386fed..1d07a11 100644 --- a/Fly App/lib/fly/state/key_state.dart +++ b/Fly App/lib/fly/state/key_state.dart @@ -11,6 +11,7 @@ import 'settings_overlay.dart'; /// known to be unsupported, and stays visible (as "—") while still unknown. class KeyState extends ChangeNotifier with SettingsOverlay { final Map _supported = {}; + final Set _unreadable = {}; /// True / false once asked, null while unknown. bool? supported(DjiKey key) => _supported[key.id]; @@ -25,30 +26,34 @@ class KeyState extends ChangeNotifier with SettingsOverlay { notifyListeners(); } - /// A key the aircraft calls supported but will not read: hidden like an - /// unsupported one, until a value turns up after all. + /// The aircraft calls the key supported but will not read it (the Mavic Pro + /// remote's gimbal dial speed). Rows that show its value hide; action rows, + /// which never read it, do not. + bool unreadable(DjiKey key) => _unreadable.contains(key.id); + void markUnreadable(String id) { - if (_supported[id] == false || reportedSettings.containsKey(id)) return; - _supported[id] = false; + if (reportedSettings.containsKey(id) || !_unreadable.add(id)) return; notifyListeners(); } /// One `key` event: `{id, value}`. void apply(String id, Object? value) { - if (_supported[id] == false) _supported.remove(id); + _unreadable.remove(id); replaceReported({...reportedSettings, id: value}); notifyListeners(); } /// A new aircraft answers `isKeySupported` afresh. void forgetSupport() { - if (_supported.isEmpty) return; + if (_supported.isEmpty && _unreadable.isEmpty) return; _supported.clear(); + _unreadable.clear(); notifyListeners(); } void clear() { _supported.clear(); + _unreadable.clear(); clearSettings(); notifyListeners(); } diff --git a/Fly App/test/fly/settings_test.dart b/Fly App/test/fly/settings_test.dart index 1d35812..856748d 100644 --- a/Fly App/test/fly/settings_test.dart +++ b/Fly App/test/fly/settings_test.dart @@ -151,12 +151,24 @@ void main() { final TelemetryReducer r = TelemetryReducer(m); m.keys.setSupported(SettingsKeys.gimbalDialSpeed, true); r.apply({'type': 'key', 'id': SettingsKeys.gimbalDialSpeed.id, 'unreadable': true}); - expect(m.keys.supported(SettingsKeys.gimbalDialSpeed), isFalse); + expect(m.keys.unreadable(SettingsKeys.gimbalDialSpeed), isTrue); + final SettingsContext c = SettingsContext(profile: m.profile, keys: m.keys, aircraft: m.aircraft); + final SettingSpec dial = spec(SettingsTabs.rc, 'gimbalDialSpeed'); + expect(dial.visible(c), isFalse); r.apply({'type': 'key', 'id': SettingsKeys.gimbalDialSpeed.id, 'value': 40}); - expect(m.keys.supported(SettingsKeys.gimbalDialSpeed), isNot(isFalse)); + expect(dial.visible(c), isTrue); expect(m.keys.value(SettingsKeys.gimbalDialSpeed), 40); }); + test('an action row stays when its key cannot be read (it never reads it)', () { + final FlightModel m = connectedModel(phantom4); + m.keys.setSupported(SettingsKeys.startPairing, true); + TelemetryReducer(m).apply({'type': 'key', 'id': SettingsKeys.startPairing.id, 'unreadable': true}); + final SettingsContext c = SettingsContext(profile: m.profile, keys: m.keys, aircraft: m.aircraft); + final SettingSpec link = spec(SettingsTabs.rc, 'startPairing'); + expect(link.visible(c), isTrue); + }); + test('subscriptions listen only to supported keys and cancel on dispose', () async { final FakeDjiService dji = FakeDjiService()..unsupportedKeys.add('ESC_BEEPING_ENABLED'); final KeyState s = KeyState();