#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
|
- Video: use `media-kit` instead of `ffmpeg-kit` for metadata fetch
|
||||||
- Info: show video chapters
|
- Info: show video chapters
|
||||||
|
- Accessibility: apply system "touch and hold delay" setting
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.ViewConfiguration
|
||||||
import android.view.accessibility.AccessibilityManager
|
import android.view.accessibility.AccessibilityManager
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
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) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"areAnimationsRemoved" -> safe(call, result, ::areAnimationsRemoved)
|
"areAnimationsRemoved" -> safe(call, result, ::areAnimationsRemoved)
|
||||||
|
"getLongPressTimeout" -> safe(call, result, ::getLongPressTimeout)
|
||||||
"hasRecommendedTimeouts" -> safe(call, result, ::hasRecommendedTimeouts)
|
"hasRecommendedTimeouts" -> safe(call, result, ::hasRecommendedTimeouts)
|
||||||
"getRecommendedTimeoutMillis" -> safe(call, result, ::getRecommendedTimeoutMillis)
|
"getRecommendedTimeoutMillis" -> safe(call, result, ::getRecommendedTimeoutMillis)
|
||||||
"shouldUseBoldFont" -> safe(call, result, ::shouldUseBoldFont)
|
"shouldUseBoldFont" -> safe(call, result, ::shouldUseBoldFont)
|
||||||
|
@ -34,6 +36,10 @@ class AccessibilityHandler(private val contextWrapper: ContextWrapper) : MethodC
|
||||||
result.success(removed)
|
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) {
|
private fun hasRecommendedTimeouts(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||||
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.ViewConfiguration
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
import io.flutter.plugin.common.EventChannel
|
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 val contentObserver = object : ContentObserver(null) {
|
||||||
private var accelerometerRotation: Int = 0
|
private var accelerometerRotation: Int = 0
|
||||||
private var transitionAnimationScale: Float = 1f
|
private var transitionAnimationScale: Float = 1f
|
||||||
|
private var longPressTimeoutMillis: Int = 0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
update()
|
update()
|
||||||
|
@ -36,6 +38,7 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
|
||||||
hashMapOf(
|
hashMapOf(
|
||||||
Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation,
|
Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation,
|
||||||
Settings.Global.TRANSITION_ANIMATION_SCALE to transitionAnimationScale,
|
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
|
transitionAnimationScale = newTransitionAnimationScale
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
val newLongPressTimeout = ViewConfiguration.getLongPressTimeout()
|
||||||
|
if (longPressTimeoutMillis != newLongPressTimeout) {
|
||||||
|
longPressTimeoutMillis = newLongPressTimeout
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null)
|
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 {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<SettingsChangeStreamHandler>()
|
private val LOG_TAG = LogUtils.createTag<SettingsChangeStreamHandler>()
|
||||||
const val CHANNEL = "deckers.thibault/aves/settings_change"
|
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:collection/collection.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
|
@ -319,6 +320,10 @@ class Settings with ChangeNotifier, SettingsAccess, AppSettings, DisplaySettings
|
||||||
if (value is num) {
|
if (value is num) {
|
||||||
areAnimationsRemoved = value == 0;
|
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);
|
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
|
// import/export
|
||||||
|
|
||||||
Map<String, dynamic> export() => Map.fromEntries(
|
Map<String, dynamic> export() => Map.fromEntries(
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class AccessibilityService {
|
class AccessibilityService {
|
||||||
static const _platform = MethodChannel('deckers.thibault/aves/accessibility');
|
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 {
|
static Future<bool> areAnimationsRemoved() async {
|
||||||
try {
|
try {
|
||||||
final result = await _platform.invokeMethod('areAnimationsRemoved');
|
final result = await _platform.invokeMethod('areAnimationsRemoved');
|
||||||
|
@ -25,6 +16,16 @@ class AccessibilityService {
|
||||||
return false;
|
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 bool? _hasRecommendedTimeouts;
|
||||||
|
|
||||||
static Future<bool> hasRecommendedTimeouts() async {
|
static Future<bool> hasRecommendedTimeouts() async {
|
||||||
|
@ -65,4 +66,14 @@ class AccessibilityService {
|
||||||
}
|
}
|
||||||
return originalTimeoutMillis;
|
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 mobileServices.init();
|
||||||
await settings.init(monitorPlatformSettings: true);
|
await settings.init(monitorPlatformSettings: true);
|
||||||
settings.isRotationLocked = await windowService.isRotationLocked();
|
settings.isRotationLocked = await windowService.isRotationLocked();
|
||||||
|
settings.longPressTimeoutMillis = await AccessibilityService.getLongPressTimeout();
|
||||||
settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
|
settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
|
||||||
await _onTvLayoutChanged();
|
await _onTvLayoutChanged();
|
||||||
_monitorSettings();
|
_monitorSettings();
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/durations.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/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:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -79,7 +81,7 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
|
||||||
onSurfaceVariant: colorScheme.onSurface,
|
onSurfaceVariant: colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: GestureDetector(
|
child: AGestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onLongPressStart: _hasChooser ? _showChooser : null,
|
onLongPressStart: _hasChooser ? _showChooser : null,
|
||||||
onLongPressMoveUpdate: _hasChooser ? _moveUpdateStreamController.add : null,
|
onLongPressMoveUpdate: _hasChooser ? _moveUpdateStreamController.add : null,
|
||||||
|
@ -93,6 +95,7 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
onLongPressCancel: _clearChooserOverlayEntry,
|
onLongPressCancel: _clearChooserOverlayEntry,
|
||||||
|
longPressTimeout: settings.longPressTimeout,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'dart:async';
|
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/notifications.dart';
|
||||||
import 'package:aves/widgets/common/basic/draggable_scrollbar/scroll_label.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/draggable_scrollbar/transition.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/gestures/gesture_detector.dart';
|
||||||
import 'package:flutter/widgets.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
|
// exclude semantics, otherwise this layer will block access to content layers below when using TalkBack
|
||||||
ExcludeSemantics(
|
ExcludeSemantics(
|
||||||
child: RepaintBoundary(
|
child: RepaintBoundary(
|
||||||
child: GestureDetector(
|
child: AGestureDetector(
|
||||||
onLongPressStart: (details) {
|
onLongPressStart: (details) {
|
||||||
_longPressLastGlobalPosition = details.globalPosition;
|
_longPressLastGlobalPosition = details.globalPosition;
|
||||||
_onVerticalDragStart();
|
_onVerticalDragStart();
|
||||||
|
@ -235,6 +237,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
onVerticalDragStart: (_) => _onVerticalDragStart(),
|
onVerticalDragStart: (_) => _onVerticalDragStart(),
|
||||||
onVerticalDragUpdate: (details) => _onVerticalDragUpdate(details.delta.dy),
|
onVerticalDragUpdate: (details) => _onVerticalDragUpdate(details.delta.dy),
|
||||||
onVerticalDragEnd: (_) => _onVerticalDragEnd(),
|
onVerticalDragEnd: (_) => _onVerticalDragEnd(),
|
||||||
|
longPressTimeout: settings.longPressTimeout,
|
||||||
child: ValueListenableBuilder<double>(
|
child: ValueListenableBuilder<double>(
|
||||||
valueListenable: _thumbOffsetNotifier,
|
valueListenable: _thumbOffsetNotifier,
|
||||||
builder: (context, thumbOffset, child) => Container(
|
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/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/theme/styles.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/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/grid/sections/list_layout.dart';
|
import 'package:aves/widgets/common/grid/sections/list_layout.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -42,7 +43,7 @@ class SectionHeader<T> extends StatelessWidget {
|
||||||
Widget child = Container(
|
Widget child = Container(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
constraints: BoxConstraints(minHeight: leadingSize.height),
|
constraints: BoxConstraints(minHeight: leadingSize.height),
|
||||||
child: GestureDetector(
|
child: AGestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
onLongPress: selectable
|
onLongPress: selectable
|
||||||
? Feedback.wrapForLongPress(() {
|
? Feedback.wrapForLongPress(() {
|
||||||
|
@ -55,6 +56,7 @@ class SectionHeader<T> extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}, context)
|
}, context)
|
||||||
: null,
|
: null,
|
||||||
|
longPressTimeout: settings.longPressTimeout,
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -2,11 +2,12 @@ import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/utils/math_utils.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/build_context.dart';
|
||||||
import 'package:aves/widgets/common/extensions/media_query.dart';
|
import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||||
import 'package:aves/widgets/common/grid/sections/list_layout.dart';
|
import 'package:aves/widgets/common/grid/sections/list_layout.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectable = widget.selectable;
|
final selectable = widget.selectable;
|
||||||
return GestureDetector(
|
return AGestureDetector(
|
||||||
onLongPressStart: selectable
|
onLongPressStart: selectable
|
||||||
? (details) {
|
? (details) {
|
||||||
if (_isScrolling) return;
|
if (_isScrolling) return;
|
||||||
|
@ -137,6 +138,7 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
|
||||||
selection.toggleSelection(item);
|
selection.toggleSelection(item);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
longPressTimeout: settings.longPressTimeout,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -144,7 +146,7 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
|
||||||
void _onScrollChanged() {
|
void _onScrollChanged() {
|
||||||
_isScrolling = true;
|
_isScrolling = true;
|
||||||
_stopScrollMonitoringTimer();
|
_stopScrollMonitoringTimer();
|
||||||
_scrollMonitoringTimer = Timer(kLongPressTimeout + const Duration(milliseconds: 150), () {
|
_scrollMonitoringTimer = Timer(settings.longPressTimeout + const Duration(milliseconds: 150), () {
|
||||||
_isScrolling = false;
|
_isScrolling = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
import 'package:aves/widgets/aves_app.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/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/basic/insets.dart';
|
||||||
import 'package:aves/widgets/common/fx/blurred.dart';
|
import 'package:aves/widgets/common/fx/blurred.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -95,10 +96,13 @@ class AvesAppBar extends StatelessWidget {
|
||||||
child: AvesFloatingBar(
|
child: AvesFloatingBar(
|
||||||
builder: (context, backgroundColor, child) => Material(
|
builder: (context, backgroundColor, child) => Material(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
child: InkWell(
|
child: AInkResponse(
|
||||||
// absorb taps while providing visual feedback
|
// absorb taps while providing visual feedback
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
onLongPress: () {},
|
onLongPress: () {},
|
||||||
|
containedInkWell: true,
|
||||||
|
highlightShape: BoxShape.rectangle,
|
||||||
|
longPressTimeout: settings.longPressTimeout,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'package:aves/theme/themes.dart';
|
||||||
import 'package:aves/view/view.dart';
|
import 'package:aves/view/view.dart';
|
||||||
import 'package:aves/widgets/collection/filter_bar.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/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/basic/popup/menu_row.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
@ -347,13 +348,16 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
),
|
),
|
||||||
child: InkWell(
|
child: AInkResponse(
|
||||||
// as of Flutter v2.8.0, `InkWell` does not have `onLongPressStart` like `GestureDetector`,
|
// as of Flutter v2.8.0, `InkWell` does not have `onLongPressStart` like `GestureDetector`,
|
||||||
// so we get the long press details from the tap instead
|
// so we get the long press details from the tap instead
|
||||||
onTapDown: onLongPress != null ? (details) => _tapPosition = details.globalPosition : null,
|
onTapDown: onLongPress != null ? (details) => _tapPosition = details.globalPosition : null,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
onLongPress: onLongPress,
|
onLongPress: onLongPress,
|
||||||
|
containedInkWell: true,
|
||||||
|
highlightShape: BoxShape.rectangle,
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
|
longPressTimeout: settings.longPressTimeout,
|
||||||
child: FutureBuilder<Color>(
|
child: FutureBuilder<Color>(
|
||||||
future: _colorFuture,
|
future: _colorFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/utils/debouncer.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/latlng_tween.dart' as llt;
|
||||||
import 'package:aves/widgets/common/map/leaflet/scale_layer.dart';
|
import 'package:aves/widgets/common/map/leaflet/scale_layer.dart';
|
||||||
import 'package:aves/widgets/common/map/leaflet/tile_layers.dart';
|
import 'package:aves/widgets/common/map/leaflet/tile_layers.dart';
|
||||||
import 'package:aves_map/aves_map.dart';
|
import 'package:aves_map/aves_map.dart';
|
||||||
import 'package:aves_utils/aves_utils.dart';
|
import 'package:aves_utils/aves_utils.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
@ -130,14 +133,19 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
|
||||||
final markerKey = kv.key;
|
final markerKey = kv.key;
|
||||||
final geoEntry = kv.value;
|
final geoEntry = kv.value;
|
||||||
final latLng = LatLng(geoEntry.latitude!, geoEntry.longitude!);
|
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(
|
return Marker(
|
||||||
point: latLng,
|
point: latLng,
|
||||||
child: GestureDetector(
|
child: AGestureDetector(
|
||||||
onTap: () => widget.onMarkerTap?.call(geoEntry),
|
onTap: () => widget.onMarkerTap?.call(geoEntry),
|
||||||
// marker tap handling prevents the default handling of focal zoom on double tap,
|
// marker tap handling prevents the default handling of focal zoom on double tap,
|
||||||
// so we reimplement the double tap gesture here
|
// so we reimplement the double tap gesture here
|
||||||
onDoubleTap: interactive ? () => _zoomBy(1, focalPoint: latLng) : null,
|
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),
|
child: widget.markerWidgetBuilder(markerKey),
|
||||||
),
|
),
|
||||||
width: markerSize.width,
|
width: markerSize.width,
|
||||||
|
|
|
@ -76,6 +76,7 @@ class _DebugSettingsSectionState extends State<DebugSettingsSection> with Automa
|
||||||
'locale': '${settings.locale}',
|
'locale': '${settings.locale}',
|
||||||
'systemLocales': '${WidgetsBinding.instance.platformDispatcher.locales}',
|
'systemLocales': '${WidgetsBinding.instance.platformDispatcher.locales}',
|
||||||
'topEntryIds': '${settings.topEntryIds}',
|
'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/captioned_button.dart';
|
||||||
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.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,
|
maxSimultaneousDrags: 1,
|
||||||
onDragStarted: () => _setDraggedAvailableAction(action),
|
onDragStarted: () => _setDraggedAvailableAction(action),
|
||||||
onDragEnd: (details) => _setDraggedAvailableAction(null),
|
onDragEnd: (details) => _setDraggedAvailableAction(null),
|
||||||
|
delay: settings.longPressTimeout,
|
||||||
childWhenDragging: child,
|
childWhenDragging: child,
|
||||||
child: 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/identity/buttons/overlay_button.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
import 'package:flutter/widgets.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
|
// so we rely on `onDraggableCanceled` and `onDragCompleted` instead
|
||||||
onDraggableCanceled: (velocity, offset) => _setDraggedQuickAction(null),
|
onDraggableCanceled: (velocity, offset) => _setDraggedQuickAction(null),
|
||||||
onDragCompleted: () => _setDraggedQuickAction(null),
|
onDragCompleted: () => _setDraggedQuickAction(null),
|
||||||
|
delay: settings.longPressTimeout,
|
||||||
childWhenDragging: child,
|
childWhenDragging: child,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
|
@ -188,4 +188,7 @@ class SettingKeys {
|
||||||
|
|
||||||
// cf Android `Settings.Global.TRANSITION_ANIMATION_SCALE`
|
// cf Android `Settings.Global.TRANSITION_ANIMATION_SCALE`
|
||||||
static const platformTransitionAnimationScaleKey = '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`
|
# `OverlaySnackBar` in `/widgets/common/action_mixins/overlay_snack_bar.dart`
|
||||||
# adapts from Flutter v3.23.0 `SnackBar` in `/material/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`
|
# `EagerScaleGestureRecognizer` in `/widgets/common/behaviour/eager_scale_gesture_recognizer.dart`
|
||||||
# adapts from Flutter v3.16.0 `ScaleGestureRecognizer` in `/gestures/scale.dart`
|
# adapts from Flutter v3.16.0 `ScaleGestureRecognizer` in `/gestures/scale.dart`
|
||||||
#
|
#
|
||||||
|
|
Loading…
Reference in a new issue