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:
@@ -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(
|
||||
|
||||
@@ -1706,6 +1706,21 @@ class $RoutePlansTable extends RoutePlans
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const VerificationMeta _isClosedLoopMeta = const VerificationMeta(
|
||||
'isClosedLoop',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<bool> isClosedLoop = GeneratedColumn<bool>(
|
||||
'isClosedLoop',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("isClosedLoop" IN (0, 1))',
|
||||
),
|
||||
defaultValue: const Constant(false),
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [
|
||||
id,
|
||||
@@ -1715,6 +1730,7 @@ class $RoutePlansTable extends RoutePlans
|
||||
distanceM,
|
||||
estimatedMillis,
|
||||
geometry,
|
||||
isClosedLoop,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@@ -1768,6 +1784,15 @@ class $RoutePlansTable extends RoutePlans
|
||||
geometry.isAcceptableOrUnknown(data['geometry']!, _geometryMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('isClosedLoop')) {
|
||||
context.handle(
|
||||
_isClosedLoopMeta,
|
||||
isClosedLoop.isAcceptableOrUnknown(
|
||||
data['isClosedLoop']!,
|
||||
_isClosedLoopMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -1807,6 +1832,10 @@ class $RoutePlansTable extends RoutePlans
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}geometry'],
|
||||
),
|
||||
isClosedLoop: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.bool,
|
||||
data['${effectivePrefix}isClosedLoop'],
|
||||
)!,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1834,6 +1863,9 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
/// 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.
|
||||
final String? geometry;
|
||||
|
||||
/// Whether the route loops back to its first waypoint after the last one. See FB-05.
|
||||
final bool isClosedLoop;
|
||||
const RoutePlanRow({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -1842,6 +1874,7 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
required this.distanceM,
|
||||
this.estimatedMillis,
|
||||
this.geometry,
|
||||
required this.isClosedLoop,
|
||||
});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
@@ -1861,6 +1894,7 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
if (!nullToAbsent || geometry != null) {
|
||||
map['geometry'] = Variable<String>(geometry);
|
||||
}
|
||||
map['isClosedLoop'] = Variable<bool>(isClosedLoop);
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -1877,6 +1911,7 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
geometry: geometry == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(geometry),
|
||||
isClosedLoop: Value(isClosedLoop),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1895,6 +1930,7 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
distanceM: serializer.fromJson<double>(json['distanceM']),
|
||||
estimatedMillis: serializer.fromJson<int?>(json['estimatedMillis']),
|
||||
geometry: serializer.fromJson<String?>(json['geometry']),
|
||||
isClosedLoop: serializer.fromJson<bool>(json['isClosedLoop']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
@@ -1910,6 +1946,7 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
'distanceM': serializer.toJson<double>(distanceM),
|
||||
'estimatedMillis': serializer.toJson<int?>(estimatedMillis),
|
||||
'geometry': serializer.toJson<String?>(geometry),
|
||||
'isClosedLoop': serializer.toJson<bool>(isClosedLoop),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1921,6 +1958,7 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
double? distanceM,
|
||||
Value<int?> estimatedMillis = const Value.absent(),
|
||||
Value<String?> geometry = const Value.absent(),
|
||||
bool? isClosedLoop,
|
||||
}) => RoutePlanRow(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
@@ -1931,6 +1969,7 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
? estimatedMillis.value
|
||||
: this.estimatedMillis,
|
||||
geometry: geometry.present ? geometry.value : this.geometry,
|
||||
isClosedLoop: isClosedLoop ?? this.isClosedLoop,
|
||||
);
|
||||
RoutePlanRow copyWithCompanion(RoutePlansCompanion data) {
|
||||
return RoutePlanRow(
|
||||
@@ -1943,6 +1982,9 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
? data.estimatedMillis.value
|
||||
: this.estimatedMillis,
|
||||
geometry: data.geometry.present ? data.geometry.value : this.geometry,
|
||||
isClosedLoop: data.isClosedLoop.present
|
||||
? data.isClosedLoop.value
|
||||
: this.isClosedLoop,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1955,7 +1997,8 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
..write('activity: $activity, ')
|
||||
..write('distanceM: $distanceM, ')
|
||||
..write('estimatedMillis: $estimatedMillis, ')
|
||||
..write('geometry: $geometry')
|
||||
..write('geometry: $geometry, ')
|
||||
..write('isClosedLoop: $isClosedLoop')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
@@ -1969,6 +2012,7 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
distanceM,
|
||||
estimatedMillis,
|
||||
geometry,
|
||||
isClosedLoop,
|
||||
);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
@@ -1980,7 +2024,8 @@ class RoutePlanRow extends DataClass implements Insertable<RoutePlanRow> {
|
||||
other.activity == this.activity &&
|
||||
other.distanceM == this.distanceM &&
|
||||
other.estimatedMillis == this.estimatedMillis &&
|
||||
other.geometry == this.geometry);
|
||||
other.geometry == this.geometry &&
|
||||
other.isClosedLoop == this.isClosedLoop);
|
||||
}
|
||||
|
||||
class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
@@ -1991,6 +2036,7 @@ class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
final Value<double> distanceM;
|
||||
final Value<int?> estimatedMillis;
|
||||
final Value<String?> geometry;
|
||||
final Value<bool> isClosedLoop;
|
||||
const RoutePlansCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.name = const Value.absent(),
|
||||
@@ -1999,6 +2045,7 @@ class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
this.distanceM = const Value.absent(),
|
||||
this.estimatedMillis = const Value.absent(),
|
||||
this.geometry = const Value.absent(),
|
||||
this.isClosedLoop = const Value.absent(),
|
||||
});
|
||||
RoutePlansCompanion.insert({
|
||||
this.id = const Value.absent(),
|
||||
@@ -2008,6 +2055,7 @@ class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
this.distanceM = const Value.absent(),
|
||||
this.estimatedMillis = const Value.absent(),
|
||||
this.geometry = const Value.absent(),
|
||||
this.isClosedLoop = const Value.absent(),
|
||||
}) : name = Value(name),
|
||||
createdAt = Value(createdAt);
|
||||
static Insertable<RoutePlanRow> custom({
|
||||
@@ -2018,6 +2066,7 @@ class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
Expression<double>? distanceM,
|
||||
Expression<int>? estimatedMillis,
|
||||
Expression<String>? geometry,
|
||||
Expression<bool>? isClosedLoop,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
@@ -2027,6 +2076,7 @@ class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
if (distanceM != null) 'distanceM': distanceM,
|
||||
if (estimatedMillis != null) 'estimatedMillis': estimatedMillis,
|
||||
if (geometry != null) 'geometry': geometry,
|
||||
if (isClosedLoop != null) 'isClosedLoop': isClosedLoop,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2038,6 +2088,7 @@ class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
Value<double>? distanceM,
|
||||
Value<int?>? estimatedMillis,
|
||||
Value<String?>? geometry,
|
||||
Value<bool>? isClosedLoop,
|
||||
}) {
|
||||
return RoutePlansCompanion(
|
||||
id: id ?? this.id,
|
||||
@@ -2047,6 +2098,7 @@ class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
distanceM: distanceM ?? this.distanceM,
|
||||
estimatedMillis: estimatedMillis ?? this.estimatedMillis,
|
||||
geometry: geometry ?? this.geometry,
|
||||
isClosedLoop: isClosedLoop ?? this.isClosedLoop,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2076,6 +2128,9 @@ class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
if (geometry.present) {
|
||||
map['geometry'] = Variable<String>(geometry.value);
|
||||
}
|
||||
if (isClosedLoop.present) {
|
||||
map['isClosedLoop'] = Variable<bool>(isClosedLoop.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -2088,7 +2143,8 @@ class RoutePlansCompanion extends UpdateCompanion<RoutePlanRow> {
|
||||
..write('activity: $activity, ')
|
||||
..write('distanceM: $distanceM, ')
|
||||
..write('estimatedMillis: $estimatedMillis, ')
|
||||
..write('geometry: $geometry')
|
||||
..write('geometry: $geometry, ')
|
||||
..write('isClosedLoop: $isClosedLoop')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
@@ -3980,6 +4036,7 @@ typedef $$RoutePlansTableCreateCompanionBuilder = RoutePlansCompanion Function({
|
||||
Value<double> distanceM,
|
||||
Value<int?> estimatedMillis,
|
||||
Value<String?> geometry,
|
||||
Value<bool> isClosedLoop,
|
||||
});
|
||||
typedef $$RoutePlansTableUpdateCompanionBuilder = RoutePlansCompanion Function({
|
||||
Value<int> id,
|
||||
@@ -3989,6 +4046,7 @@ typedef $$RoutePlansTableUpdateCompanionBuilder = RoutePlansCompanion Function({
|
||||
Value<double> distanceM,
|
||||
Value<int?> estimatedMillis,
|
||||
Value<String?> geometry,
|
||||
Value<bool> isClosedLoop,
|
||||
});
|
||||
|
||||
final class $$RoutePlansTableReferences
|
||||
@@ -4059,6 +4117,11 @@ class $$RoutePlansTableFilterComposer
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<bool> get isClosedLoop => $composableBuilder(
|
||||
column: $table.isClosedLoop,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
Expression<bool> waypointsRefs(
|
||||
Expression<bool> Function($$WaypointsTableFilterComposer f) f,
|
||||
) {
|
||||
@@ -4128,6 +4191,11 @@ class $$RoutePlansTableOrderingComposer
|
||||
column: $table.geometry,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<bool> get isClosedLoop => $composableBuilder(
|
||||
column: $table.isClosedLoop,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$RoutePlansTableAnnotationComposer
|
||||
@@ -4162,6 +4230,11 @@ class $$RoutePlansTableAnnotationComposer
|
||||
GeneratedColumn<String> get geometry =>
|
||||
$composableBuilder(column: $table.geometry, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get isClosedLoop => $composableBuilder(
|
||||
column: $table.isClosedLoop,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
Expression<T> waypointsRefs<T extends Object>(
|
||||
Expression<T> Function($$WaypointsTableAnnotationComposer a) f,
|
||||
) {
|
||||
@@ -4223,6 +4296,7 @@ class $$RoutePlansTableTableManager
|
||||
Value<double> distanceM = const Value.absent(),
|
||||
Value<int?> estimatedMillis = const Value.absent(),
|
||||
Value<String?> geometry = const Value.absent(),
|
||||
Value<bool> isClosedLoop = const Value.absent(),
|
||||
}) => RoutePlansCompanion(
|
||||
id: id,
|
||||
name: name,
|
||||
@@ -4231,6 +4305,7 @@ class $$RoutePlansTableTableManager
|
||||
distanceM: distanceM,
|
||||
estimatedMillis: estimatedMillis,
|
||||
geometry: geometry,
|
||||
isClosedLoop: isClosedLoop,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
@@ -4241,6 +4316,7 @@ class $$RoutePlansTableTableManager
|
||||
Value<double> distanceM = const Value.absent(),
|
||||
Value<int?> estimatedMillis = const Value.absent(),
|
||||
Value<String?> geometry = const Value.absent(),
|
||||
Value<bool> isClosedLoop = const Value.absent(),
|
||||
}) => RoutePlansCompanion.insert(
|
||||
id: id,
|
||||
name: name,
|
||||
@@ -4249,6 +4325,7 @@ class $$RoutePlansTableTableManager
|
||||
distanceM: distanceM,
|
||||
estimatedMillis: estimatedMillis,
|
||||
geometry: geometry,
|
||||
isClosedLoop: isClosedLoop,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map(
|
||||
|
||||
@@ -32,6 +32,13 @@ class RoutePlanRepository {
|
||||
|
||||
Future<void> renameRoutePlan(int id, String name) => _db.renameRoutePlan(id, name);
|
||||
|
||||
/// Toggles whether the route loops back to its first waypoint. The closing segment
|
||||
/// changes the total distance, so this recomputes it same as any waypoint edit.
|
||||
Future<void> setClosedLoop(int routeId, bool value) async {
|
||||
await _db.setRoutePlanClosedLoop(routeId, value);
|
||||
await _recomputeDistance(routeId);
|
||||
}
|
||||
|
||||
/// Waypoints cascade with it.
|
||||
Future<void> deleteRoutePlan(int id) => _db.deleteRoutePlan(id);
|
||||
|
||||
@@ -93,10 +100,13 @@ class RoutePlanRepository {
|
||||
}
|
||||
|
||||
Future<void> _recomputeDistance(int routeId) async {
|
||||
final route = await _db.getRoutePlan(routeId);
|
||||
final waypoints = await _db.waypointsForRoute(routeId);
|
||||
final distance = geo.pathLengthMeters([
|
||||
for (final w in waypoints) geo.LatLon(w.latitude, w.longitude),
|
||||
]);
|
||||
final points = [for (final w in waypoints) geo.LatLon(w.latitude, w.longitude)];
|
||||
if (route?.isClosedLoop == true && points.length >= 2) {
|
||||
points.add(points.first);
|
||||
}
|
||||
final distance = geo.pathLengthMeters(points);
|
||||
await _db.setRoutePlanDistance(routeId, distance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +241,7 @@ class RoutePlan {
|
||||
this.distanceM = 0.0,
|
||||
this.estimatedMillis,
|
||||
this.geometry,
|
||||
this.isClosedLoop = false,
|
||||
});
|
||||
|
||||
final int id;
|
||||
@@ -256,6 +257,9 @@ class RoutePlan {
|
||||
/// never stored -- it is fully derived from them, so there is nothing to persist.
|
||||
final String? geometry;
|
||||
|
||||
/// Whether the route loops back to its first waypoint after the last one.
|
||||
final bool isClosedLoop;
|
||||
|
||||
RoutePlan copyWith({
|
||||
int? id,
|
||||
String? name,
|
||||
@@ -264,6 +268,7 @@ class RoutePlan {
|
||||
double? distanceM,
|
||||
int? estimatedMillis,
|
||||
String? geometry,
|
||||
bool? isClosedLoop,
|
||||
}) => RoutePlan(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
@@ -272,6 +277,7 @@ class RoutePlan {
|
||||
distanceM: distanceM ?? this.distanceM,
|
||||
estimatedMillis: estimatedMillis ?? this.estimatedMillis,
|
||||
geometry: geometry ?? this.geometry,
|
||||
isClosedLoop: isClosedLoop ?? this.isClosedLoop,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user