Files
PilotVault/Fly App/lib/fly/fly_prefs.dart
T
tajniak81andClaude Opus 5.5 e02bb0e5cd Give the Fly App GO 4-style obstacle radar, on-screen sticks, AirSense and fly zones
Vision radar: graded obstacle sectors around the centre of the video,
the nearest distance per side, the obstacle overhead, and BLOCKED on a
side whose sensors cannot see once the motors run. On aircraft with 360°
sensing, sides without their own readings are filled from the
perception ring. The same sectors sit on the attitude ball's rim, turned
by the heading. Settings > Vision gains Display radar chart and the
braking / sensing distances per direction.

On-screen sticks: the rail's STICKS button hands the aircraft to two
stick pads after a slide-to-confirm. The 25 Hz stream runs natively and
sends zeros once Dart's last command is 300 ms old, so a stalled UI
leaves the aircraft hovering. The sticks close when the remote takes
over, on RTH or on landing. They are simulator-only on aircraft with a
remote unless Settings > General > Developer allows real flights; Spark
and Mavic Air on Wi-Fi may fly them for real. Which MSDK axis is forward
in velocity + body mode is coded from DJI's documentation and must be
checked in the simulator first.

AirSense: a banner for manned aircraft at warning level 2 and up, with
distance, direction and ICAO code; the planes on the map; the full list
in a sheet and Settings > Safety; and the aircraft's own avoidance
action with its countdown.

Fly zones: DJI GEO zones around the aircraft (or the phone) on the map
with their height limits, a banner for FlySafe notices and in/near-zone
states, take-off refused in restricted and locked authorization zones,
self-unlock of authorization zones, the aircraft's licences (enable,
sync from the DJI account, remove all; ground only), and the GEO
database version with an update button.

Tested with unit and widget tests and screenshot renders only; nothing
has run on an aircraft or the simulator yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:14:12 +02:00

146 lines
5.4 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'virtual_sticks.dart' show StickMode;
/// The video overlay grid (camera menu → Other, and the rail's GRID button).
enum GridStyle {
off('Off'),
thirds('Grid'),
diagonals('Grid + diagonals');
const GridStyle(this.label);
final String label;
}
/// What a long press on the live view does (GO 4's "Long Press" setting); a
/// single tap always focuses, or meters on fixed-focus cameras.
enum LongPressAction {
meter('Spot meter'),
focus('Focus'),
off('Nothing');
const LongPressAction(this.label);
final String label;
}
/// Device-local switches for the flight screen, persisted across launches.
abstract final class FlyPrefs {
static const String _classicKey = 'fly.classicHud';
static const String _perfKey = 'fly.perfOverlay';
static const String _gridKey = 'fly.grid';
static const String _centerKey = 'fly.centerPoint';
static const String _longPressKey = 'fly.longPress';
static const String _routeKey = 'fly.showRoute';
static const String _termsKey = 'fly.modeTerms';
static const String _radarKey = 'fly.radar';
static const String _stickModeKey = 'fly.stickMode';
static const String _sticksRealKey = 'fly.sticksRealFlight';
/// Open the pre-P2 `FlightControlPage` instead of the HUD v2 — kept
/// reachable until P3's safety flows pass their simulator run (plan P2/P3).
static final ValueNotifier<bool> classicHud = ValueNotifier<bool>(false);
/// Flutter's performance overlay on the flight screen, for the P2 "no jank at
/// 10 Hz" check.
static final ValueNotifier<bool> perfOverlay = ValueNotifier<bool>(false);
static final ValueNotifier<GridStyle> grid = ValueNotifier<GridStyle>(GridStyle.off);
static final ValueNotifier<bool> centerPoint = ValueNotifier<bool>(true);
static final ValueNotifier<LongPressAction> longPress = ValueNotifier<LongPressAction>(LongPressAction.meter);
/// Draw the flown trail on the map (settings → General).
static final ValueNotifier<bool> showRoute = ValueNotifier<bool>(true);
/// Intelligent flight modes whose first-use terms the pilot accepted (plan P7), by name.
static final ValueNotifier<Set<String>> acceptedModeTerms = ValueNotifier<Set<String>>(<String>{});
/// Draw the vision radar on the video (GO 4's "Display Radar Chart", plan P8).
static final ValueNotifier<bool> showRadar = ValueNotifier<bool>(true);
/// What each on-screen stick does (plan P8); matches the remote's stick mode by default.
static final ValueNotifier<StickMode> stickMode = ValueNotifier<StickMode>(StickMode.mode2);
/// Developer switch: allow on-screen sticks outside the simulator on aircraft
/// that have a remote. Off by default (plan P8: simulator first).
static final ValueNotifier<bool> sticksInRealFlight = ValueNotifier<bool>(false);
static SharedPreferences? _prefs;
static Future<void> load() async {
try {
_prefs = await SharedPreferences.getInstance();
classicHud.value = _prefs!.getBool(_classicKey) ?? false;
perfOverlay.value = _prefs!.getBool(_perfKey) ?? false;
grid.value = _byName(GridStyle.values, _prefs!.getString(_gridKey)) ?? GridStyle.off;
centerPoint.value = _prefs!.getBool(_centerKey) ?? true;
longPress.value = _byName(LongPressAction.values, _prefs!.getString(_longPressKey)) ?? LongPressAction.meter;
showRoute.value = _prefs!.getBool(_routeKey) ?? true;
acceptedModeTerms.value = (_prefs!.getStringList(_termsKey) ?? const <String>[]).toSet();
showRadar.value = _prefs!.getBool(_radarKey) ?? true;
stickMode.value = _byName(StickMode.values, _prefs!.getString(_stickModeKey)) ?? StickMode.mode2;
sticksInRealFlight.value = _prefs!.getBool(_sticksRealKey) ?? false;
} catch (_) {
// Storage unavailable: run on the defaults.
}
}
static Future<void> setClassicHud(bool on) async {
classicHud.value = on;
await _prefs?.setBool(_classicKey, on);
}
static Future<void> setPerfOverlay(bool on) async {
perfOverlay.value = on;
await _prefs?.setBool(_perfKey, on);
}
static Future<void> setGrid(GridStyle g) async {
grid.value = g;
await _prefs?.setString(_gridKey, g.name);
}
static Future<void> setCenterPoint(bool on) async {
centerPoint.value = on;
await _prefs?.setBool(_centerKey, on);
}
static Future<void> setLongPress(LongPressAction a) async {
longPress.value = a;
await _prefs?.setString(_longPressKey, a.name);
}
static Future<void> setShowRoute(bool on) async {
showRoute.value = on;
await _prefs?.setBool(_routeKey, on);
}
static Future<void> acceptModeTerms(String mode) async {
if (acceptedModeTerms.value.contains(mode)) return;
acceptedModeTerms.value = <String>{...acceptedModeTerms.value, mode};
await _prefs?.setStringList(_termsKey, acceptedModeTerms.value.toList()..sort());
}
static Future<void> setShowRadar(bool on) async {
showRadar.value = on;
await _prefs?.setBool(_radarKey, on);
}
static Future<void> setStickMode(StickMode m) async {
stickMode.value = m;
await _prefs?.setString(_stickModeKey, m.name);
}
static Future<void> setSticksInRealFlight(bool on) async {
sticksInRealFlight.value = on;
await _prefs?.setBool(_sticksRealKey, on);
}
static T? _byName<T extends Enum>(List<T> values, String? name) {
for (final T v in values) {
if (v.name == name) return v;
}
return null;
}
}