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>
69 lines
2.0 KiB
Dart
69 lines
2.0 KiB
Dart
/// Ported from `com.rippr.ui.RipprNavHost`.
|
|
///
|
|
/// Three destinations, same shape as the Compose original. `go_router` replaces
|
|
/// `navigation-compose`; the path parameter is parsed explicitly, mirroring the Kotlin
|
|
/// comment about ids arriving as the wrong type when the argument type is left implicit.
|
|
library;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import 'detail/trip_detail_screen.dart';
|
|
import 'record/record_screen.dart';
|
|
import 'trips/trips_screen.dart';
|
|
|
|
abstract final class Routes {
|
|
static const record = '/';
|
|
static const trips = '/trips';
|
|
static const tripDetail = '/trip/:tripId';
|
|
|
|
static String detailFor(int tripId) => '/trip/$tripId';
|
|
}
|
|
|
|
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(),
|
|
);
|
|
|
|
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.')),
|
|
);
|
|
}
|