feat(train): a route's line pulled out into a new waypoint, and any waypoint taken off (ADR 0030, D59)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
32c94aa601
commit
57262ebf73
12 changed files with 371 additions and 36 deletions
File diff suppressed because one or more lines are too long
|
|
@ -395,7 +395,8 @@
|
|||
"routeProfileTrekkingBike": "Trekkingrad",
|
||||
"routeProfileGravel": "Gravelbike",
|
||||
"routeProfileMtb": "Mountainbike",
|
||||
"routeTapHint": "Tipp auf die Karte, um einen Wegpunkt hinzuzufügen; halt einen gedrückt und zieh ihn, um ihn zu verschieben.",
|
||||
"routeTapHint": "Tipp auf die Karte, um einen Wegpunkt hinzuzufügen, und auf einen, um ihn zu entfernen. Halt einen gedrückt, um ihn zu ziehen, oder die Linie, um einen weiteren herauszuziehen.",
|
||||
"routeWaypointRemove": "Diesen Wegpunkt entfernen",
|
||||
"routeUndo": "Letzten Wegpunkt entfernen",
|
||||
"routeSearchHint": "Nach einem Ort suchen",
|
||||
"routeSearchClear": "Suche leeren",
|
||||
|
|
|
|||
|
|
@ -501,7 +501,9 @@
|
|||
"routeProfileTrekkingBike": "Trekking bike",
|
||||
"routeProfileGravel": "Gravel bike",
|
||||
"routeProfileMtb": "Mountain bike",
|
||||
"routeTapHint": "Tap the map to add a waypoint; hold one and drag it to move it.",
|
||||
"routeTapHint": "Tap the map to add a waypoint, tap one to take it off. Hold one to drag it, or hold the line to pull out another.",
|
||||
"routeWaypointRemove": "Take this waypoint off",
|
||||
"@routeWaypointRemove": {"description": "The menu of a waypoint tapped on the planner's map (D59)."},
|
||||
"routeUndo": "Take the last waypoint off",
|
||||
"routeSearchHint": "Search for a place",
|
||||
"@routeSearchHint": {"description": "The planner's search field over the map (D50)."},
|
||||
|
|
|
|||
|
|
@ -2395,9 +2395,15 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @routeTapHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tap the map to add a waypoint; hold one and drag it to move it.'**
|
||||
/// **'Tap the map to add a waypoint, tap one to take it off. Hold one to drag it, or hold the line to pull out another.'**
|
||||
String get routeTapHint;
|
||||
|
||||
/// The menu of a waypoint tapped on the planner's map (D59).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Take this waypoint off'**
|
||||
String get routeWaypointRemove;
|
||||
|
||||
/// No description provided for @routeUndo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -1427,7 +1427,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get routeTapHint =>
|
||||
'Tipp auf die Karte, um einen Wegpunkt hinzuzufügen; halt einen gedrückt und zieh ihn, um ihn zu verschieben.';
|
||||
'Tipp auf die Karte, um einen Wegpunkt hinzuzufügen, und auf einen, um ihn zu entfernen. Halt einen gedrückt, um ihn zu ziehen, oder die Linie, um einen weiteren herauszuziehen.';
|
||||
|
||||
@override
|
||||
String get routeWaypointRemove => 'Diesen Wegpunkt entfernen';
|
||||
|
||||
@override
|
||||
String get routeUndo => 'Letzten Wegpunkt entfernen';
|
||||
|
|
|
|||
|
|
@ -1420,7 +1420,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get routeTapHint =>
|
||||
'Tap the map to add a waypoint; hold one and drag it to move it.';
|
||||
'Tap the map to add a waypoint, tap one to take it off. Hold one to drag it, or hold the line to pull out another.';
|
||||
|
||||
@override
|
||||
String get routeWaypointRemove => 'Take this waypoint off';
|
||||
|
||||
@override
|
||||
String get routeUndo => 'Take the last waypoint off';
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import 'package:halkyon_train_flutter/src/feature/route/model/route.dart';
|
|||
import 'package:halkyon_train_flutter/src/feature/workout/model/workout.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// A route being planned or changed (ADR 0030, D44): waypoints tapped,
|
||||
/// dragged and the last undone, a new route's first where the phone is
|
||||
/// A route being planned or changed (ADR 0030, D44, D59): waypoints
|
||||
/// tapped, dragged, pulled out of the line, taken off and the last undone, a new route's first where the phone is
|
||||
/// (`here`, null where the device can't tell); the instance plans the way
|
||||
/// through them after each change, an answer to an earlier change dropped
|
||||
/// once a later one is asked. Kept once named and planned; [id] null for a
|
||||
|
|
@ -74,6 +74,16 @@ class RouteEditorCubit(
|
|||
Future<void> move(int index, LatLng point) =>
|
||||
_replan(_route(waypoints: [...state.route.waypoints]..[index] = point));
|
||||
|
||||
/// Inserts a waypoint at [index], pulled out of the line between the two
|
||||
/// around it (D59).
|
||||
Future<void> insert(int index, LatLng point) => _replan(
|
||||
_route(waypoints: [...state.route.waypoints]..insert(index, point)),
|
||||
);
|
||||
|
||||
/// Takes waypoint [index] off (D59).
|
||||
Future<void> remove(int index) =>
|
||||
_replan(_route(waypoints: [...state.route.waypoints]..removeAt(index)));
|
||||
|
||||
/// Takes the last waypoint off.
|
||||
Future<void> undo() async {
|
||||
final waypoints = state.route.waypoints;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Where a waypoint pulled out of a route's [line] at [at] goes among its
|
||||
/// [waypoints] (ADR 0030, D59): the index it is inserted at, between the
|
||||
/// two waypoints of the stretch nearest [at], from 1 to one before the
|
||||
/// last. [line] is the way planned through the waypoints, or the waypoints
|
||||
/// themselves while none is.
|
||||
int insertionIndex(List<LatLng> waypoints, List<LatLng> line, LatLng at) {
|
||||
assert(waypoints.length > 1 && line.isNotEmpty, 'A route with a line');
|
||||
final grabbed = _nearestStretch(line, at);
|
||||
// Each waypoint's place on the line, found from the one before's on, so
|
||||
// that a way passing a waypoint twice keeps its stretches in order.
|
||||
var place = 0;
|
||||
for (var index = 1; index < waypoints.length - 1; index++) {
|
||||
place = _nearest(line, waypoints[index], from: place);
|
||||
if (grabbed < place) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return waypoints.length - 1;
|
||||
}
|
||||
|
||||
/// The index of the stretch of [line] nearest [point], from its point of
|
||||
/// that index to the next; the first of those as near.
|
||||
int _nearestStretch(List<LatLng> line, LatLng point) {
|
||||
var best = 0;
|
||||
var bestDistance = double.infinity;
|
||||
for (var index = 0; index < line.length - 1; index++) {
|
||||
final distance = _toStretch(line[index], line[index + 1], point);
|
||||
if (distance < bestDistance) {
|
||||
best = index;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/// The index of [line]'s point nearest [point], from [from] on; the first
|
||||
/// of those as near.
|
||||
int _nearest(List<LatLng> line, LatLng point, {required int from}) {
|
||||
var best = from;
|
||||
var bestDistance = double.infinity;
|
||||
for (var index = from; index < line.length; index++) {
|
||||
final distance = _toStretch(line[index], line[index], point);
|
||||
if (distance < bestDistance) {
|
||||
best = index;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/// The squared distance from [point] to the stretch from [a] to [b], in
|
||||
/// degrees of latitude, longitude scaled by the latitude: enough to compare
|
||||
/// points a route's length apart.
|
||||
double _toStretch(LatLng a, LatLng b, LatLng point) {
|
||||
final scale = cos(point.latitude * pi / 180);
|
||||
final (ax, ay) = (a.longitude * scale, a.latitude);
|
||||
final (bx, by) = (b.longitude * scale, b.latitude);
|
||||
final (px, py) = (point.longitude * scale, point.latitude);
|
||||
final (dx, dy) = (bx - ax, by - ay);
|
||||
final length = dx * dx + dy * dy;
|
||||
final along = length == 0
|
||||
? 0.0
|
||||
: (((px - ax) * dx + (py - ay) * dy) / length).clamp(0.0, 1.0);
|
||||
final (x, y) = (ax + along * dx - px, ay + along * dy - py);
|
||||
return x * x + y * y;
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import 'package:halkyon_train_flutter/src/feature/route/bloc/routes_cubit.dart';
|
|||
import 'package:halkyon_train_flutter/src/feature/route/data/place_search_gateway.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/data/route_analysis.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/model/route.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/model/route_legs.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/widget/route_import.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/widget/route_texts.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/data/workout_analysis.dart'
|
||||
|
|
@ -82,8 +83,9 @@ Future<LatLng?> _here(RecorderPlatform platform) async {
|
|||
};
|
||||
}
|
||||
|
||||
/// A route being planned or changed (ADR 0030, D44): the map, tapped for
|
||||
/// waypoints, a waypoint held and dragged to move it, the last undone;
|
||||
/// A route being planned or changed (ADR 0030, D44, D59): the map, tapped
|
||||
/// for waypoints, a waypoint held and dragged to move it, the line held and
|
||||
/// dragged to pull one out, one tapped to take it off, the last undone;
|
||||
/// under it the route's name, what it is for and its profile, and the way
|
||||
/// planned: its distance, climb and descent, its elevation and its
|
||||
/// surfaces. Saved with the button, deleted after asking.
|
||||
|
|
@ -190,8 +192,9 @@ class const RouteScreen({required final Future<MapSource?> maps, super.key})
|
|||
|
||||
/// The map: the way planned as a line (straight lines between the
|
||||
/// waypoints until it is), the waypoints numbered, the first green and the
|
||||
/// last red; a tap adds one, one held and dragged moves it, the button
|
||||
/// above takes the last off, and a place searched for and chosen is the
|
||||
/// last red; a tap adds one, one held and dragged moves it, the line held
|
||||
/// and dragged pulls one out between the two around it (D59), one tapped
|
||||
/// offers to take it off, the button above takes the last off, and a place searched for and chosen is the
|
||||
/// next (D50). The camera fits the route as it opens, and
|
||||
/// goes to a new route's first waypoint once it is known.
|
||||
class const _RouteMap({required final MapSource source})
|
||||
|
|
@ -207,6 +210,13 @@ class _RouteMapState() extends State<_RouteMap> {
|
|||
/// The waypoint being dragged and where it is now.
|
||||
(int, LatLng)? _dragged;
|
||||
|
||||
/// The waypoint being pulled out of the line: where it goes among the
|
||||
/// waypoints and where it is now (D59).
|
||||
(int, LatLng)? _pulled;
|
||||
|
||||
/// Where the line was last hit, so that a hold on it knows where.
|
||||
final _lineHit = LayerHitNotifier<Object>(null);
|
||||
|
||||
/// What the camera fits as the map opens: the route as it was opened.
|
||||
late final List<LatLng> _fit;
|
||||
|
||||
|
|
@ -219,6 +229,7 @@ class _RouteMapState() extends State<_RouteMap> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_lineHit.dispose();
|
||||
_map.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
@ -244,11 +255,13 @@ class _RouteMapState() extends State<_RouteMap> {
|
|||
(search) => search.state.offered ?? false,
|
||||
);
|
||||
final waypoints = [
|
||||
for (final (index, point) in route.waypoints.indexed)
|
||||
for (final (index, point) in route.waypoints.indexed) ...[
|
||||
if (_pulled case (final pulled, final at) when pulled == index) at,
|
||||
if (_dragged case (final dragged, final at) when dragged == index)
|
||||
at
|
||||
else
|
||||
point,
|
||||
],
|
||||
];
|
||||
return Stack(
|
||||
key: _area,
|
||||
|
|
@ -273,25 +286,48 @@ class _RouteMapState() extends State<_RouteMap> {
|
|||
),
|
||||
children: [
|
||||
train.mapTiles(widget.source),
|
||||
PolylineLayer(
|
||||
polylines: [
|
||||
if (route.planned case final planned? when _dragged == null)
|
||||
Polyline(
|
||||
points: planned.points,
|
||||
// Cobalt in either theme: the map's colours are the
|
||||
// source's, not the app's.
|
||||
color: PaletteColor.cobalt.background,
|
||||
strokeWidth: 5,
|
||||
)
|
||||
else if (waypoints.length > 1)
|
||||
Polyline(
|
||||
points: waypoints,
|
||||
color: PaletteColor.cobalt.background.withValues(
|
||||
alpha: 0.5,
|
||||
GestureDetector(
|
||||
// Held first, as a waypoint is, so that dragging the map
|
||||
// never pulls the line.
|
||||
onLongPressStart: (_) => _pull(),
|
||||
onLongPressMoveUpdate: (details) {
|
||||
if (_pulled case (final index, _)) {
|
||||
setState(
|
||||
() => _pulled = (index, _at(details.globalPosition)),
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPressEnd: (_) {
|
||||
final pulled = _pulled;
|
||||
setState(() => _pulled = null);
|
||||
if (pulled != null) {
|
||||
cubit().insert(pulled.$1, pulled.$2);
|
||||
}
|
||||
},
|
||||
child: PolylineLayer(
|
||||
hitNotifier: _lineHit,
|
||||
// A finger's width: the line is 5 wide.
|
||||
minimumHitbox: 20,
|
||||
polylines: [
|
||||
if (route.planned case final planned?
|
||||
when _dragged == null && _pulled == null)
|
||||
Polyline(
|
||||
points: planned.points,
|
||||
// Cobalt in either theme: the map's colours are the
|
||||
// source's, not the app's.
|
||||
color: PaletteColor.cobalt.background,
|
||||
strokeWidth: 5,
|
||||
)
|
||||
else if (waypoints.length > 1)
|
||||
Polyline(
|
||||
points: waypoints,
|
||||
color: PaletteColor.cobalt.background.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
strokeWidth: 3,
|
||||
),
|
||||
strokeWidth: 3,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
|
|
@ -353,6 +389,49 @@ class _RouteMapState() extends State<_RouteMap> {
|
|||
);
|
||||
}
|
||||
|
||||
/// Starts pulling a waypoint out of the line where it was held.
|
||||
void _pull() {
|
||||
final hit = _lineHit.value;
|
||||
final route = context.read<RouteEditorCubit>().state.route;
|
||||
if (hit == null || route.waypoints.length < 2) {
|
||||
return;
|
||||
}
|
||||
final line = route.planned?.points ?? route.waypoints;
|
||||
setState(
|
||||
() => _pulled = (
|
||||
insertionIndex(route.waypoints, line, hit.coordinate),
|
||||
hit.coordinate,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Where on the map a pointer at [global] is.
|
||||
LatLng _at(Offset global) {
|
||||
final area = _area.currentContext!.findRenderObject()! as RenderBox;
|
||||
return _map.camera.screenOffsetToLatLng(area.globalToLocal(global));
|
||||
}
|
||||
|
||||
/// The menu of waypoint [index], tapped at [at]: Take it off (D59).
|
||||
Future<void> _waypointMenu(int index, Offset at) async {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final cubit = context.read<RouteEditorCubit>();
|
||||
final overlay =
|
||||
Overlay.of(context).context.findRenderObject()! as RenderBox;
|
||||
final remove = await showMenu<bool>(
|
||||
context: context,
|
||||
position: RelativeRect.fromRect(
|
||||
at & Size.zero,
|
||||
Offset.zero & overlay.size,
|
||||
),
|
||||
items: [
|
||||
PopupMenuItem(value: true, child: Text(l10n.routeWaypointRemove)),
|
||||
],
|
||||
);
|
||||
if (remove ?? false) {
|
||||
await cubit.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
Marker _waypoint(int index, LatLng point, {required bool last}) {
|
||||
final color = switch (index) {
|
||||
0 => PaletteColor.forest.background,
|
||||
|
|
@ -365,6 +444,7 @@ class _RouteMapState() extends State<_RouteMap> {
|
|||
height: 28,
|
||||
child: Builder(
|
||||
builder: (context) => GestureDetector(
|
||||
onTapUp: (details) => _waypointMenu(index, details.globalPosition),
|
||||
// Held first, so that dragging the map never moves a waypoint.
|
||||
onLongPressMoveUpdate: (details) {
|
||||
final area = _area.currentContext?.findRenderObject();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/gestures.dart' show kLongPressTimeout;
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
|
|
@ -2138,8 +2139,8 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.text(
|
||||
'Tap the map to add a waypoint; hold one and drag it to '
|
||||
'move it.',
|
||||
'Tap the map to add a waypoint, tap one to take it off. Hold '
|
||||
'one to drag it, or hold the line to pull out another.',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
|
@ -2183,8 +2184,8 @@ void main() {
|
|||
expect(find.text('1'), findsOneWidget);
|
||||
expect(
|
||||
find.text(
|
||||
'Tap the map to add a waypoint; hold one and drag it to '
|
||||
'move it.',
|
||||
'Tap the map to add a waypoint, tap one to take it off. Hold '
|
||||
'one to drag it, or hold the line to pull out another.',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
|
@ -2269,6 +2270,39 @@ void main() {
|
|||
expect(find.textContaining('No way found there'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('the line held and dragged pulls out a waypoint between the '
|
||||
'two around it, and a waypoint tapped is taken off (D59)', (
|
||||
tester,
|
||||
) async {
|
||||
await openRoutes(tester, at: (lat: 48.137, lon: 11.575));
|
||||
await tester.tap(find.text('New route'));
|
||||
await tester.pumpAndSettle();
|
||||
await tapMap(tester, const Offset(120, 80));
|
||||
final [start, end] = routing.asked.last.waypoints;
|
||||
|
||||
final centre = tester.getCenter(find.byType(FlutterMap));
|
||||
final gesture = await tester.startGesture(
|
||||
centre + const Offset(60, 40),
|
||||
);
|
||||
await tester.pump(kLongPressTimeout + const Duration(milliseconds: 100));
|
||||
await gesture.moveTo(centre + const Offset(60, -60));
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final [first, pulled, last] = routing.asked.last.waypoints;
|
||||
expect((first, last), (start, end));
|
||||
expect(pulled.latitude, greaterThan(start.latitude));
|
||||
expect(find.text('3'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('1'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Take this waypoint off'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(routing.asked.last.waypoints, [pulled, end]);
|
||||
expect(find.text('3'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('the maps stop where their tiles stop being drawn', (
|
||||
tester,
|
||||
) async {
|
||||
|
|
|
|||
|
|
@ -168,6 +168,29 @@ void main() {
|
|||
await cubit.close();
|
||||
});
|
||||
|
||||
test('a waypoint pulled out of the line goes between the two around it, '
|
||||
'and any one is taken off (D59)', () async {
|
||||
final cubit = editor();
|
||||
await cubit.open();
|
||||
await cubit.add(home);
|
||||
await cubit.add(river);
|
||||
|
||||
await cubit.insert(1, park);
|
||||
expect(cubit.state.route.waypoints, [home, park, river]);
|
||||
expect(routing.asked.last.waypoints, [home, park, river]);
|
||||
expect(cubit.state.route.planned, straightRoute(const [home, park, river]));
|
||||
|
||||
await cubit.remove(0);
|
||||
expect(cubit.state.route.waypoints, [park, river]);
|
||||
expect(routing.asked.last.waypoints, [park, river]);
|
||||
|
||||
await cubit.remove(1);
|
||||
expect(cubit.state.route.waypoints, [park]);
|
||||
expect(cubit.state.route.planned, isNull);
|
||||
expect(routing.asked, hasLength(3));
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
test('the activity picks its profile, which the person can change and '
|
||||
'which plans again', () async {
|
||||
final cubit = editor();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/route/model/route_legs.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Where a waypoint pulled out of the route's line goes (ADR 0030, D59):
|
||||
/// between the two waypoints of the stretch it was grabbed on.
|
||||
void main() {
|
||||
const start = LatLng(47.55, 9.68);
|
||||
const middle = LatLng(47.53, 9.71);
|
||||
const end = LatLng(47.50, 9.74);
|
||||
|
||||
/// The way planned through start, middle and end: points between each.
|
||||
const line = [
|
||||
start,
|
||||
LatLng(47.545, 9.69),
|
||||
LatLng(47.54, 9.70),
|
||||
middle,
|
||||
LatLng(47.52, 9.72),
|
||||
LatLng(47.51, 9.73),
|
||||
end,
|
||||
];
|
||||
|
||||
test(
|
||||
'a point grabbed on the first stretch goes after the first waypoint',
|
||||
() {
|
||||
expect(
|
||||
insertionIndex(
|
||||
const [start, middle, end],
|
||||
line,
|
||||
const LatLng(47.54, 9.70),
|
||||
),
|
||||
1,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('a point grabbed on the last stretch goes before the last waypoint', () {
|
||||
expect(
|
||||
insertionIndex(
|
||||
const [start, middle, end],
|
||||
line,
|
||||
const LatLng(47.515, 9.725),
|
||||
),
|
||||
2,
|
||||
);
|
||||
});
|
||||
|
||||
test('a point grabbed beside the line goes on the stretch nearest it', () {
|
||||
expect(
|
||||
insertionIndex(
|
||||
const [start, middle, end],
|
||||
line,
|
||||
const LatLng(47.50, 9.70),
|
||||
),
|
||||
2,
|
||||
);
|
||||
});
|
||||
|
||||
test('a route of two waypoints takes it between them, wherever grabbed', () {
|
||||
expect(insertionIndex(const [start, end], line, start), 1);
|
||||
expect(insertionIndex(const [start, end], line, end), 1);
|
||||
});
|
||||
|
||||
test('a route that comes back past a waypoint keeps its stretches in '
|
||||
'order', () {
|
||||
// Out to the turn and back the same way: the way back passes the
|
||||
// first stretch's points again.
|
||||
const turn = LatLng(47.53, 9.71);
|
||||
const back = [start, LatLng(47.54, 9.70), turn, LatLng(47.54, 9.70), start];
|
||||
expect(
|
||||
insertionIndex(
|
||||
const [start, turn, start],
|
||||
back,
|
||||
const LatLng(47.5401, 9.7001),
|
||||
),
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
test('without a way planned, the straight lines between the waypoints are '
|
||||
'the line', () {
|
||||
expect(
|
||||
insertionIndex(
|
||||
const [start, middle, end],
|
||||
const [start, middle, end],
|
||||
const LatLng(47.52, 9.725),
|
||||
),
|
||||
2,
|
||||
);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue