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

@@ -202,6 +202,8 @@ class _RoutePlannerScreenState extends ConsumerState<RoutePlannerScreen>
Polyline(
points: [
for (final w in waypoints) ll.LatLng(w.latitude, w.longitude),
if (route.isClosedLoop)
ll.LatLng(waypoints.first.latitude, waypoints.first.longitude),
],
strokeWidth: 4,
// Visibly distinct from a recorded path (see the
@@ -315,10 +317,14 @@ class _RoutePlannerScreenState extends ConsumerState<RoutePlannerScreen>
right: 8,
child: _OverflowMenu(
hasWaypoints: waypoints.isNotEmpty,
canCloseLoop: waypoints.length >= 2,
isClosedLoop: route.isClosedLoop,
onRename: () => _rename(context, repo, route),
onDownload: waypoints.isEmpty
? null
: () => _downloadOfflineTiles(context, waypoints),
onToggleLoop: () =>
repo.setClosedLoop(widget.routeId, !route.isClosedLoop),
onDelete: () async {
await repo.deleteRoutePlan(widget.routeId);
widget.onBack?.call();
@@ -509,14 +515,20 @@ class _DropPinTooltipState extends State<_DropPinTooltip>
class _OverflowMenu extends StatelessWidget {
const _OverflowMenu({
required this.hasWaypoints,
required this.canCloseLoop,
required this.isClosedLoop,
required this.onRename,
required this.onDownload,
required this.onToggleLoop,
required this.onDelete,
});
final bool hasWaypoints;
final bool canCloseLoop;
final bool isClosedLoop;
final VoidCallback onRename;
final VoidCallback? onDownload;
final VoidCallback onToggleLoop;
final VoidCallback onDelete;
@override
@@ -528,6 +540,7 @@ class _OverflowMenu extends StatelessWidget {
onSelected: (value) => switch (value) {
'rename' => onRename(),
'download' => onDownload?.call(),
'closeLoop' => onToggleLoop(),
'delete' => onDelete(),
_ => null,
},
@@ -543,6 +556,12 @@ class _OverflowMenu extends StatelessWidget {
enabled: hasWaypoints,
child: const Text('Download offline tiles'),
),
PopupMenuItem(
key: const Key('toggle-closed-loop'),
value: 'closeLoop',
enabled: canCloseLoop,
child: Text(isClosedLoop ? 'Open the loop' : 'Close the loop'),
),
const PopupMenuItem(
key: Key('delete-route'),
value: 'delete',