Files
PilotVault/Fly App/lib/fly/state/key_state.dart
T
tajniak81andClaude Opus 5.5 284de5442a Give the Fly App a GO 4-style settings sheet
The "•••" button now opens a right-side glass sheet over the live view
with Safety, Vision, Remote, Video link, Battery, Gimbal and General
tabs. Each row is a one-line spec bound to a KeyManager key: rows hide
when the aircraft does not support their key, writes show at once and
roll back if refused, and ground-only settings lock in flight.

KeyBridge gains argument-less action keys and converts range and LED
settings values. settings_menu_page.dart is retired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:13:45 +02:00

113 lines
3.0 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>{};
/// 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();
}
/// One `key` event: `{id, value}`.
void apply(String id, Object? value) {
replaceReported(<String, dynamic>{...reportedSettings, id: value});
notifyListeners();
}
/// A new aircraft answers `isKeySupported` afresh.
void forgetSupport() {
if (_supported.isEmpty) return;
_supported.clear();
notifyListeners();
}
void clear() {
_supported.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();
}
}