Move TapAndDragGestureRecognizer code under gestures (#119508)

diff --git a/packages/flutter/lib/gestures.dart b/packages/flutter/lib/gestures.dart
index be2f30d..8a12689 100644
--- a/packages/flutter/lib/gestures.dart
+++ b/packages/flutter/lib/gestures.dart
@@ -30,5 +30,6 @@
 export 'src/gestures/resampler.dart';
 export 'src/gestures/scale.dart';
 export 'src/gestures/tap.dart';
+export 'src/gestures/tap_and_drag.dart';
 export 'src/gestures/team.dart';
 export 'src/gestures/velocity_tracker.dart';
diff --git a/packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart b/packages/flutter/lib/src/gestures/tap_and_drag.dart
similarity index 94%
rename from packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart
rename to packages/flutter/lib/src/gestures/tap_and_drag.dart
index 51a8435..907173c 100644
--- a/packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart
+++ b/packages/flutter/lib/src/gestures/tap_and_drag.dart
@@ -5,11 +5,13 @@
 import 'dart:async';
 
 import 'package:flutter/foundation.dart';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/services.dart' show HardwareKeyboard, LogicalKeyboardKey;
 
-import 'framework.dart';
-import 'gesture_detector.dart';
+import 'constants.dart';
+import 'events.dart';
+import 'monodrag.dart';
+import 'recognizer.dart';
+import 'scale.dart';
+import 'tap.dart';
 
 // Examples can assume:
 // void setState(VoidCallback fn) { }
@@ -75,14 +77,13 @@
 class TapDragDownDetails with Diagnosticable {
   /// Creates details for a [GestureTapDragDownCallback].
   ///
-  /// The [globalPosition], [localPosition], [consecutiveTapCount], and
-  /// [keysPressedOnDown] arguments must be provided and must not be null.
+  /// The [globalPosition], [localPosition], and [consecutiveTapCount]
+  /// arguments must be provided and must not be null.
   TapDragDownDetails({
     required this.globalPosition,
     required this.localPosition,
     this.kind,
     required this.consecutiveTapCount,
-    required this.keysPressedOnDown,
   });
 
   /// The global position at which the pointer contacted the screen.
@@ -98,9 +99,6 @@
   /// the number in the series this tap is.
   final int consecutiveTapCount;
 
-  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
-  final Set<LogicalKeyboardKey> keysPressedOnDown;
-
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
@@ -108,7 +106,6 @@
     properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition));
     properties.add(DiagnosticsProperty<PointerDeviceKind?>('kind', kind));
     properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
-    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
   }
 }
 
@@ -134,14 +131,13 @@
 class TapDragUpDetails with Diagnosticable {
   /// Creates details for a [GestureTapDragUpCallback].
   ///
-  /// The [kind], [globalPosition], [localPosition], [consecutiveTapCount], and
-  /// [keysPressedOnDown] arguments must be provided and must not be null.
+  /// The [kind], [globalPosition], [localPosition], and [consecutiveTapCount]
+  /// arguments must be provided and must not be null.
   TapDragUpDetails({
     required this.kind,
     required this.globalPosition,
     required this.localPosition,
     required this.consecutiveTapCount,
-    required this.keysPressedOnDown,
   });
 
   /// The global position at which the pointer contacted the screen.
@@ -157,9 +153,6 @@
   /// the number in the series this tap is.
   final int consecutiveTapCount;
 
-  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
-  final Set<LogicalKeyboardKey> keysPressedOnDown;
-
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
@@ -167,7 +160,6 @@
     properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition));
     properties.add(DiagnosticsProperty<PointerDeviceKind?>('kind', kind));
     properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
-    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
   }
 }
 
