Fix: BaseTapAndDragGestureRecognizer should reset drag state after losing gesture arena (#151989)

This PR properly resets the drag state when losing the gesture arena or when the recognizer stops tracking the current pointer. The _dragState enum was reset properly, but I had forgotten to also reset the `_start`, this caused an issue when the recognizer won the gesture arena the next time, as it tries to detect a drag given the old `_start` in `acceptGesture`, but the `_dragState` has been reset causing an assertion to trigger.
This commit is contained in:
Renzo Olivares 2024-07-18 14:01:25 -07:00 committed by GitHub
parent a08d1bbdb8
commit 6e877226bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 73 additions and 1 deletions

View File

@ -1065,6 +1065,7 @@ sealed class BaseTapAndDragGestureRecognizer extends OneSequenceGestureRecognize
}
_stopDeadlineTimer();
_start = null;
_dragState = _DragState.ready;
_pastSlopTolerance = false;
}

View File

@ -45,9 +45,12 @@ void main() {
addTearDown(tapAndDrag.dispose);
}
void setUpTapAndHorizontalDragGestureRecognizer() {
void setUpTapAndHorizontalDragGestureRecognizer({
bool eagerVictoryOnDrag = true, // This is the default for [BaseTapAndDragGestureRecognizer].
}) {
tapAndDrag = TapAndHorizontalDragGestureRecognizer()
..dragStartBehavior = DragStartBehavior.down
..eagerVictoryOnDrag = eagerVictoryOnDrag
..maxConsecutiveTap = 3
..onTapDown = (TapDragDownDetails details) {
events.add('down#${details.consecutiveTapCount}');
@ -692,6 +695,74 @@ void main() {
);
});
testGesture('Drag state is properly reset after losing GestureArena', (GestureTester tester) {
setUpTapAndHorizontalDragGestureRecognizer(eagerVictoryOnDrag: false);
final HorizontalDragGestureRecognizer horizontalDrag = HorizontalDragGestureRecognizer()
..onStart = (DragStartDetails details) {
events.add('basichorizontalstart');
}
..onUpdate = (DragUpdateDetails details) {
events.add('basichorizontalupdate');
}
..onEnd = (DragEndDetails details) {
events.add('basichorizontalend');
}
..onCancel = () {
events.add('basichorizontalcancel');
};
addTearDown(horizontalDrag.dispose);
final LongPressGestureRecognizer longpress = LongPressGestureRecognizer()
..onLongPressStart = (LongPressStartDetails details) {
events.add('longpressstart');
}
..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
events.add('longpressmoveupdate');
}
..onLongPressEnd = (LongPressEndDetails details) {
events.add('longpressend');
}
..onLongPressCancel = () {
events.add('longpresscancel');
};
addTearDown(longpress.dispose);
FlutterErrorDetails? errorDetails;
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails details) {
errorDetails = details;
};
final TestPointer pointer = TestPointer(5);
final PointerDownEvent downB = pointer.down(const Offset(10.0, 10.0));
// When competing against another [DragGestureRecognizer], the [TapAndPanGestureRecognizer]
// will only win when it is the last recognizer in the arena.
tapAndDrag.addPointer(downB);
horizontalDrag.addPointer(downB);
longpress.addPointer(downB);
tester.closeArena(5);
tester.route(downB);
tester.route(pointer.move(const Offset(28.1, 10.0)));
tester.route(pointer.up());
expect(
events,
<String>[
'basichorizontalstart',
'basichorizontalend',
],
);
final PointerDownEvent downC = pointer.down(const Offset(10.0, 10.0));
tapAndDrag.addPointer(downC);
horizontalDrag.addPointer(downC);
longpress.addPointer(downC);
tester.closeArena(5);
tester.route(downC);
tester.route(pointer.up());
FlutterError.onError = oldHandler;
expect(errorDetails, isNull);
});
testGesture('Beats LongPressGestureRecognizer on a consecutive tap greater than one', (GestureTester tester) {
setUpTapAndPanGestureRecognizer();