FB-10: close the ambient-tick-vs-drag race that snapped the map back
A pointer down/up Listener around the FlutterMap now tracks whether a real gesture is in progress, and didUpdateWidget's follow-recenter callbacks skip the camera move while one is -- closing the timing window where a once-per- second ambient GPS tick's postFrameCallback could land after a finger touched the map but before flutter_map reported hasGesture: true, snapping the camera back out from under an in-progress pan.
This commit is contained in:
@@ -520,6 +520,79 @@ void main() {
|
||||
expect(center.longitude, closeTo(fix1.longitude, 1e-9));
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'a real drag beats an ambient tick that lands mid-drag (FB-10)',
|
||||
(tester) async {
|
||||
const fix1 = ll.LatLng(51.0, -114.0);
|
||||
// The ambient tick that lands while the finger is down but before
|
||||
// flutter_map's own gesture recognizer has reported `hasGesture: true` --
|
||||
// this is the exact race the ticket describes.
|
||||
const fix2 = ll.LatLng(51.5, -114.5);
|
||||
|
||||
Widget build(ll.LatLng ambient) => MaterialApp(
|
||||
theme: ripprTheme(),
|
||||
home: Scaffold(
|
||||
body: RideMap(
|
||||
points: const [],
|
||||
segments: const [],
|
||||
showEmptyLabel: false,
|
||||
follow: true,
|
||||
ambientPosition: ambient,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(build(fix1));
|
||||
await tester.pump();
|
||||
|
||||
// Put a real pointer down on the map -- this is what a rider's finger
|
||||
// touching the screen looks like, well before flutter_map decides the
|
||||
// movement counts as a drag.
|
||||
final gesture =
|
||||
await tester.startGesture(tester.getCenter(find.byType(FlutterMap)));
|
||||
addTearDown(() => gesture.removePointer());
|
||||
|
||||
// Simulate a once-per-second ambient GPS tick landing while the pointer
|
||||
// is already down but before it has moved -- exactly the race window
|
||||
// the ticket describes: flutter_map has not yet reported `hasGesture:
|
||||
// true`, so nothing has flipped `_following` off yet.
|
||||
await tester.pumpWidget(build(fix2));
|
||||
await tester.pump();
|
||||
|
||||
// The critical assertion: with the pointer still down and untouched by
|
||||
// any real drag, the camera must not have snapped to the ambient tick's
|
||||
// position. Without the fix, the race lets the scheduled
|
||||
// `postFrameCallback` win here and the camera jumps to `fix2` before
|
||||
// the rider's finger has moved at all.
|
||||
var map = tester.widget<FlutterMap>(find.byType(FlutterMap));
|
||||
var center = map.mapController!.camera.center;
|
||||
expect(
|
||||
center.latitude,
|
||||
isNot(closeTo(fix2.latitude, 1e-6)),
|
||||
reason: 'the mid-drag ambient tick must not win the race and snap the '
|
||||
'camera back to the ambient position while the gesture is still '
|
||||
'down',
|
||||
);
|
||||
expect(center.longitude, isNot(closeTo(fix2.longitude, 1e-6)));
|
||||
|
||||
// Now the finger actually moves and lifts -- the real drag completes
|
||||
// normally, and the final position reflects it, not the ambient tick.
|
||||
await gesture.moveBy(const Offset(-100, -100));
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
map = tester.widget<FlutterMap>(find.byType(FlutterMap));
|
||||
center = map.mapController!.camera.center;
|
||||
expect(
|
||||
center.latitude,
|
||||
isNot(closeTo(fix2.latitude, 1e-6)),
|
||||
reason: 'the completed drag must still reflect the rider\'s own pan, '
|
||||
'not the ambient position the mid-drag tick tried to recenter to',
|
||||
);
|
||||
expect(center.longitude, isNot(closeTo(fix2.longitude, 1e-6)));
|
||||
});
|
||||
|
||||
testWidgets('tapping recenter moves the camera to the latest recorded '
|
||||
'point when recording', (tester) async {
|
||||
final points = [for (var i = 0; i < 4; i++) p(1, i)];
|
||||
|
||||
Reference in New Issue
Block a user