import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'rc_buttons.dart'; import 'units.dart'; import 'virtual_sticks.dart' show StickMode; export 'rc_buttons.dart' show RcButton, RcButtonAction; export 'units.dart' show UnitSystem; /// 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'; static const String _autoUploadKey = 'fly.autoUploadFlights'; static const String _liveUrlKey = 'fly.liveStreamUrl'; static const String _liveAudioKey = 'fly.liveStreamAudio'; static const String _unitsKey = 'fly.units'; static const String _c1Key = 'fly.rcC1'; static const String _c2Key = 'fly.rcC2'; static const String _videoCacheKey = 'fly.videoCache'; static const String _videoCacheLimitKey = 'fly.videoCacheLimitMb'; static const String _lensCalibratedKey = 'fly.lensCalibrated'; static const String _tutorialsKey = 'fly.modeTutorials'; /// 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 classicHud = ValueNotifier(false); /// Flutter's performance overlay on the flight screen, for the P2 "no jank at /// 10 Hz" check. static final ValueNotifier perfOverlay = ValueNotifier(false); static final ValueNotifier grid = ValueNotifier(GridStyle.off); static final ValueNotifier centerPoint = ValueNotifier(true); static final ValueNotifier longPress = ValueNotifier(LongPressAction.meter); /// Draw the flown trail on the map (settings → General). static final ValueNotifier showRoute = ValueNotifier(true); /// Intelligent flight modes whose first-use terms the pilot accepted (plan P7), by name. static final ValueNotifier> acceptedModeTerms = ValueNotifier>({}); /// Draw the vision radar on the video (GO 4's "Display Radar Chart", plan P8). static final ValueNotifier showRadar = ValueNotifier(true); /// What each on-screen stick does (plan P8); matches the remote's stick mode by default. static final ValueNotifier stickMode = ValueNotifier(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 sticksInRealFlight = ValueNotifier(false); /// Upload each recorded flight to the API Server's logbook when it lands (plan P9). static final ValueNotifier autoUploadFlights = ValueNotifier(true); /// The RTMP address live streaming sends to (plan P9), `rtmp://host/app/key`. static final ValueNotifier liveStreamUrl = ValueNotifier(''); /// Send the phone's microphone with the live stream. static final ValueNotifier liveStreamAudio = ValueNotifier(false); /// Metric or imperial readouts on the HUD and in settings (plan P6). static final ValueNotifier units = ValueNotifier(UnitSystem.metric); /// What the remote's C1 / C2 buttons do (plan P6); GO 4's defaults. static final ValueNotifier c1 = ValueNotifier(RcButtonAction.gimbalRecenter); static final ValueNotifier c2 = ValueNotifier(RcButtonAction.gimbalToggleDown); /// Record the live feed to the phone while flying (GO 4's video cache, plan P6). static final ValueNotifier videoCache = ValueNotifier(false); /// The cache's size cap; the oldest recordings go first. static final ValueNotifier videoCacheLimitMb = ValueNotifier(2048); /// Cameras (by display name) whose manual-focus lens was calibrated here, so /// the first-MF prompt is not repeated (plan P6b). static final ValueNotifier> lensCalibrated = ValueNotifier>({}); /// Intelligent modes whose tutorial the pilot has seen (plan P7), by name. static final ValueNotifier> seenTutorials = ValueNotifier>({}); static SharedPreferences? _prefs; static Future 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 []).toSet(); showRadar.value = _prefs!.getBool(_radarKey) ?? true; stickMode.value = _byName(StickMode.values, _prefs!.getString(_stickModeKey)) ?? StickMode.mode2; sticksInRealFlight.value = _prefs!.getBool(_sticksRealKey) ?? false; autoUploadFlights.value = _prefs!.getBool(_autoUploadKey) ?? true; liveStreamUrl.value = _prefs!.getString(_liveUrlKey) ?? ''; liveStreamAudio.value = _prefs!.getBool(_liveAudioKey) ?? false; units.value = _byName(UnitSystem.values, _prefs!.getString(_unitsKey)) ?? UnitSystem.metric; c1.value = _byName(RcButtonAction.values, _prefs!.getString(_c1Key)) ?? RcButtonAction.gimbalRecenter; c2.value = _byName(RcButtonAction.values, _prefs!.getString(_c2Key)) ?? RcButtonAction.gimbalToggleDown; videoCache.value = _prefs!.getBool(_videoCacheKey) ?? false; videoCacheLimitMb.value = _prefs!.getInt(_videoCacheLimitKey) ?? 2048; lensCalibrated.value = (_prefs!.getStringList(_lensCalibratedKey) ?? const []).toSet(); seenTutorials.value = (_prefs!.getStringList(_tutorialsKey) ?? const []).toSet(); } catch (_) { // Storage unavailable: run on the defaults. } } static Future setClassicHud(bool on) async { classicHud.value = on; await _prefs?.setBool(_classicKey, on); } static Future setPerfOverlay(bool on) async { perfOverlay.value = on; await _prefs?.setBool(_perfKey, on); } static Future setGrid(GridStyle g) async { grid.value = g; await _prefs?.setString(_gridKey, g.name); } static Future setCenterPoint(bool on) async { centerPoint.value = on; await _prefs?.setBool(_centerKey, on); } static Future setLongPress(LongPressAction a) async { longPress.value = a; await _prefs?.setString(_longPressKey, a.name); } static Future setShowRoute(bool on) async { showRoute.value = on; await _prefs?.setBool(_routeKey, on); } static Future acceptModeTerms(String mode) async { if (acceptedModeTerms.value.contains(mode)) return; acceptedModeTerms.value = {...acceptedModeTerms.value, mode}; await _prefs?.setStringList(_termsKey, acceptedModeTerms.value.toList()..sort()); } static Future setShowRadar(bool on) async { showRadar.value = on; await _prefs?.setBool(_radarKey, on); } static Future setStickMode(StickMode m) async { stickMode.value = m; await _prefs?.setString(_stickModeKey, m.name); } static Future setSticksInRealFlight(bool on) async { sticksInRealFlight.value = on; await _prefs?.setBool(_sticksRealKey, on); } static Future setAutoUploadFlights(bool on) async { autoUploadFlights.value = on; await _prefs?.setBool(_autoUploadKey, on); } static Future setLiveStreamUrl(String url) async { liveStreamUrl.value = url.trim(); await _prefs?.setString(_liveUrlKey, url.trim()); } static Future setLiveStreamAudio(bool on) async { liveStreamAudio.value = on; await _prefs?.setBool(_liveAudioKey, on); } static Future setUnits(UnitSystem u) async { units.value = u; await _prefs?.setString(_unitsKey, u.name); } static Future setRcButton(RcButton b, RcButtonAction a) async { (b == RcButton.c1 ? c1 : c2).value = a; await _prefs?.setString(b == RcButton.c1 ? _c1Key : _c2Key, a.name); } static RcButtonAction rcButton(RcButton b) => (b == RcButton.c1 ? c1 : c2).value; static Future setVideoCache(bool on) async { videoCache.value = on; await _prefs?.setBool(_videoCacheKey, on); } static Future setVideoCacheLimitMb(int mb) async { videoCacheLimitMb.value = mb; await _prefs?.setInt(_videoCacheLimitKey, mb); } static Future markLensCalibrated(String camera) async { if (lensCalibrated.value.contains(camera)) return; lensCalibrated.value = {...lensCalibrated.value, camera}; await _prefs?.setStringList(_lensCalibratedKey, lensCalibrated.value.toList()..sort()); } static Future markTutorialSeen(String mode) async { if (seenTutorials.value.contains(mode)) return; seenTutorials.value = {...seenTutorials.value, mode}; await _prefs?.setStringList(_tutorialsKey, seenTutorials.value.toList()..sort()); } static T? _byName(List values, String? name) { for (final T v in values) { if (v.name == name) return v; } return null; } }