T19/T21: map and export, completing Phase 4

flutter_map with the same OSM tiles and no-API-key reasoning that chose osmdroid.
All four load-bearing behaviours ported and, unlike the native app's map, tested:
one polyline per segment so a pause is a real gap, render-only decimation, the
zoom clamp at OSM's max tile zoom with a short-ride fallback, and a real user
agent. Speed colouring is bucketed per run rather than per-vertex, since neither
osmdroid nor flutter_map makes per-vertex paint reasonable.

Export via share_plus, which also handles the iPad popover anchor a naive port
forgets. It passes the raw stored points, never the map's decimated path.

164 tests passing, analyze clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 16:27:42 -05:00
parent bcc12b314f
commit d501528e69
20 changed files with 1085 additions and 478 deletions

View File

@@ -42,22 +42,21 @@ String formatDuration(int millis) {
/// boundary. Hoisting them to batch level would silently mislabel points.
String encodeBatch(String deviceId, List<TrackPoint> points) {
final array = points
.map((p) => <String, Object?>{
'id': p.id,
'trip_id': p.tripId,
'segment_id': p.segmentId,
'ts': p.timestamp,
'lat': p.latitude,
'lon': p.longitude,
'speed_kmh': p.speedKmh,
'alt_m': p.altitudeM,
'acc_m': p.accuracyM,
'bearing': p.bearingDeg,
})
.map(
(p) => <String, Object?>{
'id': p.id,
'trip_id': p.tripId,
'segment_id': p.segmentId,
'ts': p.timestamp,
'lat': p.latitude,
'lon': p.longitude,
'speed_kmh': p.speedKmh,
'alt_m': p.altitudeM,
'acc_m': p.accuracyM,
'bearing': p.bearingDeg,
},
)
.toList(growable: false);
return jsonEncode(<String, Object?>{
'device_id': deviceId,
'points': array,
});
return jsonEncode(<String, Object?>{'device_id': deviceId, 'points': array});
}