_TapStatusTrackerMixin
should wait until the next PointerDownEvent
before resetting its state when the tap timer has elapsed (#129312)
`_TapStatusTrackerMixin` used by `BaseTapAndDragGestureRecognizer` should wait until the next tap down before resetting its state when the `_consecutiveTapTimer` times out. This is because `BaseTapAndDragGestureRecognizer` may not have fired its tap down/tap up event before the state has been reset preventing it from firing the tap down/tap up callbacks at all because `currentDown` and `currentUp` are reset to `null`. Fixes #129161
This commit is contained in:
parent
3df1de4c80
commit
3c366b7011
@ -539,6 +539,9 @@ mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer {
|
|||||||
@override
|
@override
|
||||||
void addAllowedPointer(PointerDownEvent event) {
|
void addAllowedPointer(PointerDownEvent event) {
|
||||||
super.addAllowedPointer(event);
|
super.addAllowedPointer(event);
|
||||||
|
if (_consecutiveTapTimer != null && !_consecutiveTapTimer!.isActive) {
|
||||||
|
_tapTrackerReset();
|
||||||
|
}
|
||||||
if (maxConsecutiveTap == _consecutiveTapCount) {
|
if (maxConsecutiveTap == _consecutiveTapCount) {
|
||||||
_tapTrackerReset();
|
_tapTrackerReset();
|
||||||
}
|
}
|
||||||
@ -623,7 +626,7 @@ mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _consecutiveTapTimerStart() {
|
void _consecutiveTapTimerStart() {
|
||||||
_consecutiveTapTimer ??= Timer(kDoubleTapTimeout, _tapTrackerReset);
|
_consecutiveTapTimer ??= Timer(kDoubleTapTimeout, _consecutiveTapTimerTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _consecutiveTapTimerStop() {
|
void _consecutiveTapTimerStop() {
|
||||||
@ -633,6 +636,13 @@ mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _consecutiveTapTimerTimeout() {
|
||||||
|
// The consecutive tap timer may time out before a tap down/tap up event is
|
||||||
|
// fired. In this case we should not reset the tap tracker state immediately.
|
||||||
|
// Instead we should reset the tap tracker on the next call to [addAllowedPointer],
|
||||||
|
// if the timer is no longer active.
|
||||||
|
}
|
||||||
|
|
||||||
void _tapTrackerReset() {
|
void _tapTrackerReset() {
|
||||||
// The timer has timed out, i.e. the time between a [PointerUpEvent] and the subsequent
|
// The timer has timed out, i.e. the time between a [PointerUpEvent] and the subsequent
|
||||||
// [PointerDownEvent] exceeded the duration of [kDoubleTapTimeout], so the tap belonging
|
// [PointerDownEvent] exceeded the duration of [kDoubleTapTimeout], so the tap belonging
|
||||||
|
@ -2147,6 +2147,52 @@ void main() {
|
|||||||
variant: TargetPlatformVariant.mobile(),
|
variant: TargetPlatformVariant.mobile(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
testWidgets('Can select text with a mouse when wrapped in a GestureDetector with tap/double tap callbacks', (WidgetTester tester) async {
|
||||||
|
// This is a regression test for https://github.com/flutter/flutter/issues/129161.
|
||||||
|
final TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
onDoubleTap: () {},
|
||||||
|
child: TextField(
|
||||||
|
dragStartBehavior: DragStartBehavior.down,
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const String testValue = 'abc def ghi';
|
||||||
|
await tester.enterText(find.byType(TextField), testValue);
|
||||||
|
await skipPastScrollingAnimation(tester);
|
||||||
|
|
||||||
|
final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
|
||||||
|
final Offset gPos = textOffsetToPosition(tester, testValue.indexOf('g'));
|
||||||
|
|
||||||
|
final TestGesture gesture = await tester.startGesture(ePos, kind: PointerDeviceKind.mouse);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
// This is to allow the GestureArena to decide a winner between TapGestureRecognizer,
|
||||||
|
// DoubleTapGestureRecognizer, and BaseTapAndDragGestureRecognizer.
|
||||||
|
await tester.pumpAndSettle(kDoubleTapTimeout);
|
||||||
|
expect(controller.selection.isCollapsed, true);
|
||||||
|
expect(controller.selection.baseOffset, testValue.indexOf('e'));
|
||||||
|
|
||||||
|
await gesture.down(ePos);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.moveTo(gPos);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(controller.selection.baseOffset, testValue.indexOf('e'));
|
||||||
|
expect(controller.selection.extentOffset, testValue.indexOf('g'));
|
||||||
|
}, variant: TargetPlatformVariant.desktop());
|
||||||
|
|
||||||
testWidgets('Can select text by dragging with a mouse', (WidgetTester tester) async {
|
testWidgets('Can select text by dragging with a mouse', (WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
|
@ -694,6 +694,44 @@ void main() {
|
|||||||
'panend#2']);
|
'panend#2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This is a regression test for https://github.com/flutter/flutter/issues/129161.
|
||||||
|
testGesture('Beats TapGestureRecognizer and DoubleTapGestureRecognizer when the pointer has not moved and this recognizer is the first in the arena', (GestureTester tester) {
|
||||||
|
setUpTapAndPanGestureRecognizer();
|
||||||
|
|
||||||
|
final TapGestureRecognizer taps = TapGestureRecognizer()
|
||||||
|
..onTapDown = (TapDownDetails details) {
|
||||||
|
events.add('tapdown');
|
||||||
|
}
|
||||||
|
..onTapUp = (TapUpDetails details) {
|
||||||
|
events.add('tapup');
|
||||||
|
}
|
||||||
|
..onTapCancel = () {
|
||||||
|
events.add('tapscancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
final DoubleTapGestureRecognizer doubleTaps = DoubleTapGestureRecognizer()
|
||||||
|
..onDoubleTapDown = (TapDownDetails details) {
|
||||||
|
events.add('doubletapdown');
|
||||||
|
}
|
||||||
|
..onDoubleTap = () {
|
||||||
|
events.add('doubletapup');
|
||||||
|
}
|
||||||
|
..onDoubleTapCancel = () {
|
||||||
|
events.add('doubletapcancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
tapAndDrag.addPointer(down1);
|
||||||
|
taps.addPointer(down1);
|
||||||
|
doubleTaps.addPointer(down1);
|
||||||
|
tester.closeArena(1);
|
||||||
|
tester.route(down1);
|
||||||
|
tester.route(up1);
|
||||||
|
GestureBinding.instance.gestureArena.sweep(1);
|
||||||
|
// Wait for GestureArena to resolve itself.
|
||||||
|
tester.async.elapse(kDoubleTapTimeout);
|
||||||
|
expect(events, <String>['down#1', 'up#1']);
|
||||||
|
});
|
||||||
|
|
||||||
testGesture('Beats TapGestureRecognizer when the pointer has not moved and this recognizer is the first in the arena', (GestureTester tester) {
|
testGesture('Beats TapGestureRecognizer when the pointer has not moved and this recognizer is the first in the arena', (GestureTester tester) {
|
||||||
setUpTapAndPanGestureRecognizer();
|
setUpTapAndPanGestureRecognizer();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user