@@ -193,15 +185,14 @@
 class TapDragStartDetails with Diagnosticable {
   /// Creates details for a [GestureTapDragStartCallback].
   ///
-  /// The [globalPosition], [localPosition], [consecutiveTapCount], and
-  /// [keysPressedOnDown] arguments must be provided and must not be null.
+  /// The [globalPosition], [localPosition], and [consecutiveTapCount]
+  /// arguments must be provided and must not be null.
   TapDragStartDetails({
     this.sourceTimeStamp,
     required this.globalPosition,
     required this.localPosition,
     this.kind,
     required this.consecutiveTapCount,
-    required this.keysPressedOnDown,
   });
 
   /// Recorded timestamp of the source pointer event that triggered the drag
@@ -229,9 +220,6 @@
   /// the number in the series this tap is.
   final int consecutiveTapCount;
 
-  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
-  final Set<LogicalKeyboardKey> keysPressedOnDown;
-
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
@@ -240,7 +228,6 @@
     properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition));
     properties.add(DiagnosticsProperty<PointerDeviceKind?>('kind', kind));
     properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
-    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
   }
 }
 
@@ -272,8 +259,7 @@
   /// coordinates of [delta] and the other coordinate must be zero.
   ///
   /// The [globalPosition], [localPosition], [offsetFromOrigin], [localOffsetFromOrigin],
-  /// [consecutiveTapCount], and [keysPressedOnDown] arguments must be provided and must
-  /// not be null.
+  /// and [consecutiveTapCount] arguments must be provided and must not be null.
   TapDragUpdateDetails({
     this.sourceTimeStamp,
     this.delta = Offset.zero,
@@ -284,7 +270,6 @@
     required this.offsetFromOrigin,
     required this.localOffsetFromOrigin,
     required this.consecutiveTapCount,
-    required this.keysPressedOnDown,
   }) : assert(
          primaryDelta == null
            || (primaryDelta == delta.dx && delta.dy == 0.0)
@@ -357,9 +342,6 @@
   /// the number in the series this tap is.
   final int consecutiveTapCount;
 
-  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
-  final Set<LogicalKeyboardKey> keysPressedOnDown;
-
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
@@ -372,7 +354,6 @@
     properties.add(DiagnosticsProperty<Offset>('offsetFromOrigin', offsetFromOrigin));
     properties.add(DiagnosticsProperty<Offset>('localOffsetFromOrigin', localOffsetFromOrigin));
     properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
-    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
   }
 }
 
@@ -400,13 +381,11 @@
   ///
   /// The [velocity] argument must not be null.
   ///
-  /// The [consecutiveTapCount], and [keysPressedOnDown] arguments must
-  /// be provided and must not be null.
+  /// The [consecutiveTapCount] argument must be provided and must not be null.
   TapDragEndDetails({
     this.velocity = Velocity.zero,
     this.primaryVelocity,
     required this.consecutiveTapCount,
-    required this.keysPressedOnDown,
   }) : assert(
          primaryVelocity == null
            || primaryVelocity == velocity.pixelsPerSecond.dx
@@ -434,16 +413,12 @@
   /// the number in the series this tap is.
   final int consecutiveTapCount;
 
-  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
-  final Set<LogicalKeyboardKey> keysPressedOnDown;
-
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
     properties.add(DiagnosticsProperty<Velocity>('velocity', velocity));
     properties.add(DiagnosticsProperty<double?>('primaryVelocity', primaryVelocity));
     properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
-    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
   }
 }
 
@@ -506,15 +481,6 @@
   // this value will be set to `0`, and a new series will begin.
   int get consecutiveTapCount => _consecutiveTapCount;
 
-  // The set of [LogicalKeyboardKey]s pressed when the most recent [PointerDownEvent]
-  // was tracked in [addAllowedPointer].
-  //
-  // This value defaults to an empty set.
-  //
-  // When the timer between two taps elapses, the recognizer loses the arena, the gesture is cancelled
-  // or the recognizer is disposed of then this value is reset.
-  Set<LogicalKeyboardKey> get keysPressedOnDown => _keysPressedOnDown ?? <LogicalKeyboardKey>{};
-
   // The upper limit for the [consecutiveTapCount]. When this limit is reached
   // all tap related state is reset and a new tap series is tracked.
   //
@@ -525,7 +491,6 @@
   PointerDownEvent? _down;
   PointerUpEvent? _up;
   int _consecutiveTapCount = 0;
-  Set<LogicalKeyboardKey>? _keysPressedOnDown;
 
   OffsetPair? _originPosition;
   int? _previousButtons;
