Files
PilotVault/Fly Agent App/lib/ui/track_map.dart
T
tajniak81andClaude Opus 5.5 3dd31b257a Add the Fly Agent App, a Remote ID companion to DJI Fly
A Flutter app that runs next to DJI Fly without the DJI SDK. It receives the
aircraft's Remote ID (Bluetooth, BT5, Wi-Fi NaN and Beacon) in a foreground
service, streams the paired aircraft to the hub over /ws/device, and turns
take-off to landing into a logbook entry filed through /api/flights, queued
when offline. Covers phases A0-A3 of the Fly Agent plan; needs no server change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:25:52 +02:00

82 lines
2.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import '../session/flight_session.dart';
import '../theme.dart';
/// OpenStreetMap with the session track, the aircraft and the pilot.
class TrackMap extends StatelessWidget {
const TrackMap({super.key, required this.track, this.aircraft, this.pilot, this.height = 220});
final List<TrackPoint> track;
final LatLng? aircraft;
final LatLng? pilot;
final double height;
@override
Widget build(BuildContext context) {
final List<LatLng> line = track.map((TrackPoint p) => LatLng(p.lat, p.lon)).toList();
final List<LatLng> all = <LatLng>[...line, if (aircraft != null) aircraft!, if (pilot != null) pilot!];
if (all.isEmpty) {
return Container(
height: height,
alignment: Alignment.center,
decoration: BoxDecoration(color: PV.surface2, borderRadius: BorderRadius.circular(PV.radius)),
child: const Text('No position yet', style: PV.caption),
);
}
final MapOptions options = all.length == 1
? MapOptions(initialCenter: all.first, initialZoom: 16)
: MapOptions(
initialCameraFit: CameraFit.coordinates(
coordinates: all,
padding: const EdgeInsets.all(32),
maxZoom: 18,
),
);
return ClipRRect(
borderRadius: BorderRadius.circular(PV.radius),
child: SizedBox(
height: height,
child: FlutterMap(
// The camera only fits on creation; a new key refits as the track grows.
key: ValueKey<int>(all.length ~/ 10),
options: options,
children: <Widget>[
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.pilotvault.flyagent',
),
if (line.length >= 2)
PolylineLayer(polylines: <Polyline>[Polyline(points: line, strokeWidth: 3, color: PV.accent)]),
MarkerLayer(markers: <Marker>[
if (pilot != null)
Marker(
point: pilot!,
width: 22,
height: 22,
child: const Icon(Icons.person_pin_circle, color: PV.navy, size: 22),
),
if (aircraft != null)
Marker(
point: aircraft!,
width: 26,
height: 26,
child: Container(
decoration: BoxDecoration(
color: PV.accent,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
child: const Icon(Icons.flight, size: 14, color: Colors.white),
),
),
]),
],
),
),
);
}
}