FB-05: closed-loop routes in Route Planner

Add an isClosedLoop toggle to route plans (schema v3 -> v4 migration),
threaded through the domain model, repository distance recomputation,
the overflow menu, and the drawn polyline, so a route can loop back to
its first waypoint.
This commit is contained in:
2026-08-24 16:22:51 -05:00
parent 57bc3c9233
commit 9ac0be6ee5
9 changed files with 359 additions and 8 deletions

View File

@@ -121,6 +121,9 @@ class RoutePlans extends Table {
/// Null in this ticket -- the polyline is derived from waypoints, not stored. V3-08
/// fills it with the road-snapped geometry, which is not cheaply re-derivable.
TextColumn get geometry => text().nullable()();
/// Whether the route loops back to its first waypoint after the last one. See FB-05.
BoolColumn get isClosedLoop => boolean().withDefault(const Constant(false))();
}
/// One pin on a [RoutePlans] row.
@@ -146,7 +149,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase(super.e);
@override
int get schemaVersion => 3;
int get schemaVersion => 4;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -162,6 +165,13 @@ class AppDatabase extends _$AppDatabase {
await m.createTable(routePlans);
await m.createTable(waypoints);
}
// FB-05: closed-loop toggle, defaults to false for every pre-existing route.
// Guarded to `from >= 3` because a v2 -> v4+ upgrade just created `routePlans`
// fresh (above) with every current column, `isClosedLoop` included -- adding it
// again here would be a duplicate-column error.
if (from >= 3 && from < 4) {
await m.addColumn(routePlans, routePlans.isClosedLoop);
}
},
beforeOpen: (details) async {
// Non-negotiable: without this the CASCADE relationships above do nothing.
@@ -553,6 +563,7 @@ class AppDatabase extends _$AppDatabase {
distanceM: Value(route.distanceM),
estimatedMillis: Value(route.estimatedMillis),
geometry: Value(route.geometry),
isClosedLoop: Value(route.isClosedLoop),
),
);
@@ -566,6 +577,12 @@ class AppDatabase extends _$AppDatabase {
RoutePlansCompanion(distanceM: Value(distanceM)),
);
Future<void> setRoutePlanClosedLoop(int id, bool value) => (update(
routePlans,
)..where((r) => r.id.equals(id))).write(
RoutePlansCompanion(isClosedLoop: Value(value)),
);
/// Waypoints cascade with it.
Future<void> deleteRoutePlan(int id) =>
(delete(routePlans)..where((r) => r.id.equals(id))).go();
@@ -617,6 +634,7 @@ domain.RoutePlan _toRoutePlan(RoutePlanRow r) => domain.RoutePlan(
distanceM: r.distanceM,
estimatedMillis: r.estimatedMillis,
geometry: r.geometry,
isClosedLoop: r.isClosedLoop,
);
domain.Waypoint _toWaypoint(WaypointRow r) => domain.Waypoint(