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:
@@ -21,50 +21,48 @@ abstract final class Routes {
|
||||
}
|
||||
|
||||
GoRouter buildRouter() => GoRouter(
|
||||
initialLocation: Routes.record,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: Routes.record,
|
||||
builder: (context, state) => RecordScreen(
|
||||
onOpenTrips: () => context.push(Routes.trips),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.trips,
|
||||
builder: (context, state) => TripsScreen(
|
||||
onOpenTrip: (id) => context.push(Routes.detailFor(id)),
|
||||
onBack: () => context.pop(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.tripDetail,
|
||||
builder: (context, state) {
|
||||
// Parsed explicitly. A malformed or missing id must land on a real screen
|
||||
// saying so, never on a blank one or a crash.
|
||||
final id = int.tryParse(state.pathParameters['tripId'] ?? '');
|
||||
if (id == null) {
|
||||
return const _NotFound();
|
||||
}
|
||||
return TripDetailScreen(tripId: id, onBack: () => context.pop());
|
||||
},
|
||||
),
|
||||
],
|
||||
errorBuilder: (context, state) => const _NotFound(),
|
||||
);
|
||||
initialLocation: Routes.record,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: Routes.record,
|
||||
builder: (context, state) =>
|
||||
RecordScreen(onOpenTrips: () => context.push(Routes.trips)),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.trips,
|
||||
builder: (context, state) => TripsScreen(
|
||||
onOpenTrip: (id) => context.push(Routes.detailFor(id)),
|
||||
onBack: () => context.pop(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.tripDetail,
|
||||
builder: (context, state) {
|
||||
// Parsed explicitly. A malformed or missing id must land on a real screen
|
||||
// saying so, never on a blank one or a crash.
|
||||
final id = int.tryParse(state.pathParameters['tripId'] ?? '');
|
||||
if (id == null) {
|
||||
return const _NotFound();
|
||||
}
|
||||
return TripDetailScreen(tripId: id, onBack: () => context.pop());
|
||||
},
|
||||
),
|
||||
],
|
||||
errorBuilder: (context, state) => const _NotFound(),
|
||||
);
|
||||
|
||||
class _NotFound extends StatelessWidget {
|
||||
const _NotFound();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => context.canPop()
|
||||
? context.pop()
|
||||
: context.go(Routes.record),
|
||||
),
|
||||
),
|
||||
body: const Center(child: Text('That ride could not be found.')),
|
||||
);
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () =>
|
||||
context.canPop() ? context.pop() : context.go(Routes.record),
|
||||
),
|
||||
),
|
||||
body: const Center(child: Text('That ride could not be found.')),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user