#1378 accessibility: apply system "touch and hold delay" setting
This commit is contained in:
parent
8c11a7bbd4
commit
bb5bbcc069
20 changed files with 2186 additions and 20 deletions
|
@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
- Video: use `media-kit` instead of `ffmpeg-kit` for metadata fetch
|
||||
- Info: show video chapters
|
||||
- Accessibility: apply system "touch and hold delay" setting
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.content.res.Configuration
|
|||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.ViewConfiguration
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
|
@ -17,6 +18,7 @@ class AccessibilityHandler(private val contextWrapper: ContextWrapper) : MethodC
|
|||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"areAnimationsRemoved" -> safe(call, result, ::areAnimationsRemoved)
|
||||
"getLongPressTimeout" -> safe(call, result, ::getLongPressTimeout)
|
||||
"hasRecommendedTimeouts" -> safe(call, result, ::hasRecommendedTimeouts)
|
||||
"getRecommendedTimeoutMillis" -> safe(call, result, ::getRecommendedTimeoutMillis)
|
||||
"shouldUseBoldFont" -> safe(call, result, ::shouldUseBoldFont)
|
||||
|
@ -34,6 +36,10 @@ class AccessibilityHandler(private val contextWrapper: ContextWrapper) : MethodC
|
|||
result.success(removed)
|
||||
}
|
||||
|
||||
private fun getLongPressTimeout(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||
result.success(ViewConfiguration.getLongPressTimeout())
|
||||
}
|
||||
|
||||
private fun hasRecommendedTimeouts(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.os.Handler
|
|||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.ViewConfiguration
|
||||
import deckers.thibault.aves.model.FieldMap
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
|
@ -21,6 +22,7 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
|
|||
private val contentObserver = object : ContentObserver(null) {
|
||||
private var accelerometerRotation: Int = 0
|
||||
private var transitionAnimationScale: Float = 1f
|
||||
private var longPressTimeoutMillis: Int = 0
|
||||
|
||||
init {
|
||||
update()
|
||||
|
@ -36,6 +38,7 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
|
|||
hashMapOf(
|
||||
Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation,
|
||||
Settings.Global.TRANSITION_ANIMATION_SCALE to transitionAnimationScale,
|
||||
KEY_LONG_PRESS_TIMEOUT_MILLIS to longPressTimeoutMillis,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -54,6 +57,11 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
|
|||
transitionAnimationScale = newTransitionAnimationScale
|
||||
changed = true
|
||||
}
|
||||
val newLongPressTimeout = ViewConfiguration.getLongPressTimeout()
|
||||
if (longPressTimeoutMillis != newLongPressTimeout) {
|
||||
longPressTimeoutMillis = newLongPressTimeout
|
||||
changed = true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null)
|
||||
}
|
||||
|
@ -93,5 +101,8 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
|
|||
companion object {
|
||||
private val LOG_TAG = LogUtils.createTag<SettingsChangeStreamHandler>()
|
||||
const val CHANNEL = "deckers.thibault/aves/settings_change"
|
||||
|
||||
// cf `Settings.Secure.LONG_PRESS_TIMEOUT`
|
||||
const val KEY_LONG_PRESS_TIMEOUT_MILLIS = "long_press_timeout"
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ import 'package:aves_video/aves_video.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
|
@ -319,6 +320,10 @@ class Settings with ChangeNotifier, SettingsAccess, AppSettings, DisplaySettings
|
|||
if (value is num) {
|
||||
areAnimationsRemoved = value == 0;
|
||||
}
|
||||
case SettingKeys.platformLongPressTimeoutMillisKey:
|
||||
if (value is num) {
|
||||
longPressTimeoutMillis = value.toInt();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -331,6 +336,10 @@ class Settings with ChangeNotifier, SettingsAccess, AppSettings, DisplaySettings
|
|||
|
||||
set areAnimationsRemoved(bool newValue) => set(SettingKeys.platformTransitionAnimationScaleKey, newValue);
|
||||
|
||||
Duration get longPressTimeout => Duration(milliseconds: getInt(SettingKeys.platformLongPressTimeoutMillisKey) ?? kLongPressTimeout.inMilliseconds);
|
||||
|
||||
set longPressTimeoutMillis(int newValue) => set(SettingKeys.platformLongPressTimeoutMillisKey, newValue);
|
||||
|
||||
// import/export
|
||||
|
||||
Map<String, dynamic> export() => Map.fromEntries(
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class AccessibilityService {
|
||||
static const _platform = MethodChannel('deckers.thibault/aves/accessibility');
|
||||
|
||||
static Future<bool> shouldUseBoldFont() async {
|
||||
try {
|
||||
final result = await _platform.invokeMethod('shouldUseBoldFont');
|
||||
if (result != null) return result as bool;
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Future<bool> areAnimationsRemoved() async {
|
||||
try {
|
||||
final result = await _platform.invokeMethod('areAnimationsRemoved');
|
||||
|
@ -25,6 +16,16 @@ class AccessibilityService {
|
|||
return false;
|
||||
}
|
||||
|
||||
static Future<int> getLongPressTimeout() async {
|
||||
try {
|
||||
final result = await _platform.invokeMethod('getLongPressTimeout');
|
||||
if (result != null) return result as int;
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return kLongPressTimeout.inMilliseconds;
|
||||
}
|
||||
|
||||
static bool? _hasRecommendedTimeouts;
|
||||
|
||||
static Future<bool> hasRecommendedTimeouts() async {
|
||||
|
@ -65,4 +66,14 @@ class AccessibilityService {
|
|||
}
|
||||
return originalTimeoutMillis;
|
||||
}
|
||||
|
||||
static Future<bool> shouldUseBoldFont() async {
|
||||
try {
|
||||
final result = await _platform.invokeMethod('shouldUseBoldFont');
|
||||
if (result != null) return result as bool;
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -494,6 +494,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
|||
await mobileServices.init();
|
||||
await settings.init(monitorPlatformSettings: true);
|
||||
settings.isRotationLocked = await windowService.isRotationLocked();
|
||||
settings.longPressTimeoutMillis = await AccessibilityService.getLongPressTimeout();
|
||||
settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
|
||||
await _onTvLayoutChanged();
|
||||
_monitorSettings();
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/action_controls/quick_choosers/common/route_layout.dart';
|
||||
import 'package:aves/widgets/common/basic/gestures/gesture_detector.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -79,7 +81,7 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
|
|||
onSurfaceVariant: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
child: GestureDetector(
|
||||
child: AGestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onLongPressStart: _hasChooser ? _showChooser : null,
|
||||
onLongPressMoveUpdate: _hasChooser ? _moveUpdateStreamController.add : null,
|
||||
|
@ -93,6 +95,7 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
|
|||
}
|
||||
: null,
|
||||
onLongPressCancel: _clearChooserOverlayEntry,
|
||||
longPressTimeout: settings.longPressTimeout,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/basic/draggable_scrollbar/notifications.dart';
|
||||
import 'package:aves/widgets/common/basic/draggable_scrollbar/scroll_label.dart';
|
||||
import 'package:aves/widgets/common/basic/draggable_scrollbar/transition.dart';
|
||||
import 'package:aves/widgets/common/basic/gestures/gesture_detector.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/*
|
||||
|
@ -221,7 +223,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
|||
// exclude semantics, otherwise this layer will block access to content layers below when using TalkBack
|
||||
ExcludeSemantics(
|
||||
child: RepaintBoundary(
|
||||
child: GestureDetector(
|
||||
child: AGestureDetector(
|
||||
onLongPressStart: (details) {
|
||||
_longPressLastGlobalPosition = details.globalPosition;
|
||||
_onVerticalDragStart();
|
||||
|
@ -235,6 +237,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
|||
onVerticalDragStart: (_) => _onVerticalDragStart(),
|
||||
onVerticalDragUpdate: (details) => _onVerticalDragUpdate(details.delta.dy),
|
||||
onVerticalDragEnd: (_) => _onVerticalDragEnd(),
|
||||
longPressTimeout: settings.longPressTimeout,
|
||||
child: ValueListenableBuilder<double>(
|
||||
valueListenable: _thumbOffsetNotifier,
|
||||
builder: (context, thumbOffset, child) => Container(
|
||||
|
|
992
lib/widgets/common/basic/gestures/gesture_detector.dart
Normal file
992
lib/widgets/common/basic/gestures/gesture_detector.dart
Normal file
|
@ -0,0 +1,992 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
// as of Flutter v3.27.1, `GestureDetector` does not allow setting long press delay
|
||||
// adapted from Flutter `GestureDetector` in `/widgets/gesture_detector.dart`
|
||||
class AGestureDetector extends StatelessWidget {
|
||||
/// Creates a widget that detects gestures.
|
||||
///
|
||||
/// Pan and scale callbacks cannot be used simultaneously because scale is a
|
||||
/// superset of pan. Use the scale callbacks instead.
|
||||
///
|
||||
/// Horizontal and vertical drag callbacks cannot be used simultaneously
|
||||
/// because a combination of a horizontal and vertical drag is a pan.
|
||||
/// Use the pan callbacks instead.
|
||||
///
|
||||
/// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4}
|
||||
///
|
||||
/// By default, gesture detectors contribute semantic information to the tree
|
||||
/// that is used by assistive technology.
|
||||
AGestureDetector({
|
||||
super.key,
|
||||
this.child,
|
||||
this.onTapDown,
|
||||
this.onTapUp,
|
||||
this.onTap,
|
||||
this.onTapCancel,
|
||||
this.onSecondaryTap,
|
||||
this.onSecondaryTapDown,
|
||||
this.onSecondaryTapUp,
|
||||
this.onSecondaryTapCancel,
|
||||
this.onTertiaryTapDown,
|
||||
this.onTertiaryTapUp,
|
||||
this.onTertiaryTapCancel,
|
||||
this.onDoubleTapDown,
|
||||
this.onDoubleTap,
|
||||
this.onDoubleTapCancel,
|
||||
this.onLongPressDown,
|
||||
this.onLongPressCancel,
|
||||
this.onLongPress,
|
||||
this.onLongPressStart,
|
||||
this.onLongPressMoveUpdate,
|
||||
this.onLongPressUp,
|
||||
this.onLongPressEnd,
|
||||
this.onSecondaryLongPressDown,
|
||||
this.onSecondaryLongPressCancel,
|
||||
this.onSecondaryLongPress,
|
||||
this.onSecondaryLongPressStart,
|
||||
this.onSecondaryLongPressMoveUpdate,
|
||||
this.onSecondaryLongPressUp,
|
||||
this.onSecondaryLongPressEnd,
|
||||
this.onTertiaryLongPressDown,
|
||||
this.onTertiaryLongPressCancel,
|
||||
this.onTertiaryLongPress,
|
||||
this.onTertiaryLongPressStart,
|
||||
this.onTertiaryLongPressMoveUpdate,
|
||||
this.onTertiaryLongPressUp,
|
||||
this.onTertiaryLongPressEnd,
|
||||
this.onVerticalDragDown,
|
||||
this.onVerticalDragStart,
|
||||
this.onVerticalDragUpdate,
|
||||
this.onVerticalDragEnd,
|
||||
this.onVerticalDragCancel,
|
||||
this.onHorizontalDragDown,
|
||||
this.onHorizontalDragStart,
|
||||
this.onHorizontalDragUpdate,
|
||||
this.onHorizontalDragEnd,
|
||||
this.onHorizontalDragCancel,
|
||||
this.onForcePressStart,
|
||||
this.onForcePressPeak,
|
||||
this.onForcePressUpdate,
|
||||
this.onForcePressEnd,
|
||||
this.onPanDown,
|
||||
this.onPanStart,
|
||||
this.onPanUpdate,
|
||||
this.onPanEnd,
|
||||
this.onPanCancel,
|
||||
this.onScaleStart,
|
||||
this.onScaleUpdate,
|
||||
this.onScaleEnd,
|
||||
this.behavior,
|
||||
this.excludeFromSemantics = false,
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
this.trackpadScrollCausesScale = false,
|
||||
this.trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor,
|
||||
this.supportedDevices,
|
||||
this.longPressTimeout = kLongPressTimeout,
|
||||
}) : assert(() {
|
||||
final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null;
|
||||
final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null;
|
||||
final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
|
||||
final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
|
||||
if (havePan || haveScale) {
|
||||
if (havePan && haveScale) {
|
||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||
ErrorSummary('Incorrect GestureDetector arguments.'),
|
||||
ErrorDescription(
|
||||
'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.',
|
||||
),
|
||||
ErrorHint('Just use the scale gesture recognizer.'),
|
||||
]);
|
||||
}
|
||||
final String recognizer = havePan ? 'pan' : 'scale';
|
||||
if (haveVerticalDrag && haveHorizontalDrag) {
|
||||
throw FlutterError(
|
||||
'Incorrect GestureDetector arguments.\n'
|
||||
'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
|
||||
'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.',
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.ProxyWidget.child}
|
||||
final Widget? child;
|
||||
|
||||
/// A pointer that might cause a tap with a primary button has contacted the
|
||||
/// screen at a particular location.
|
||||
///
|
||||
/// This is called after a short timeout, even if the winning gesture has not
|
||||
/// yet been selected. If the tap gesture wins, [onTapUp] will be called,
|
||||
/// otherwise [onTapCancel] will be called.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureTapDownCallback? onTapDown;
|
||||
|
||||
/// A pointer that will trigger a tap with a primary button has stopped
|
||||
/// contacting the screen at a particular location.
|
||||
///
|
||||
/// This triggers immediately before [onTap] in the case of the tap gesture
|
||||
/// winning. If the tap gesture did not win, [onTapCancel] is called instead.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureTapUpCallback? onTapUp;
|
||||
|
||||
/// A tap with a primary button has occurred.
|
||||
///
|
||||
/// This triggers when the tap gesture wins. If the tap gesture did not win,
|
||||
/// [onTapCancel] is called instead.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
/// * [onTapUp], which is called at the same time but includes details
|
||||
/// regarding the pointer position.
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
/// The pointer that previously triggered [onTapDown] will not end up causing
|
||||
/// a tap.
|
||||
///
|
||||
/// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
|
||||
/// the tap gesture did not win.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureTapCancelCallback? onTapCancel;
|
||||
|
||||
/// A tap with a secondary button has occurred.
|
||||
///
|
||||
/// This triggers when the tap gesture wins. If the tap gesture did not win,
|
||||
/// [onSecondaryTapCancel] is called instead.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
/// * [onSecondaryTapUp], which is called at the same time but includes details
|
||||
/// regarding the pointer position.
|
||||
final GestureTapCallback? onSecondaryTap;
|
||||
|
||||
/// A pointer that might cause a tap with a secondary button has contacted the
|
||||
/// screen at a particular location.
|
||||
///
|
||||
/// This is called after a short timeout, even if the winning gesture has not
|
||||
/// yet been selected. If the tap gesture wins, [onSecondaryTapUp] will be
|
||||
/// called, otherwise [onSecondaryTapCancel] will be called.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
final GestureTapDownCallback? onSecondaryTapDown;
|
||||
|
||||
/// A pointer that will trigger a tap with a secondary button has stopped
|
||||
/// contacting the screen at a particular location.
|
||||
///
|
||||
/// This triggers in the case of the tap gesture winning. If the tap gesture
|
||||
/// did not win, [onSecondaryTapCancel] is called instead.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [onSecondaryTap], a handler triggered right after this one that doesn't
|
||||
/// pass any details about the tap.
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
final GestureTapUpCallback? onSecondaryTapUp;
|
||||
|
||||
/// The pointer that previously triggered [onSecondaryTapDown] will not end up
|
||||
/// causing a tap.
|
||||
///
|
||||
/// This is called after [onSecondaryTapDown], and instead of
|
||||
/// [onSecondaryTapUp], if the tap gesture did not win.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
final GestureTapCancelCallback? onSecondaryTapCancel;
|
||||
|
||||
/// A pointer that might cause a tap with a tertiary button has contacted the
|
||||
/// screen at a particular location.
|
||||
///
|
||||
/// This is called after a short timeout, even if the winning gesture has not
|
||||
/// yet been selected. If the tap gesture wins, [onTertiaryTapUp] will be
|
||||
/// called, otherwise [onTertiaryTapCancel] will be called.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
final GestureTapDownCallback? onTertiaryTapDown;
|
||||
|
||||
/// A pointer that will trigger a tap with a tertiary button has stopped
|
||||
/// contacting the screen at a particular location.
|
||||
///
|
||||
/// This triggers in the case of the tap gesture winning. If the tap gesture
|
||||
/// did not win, [onTertiaryTapCancel] is called instead.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
final GestureTapUpCallback? onTertiaryTapUp;
|
||||
|
||||
/// The pointer that previously triggered [onTertiaryTapDown] will not end up
|
||||
/// causing a tap.
|
||||
///
|
||||
/// This is called after [onTertiaryTapDown], and instead of
|
||||
/// [onTertiaryTapUp], if the tap gesture did not win.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
final GestureTapCancelCallback? onTertiaryTapCancel;
|
||||
|
||||
/// A pointer that might cause a double tap has contacted the screen at a
|
||||
/// particular location.
|
||||
///
|
||||
/// Triggered immediately after the down event of the second tap.
|
||||
///
|
||||
/// If the user completes the double tap and the gesture wins, [onDoubleTap]
|
||||
/// will be called after this callback. Otherwise, [onDoubleTapCancel] will
|
||||
/// be called after this callback.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureTapDownCallback? onDoubleTapDown;
|
||||
|
||||
/// The user has tapped the screen with a primary button at the same location
|
||||
/// twice in quick succession.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureTapCallback? onDoubleTap;
|
||||
|
||||
/// The pointer that previously triggered [onDoubleTapDown] will not end up
|
||||
/// causing a double tap.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureTapCancelCallback? onDoubleTapCancel;
|
||||
|
||||
/// The pointer has contacted the screen with a primary button, which might
|
||||
/// be the start of a long-press.
|
||||
///
|
||||
/// This triggers after the pointer down event.
|
||||
///
|
||||
/// If the user completes the long-press, and this gesture wins,
|
||||
/// [onLongPressStart] will be called after this callback. Otherwise,
|
||||
/// [onLongPressCancel] will be called after this callback.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
/// * [onSecondaryLongPressDown], a similar callback but for a secondary button.
|
||||
/// * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
|
||||
/// * [LongPressGestureRecognizer.onLongPressDown], which exposes this
|
||||
/// callback at the gesture layer.
|
||||
final GestureLongPressDownCallback? onLongPressDown;
|
||||
|
||||
/// A pointer that previously triggered [onLongPressDown] will not end up
|
||||
/// causing a long-press.
|
||||
///
|
||||
/// This triggers once the gesture loses if [onLongPressDown] has previously
|
||||
/// been triggered.
|
||||
///
|
||||
/// If the user completed the long-press, and the gesture won, then
|
||||
/// [onLongPressStart] and [onLongPress] are called instead.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onLongPressCancel], which exposes this
|
||||
/// callback at the gesture layer.
|
||||
final GestureLongPressCancelCallback? onLongPressCancel;
|
||||
|
||||
/// Called when a long press gesture with a primary button has been recognized.
|
||||
///
|
||||
/// Triggered when a pointer has remained in contact with the screen at the
|
||||
/// same location for a long period of time.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately after) [onLongPressStart].
|
||||
/// The only difference between the two is that this callback does not
|
||||
/// contain details of the position at which the pointer initially contacted
|
||||
/// the screen.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onLongPress], which exposes this
|
||||
/// callback at the gesture layer.
|
||||
final GestureLongPressCallback? onLongPress;
|
||||
|
||||
/// Called when a long press gesture with a primary button has been recognized.
|
||||
///
|
||||
/// Triggered when a pointer has remained in contact with the screen at the
|
||||
/// same location for a long period of time.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately before) [onLongPress].
|
||||
/// The only difference between the two is that this callback contains
|
||||
/// details of the position at which the pointer initially contacted the
|
||||
/// screen, whereas [onLongPress] does not.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onLongPressStart], which exposes this
|
||||
/// callback at the gesture layer.
|
||||
final GestureLongPressStartCallback? onLongPressStart;
|
||||
|
||||
/// A pointer has been drag-moved after a long-press with a primary button.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onLongPressMoveUpdate], which exposes this
|
||||
/// callback at the gesture layer.
|
||||
final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
|
||||
|
||||
/// A pointer that has triggered a long-press with a primary button has
|
||||
/// stopped contacting the screen.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately after) [onLongPressEnd].
|
||||
/// The only difference between the two is that this callback does not
|
||||
/// contain details of the state of the pointer when it stopped contacting
|
||||
/// the screen.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onLongPressUp], which exposes this
|
||||
/// callback at the gesture layer.
|
||||
final GestureLongPressUpCallback? onLongPressUp;
|
||||
|
||||
/// A pointer that has triggered a long-press with a primary button has
|
||||
/// stopped contacting the screen.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately before) [onLongPressUp].
|
||||
/// The only difference between the two is that this callback contains
|
||||
/// details of the state of the pointer when it stopped contacting the
|
||||
/// screen, whereas [onLongPressUp] does not.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onLongPressEnd], which exposes this
|
||||
/// callback at the gesture layer.
|
||||
final GestureLongPressEndCallback? onLongPressEnd;
|
||||
|
||||
/// The pointer has contacted the screen with a secondary button, which might
|
||||
/// be the start of a long-press.
|
||||
///
|
||||
/// This triggers after the pointer down event.
|
||||
///
|
||||
/// If the user completes the long-press, and this gesture wins,
|
||||
/// [onSecondaryLongPressStart] will be called after this callback. Otherwise,
|
||||
/// [onSecondaryLongPressCancel] will be called after this callback.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
/// * [onLongPressDown], a similar callback but for a secondary button.
|
||||
/// * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
|
||||
/// * [LongPressGestureRecognizer.onSecondaryLongPressDown], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressDownCallback? onSecondaryLongPressDown;
|
||||
|
||||
/// A pointer that previously triggered [onSecondaryLongPressDown] will not
|
||||
/// end up causing a long-press.
|
||||
///
|
||||
/// This triggers once the gesture loses if [onSecondaryLongPressDown] has
|
||||
/// previously been triggered.
|
||||
///
|
||||
/// If the user completed the long-press, and the gesture won, then
|
||||
/// [onSecondaryLongPressStart] and [onSecondaryLongPress] are called instead.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onSecondaryLongPressCancel], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressCancelCallback? onSecondaryLongPressCancel;
|
||||
|
||||
/// Called when a long press gesture with a secondary button has been
|
||||
/// recognized.
|
||||
///
|
||||
/// Triggered when a pointer has remained in contact with the screen at the
|
||||
/// same location for a long period of time.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately after)
|
||||
/// [onSecondaryLongPressStart]. The only difference between the two is that
|
||||
/// this callback does not contain details of the position at which the
|
||||
/// pointer initially contacted the screen.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onSecondaryLongPress], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressCallback? onSecondaryLongPress;
|
||||
|
||||
/// Called when a long press gesture with a secondary button has been
|
||||
/// recognized.
|
||||
///
|
||||
/// Triggered when a pointer has remained in contact with the screen at the
|
||||
/// same location for a long period of time.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately before)
|
||||
/// [onSecondaryLongPress]. The only difference between the two is that this
|
||||
/// callback contains details of the position at which the pointer initially
|
||||
/// contacted the screen, whereas [onSecondaryLongPress] does not.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onSecondaryLongPressStart], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressStartCallback? onSecondaryLongPressStart;
|
||||
|
||||
/// A pointer has been drag-moved after a long press with a secondary button.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onSecondaryLongPressMoveUpdate], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate;
|
||||
|
||||
/// A pointer that has triggered a long-press with a secondary button has
|
||||
/// stopped contacting the screen.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately after)
|
||||
/// [onSecondaryLongPressEnd]. The only difference between the two is that
|
||||
/// this callback does not contain details of the state of the pointer when
|
||||
/// it stopped contacting the screen.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onSecondaryLongPressUp], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressUpCallback? onSecondaryLongPressUp;
|
||||
|
||||
/// A pointer that has triggered a long-press with a secondary button has
|
||||
/// stopped contacting the screen.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately before)
|
||||
/// [onSecondaryLongPressUp]. The only difference between the two is that
|
||||
/// this callback contains details of the state of the pointer when it
|
||||
/// stopped contacting the screen, whereas [onSecondaryLongPressUp] does not.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kSecondaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onSecondaryLongPressEnd], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressEndCallback? onSecondaryLongPressEnd;
|
||||
|
||||
/// The pointer has contacted the screen with a tertiary button, which might
|
||||
/// be the start of a long-press.
|
||||
///
|
||||
/// This triggers after the pointer down event.
|
||||
///
|
||||
/// If the user completes the long-press, and this gesture wins,
|
||||
/// [onTertiaryLongPressStart] will be called after this callback. Otherwise,
|
||||
/// [onTertiaryLongPressCancel] will be called after this callback.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
/// * [onLongPressDown], a similar callback but for a primary button.
|
||||
/// * [onSecondaryLongPressDown], a similar callback but for a secondary button.
|
||||
/// * [LongPressGestureRecognizer.onTertiaryLongPressDown], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressDownCallback? onTertiaryLongPressDown;
|
||||
|
||||
/// A pointer that previously triggered [onTertiaryLongPressDown] will not
|
||||
/// end up causing a long-press.
|
||||
///
|
||||
/// This triggers once the gesture loses if [onTertiaryLongPressDown] has
|
||||
/// previously been triggered.
|
||||
///
|
||||
/// If the user completed the long-press, and the gesture won, then
|
||||
/// [onTertiaryLongPressStart] and [onTertiaryLongPress] are called instead.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onTertiaryLongPressCancel], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressCancelCallback? onTertiaryLongPressCancel;
|
||||
|
||||
/// Called when a long press gesture with a tertiary button has been
|
||||
/// recognized.
|
||||
///
|
||||
/// Triggered when a pointer has remained in contact with the screen at the
|
||||
/// same location for a long period of time.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately after)
|
||||
/// [onTertiaryLongPressStart]. The only difference between the two is that
|
||||
/// this callback does not contain details of the position at which the
|
||||
/// pointer initially contacted the screen.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onTertiaryLongPress], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressCallback? onTertiaryLongPress;
|
||||
|
||||
/// Called when a long press gesture with a tertiary button has been
|
||||
/// recognized.
|
||||
///
|
||||
/// Triggered when a pointer has remained in contact with the screen at the
|
||||
/// same location for a long period of time.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately before)
|
||||
/// [onTertiaryLongPress]. The only difference between the two is that this
|
||||
/// callback contains details of the position at which the pointer initially
|
||||
/// contacted the screen, whereas [onTertiaryLongPress] does not.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onTertiaryLongPressStart], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressStartCallback? onTertiaryLongPressStart;
|
||||
|
||||
/// A pointer has been drag-moved after a long press with a tertiary button.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onTertiaryLongPressMoveUpdate], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressMoveUpdateCallback? onTertiaryLongPressMoveUpdate;
|
||||
|
||||
/// A pointer that has triggered a long-press with a tertiary button has
|
||||
/// stopped contacting the screen.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately after)
|
||||
/// [onTertiaryLongPressEnd]. The only difference between the two is that
|
||||
/// this callback does not contain details of the state of the pointer when
|
||||
/// it stopped contacting the screen.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onTertiaryLongPressUp], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressUpCallback? onTertiaryLongPressUp;
|
||||
|
||||
/// A pointer that has triggered a long-press with a tertiary button has
|
||||
/// stopped contacting the screen.
|
||||
///
|
||||
/// This is equivalent to (and is called immediately before)
|
||||
/// [onTertiaryLongPressUp]. The only difference between the two is that
|
||||
/// this callback contains details of the state of the pointer when it
|
||||
/// stopped contacting the screen, whereas [onTertiaryLongPressUp] does not.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kTertiaryButton], the button this callback responds to.
|
||||
/// * [LongPressGestureRecognizer.onTertiaryLongPressEnd], which exposes
|
||||
/// this callback at the gesture layer.
|
||||
final GestureLongPressEndCallback? onTertiaryLongPressEnd;
|
||||
|
||||
/// A pointer has contacted the screen with a primary button and might begin
|
||||
/// to move vertically.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragDownCallback? onVerticalDragDown;
|
||||
|
||||
/// A pointer has contacted the screen with a primary button and has begun to
|
||||
/// move vertically.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragStartCallback? onVerticalDragStart;
|
||||
|
||||
/// A pointer that is in contact with the screen with a primary button and
|
||||
/// moving vertically has moved in the vertical direction.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragUpdateCallback? onVerticalDragUpdate;
|
||||
|
||||
/// A pointer that was previously in contact with the screen with a primary
|
||||
/// button and moving vertically is no longer in contact with the screen and
|
||||
/// was moving at a specific velocity when it stopped contacting the screen.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragEndCallback? onVerticalDragEnd;
|
||||
|
||||
/// The pointer that previously triggered [onVerticalDragDown] did not
|
||||
/// complete.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragCancelCallback? onVerticalDragCancel;
|
||||
|
||||
/// A pointer has contacted the screen with a primary button and might begin
|
||||
/// to move horizontally.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragDownCallback? onHorizontalDragDown;
|
||||
|
||||
/// A pointer has contacted the screen with a primary button and has begun to
|
||||
/// move horizontally.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragStartCallback? onHorizontalDragStart;
|
||||
|
||||
/// A pointer that is in contact with the screen with a primary button and
|
||||
/// moving horizontally has moved in the horizontal direction.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragUpdateCallback? onHorizontalDragUpdate;
|
||||
|
||||
/// A pointer that was previously in contact with the screen with a primary
|
||||
/// button and moving horizontally is no longer in contact with the screen and
|
||||
/// was moving at a specific velocity when it stopped contacting the screen.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragEndCallback? onHorizontalDragEnd;
|
||||
|
||||
/// The pointer that previously triggered [onHorizontalDragDown] did not
|
||||
/// complete.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragCancelCallback? onHorizontalDragCancel;
|
||||
|
||||
/// A pointer has contacted the screen with a primary button and might begin
|
||||
/// to move.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragDownCallback? onPanDown;
|
||||
|
||||
/// A pointer has contacted the screen with a primary button and has begun to
|
||||
/// move.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragStartCallback? onPanStart;
|
||||
|
||||
/// A pointer that is in contact with the screen with a primary button and
|
||||
/// moving has moved again.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragUpdateCallback? onPanUpdate;
|
||||
|
||||
/// A pointer that was previously in contact with the screen with a primary
|
||||
/// button and moving is no longer in contact with the screen and was moving
|
||||
/// at a specific velocity when it stopped contacting the screen.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragEndCallback? onPanEnd;
|
||||
|
||||
/// The pointer that previously triggered [onPanDown] did not complete.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
final GestureDragCancelCallback? onPanCancel;
|
||||
|
||||
/// The pointers in contact with the screen have established a focal point and
|
||||
/// initial scale of 1.0.
|
||||
final GestureScaleStartCallback? onScaleStart;
|
||||
|
||||
/// The pointers in contact with the screen have indicated a new focal point
|
||||
/// and/or scale.
|
||||
final GestureScaleUpdateCallback? onScaleUpdate;
|
||||
|
||||
/// The pointers are no longer in contact with the screen.
|
||||
final GestureScaleEndCallback? onScaleEnd;
|
||||
|
||||
/// The pointer is in contact with the screen and has pressed with sufficient
|
||||
/// force to initiate a force press. The amount of force is at least
|
||||
/// [ForcePressGestureRecognizer.startPressure].
|
||||
///
|
||||
/// This callback will only be fired on devices with pressure
|
||||
/// detecting screens.
|
||||
final GestureForcePressStartCallback? onForcePressStart;
|
||||
|
||||
/// The pointer is in contact with the screen and has pressed with the maximum
|
||||
/// force. The amount of force is at least
|
||||
/// [ForcePressGestureRecognizer.peakPressure].
|
||||
///
|
||||
/// This callback will only be fired on devices with pressure
|
||||
/// detecting screens.
|
||||
final GestureForcePressPeakCallback? onForcePressPeak;
|
||||
|
||||
/// A pointer is in contact with the screen, has previously passed the
|
||||
/// [ForcePressGestureRecognizer.startPressure] and is either moving on the
|
||||
/// plane of the screen, pressing the screen with varying forces or both
|
||||
/// simultaneously.
|
||||
///
|
||||
/// This callback will only be fired on devices with pressure
|
||||
/// detecting screens.
|
||||
final GestureForcePressUpdateCallback? onForcePressUpdate;
|
||||
|
||||
/// The pointer tracked by [onForcePressStart] is no longer in contact with the screen.
|
||||
///
|
||||
/// This callback will only be fired on devices with pressure
|
||||
/// detecting screens.
|
||||
final GestureForcePressEndCallback? onForcePressEnd;
|
||||
|
||||
/// How this gesture detector should behave during hit testing when deciding
|
||||
/// how the hit test propagates to children and whether to consider targets
|
||||
/// behind this one.
|
||||
///
|
||||
/// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
|
||||
/// [HitTestBehavior.translucent] if child is null.
|
||||
///
|
||||
/// See [HitTestBehavior] for the allowed values and their meanings.
|
||||
final HitTestBehavior? behavior;
|
||||
|
||||
/// Whether to exclude these gestures from the semantics tree. For
|
||||
/// example, the long-press gesture for showing a tooltip is
|
||||
/// excluded because the tooltip itself is included in the semantics
|
||||
/// tree directly and so having a gesture to show it would result in
|
||||
/// duplication of information.
|
||||
final bool excludeFromSemantics;
|
||||
|
||||
/// Determines the way that drag start behavior is handled.
|
||||
///
|
||||
/// If set to [DragStartBehavior.start], gesture drag behavior will
|
||||
/// begin at the position where the drag gesture won the arena. If set to
|
||||
/// [DragStartBehavior.down] it will begin at the position where a down event
|
||||
/// is first detected.
|
||||
///
|
||||
/// In general, setting this to [DragStartBehavior.start] will make drag
|
||||
/// animation smoother and setting it to [DragStartBehavior.down] will make
|
||||
/// drag behavior feel slightly more reactive.
|
||||
///
|
||||
/// By default, the drag start behavior is [DragStartBehavior.start].
|
||||
///
|
||||
/// Only the [DragGestureRecognizer.onStart] callbacks for the
|
||||
/// [VerticalDragGestureRecognizer], [HorizontalDragGestureRecognizer] and
|
||||
/// [PanGestureRecognizer] are affected by this setting.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
|
||||
final DragStartBehavior dragStartBehavior;
|
||||
|
||||
/// The kind of devices that are allowed to be recognized.
|
||||
///
|
||||
/// If set to null, events from all device types will be recognized. Defaults to null.
|
||||
final Set<PointerDeviceKind>? supportedDevices;
|
||||
|
||||
/// {@macro flutter.gestures.scale.trackpadScrollCausesScale}
|
||||
final bool trackpadScrollCausesScale;
|
||||
|
||||
/// {@macro flutter.gestures.scale.trackpadScrollToScaleFactor}
|
||||
final Offset trackpadScrollToScaleFactor;
|
||||
|
||||
final Duration longPressTimeout;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
|
||||
final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context);
|
||||
final ScrollBehavior configuration = ScrollConfiguration.of(context);
|
||||
|
||||
if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null || onSecondaryTap != null || onSecondaryTapDown != null || onSecondaryTapUp != null || onSecondaryTapCancel != null || onTertiaryTapDown != null || onTertiaryTapUp != null || onTertiaryTapCancel != null) {
|
||||
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||||
() => TapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
|
||||
(instance) {
|
||||
instance
|
||||
..onTapDown = onTapDown
|
||||
..onTapUp = onTapUp
|
||||
..onTap = onTap
|
||||
..onTapCancel = onTapCancel
|
||||
..onSecondaryTap = onSecondaryTap
|
||||
..onSecondaryTapDown = onSecondaryTapDown
|
||||
..onSecondaryTapUp = onSecondaryTapUp
|
||||
..onSecondaryTapCancel = onSecondaryTapCancel
|
||||
..onTertiaryTapDown = onTertiaryTapDown
|
||||
..onTertiaryTapUp = onTertiaryTapUp
|
||||
..onTertiaryTapCancel = onTertiaryTapCancel
|
||||
..gestureSettings = gestureSettings
|
||||
..supportedDevices = supportedDevices;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (onDoubleTap != null || onDoubleTapDown != null || onDoubleTapCancel != null) {
|
||||
gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
|
||||
() => DoubleTapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
|
||||
(instance) {
|
||||
instance
|
||||
..onDoubleTapDown = onDoubleTapDown
|
||||
..onDoubleTap = onDoubleTap
|
||||
..onDoubleTapCancel = onDoubleTapCancel
|
||||
..gestureSettings = gestureSettings
|
||||
..supportedDevices = supportedDevices;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (onLongPressDown != null || onLongPressCancel != null || onLongPress != null || onLongPressStart != null || onLongPressMoveUpdate != null || onLongPressUp != null || onLongPressEnd != null || onSecondaryLongPressDown != null || onSecondaryLongPressCancel != null || onSecondaryLongPress != null || onSecondaryLongPressStart != null || onSecondaryLongPressMoveUpdate != null || onSecondaryLongPressUp != null || onSecondaryLongPressEnd != null || onTertiaryLongPressDown != null || onTertiaryLongPressCancel != null || onTertiaryLongPress != null || onTertiaryLongPressStart != null || onTertiaryLongPressMoveUpdate != null || onTertiaryLongPressUp != null || onTertiaryLongPressEnd != null) {
|
||||
gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
||||
() => LongPressGestureRecognizer(duration: longPressTimeout, debugOwner: this, supportedDevices: supportedDevices),
|
||||
(instance) {
|
||||
instance
|
||||
..onLongPressDown = onLongPressDown
|
||||
..onLongPressCancel = onLongPressCancel
|
||||
..onLongPress = onLongPress
|
||||
..onLongPressStart = onLongPressStart
|
||||
..onLongPressMoveUpdate = onLongPressMoveUpdate
|
||||
..onLongPressUp = onLongPressUp
|
||||
..onLongPressEnd = onLongPressEnd
|
||||
..onSecondaryLongPressDown = onSecondaryLongPressDown
|
||||
..onSecondaryLongPressCancel = onSecondaryLongPressCancel
|
||||
..onSecondaryLongPress = onSecondaryLongPress
|
||||
..onSecondaryLongPressStart = onSecondaryLongPressStart
|
||||
..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate
|
||||
..onSecondaryLongPressUp = onSecondaryLongPressUp
|
||||
..onSecondaryLongPressEnd = onSecondaryLongPressEnd
|
||||
..onTertiaryLongPressDown = onTertiaryLongPressDown
|
||||
..onTertiaryLongPressCancel = onTertiaryLongPressCancel
|
||||
..onTertiaryLongPress = onTertiaryLongPress
|
||||
..onTertiaryLongPressStart = onTertiaryLongPressStart
|
||||
..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate
|
||||
..onTertiaryLongPressUp = onTertiaryLongPressUp
|
||||
..onTertiaryLongPressEnd = onTertiaryLongPressEnd
|
||||
..gestureSettings = gestureSettings
|
||||
..supportedDevices = supportedDevices;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (onVerticalDragDown != null || onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null || onVerticalDragCancel != null) {
|
||||
gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
|
||||
() => VerticalDragGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
|
||||
(instance) {
|
||||
instance
|
||||
..onDown = onVerticalDragDown
|
||||
..onStart = onVerticalDragStart
|
||||
..onUpdate = onVerticalDragUpdate
|
||||
..onEnd = onVerticalDragEnd
|
||||
..onCancel = onVerticalDragCancel
|
||||
..dragStartBehavior = dragStartBehavior
|
||||
..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context)
|
||||
..gestureSettings = gestureSettings
|
||||
..supportedDevices = supportedDevices;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (onHorizontalDragDown != null || onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null || onHorizontalDragCancel != null) {
|
||||
gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
|
||||
() => HorizontalDragGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
|
||||
(instance) {
|
||||
instance
|
||||
..onDown = onHorizontalDragDown
|
||||
..onStart = onHorizontalDragStart
|
||||
..onUpdate = onHorizontalDragUpdate
|
||||
..onEnd = onHorizontalDragEnd
|
||||
..onCancel = onHorizontalDragCancel
|
||||
..dragStartBehavior = dragStartBehavior
|
||||
..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context)
|
||||
..gestureSettings = gestureSettings
|
||||
..supportedDevices = supportedDevices;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (onPanDown != null || onPanStart != null || onPanUpdate != null || onPanEnd != null || onPanCancel != null) {
|
||||
gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
|
||||
() => PanGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
|
||||
(instance) {
|
||||
instance
|
||||
..onDown = onPanDown
|
||||
..onStart = onPanStart
|
||||
..onUpdate = onPanUpdate
|
||||
..onEnd = onPanEnd
|
||||
..onCancel = onPanCancel
|
||||
..dragStartBehavior = dragStartBehavior
|
||||
..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context)
|
||||
..gestureSettings = gestureSettings
|
||||
..supportedDevices = supportedDevices;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
|
||||
gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
|
||||
() => ScaleGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
|
||||
(instance) {
|
||||
instance
|
||||
..onStart = onScaleStart
|
||||
..onUpdate = onScaleUpdate
|
||||
..onEnd = onScaleEnd
|
||||
..dragStartBehavior = dragStartBehavior
|
||||
..gestureSettings = gestureSettings
|
||||
..trackpadScrollCausesScale = trackpadScrollCausesScale
|
||||
..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor
|
||||
..supportedDevices = supportedDevices;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (onForcePressStart != null || onForcePressPeak != null || onForcePressUpdate != null || onForcePressEnd != null) {
|
||||
gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
|
||||
() => ForcePressGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices),
|
||||
(instance) {
|
||||
instance
|
||||
..onStart = onForcePressStart
|
||||
..onPeak = onForcePressPeak
|
||||
..onUpdate = onForcePressUpdate
|
||||
..onEnd = onForcePressEnd
|
||||
..gestureSettings = gestureSettings
|
||||
..supportedDevices = supportedDevices;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return RawGestureDetector(
|
||||
gestures: gestures,
|
||||
behavior: behavior,
|
||||
excludeFromSemantics: excludeFromSemantics,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
|
||||
}
|
||||
}
|
1095
lib/widgets/common/basic/gestures/ink_well.dart
Normal file
1095
lib/widgets/common/basic/gestures/ink_well.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -4,6 +4,7 @@ import 'package:aves/model/source/section_keys.dart';
|
|||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/theme/styles.dart';
|
||||
import 'package:aves/widgets/common/basic/gestures/gesture_detector.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/grid/sections/list_layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -42,7 +43,7 @@ class SectionHeader<T> extends StatelessWidget {
|
|||
Widget child = Container(
|
||||
padding: padding,
|
||||
constraints: BoxConstraints(minHeight: leadingSize.height),
|
||||
child: GestureDetector(
|
||||
child: AGestureDetector(
|
||||
onTap: onTap,
|
||||
onLongPress: selectable
|
||||
? Feedback.wrapForLongPress(() {
|
||||
|
@ -55,6 +56,7 @@ class SectionHeader<T> extends StatelessWidget {
|
|||
}
|
||||
}, context)
|
||||
: null,
|
||||
longPressTimeout: settings.longPressTimeout,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
|
|
|
@ -2,11 +2,12 @@ import 'dart:async';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/selection.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/utils/math_utils.dart';
|
||||
import 'package:aves/widgets/common/basic/gestures/gesture_detector.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||
import 'package:aves/widgets/common/grid/sections/list_layout.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -90,7 +91,7 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectable = widget.selectable;
|
||||
return GestureDetector(
|
||||
return AGestureDetector(
|
||||
onLongPressStart: selectable
|
||||
? (details) {
|
||||
if (_isScrolling) return;
|
||||
|
@ -137,6 +138,7 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
|
|||
selection.toggleSelection(item);
|
||||
}
|
||||
: null,
|
||||
longPressTimeout: settings.longPressTimeout,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
@ -144,7 +146,7 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
|
|||
void _onScrollChanged() {
|
||||
_isScrolling = true;
|
||||
_stopScrollMonitoringTimer();
|
||||
_scrollMonitoringTimer = Timer(kLongPressTimeout + const Duration(milliseconds: 150), () {
|
||||
_scrollMonitoringTimer = Timer(settings.longPressTimeout + const Duration(milliseconds: 150), () {
|
||||
_isScrolling = false;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/theme/durations.dart';
|
|||
import 'package:aves/theme/themes.dart';
|
||||
import 'package:aves/widgets/aves_app.dart';
|
||||
import 'package:aves/widgets/common/basic/font_size_icon_theme.dart';
|
||||
import 'package:aves/widgets/common/basic/gestures/ink_well.dart';
|
||||
import 'package:aves/widgets/common/basic/insets.dart';
|
||||
import 'package:aves/widgets/common/fx/blurred.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -95,10 +96,13 @@ class AvesAppBar extends StatelessWidget {
|
|||
child: AvesFloatingBar(
|
||||
builder: (context, backgroundColor, child) => Material(
|
||||
color: backgroundColor,
|
||||
child: InkWell(
|
||||
child: AInkResponse(
|
||||
// absorb taps while providing visual feedback
|
||||
onTap: () {},
|
||||
onLongPress: () {},
|
||||
containedInkWell: true,
|
||||
highlightShape: BoxShape.rectangle,
|
||||
longPressTimeout: settings.longPressTimeout,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'package:aves/theme/themes.dart';
|
|||
import 'package:aves/view/view.dart';
|
||||
import 'package:aves/widgets/collection/filter_bar.dart';
|
||||
import 'package:aves/widgets/common/basic/font_size_icon_theme.dart';
|
||||
import 'package:aves/widgets/common/basic/gestures/ink_well.dart';
|
||||
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
|
@ -347,13 +348,16 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: InkWell(
|
||||
child: AInkResponse(
|
||||
// as of Flutter v2.8.0, `InkWell` does not have `onLongPressStart` like `GestureDetector`,
|
||||
// so we get the long press details from the tap instead
|
||||
onTapDown: onLongPress != null ? (details) => _tapPosition = details.globalPosition : null,
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
containedInkWell: true,
|
||||
highlightShape: BoxShape.rectangle,
|
||||
borderRadius: borderRadius,
|
||||
longPressTimeout: settings.longPressTimeout,
|
||||
child: FutureBuilder<Color>(
|
||||
future: _colorFuture,
|
||||
builder: (context, snapshot) {
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/utils/debouncer.dart';
|
||||
import 'package:aves/widgets/common/basic/gestures/gesture_detector.dart';
|
||||
import 'package:aves/widgets/common/map/leaflet/latlng_tween.dart' as llt;
|
||||
import 'package:aves/widgets/common/map/leaflet/scale_layer.dart';
|
||||
import 'package:aves/widgets/common/map/leaflet/tile_layers.dart';
|
||||
import 'package:aves_map/aves_map.dart';
|
||||
import 'package:aves_utils/aves_utils.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
@ -130,14 +133,19 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
|
|||
final markerKey = kv.key;
|
||||
final geoEntry = kv.value;
|
||||
final latLng = LatLng(geoEntry.latitude!, geoEntry.longitude!);
|
||||
final onMarkerLongPress = widget.onMarkerLongPress;
|
||||
final onLongPress = onMarkerLongPress != null ? Feedback.wrapForLongPress(() => onMarkerLongPress.call(geoEntry, LatLng(geoEntry.latitude!, geoEntry.longitude!)), context) : null;
|
||||
return Marker(
|
||||
point: latLng,
|
||||
child: GestureDetector(
|
||||
child: AGestureDetector(
|
||||
onTap: () => widget.onMarkerTap?.call(geoEntry),
|
||||
// marker tap handling prevents the default handling of focal zoom on double tap,
|
||||
// so we reimplement the double tap gesture here
|
||||
onDoubleTap: interactive ? () => _zoomBy(1, focalPoint: latLng) : null,
|
||||
onLongPress: Feedback.wrapForLongPress(() => widget.onMarkerLongPress?.call(geoEntry, LatLng(geoEntry.latitude!, geoEntry.longitude!)), context),
|
||||
onLongPress: onLongPress,
|
||||
// `MapInteractiveViewer` already declares a `LongPressGestureRecognizer` with the default delay (`kLongPressTimeout`),
|
||||
// so this one should have a shorter delay to win in the gesture arena
|
||||
longPressTimeout: Duration(milliseconds: min(settings.longPressTimeout.inMilliseconds, kLongPressTimeout.inMilliseconds)),
|
||||
child: widget.markerWidgetBuilder(markerKey),
|
||||
),
|
||||
width: markerSize.width,
|
||||
|
|
|
@ -76,6 +76,7 @@ class _DebugSettingsSectionState extends State<DebugSettingsSection> with Automa
|
|||
'locale': '${settings.locale}',
|
||||
'systemLocales': '${WidgetsBinding.instance.platformDispatcher.locales}',
|
||||
'topEntryIds': '${settings.topEntryIds}',
|
||||
'longPressTimeout': '${settings.longPressTimeout}',
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
|
@ -98,6 +99,7 @@ class AvailableActionPanel<T extends Object> extends StatelessWidget {
|
|||
maxSimultaneousDrags: 1,
|
||||
onDragStarted: () => _setDraggedAvailableAction(action),
|
||||
onDragEnd: (details) => _setDraggedAvailableAction(null),
|
||||
delay: settings.longPressTimeout,
|
||||
childWhenDragging: child,
|
||||
child: child,
|
||||
);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -72,6 +73,7 @@ class QuickActionButton<T extends Object> extends StatelessWidget {
|
|||
// so we rely on `onDraggableCanceled` and `onDragCompleted` instead
|
||||
onDraggableCanceled: (velocity, offset) => _setDraggedQuickAction(null),
|
||||
onDragCompleted: () => _setDraggedQuickAction(null),
|
||||
delay: settings.longPressTimeout,
|
||||
childWhenDragging: child,
|
||||
child: child,
|
||||
);
|
||||
|
|
|
@ -188,4 +188,7 @@ class SettingKeys {
|
|||
|
||||
// cf Android `Settings.Global.TRANSITION_ANIMATION_SCALE`
|
||||
static const platformTransitionAnimationScaleKey = 'transition_animation_scale';
|
||||
|
||||
// cf Android `Settings.Secure.LONG_PRESS_TIMEOUT`
|
||||
static const platformLongPressTimeoutMillisKey = 'long_press_timeout';
|
||||
}
|
||||
|
|
|
@ -199,6 +199,12 @@ flutter:
|
|||
# `OverlaySnackBar` in `/widgets/common/action_mixins/overlay_snack_bar.dart`
|
||||
# adapts from Flutter v3.23.0 `SnackBar` in `/material/snack_bar.dart`
|
||||
#
|
||||
# `AGestureDetector` in `/widgets/common/basic/gestures/gesture_detector.dart`
|
||||
# adapts from Flutter v3.21.1 `GestureDetector` in `/widgets/gesture_detector.dart`
|
||||
#
|
||||
# `AInkResponse` in `/widgets/common/basic/gestures/ink_well.dart`
|
||||
# adapts from Flutter v3.21.1 `InkResponse` and related classes in `/material/ink_well.dart`
|
||||
#
|
||||
# `EagerScaleGestureRecognizer` in `/widgets/common/behaviour/eager_scale_gesture_recognizer.dart`
|
||||
# adapts from Flutter v3.16.0 `ScaleGestureRecognizer` in `/gestures/scale.dart`
|
||||
#
|
||||
|
|
Loading…
Reference in a new issue