Skip to content

Commit

Permalink
[CP] Fix TextField not responding to taps when wrapped in a GestureDe…
Browse files Browse the repository at this point in the history
…tector (flutter#129576)

Cherry pick for issue flutter#129161 / PR flutter#129312

CP Issue: flutter#129577
  • Loading branch information
Renzo-Olivares authored Jul 10, 2023
1 parent 796c8ef commit 9a3a31d
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 1 deletion.
12 changes: 11 additions & 1 deletion packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,9 @@ mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer {
@override
void addAllowedPointer(PointerDownEvent event) {
super.addAllowedPointer(event);
if (_consecutiveTapTimer != null && !_consecutiveTapTimer!.isActive) {
_tapTrackerReset();
}
if (maxConsecutiveTap == _consecutiveTapCount) {
_tapTrackerReset();
}
Expand Down Expand Up @@ -623,7 +626,7 @@ mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer {
}

void _consecutiveTapTimerStart() {
_consecutiveTapTimer ??= Timer(kDoubleTapTimeout, _tapTrackerReset);
_consecutiveTapTimer ??= Timer(kDoubleTapTimeout, _consecutiveTapTimerTimeout);
}

void _consecutiveTapTimerStop() {
Expand All @@ -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() {
// 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
Expand Down
46 changes: 46 additions & 0 deletions packages/flutter/test/material/text_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2110,6 +2110,52 @@ void main() {
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 {
final TextEditingController controller = TextEditingController();

Expand Down
38 changes: 38 additions & 0 deletions packages/flutter/test/widgets/tap_and_drag_gestures_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,44 @@ void main() {
'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) {
setUpTapAndPanGestureRecognizer();

Expand Down

0 comments on commit 9a3a31d

Please sign in to comment.