Files
PilotVault/Fly App/lib/fly/state/key_state.dart
T
tajniak81andClaude Opus 5.5 415c6184b1 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 <noreply@anthropic.com>
2026-10-22 15:16:04 +02:00

127 lines
3.5 KiB
Dart

import 'package:flutter/foundation.dart';
import '../../dji_service.dart';
import 'settings_overlay.dart';
/// KeyManager values the settings sheet watches (plan P6), keyed by
/// [DjiKey.id], from the bridge's `key` events — overlaid with the sheet's
/// optimistic writes through [SettingsOverlay], like camera and gimbal settings.
///
/// [supported] records `isKeySupported` per key: a row hides once its key is
/// known to be unsupported, and stays visible (as "—") while still unknown.
class KeyState extends ChangeNotifier with SettingsOverlay {
final Map<String, bool> _supported = <String, bool>{};
final Set<String> _unreadable = <String>{};
/// True / false once asked, null while unknown.
bool? supported(DjiKey key) => _supported[key.id];
Object? value(DjiKey key) => setting(key.id);
bool pendingFor(DjiKey key) => isPending(key.id);
void setSupported(DjiKey key, bool on) {
if (_supported[key.id] == on) return;
_supported[key.id] = on;
notifyListeners();
}
/// 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 (reportedSettings.containsKey(id) || !_unreadable.add(id)) return;
notifyListeners();
}
/// One `key` event: `{id, value}`.
void apply(String id, Object? value) {
_unreadable.remove(id);
replaceReported(<String, dynamic>{...reportedSettings, id: value});
notifyListeners();
}
/// A new aircraft answers `isKeySupported` afresh.
void forgetSupport() {
if (_supported.isEmpty && _unreadable.isEmpty) return;
_supported.clear();
_unreadable.clear();
notifyListeners();
}
void clear() {
_supported.clear();
_unreadable.clear();
clearSettings();
notifyListeners();
}
}
/// The `listenKey` subscriptions one screen holds open — the settings sheet
/// while it is up. [watch] asks `isKeySupported` once per key and listens to
/// the supported ones; [dispose] cancels them all.
class KeySubscriptions {
KeySubscriptions(this.dji, this.state);
final DjiService dji;
final KeyState state;
final Set<DjiKey> _asked = <DjiKey>{};
final Set<String> _listening = <String>{};
bool _disposed = false;
Future<void> watch(Iterable<DjiKey> keys) async {
await Future.wait(<Future<void>>[
for (final DjiKey k in keys)
if (_asked.add(k)) _watchOne(k),
]);
}
Future<void> _watchOne(DjiKey key) async {
bool ok;
try {
ok = await dji.isKeySupported(key);
} catch (_) {
ok = false;
}
if (_disposed) return;
state.setSupported(key, ok);
if (!ok) return;
try {
final String id = await dji.listenKey(key);
if (_disposed) {
await dji.cancelKey(id);
} else {
_listening.add(id);
}
} catch (_) {
// The row shows "—" until a value arrives some other way.
}
}
/// Asks every key again — after a reconnect or product change.
Future<void> rewatch(Iterable<DjiKey> keys) async {
await _cancelAll();
_asked.clear();
state.forgetSupport();
await watch(keys);
}
Future<void> _cancelAll() async {
final List<String> ids = _listening.toList();
_listening.clear();
for (final String id in ids) {
try {
await dji.cancelKey(id);
} catch (_) {}
}
}
void dispose() {
_disposed = true;
_cancelAll();
}
}