@@ -534,6 +499,21 @@
   Timer? _consecutiveTapTimer;
   Offset? _lastTapOffset;
 
+  /// {@template flutter.gestures.selectionrecognizers.BaseTapAndDragGestureRecognizer.onTapTrackStart}
+  /// Callback used to indicate that a tap tracking has started upon
+  /// a [PointerDownEvent].
+  /// {@endtemplate}
+  VoidCallback? onTapTrackStart;
+
+  /// {@template flutter.gestures.selectionrecognizers.BaseTapAndDragGestureRecognizer.onTapTrackReset}
+  /// Callback used to indicate that a tap tracking has been reset which
+  /// happens on the next [PointerDownEvent] after the timer between two taps
+  /// elapses, the recognizer loses the arena, the gesture is cancelled or
+  /// the recognizer is disposed of.
+  /// {@endtemplate}
+
+  VoidCallback? onTapTrackReset;
+
   // When tracking a tap, the [consecutiveTapCount] is incremented if the given tap
   // falls under the tolerance specifications and reset to 1 if not.
   @override
@@ -595,10 +575,10 @@
 
   void _trackTap(PointerDownEvent event) {
     _down = event;
-    _keysPressedOnDown = HardwareKeyboard.instance.logicalKeysPressed;
     _previousButtons = event.buttons;
     _lastTapOffset = event.position;
     _originPosition = OffsetPair(local: event.localPosition, global: event.position);
+    onTapTrackStart?.call();
   }
 
   bool _hasSameButton(int buttons) {
@@ -653,9 +633,9 @@
     _originPosition = null;
     _lastTapOffset = null;
     _consecutiveTapCount = 0;
-    _keysPressedOnDown = null;
     _down = null;
     _up = null;
+    onTapTrackReset?.call();
   }
 }
 
@@ -1204,7 +1184,6 @@
       localPosition: event.localPosition,
       kind: getKindForPointer(event.pointer),
       consecutiveTapCount: consecutiveTapCount,
-      keysPressedOnDown: keysPressedOnDown,
     );
 
     if (onTapDown != null) {
@@ -1224,7 +1203,6 @@
       globalPosition: event.position,
       localPosition: event.localPosition,
       consecutiveTapCount: consecutiveTapCount,
-      keysPressedOnDown: keysPressedOnDown,
     );
 
     if (onTapUp != null) {
@@ -1245,7 +1223,6 @@
         localPosition: _initialPosition.local,
         kind: getKindForPointer(event.pointer),
         consecutiveTapCount: consecutiveTapCount,
-        keysPressedOnDown: keysPressedOnDown,
       );
 
       invokeCallback<void>('onDragStart', () => onDragStart!(details));
@@ -1267,7 +1244,6 @@
       offsetFromOrigin: globalPosition - _initialPosition.global,
       localOffsetFromOrigin: localPosition - _initialPosition.local,
       consecutiveTapCount: consecutiveTapCount,
