import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../dji_service.dart'; import '../flight_model.dart'; import '../theme.dart'; import 'flight_overlay_scaffold.dart'; import 'pv_icons.dart'; /// Capture-mode selector — mirrors the v2 "Capture modes" mockup. Photo/Video /// modes drive the camera directly; QuickShots + Intelligent modes start the /// matching SDK mission (Phase 5 native — until then they report "unavailable"). class CaptureModesPage extends StatefulWidget { const CaptureModesPage({super.key, required this.model, required this.dji}); final FlightModel model; final DjiService dji; @override State createState() => _CaptureModesPageState(); } class _CaptureModesPageState extends State { DjiService get _dji => widget.dji; FlightModel get _m => widget.model; // (icon, label, wire) — wire drives the SDK call per group. static const List<(String, String, List<(String, String, String)>)> _groups = <(String, String, List<(String, String, String)>)>[ ('Photo', 'photo', <(String, String, String)>[ ('image', 'Single', 'SINGLE'), ('aperture', 'AEB', 'AEB'), ('burst', 'Burst', 'BURST'), ('timer', 'Timed', 'INTERVAL'), ('hdr', 'HDR', 'HDR'), ]), ('Video', 'video', <(String, String, String)>[ ('video', 'Normal', 'NORMAL'), ('slowmo', 'Slow-Mo', 'SLOW_MOTION'), ('hyperlapse', 'Hyperlapse', 'HYPERLAPSE'), ]), ('QuickShots', 'quick', <(String, String, String)>[ ('dronie', 'Dronie', 'Dronie'), ('rocket', 'Rocket', 'Rocket'), ('circle', 'Circle', 'Circle'), ('helix', 'Helix', 'Helix'), ('boomerang', 'Boomerang', 'Boomerang'), ('asteroid', 'Asteroid', 'Asteroid'), ]), ('Intelligent', 'smart', <(String, String, String)>[ ('master', 'MasterShot', 'MasterShot'), ('pano', 'Pano', 'PANORAMA'), ('crosshair', 'Track', 'TRACK'), ]), ]; String? _selected; @override void initState() { super.initState(); _selected = _m.shootPhotoMode ?? 'SINGLE'; } void _snack(String msg) { ScaffoldMessenger.of(context) ..clearSnackBars() ..showSnackBar(SnackBar(content: Text(msg))); } Future _run(String label, Future Function() action) async { try { await action(); } catch (e) { _snack('$label: ${e is PlatformException ? (e.message ?? e.code) : e}'); } } Future _pick(String group, String label, String wire) async { setState(() => _selected = wire); switch (group) { case 'photo': _m.captureMode = CaptureMode.photo; _m.shootPhotoMode = wire; await _run('Photo mode', () => _dji.setCameraMode('photo')); await _run('Photo mode', () => _dji.setShootPhotoMode(wire)); break; case 'video': _m.captureMode = CaptureMode.video; await _run('Video mode', () => _dji.setCameraMode('video')); if (wire != 'NORMAL') _snack('$label is applied on the aircraft camera'); break; case 'quick': await _run('QuickShot', () => _dji.startQuickShot(wire)); break; case 'smart': if (wire == 'PANORAMA') { _m.captureMode = CaptureMode.photo; await _run('Pano', () => _dji.setCameraMode('photo')); await _run('Pano', () => _dji.setShootPhotoMode('PANORAMA')); } else if (wire == 'MasterShot') { await _run('MasterShot', () => _dji.startQuickShot('MasterShot')); } else { _snack('Track: draw a box around a subject in the flight view'); } break; } if (mounted) _m.bump(); } @override Widget build(BuildContext context) { return FlightOverlayScaffold( title: 'Capture modes', body: ListView( children: [ for (final (String label, String group, List<(String, String, String)> items) in _groups) ...[ Padding( padding: const EdgeInsets.only(top: 4, bottom: 8), child: Text(label.toUpperCase(), style: const TextStyle(fontFamily: PV.fontMono, fontSize: 10, letterSpacing: 1.4, color: Color(0x8CEAF0FA))), ), Wrap(spacing: 10, runSpacing: 10, children: [ for (final (String ic, String l, String wire) in items) _cell(group, ic, l, wire), ]), const SizedBox(height: 18), ], ], ), ); } Widget _cell(String group, String icon, String label, String wire) { final bool sel = _selected == wire; return GestureDetector( onTap: () => _pick(group, label, wire), child: SizedBox( width: 66, child: Column(children: [ Container( width: 46, height: 46, decoration: BoxDecoration( color: sel ? const Color(0x403D7BF0) : const Color(0x0DFFFFFF), borderRadius: BorderRadius.circular(12), border: Border.all(color: sel ? const Color(0xFF5B93F5) : Glass.hairline, width: sel ? 2 : 1), ), alignment: Alignment.center, child: PVIcon(icon, size: 20, stroke: 1.7, color: sel ? const Color(0xFF8FB4F6) : Glass.ink), ), const SizedBox(height: 5), Text(label, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontFamily: PV.fontMono, fontSize: 9.5, color: Color(0xCCEAF0FA))), ]), ), ); } }