Files
PilotVault/Fly App/lib/flight_model.dart
T
tajniak81andClaude Opus 4.8 a4a2709456 Rebuild Fly App to design v2 with full DJI SDK integration
Rebuild the Flutter/DJI-MSDK-V4 Fly App to the v2 UI kit and wire the
full SDK surface behind it.

Native (Kotlin): split DjiSdkBridge into a method/event router delegating
to per-subsystem SubBridge helpers sharing a BridgeCtx — FlightController
(takeoff/land/RTH + rich telemetry), Camera (mode/record/photo/exposure),
Gimbal, Mission (Waypoint + ActiveTrack; QuickShots via ActiveTrack
QUICK_SHOT), Media (MediaManager list/thumbnail/download), and optional
DJI account login. Manifest gains scoped media permissions.

Flutter: ten screens under lib/ui/ (Flight HUD, capture modes, camera
settings, settings menu, map+waypoints, home, album, academy, profile,
routes/flight logs), driven by an expanded FlightModel. New PVIcon renders
the kit's SVG paths via flutter_svg; map uses flutter_map + latlong2.

Pin transitive androidx.core/browser down to SDK-35-compatible versions
so the newer plugins don't force AGP 8.9.1 onto the DJI toolchain.

Verified with `flutter build apk --debug` (compiles Dart + all Kotlin);
runtime behaviour is untested here — it needs a physical DJI-connected
device.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:51:08 +02:00

152 lines
6.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String, dynamic> 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<double>? 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<MediaItem> media = <MediaItem>[];
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();
}