-      keysPressedOnDown: keysPressedOnDown,
     );
 
     if (dragUpdateThrottleFrequency != null) {
@@ -1293,7 +1269,6 @@
       TapDragEndDetails(
         primaryVelocity: 0.0,
         consecutiveTapCount: consecutiveTapCount,
-        keysPressedOnDown: keysPressedOnDown,
       );
 
     if (onDragEnd != null) {
diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart
index a447e6e..8d45921 100644
--- a/packages/flutter/lib/src/widgets/text_selection.dart
+++ b/packages/flutter/lib/src/widgets/text_selection.dart
@@ -24,7 +24,6 @@
 import 'magnifier.dart';
 import 'overlay.dart';
 import 'scrollable.dart';
-import 'tap_and_drag_gestures.dart';
 import 'tap_region.dart';
 import 'ticker_provider.dart';
 import 'transitions.dart';
@@ -1993,11 +1992,6 @@
         && targetSelection.end >= textPosition.offset;
   }
 
-  /// Returns true if shift left or right is contained in the given set.
-  static bool _containsShift(Set<LogicalKeyboardKey> keysPressed) {
-    return keysPressed.any(<LogicalKeyboardKey>{ LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight }.contains);
-  }
-
   // Expand the selection to the given global position.
   //
   // Either base or extent will be moved to the last tapped position, whichever
@@ -2074,6 +2068,10 @@
   @protected
   RenderEditable get renderEditable => editableText.renderEditable;
 
+  /// Whether the Shift key was pressed when the most recent [PointerDownEvent]
+  /// was tracked by the [BaseTapAndDragGestureRecognizer].
+  bool _isShiftPressed = false;
+
   /// The viewport offset pixels of any [Scrollable] containing the
   /// [RenderEditable] at the last drag start.
   double _dragStartScrollOffset = 0.0;
@@ -2113,6 +2111,30 @@
   // focused, the cursor moves to the long press position.
   bool _longPressStartedWithoutFocus = false;
 
+  /// Handler for [TextSelectionGestureDetector.onTapTrackStart].
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onTapTrackStart], which triggers this
+  ///    callback.
+  @protected
+  void onTapTrackStart() {
+    _isShiftPressed = HardwareKeyboard.instance.logicalKeysPressed
+        .intersection(<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight})
+        .isNotEmpty;
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onTapTrackReset].
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onTapTrackReset], which triggers this
+  ///    callback.
+  @protected
+  void onTapTrackReset() {
+    _isShiftPressed = false;
+  }
+
   /// Handler for [TextSelectionGestureDetector.onTapDown].
   ///
   /// By default, it forwards the tap to [RenderEditable.handleTapDown] and sets
@@ -2145,11 +2167,9 @@
       || kind == PointerDeviceKind.touch
       || kind == PointerDeviceKind.stylus;
 
-    // Handle shift + click selection if needed.
-    final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
     // It is impossible to extend the selection when the shift key is pressed, if the
     // renderEditable.selection is invalid.
