V3-07: route drawing (pins and straight lines)
Route plans are their own entity (RoutePlan/Waypoint, schema v3), never joined into ride totals or the rides list. RoutePlanRepository recomputes straight-line distance after every waypoint mutation. RoutePlannerScreen supports tap-to-add, tap-to-delete, and hand-rolled drag-to-move via MapCamera's screen/latlng conversions. Fixed a real bug found by testing: FlutterMap's initialCenter/initialZoom are read once at construction, so building the map before the waypoints stream's first emission froze the camera on null-island. Also documents a genuine ten-minute test hang traced to Stream.first on a Drift watch() query inside testWidgets, which needs a pump-driven Timer that flutter_test's fake zone never advances on its own.
This commit is contained in:
@@ -225,3 +225,86 @@ class RideStats {
|
||||
int get durationMillis =>
|
||||
pointCount == 0 ? 0 : lastTimestamp - firstTimestamp;
|
||||
}
|
||||
|
||||
/// A planned ride, sketched before setting off. See V3-07 in `docs/v3/`.
|
||||
///
|
||||
/// Deliberately **not** a [Trip] and never joined into ride totals or the rides list — a
|
||||
/// plan is not a recording, and conflating them would put unridden kilometres into ride
|
||||
/// totals. [distanceM] is the straight-line sum between waypoints in this ticket;
|
||||
/// [geometry] and [estimatedMillis] stay null until V3-08 adds road-snapped routing.
|
||||
class RoutePlan {
|
||||
const RoutePlan({
|
||||
this.id = 0,
|
||||
required this.name,
|
||||
required this.createdAt,
|
||||
this.activity = Activity.motorcycle,
|
||||
this.distanceM = 0.0,
|
||||
this.estimatedMillis,
|
||||
this.geometry,
|
||||
});
|
||||
|
||||
final int id;
|
||||
final String name;
|
||||
final int createdAt;
|
||||
final Activity activity;
|
||||
final double distanceM;
|
||||
|
||||
/// Null until V3-08 fills it in from a routing engine.
|
||||
final int? estimatedMillis;
|
||||
|
||||
/// Null until V3-08. This ticket's polyline is drawn straight between waypoints and
|
||||
/// never stored -- it is fully derived from them, so there is nothing to persist.
|
||||
final String? geometry;
|
||||
|
||||
RoutePlan copyWith({
|
||||
int? id,
|
||||
String? name,
|
||||
int? createdAt,
|
||||
Activity? activity,
|
||||
double? distanceM,
|
||||
int? estimatedMillis,
|
||||
String? geometry,
|
||||
}) => RoutePlan(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
activity: activity ?? this.activity,
|
||||
distanceM: distanceM ?? this.distanceM,
|
||||
estimatedMillis: estimatedMillis ?? this.estimatedMillis,
|
||||
geometry: geometry ?? this.geometry,
|
||||
);
|
||||
}
|
||||
|
||||
/// One pin on a [RoutePlan].
|
||||
///
|
||||
/// Ordered by [ordinal] rather than insertion id, so a pin can be dragged into a new
|
||||
/// position without touching every other row's identity.
|
||||
class Waypoint {
|
||||
const Waypoint({
|
||||
this.id = 0,
|
||||
required this.routeId,
|
||||
required this.ordinal,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
this.name,
|
||||
});
|
||||
|
||||
final int id;
|
||||
final int routeId;
|
||||
final int ordinal;
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
|
||||
/// Null means the UI derives a label from [ordinal] ("Point 3"). Never store an empty
|
||||
/// string -- the same rule as [Trip.name].
|
||||
final String? name;
|
||||
|
||||
Waypoint copyWith({int? id, int? routeId, int? ordinal}) => Waypoint(
|
||||
id: id ?? this.id,
|
||||
routeId: routeId ?? this.routeId,
|
||||
ordinal: ordinal ?? this.ordinal,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
name: name,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user