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>
113 lines
3.0 KiB
Dart
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();
|
|
}
|
|
}
|