-    final bool isShiftPressedValid = isShiftPressed && renderEditable.selection?.baseOffset != null;
+    final bool isShiftPressedValid = _isShiftPressed && renderEditable.selection?.baseOffset != null;
     switch (defaultTargetPlatform) {
       case TargetPlatform.android:
       case TargetPlatform.fuchsia:
@@ -2246,11 +2266,9 @@
   @protected
   void onSingleTapUp(TapDragUpDetails details) {
     if (delegate.selectionEnabled) {
-      // Handle shift + click selection if needed.
-      final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
       // It is impossible to extend the selection when the shift key is pressed, if the
       // renderEditable.selection is invalid.
-      final bool isShiftPressedValid = isShiftPressed && renderEditable.selection?.baseOffset != null;
+      final bool isShiftPressedValid = _isShiftPressed && renderEditable.selection?.baseOffset != null;
       switch (defaultTargetPlatform) {
         case TargetPlatform.linux:
         case TargetPlatform.macOS:
@@ -2641,9 +2659,7 @@
       return;
     }
 
-    final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
-
-    if (isShiftPressed && renderEditable.selection != null && renderEditable.selection!.isValid) {
+    if (_isShiftPressed && renderEditable.selection != null && renderEditable.selection!.isValid) {
       switch (defaultTargetPlatform) {
         case TargetPlatform.iOS:
         case TargetPlatform.macOS:
@@ -2730,9 +2746,7 @@
       return;
     }
 
-    final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
-
-    if (!isShiftPressed) {
+    if (!_isShiftPressed) {
       // Adjust the drag start offset for possible viewport offset changes.
       final Offset editableOffset = renderEditable.maxLines == 1
           ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0)
@@ -2931,14 +2945,13 @@
   ///    callback.
   @protected
   void onDragSelectionEnd(TapDragEndDetails details) {
-    final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
     _dragBeganOnPreviousSelection = null;
 
     if (_shouldShowSelectionToolbar && _TextSelectionGestureDetectorState._getEffectiveConsecutiveTapCount(details.consecutiveTapCount) == 2) {
       editableText.showToolbar();
     }
 
-    if (isShiftPressed) {
+    if (_isShiftPressed) {
       _dragStartSelection = null;
     }
 
@@ -2956,6 +2969,8 @@
   }) {
     return TextSelectionGestureDetector(
       key: key,
+      onTapTrackStart: onTapTrackStart,
+      onTapTrackReset: onTapTrackReset,
       onTapDown: onTapDown,
       onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null,
       onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null,
@@ -2996,6 +3011,8 @@
   /// The [child] parameter must not be null.
   const TextSelectionGestureDetector({
     super.key,
+    this.onTapTrackStart,
+    this.onTapTrackReset,
     this.onTapDown,
     this.onForcePressStart,
     this.onForcePressEnd,
@@ -3015,6 +3032,12 @@
     required this.child,
   });
 
+  /// {@macro flutter.gestures.selectionrecognizers.BaseTapAndDragGestureRecognizer.onTapTrackStart}
+  final VoidCallback? onTapTrackStart;
+
+  /// {@macro flutter.gestures.selectionrecognizers.BaseTapAndDragGestureRecognizer.onTapTrackReset}
+  final VoidCallback? onTapTrackReset;
+
   /// Called for every tap down including every tap down that's part of a
   /// double click or a long press, except touches that include enough movement
   /// to not qualify as taps (e.g. pans and flings).
@@ -3125,6 +3148,14 @@
     }
   }
 
+  void _handleTapTrackStart() {
+    widget.onTapTrackStart?.call();
+  }
+
+  void _handleTapTrackReset() {
+    widget.onTapTrackReset?.call();
+  }
+
   // The down handler is force-run on success of a single tap and optimistically
   // run before a long press success.
   void _handleTapDown(TapDragDownDetails details) {
@@ -3231,6 +3262,8 @@
                 // Text selection should start from the position of the first pointer
                 // down event.
                 ..dragStartBehavior = DragStartBehavior.down
+                ..onTapTrackStart = _handleTapTrackStart
+                ..onTapTrackReset = _handleTapTrackReset
                 ..onTapDown = _handleTapDown
                 ..onDragStart = _handleDragStart
                 ..onDragUpdate = _handleDragUpdate
@@ -3249,6 +3282,8 @@
                 // Text selection should start from the position of the first pointer
                 // down event.
                 ..dragStartBehavior = DragStartBehavior.down
+                ..onTapTrackStart = _handleTapTrackStart
+                ..onTapTrackReset = _handleTapTrackReset
                 ..onTapDown = _handleTapDown
                 ..onDragStart = _handleDragStart
                 ..onDragUpdate = _handleDragUpdate
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart
index 092cd82..539d6aa 100644
--- a/packages/flutter/lib/widgets.dart
+++ b/packages/flutter/lib/widgets.dart
@@ -140,7 +140,6 @@
 export 'src/widgets/spell_check.dart';
 export 'src/widgets/status_transitions.dart';
 export 'src/widgets/table.dart';
-export 'src/widgets/tap_and_drag_gestures.dart';
 export 'src/widgets/tap_region.dart';
 export 'src/widgets/text.dart';
 export 'src/widgets/text_editing_intents.dart';
diff --git a/packages/flutter/test/widgets/tap_and_drag_gestures_test.dart b/packages/flutter/test/gestures/tap_and_drag_test.dart
similarity index 99%
rename from packages/flutter/test/widgets/tap_and_drag_gestures_test.dart
rename to packages/flutter/test/gestures/tap_and_drag_test.dart
index 33027be..152341b 100644
--- a/packages/flutter/test/widgets/tap_and_drag_gestures_test.dart
+++ b/packages/flutter/test/gestures/tap_and_drag_test.dart
@@ -4,7 +4,6 @@
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
-import 'package:flutter/widgets.dart';
 import 'package:flutter_test/flutter_test.dart';
 
 import '../gestures/gesture_tester.dart';
diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart
index 41fa392..67d49cb 100644
--- a/packages/flutter/test/widgets/text_selection_test.dart
+++ b/packages/flutter/test/widgets/text_selection_test.dart
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/foundation.dart' show ValueListenable, defaultTargetPlatform;
-import 'package:flutter/gestures.dart' show PointerDeviceKind, kSecondaryButton;
+import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/services.dart';