import 'package:flutter/foundation.dart'; import 'uploader.dart'; enum RegistrationState { idle, registering, success, failed } /// Camera capture family, mirrors the right-rail Photo/Video/Pano switch. enum CaptureMode { photo, video, pano } /// Exposure program — Auto (PROGRAM) vs Pro (MANUAL) tab on Camera settings. enum ExposureProgram { auto, pro } /// A media file listed off the aircraft's SD card by the native MediaManager. class MediaItem { MediaItem({ required this.index, required this.fileName, required this.isVideo, this.durationSeconds, this.sizeBytes, this.createdMs, this.thumbPath, this.localPath, }); final int index; final String fileName; final bool isVideo; final int? durationSeconds; final int? sizeBytes; final int? createdMs; /// On-disk path of the fetched thumbnail (cache dir), once available. String? thumbPath; /// On-disk path of the fully downloaded original, once available. String? localPath; String? get durationLabel { if (durationSeconds == null) return null; final int s = durationSeconds!; final String mm = (s ~/ 60).toString(); final String ss = (s % 60).toString().padLeft(2, '0'); return '$mm:$ss'; } factory MediaItem.fromMap(Map m) => MediaItem( index: (m['index'] as num).toInt(), fileName: (m['fileName'] as String?) ?? 'file', isVideo: (m['type'] as String?) == 'video', durationSeconds: (m['durationSeconds'] as num?)?.toInt(), sizeBytes: (m['sizeBytes'] as num?)?.toInt(), createdMs: (m['createdMs'] as num?)?.toInt(), ); } /// Live aircraft/session state, shared across every screen. `_HomePageState` /// owns the DJI/uploader plumbing and pushes updates here; the screens observe /// it via [AnimatedBuilder]. Call [bump] after a batch of field writes. class FlightModel extends ChangeNotifier { // ── SDK / registration / connection ──────────────────────────────────────── String sdkVersion = '…'; RegistrationState registration = RegistrationState.idle; String? registrationError; bool connected = false; String? model; String? firmwareVersion; // ── Flight controller telemetry ──────────────────────────────────────────── int? satellites; int? gpsSignalLevel; // 0..5 bool? isFlying; bool? motorsOn; String? flightMode; double? altitude; // m, relative to home double? latitude; double? longitude; double? homeLatitude; double? homeLongitude; double? homeDistance; // m, aircraft ↔ home double? horizontalSpeed; // m/s double? verticalSpeed; // m/s (up positive) double? heading; // deg int? flightTimeSeconds; double? goHomeHeight; // m // Phone's own GPS (independent of the drone's fix); streamed to the server as // a location fallback for the Web App's automatic bounding box. double? phoneLatitude; double? phoneLongitude; // ── Battery ───────────────────────────────────────────────────────────────── int? batteryPercent; double? batteryVoltage; // V double? batteryTemperature; // °C // ── Camera ────────────────────────────────────────────────────────────────── CaptureMode captureMode = CaptureMode.video; String? shootPhotoMode; // SINGLE / HDR / BURST / AEB / INTERVAL / PANORAMA bool isRecording = false; int recordSeconds = 0; bool sdInserted = false; int? sdRemainingMB; int? sdPhotoCount; int? sdVideoCount; // ── Exposure ──────────────────────────────────────────────────────────────── ExposureProgram exposureProgram = ExposureProgram.auto; String? iso; // "100", "AUTO", … String? shutter; // "1/240" String? aperture; // "f/2.8" String? ev; // "-0.3" String? whiteBalance; // "5200K" / "AUTO" List? histogram; // 0..1 normalized bins // ── Gimbal ────────────────────────────────────────────────────────────────── double? gimbalPitch; // deg (−90..30-ish) double? gimbalRoll; double? gimbalYaw; // ── Flight settings (Safety / Control) ────────────────────────────────────── int? maxHeight; // m int? maxRadius; // m bool? maxRadiusEnabled; int? rthHeight; // m String? obstacleAvoidance; // "On" / "Bypass" / "Off" bool? noviceMode; bool arHomePoint = true; // client-side toggle (AR overlay) // ── Missions ──────────────────────────────────────────────────────────────── String missionState = 'idle'; // idle / ready / uploading / executing / … bool missionRunning = false; bool tracking = false; // ActiveTrack engaged String? missionError; // ── On-drone media ────────────────────────────────────────────────────────── List media = []; bool mediaLoading = false; // ── DJI account (optional, in addition to PilotVault) ─────────────────────── String djiAccountState = 'unknown'; // notLoggedIn / tokenOutOfDate / authorized / … String? djiAccountUser; // ── Telemetry upload channel ──────────────────────────────────────────────── UploadStatus upload = UploadStatus.disabled; bool get registered => registration == RegistrationState.success; /// Notify observers after a batch of field writes. void bump() => notifyListeners(); }