Files
PilotVault/Fly App/lib/dji_service.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
8.5 KiB
Dart

import 'package:flutter/services.dart';
/// Thin Dart wrapper over the native DJI Mobile SDK bridge.
///
/// Mirrors the channels defined in `DjiSdkBridge.kt` (+ its per-subsystem
/// helpers):
/// * method channel `dji_msdk/methods` for imperative calls
/// * event channel `dji_msdk/events` for the SDK's async updates
///
/// Every method returns cleanly whether or not a product is connected; the
/// native side answers with a typed error the UI can surface. The event stream
/// carries maps keyed by `type`: `registration`, `connection`, `telemetry`,
/// `battery`, `camera`, `exposure`, `gimbal`, `mission`, `mediaList`,
/// `mediaDownload`, `djiAccount`, `database`, `init`.
class DjiService {
static const MethodChannel _methods = MethodChannel('dji_msdk/methods');
static const EventChannel _events = EventChannel('dji_msdk/events');
Stream<Map<String, dynamic>> events() {
return _events
.receiveBroadcastStream()
.map((dynamic e) => Map<String, dynamic>.from(e as Map));
}
// ── SDK / registration / connection ─────────────────────────────────────────
Future<String> getSdkVersion() async =>
await _methods.invokeMethod<String>('getSdkVersion') ?? 'unknown';
Future<void> registerApp() => _methods.invokeMethod<void>('registerApp');
Future<bool> startConnection() async =>
await _methods.invokeMethod<bool>('startConnection') ?? false;
Future<void> stopConnection() => _methods.invokeMethod<void>('stopConnection');
Future<Map<String, dynamic>> getProductInfo() async {
final dynamic info = await _methods.invokeMethod('getProductInfo');
return Map<String, dynamic>.from(info as Map);
}
// ── Flight controller commands ──────────────────────────────────────────────
Future<void> takeOff() => _methods.invokeMethod<void>('takeOff');
Future<void> land() => _methods.invokeMethod<void>('land');
Future<void> confirmLanding() => _methods.invokeMethod<void>('confirmLanding');
Future<void> cancelLanding() => _methods.invokeMethod<void>('cancelLanding');
Future<void> startGoHome() => _methods.invokeMethod<void>('startGoHome');
Future<void> cancelGoHome() => _methods.invokeMethod<void>('cancelGoHome');
Future<void> setHomeToCurrent() => _methods.invokeMethod<void>('setHomeToCurrent');
// ── Flight settings (Safety / Control) ──────────────────────────────────────
Future<void> setMaxFlightHeight(int m) =>
_methods.invokeMethod<void>('setMaxFlightHeight', <String, dynamic>{'value': m});
Future<void> setMaxFlightRadius(int m) =>
_methods.invokeMethod<void>('setMaxFlightRadius', <String, dynamic>{'value': m});
Future<void> setMaxRadiusEnabled(bool on) =>
_methods.invokeMethod<void>('setMaxRadiusEnabled', <String, dynamic>{'value': on});
Future<void> setGoHomeHeight(int m) =>
_methods.invokeMethod<void>('setGoHomeHeight', <String, dynamic>{'value': m});
Future<void> setNoviceMode(bool on) =>
_methods.invokeMethod<void>('setNoviceMode', <String, dynamic>{'value': on});
Future<void> setObstacleAvoidance(bool on) =>
_methods.invokeMethod<void>('setObstacleAvoidance', <String, dynamic>{'value': on});
// ── Camera ──────────────────────────────────────────────────────────────────
/// mode: `photo` | `video` | `mediaDownload` | `playback`
Future<void> setCameraMode(String mode) =>
_methods.invokeMethod<void>('setCameraMode', <String, dynamic>{'mode': mode});
Future<void> startShootPhoto() => _methods.invokeMethod<void>('startShootPhoto');
Future<void> stopShootPhoto() => _methods.invokeMethod<void>('stopShootPhoto');
Future<void> startRecordVideo() => _methods.invokeMethod<void>('startRecordVideo');
Future<void> stopRecordVideo() => _methods.invokeMethod<void>('stopRecordVideo');
/// mode: SINGLE | HDR | BURST | AEB | INTERVAL | PANORAMA
Future<void> setShootPhotoMode(String mode) =>
_methods.invokeMethod<void>('setShootPhotoMode', <String, dynamic>{'mode': mode});
/// program: `auto` (PROGRAM) | `pro` (MANUAL)
Future<void> setExposureProgram(String program) =>
_methods.invokeMethod<void>('setExposureProgram', <String, dynamic>{'program': program});
Future<void> setISO(String value) =>
_methods.invokeMethod<void>('setISO', <String, dynamic>{'value': value});
Future<void> setShutterSpeed(String value) =>
_methods.invokeMethod<void>('setShutterSpeed', <String, dynamic>{'value': value});
Future<void> setAperture(String value) =>
_methods.invokeMethod<void>('setAperture', <String, dynamic>{'value': value});
Future<void> setEV(String value) =>
_methods.invokeMethod<void>('setEV', <String, dynamic>{'value': value});
Future<void> setWhiteBalance(String value) =>
_methods.invokeMethod<void>('setWhiteBalance', <String, dynamic>{'value': value});
Future<void> setHistogramEnabled(bool on) =>
_methods.invokeMethod<void>('setHistogramEnabled', <String, dynamic>{'value': on});
// ── Gimbal ──────────────────────────────────────────────────────────────────
Future<void> rotateGimbalPitch(double deg) =>
_methods.invokeMethod<void>('rotateGimbalPitch', <String, dynamic>{'pitch': deg});
Future<void> resetGimbal() => _methods.invokeMethod<void>('resetGimbal');
// ── Missions ────────────────────────────────────────────────────────────────
/// points: [{lat, lon, altitude}], finishAction: NO_ACTION|GO_HOME|AUTO_LAND|GO_FIRST_WAYPOINT
Future<void> uploadWaypointMission(
List<Map<String, dynamic>> points, {
double speed = 8,
String finishAction = 'NO_ACTION',
}) =>
_methods.invokeMethod<void>('uploadWaypointMission', <String, dynamic>{
'points': points,
'speed': speed,
'finishAction': finishAction,
});
Future<void> startWaypointMission() => _methods.invokeMethod<void>('startWaypointMission');
Future<void> stopWaypointMission() => _methods.invokeMethod<void>('stopWaypointMission');
Future<void> pauseWaypointMission() => _methods.invokeMethod<void>('pauseWaypointMission');
Future<void> resumeWaypointMission() => _methods.invokeMethod<void>('resumeWaypointMission');
/// Rect is normalized 0..1 in the live view. mode: TRACE|PROFILE|SPOTLIGHT|QUICK_SHOT
Future<void> startActiveTrack(
double x,
double y,
double w,
double h, {
String mode = 'TRACE',
}) =>
_methods.invokeMethod<void>('startActiveTrack', <String, dynamic>{
'x': x,
'y': y,
'w': w,
'h': h,
'mode': mode,
});
Future<void> stopActiveTrack() => _methods.invokeMethod<void>('stopActiveTrack');
/// A DJI-Fly QuickShot by name (Dronie/Rocket/Circle/Helix/Boomerang/Asteroid).
/// The native side maps each to the closest real operator or returns a typed
/// "unsupported on this aircraft" error.
Future<void> startQuickShot(String name) =>
_methods.invokeMethod<void>('startQuickShot', <String, dynamic>{'name': name});
// ── Media (MediaManager) ────────────────────────────────────────────────────
Future<void> refreshMediaList() => _methods.invokeMethod<void>('refreshMediaList');
Future<String?> fetchThumbnail(int index) =>
_methods.invokeMethod<String>('fetchThumbnail', <String, dynamic>{'index': index});
Future<String?> downloadMedia(int index) =>
_methods.invokeMethod<String>('downloadMedia', <String, dynamic>{'index': index});
Future<void> deleteMedia(int index) =>
_methods.invokeMethod<void>('deleteMedia', <String, dynamic>{'index': index});
// ── DJI account (optional) ──────────────────────────────────────────────────
Future<void> djiLogin() => _methods.invokeMethod<void>('djiLogin');
Future<void> djiLogout() => _methods.invokeMethod<void>('djiLogout');
Future<void> refreshDjiAccountState() => _methods.invokeMethod<void>('getDjiAccountState');
}