#622 viewer: fixed side gesture precedence
This commit is contained in:
parent
46aef919be
commit
e1c3bae90b
4 changed files with 114 additions and 45 deletions
|
@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
- crash when decoding large region
|
||||
- viewer position drift during scale
|
||||
- viewer side gesture precedence (next entry by single tap vs zoom by double tap)
|
||||
|
||||
## <a id="v1.10.7"></a>[v1.10.7] - 2024-03-12
|
||||
|
||||
|
|
|
@ -405,6 +405,7 @@ class _EntryPageViewState extends State<EntryPageView> with TickerProviderStateM
|
|||
controller: controller ?? _magnifierController,
|
||||
contentSize: displaySize ?? entry.displaySize,
|
||||
allowOriginalScaleBeyondRange: !isWallpaperMode,
|
||||
allowDoubleTap: _allowDoubleTap,
|
||||
minScale: minScale,
|
||||
maxScale: maxScale,
|
||||
initialScale: viewerController.initialScale,
|
||||
|
@ -434,22 +435,34 @@ class _EntryPageViewState extends State<EntryPageView> with TickerProviderStateM
|
|||
}
|
||||
}
|
||||
|
||||
void _onTap({Alignment? alignment}) {
|
||||
Notification? _handleSideSingleTap(Alignment? alignment) {
|
||||
if (settings.viewerGestureSideTapNext && alignment != null) {
|
||||
final x = alignment.x;
|
||||
final sideRatio = _getSideRatio();
|
||||
if (sideRatio != null) {
|
||||
const animate = false;
|
||||
if (x < sideRatio) {
|
||||
(context.isRtl ? const ShowNextEntryNotification(animate: animate) : const ShowPreviousEntryNotification(animate: animate)).dispatch(context);
|
||||
return;
|
||||
return context.isRtl ? const ShowNextEntryNotification(animate: animate) : const ShowPreviousEntryNotification(animate: animate);
|
||||
} else if (x > 1 - sideRatio) {
|
||||
(context.isRtl ? const ShowPreviousEntryNotification(animate: animate) : const ShowNextEntryNotification(animate: animate)).dispatch(context);
|
||||
return;
|
||||
return context.isRtl ? const ShowPreviousEntryNotification(animate: animate) : const ShowNextEntryNotification(animate: animate);
|
||||
}
|
||||
}
|
||||
}
|
||||
const ToggleOverlayNotification().dispatch(context);
|
||||
return null;
|
||||
}
|
||||
|
||||
void _onTap({Alignment? alignment}) => (_handleSideSingleTap(alignment) ?? const ToggleOverlayNotification()).dispatch(context);
|
||||
|
||||
// side gesture handling by precedence:
|
||||
// - seek in video by side double tap (if enabled)
|
||||
// - go to previous/next entry by side single tap (if enabled)
|
||||
// - zoom in/out by double tap
|
||||
bool _allowDoubleTap(Alignment alignment) {
|
||||
if (entry.isVideo && settings.videoGestureSideDoubleTapSeek) {
|
||||
return true;
|
||||
}
|
||||
final actionNotification = _handleSideSingleTap(alignment);
|
||||
return actionNotification == null;
|
||||
}
|
||||
|
||||
void _onMediaCommand(MediaCommandEvent event) {
|
||||
|
|
|
@ -36,6 +36,7 @@ class AvesMagnifier extends StatefulWidget {
|
|||
|
||||
final bool allowOriginalScaleBeyondRange;
|
||||
final bool allowGestureScaleBeyondRange;
|
||||
final MagnifierDoubleTapCallback? allowDoubleTap;
|
||||
final double panInertia;
|
||||
|
||||
// Defines the minimum size in which the image will be allowed to assume, it is proportional to the original image size.
|
||||
|
@ -64,6 +65,7 @@ class AvesMagnifier extends StatefulWidget {
|
|||
this.viewportPadding = EdgeInsets.zero,
|
||||
this.allowOriginalScaleBeyondRange = true,
|
||||
this.allowGestureScaleBeyondRange = true,
|
||||
this.allowDoubleTap,
|
||||
this.minScale = const ScaleLevel(factor: .0),
|
||||
this.maxScale = const ScaleLevel(factor: double.infinity),
|
||||
this.initialScale = const ScaleLevel(ref: ScaleReference.contained),
|
||||
|
@ -356,35 +358,55 @@ class _AvesMagnifierState extends State<AvesMagnifier> with TickerProviderStateM
|
|||
return Duration(milliseconds: gestureVelocity != 0 ? (animationVelocity / gestureVelocity * 1000).round() : 0);
|
||||
}
|
||||
|
||||
void onTap(TapUpDetails details) {
|
||||
Alignment? _getTapAlignment(Offset viewportTapPosition) {
|
||||
final boundaries = scaleBoundaries;
|
||||
if (boundaries == null) return null;
|
||||
|
||||
final viewportSize = boundaries.viewportSize;
|
||||
return Alignment(viewportTapPosition.dx / viewportSize.width, viewportTapPosition.dy / viewportSize.height);
|
||||
}
|
||||
|
||||
Offset? _getChildTapPosition(Offset viewportTapPosition) {
|
||||
final boundaries = scaleBoundaries;
|
||||
if (boundaries == null) return null;
|
||||
|
||||
return boundaries.viewportToContentPosition(controller, viewportTapPosition);
|
||||
}
|
||||
|
||||
void _onTapUp(TapUpDetails details) {
|
||||
final onTap = widget.onTap;
|
||||
if (onTap == null) return;
|
||||
|
||||
final boundaries = scaleBoundaries;
|
||||
if (boundaries == null) return;
|
||||
|
||||
final viewportTapPosition = details.localPosition;
|
||||
final viewportSize = boundaries.viewportSize;
|
||||
final alignment = Alignment(viewportTapPosition.dx / viewportSize.width, viewportTapPosition.dy / viewportSize.height);
|
||||
final childTapPosition = boundaries.viewportToContentPosition(controller, viewportTapPosition);
|
||||
|
||||
onTap(context, controller.currentState, alignment, childTapPosition);
|
||||
final alignment = _getTapAlignment(viewportTapPosition);
|
||||
final childTapPosition = _getChildTapPosition(viewportTapPosition);
|
||||
if (alignment != null && childTapPosition != null) {
|
||||
onTap(context, controller.currentState, alignment, childTapPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void onDoubleTap(TapDownDetails details) {
|
||||
final boundaries = scaleBoundaries;
|
||||
if (boundaries == null) return;
|
||||
bool _allowDoubleTap(Offset localPosition) {
|
||||
final allowDoubleTap = widget.allowDoubleTap;
|
||||
if (allowDoubleTap != null) {
|
||||
final alignment = _getTapAlignment(localPosition);
|
||||
if (alignment != null) {
|
||||
return allowDoubleTap(alignment);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
final viewportTapPosition = details.localPosition;
|
||||
void _onDoubleTap(TapDownDetails details) {
|
||||
final onDoubleTap = widget.onDoubleTap;
|
||||
if (onDoubleTap != null) {
|
||||
final viewportSize = boundaries.viewportSize;
|
||||
final alignment = Alignment(viewportTapPosition.dx / viewportSize.width, viewportTapPosition.dy / viewportSize.height);
|
||||
if (onDoubleTap(alignment) == true) return;
|
||||
final alignment = _getTapAlignment(details.localPosition);
|
||||
if (alignment != null && onDoubleTap(alignment)) return;
|
||||
}
|
||||
|
||||
final childTapPosition = boundaries.viewportToContentPosition(controller, viewportTapPosition);
|
||||
nextScaleState(ChangeSource.gesture, childFocalPoint: childTapPosition);
|
||||
final childTapPosition = _getChildTapPosition(details.localPosition);
|
||||
if (childTapPosition != null) {
|
||||
nextScaleState(ChangeSource.gesture, childFocalPoint: childTapPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void animateScale(double? from, double? to) {
|
||||
|
@ -454,8 +476,9 @@ class _AvesMagnifierState extends State<AvesMagnifier> with TickerProviderStateM
|
|||
onScaleStart: onScaleStart,
|
||||
onScaleUpdate: onScaleUpdate,
|
||||
onScaleEnd: onScaleEnd,
|
||||
onTapUp: widget.onTap == null ? null : onTap,
|
||||
onDoubleTap: onDoubleTap,
|
||||
onTapUp: widget.onTap == null ? null : _onTapUp,
|
||||
onDoubleTap: _onDoubleTap,
|
||||
allowDoubleTap: _allowDoubleTap,
|
||||
child: Padding(
|
||||
padding: widget.viewportPadding,
|
||||
child: LayoutBuilder(
|
||||
|
@ -533,6 +556,7 @@ typedef MagnifierTapCallback = Function(
|
|||
Alignment alignment,
|
||||
Offset childTapPosition,
|
||||
);
|
||||
typedef MagnifierDoubleTapPredicate = bool Function(Offset localPosition);
|
||||
typedef MagnifierDoubleTapCallback = bool Function(Alignment alignment);
|
||||
typedef MagnifierGestureScaleStartCallback = void Function(ScaleStartDetails details, bool doubleTap, ScaleBoundaries boundaries);
|
||||
typedef MagnifierGestureScaleUpdateCallback = bool Function(ScaleUpdateDetails details);
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
import 'package:aves_magnifier/src/core/scale_gesture_recognizer.dart';
|
||||
import 'package:aves_magnifier/aves_magnifier.dart';
|
||||
import 'package:aves_magnifier/src/pan/edge_hit_detector.dart';
|
||||
import 'package:aves_magnifier/src/pan/gesture_detector_scope.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MagnifierGestureDetector extends StatefulWidget {
|
||||
final EdgeHitDetector hitDetector;
|
||||
|
||||
final void Function(ScaleStartDetails details, bool doubleTap)? onScaleStart;
|
||||
final GestureScaleUpdateCallback? onScaleUpdate;
|
||||
final GestureScaleEndCallback? onScaleEnd;
|
||||
|
||||
final GestureTapDownCallback? onTapDown;
|
||||
final GestureTapUpCallback? onTapUp;
|
||||
final GestureTapDownCallback? onDoubleTap;
|
||||
|
||||
final MagnifierDoubleTapPredicate? allowDoubleTap;
|
||||
final HitTestBehavior? behavior;
|
||||
final Widget? child;
|
||||
|
||||
const MagnifierGestureDetector({
|
||||
super.key,
|
||||
required this.hitDetector,
|
||||
|
@ -14,22 +27,11 @@ class MagnifierGestureDetector extends StatefulWidget {
|
|||
this.onTapDown,
|
||||
this.onTapUp,
|
||||
this.onDoubleTap,
|
||||
this.allowDoubleTap,
|
||||
this.behavior,
|
||||
this.child,
|
||||
});
|
||||
|
||||
final EdgeHitDetector hitDetector;
|
||||
final void Function(ScaleStartDetails details, bool doubleTap)? onScaleStart;
|
||||
final GestureScaleUpdateCallback? onScaleUpdate;
|
||||
final GestureScaleEndCallback? onScaleEnd;
|
||||
|
||||
final GestureTapDownCallback? onTapDown;
|
||||
final GestureTapUpCallback? onTapUp;
|
||||
final GestureTapDownCallback? onDoubleTap;
|
||||
|
||||
final HitTestBehavior? behavior;
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
State<MagnifierGestureDetector> createState() => _MagnifierGestureDetectorState();
|
||||
}
|
||||
|
@ -78,8 +80,11 @@ class _MagnifierGestureDetectorState extends State<MagnifierGestureDetector> {
|
|||
);
|
||||
}
|
||||
|
||||
gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
|
||||
() => DoubleTapGestureRecognizer(debugOwner: this),
|
||||
gestures[MagnifierDoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<MagnifierDoubleTapGestureRecognizer>(
|
||||
() => MagnifierDoubleTapGestureRecognizer(
|
||||
debugOwner: this,
|
||||
allowDoubleTap: widget.allowDoubleTap ?? (_) => true,
|
||||
),
|
||||
(instance) {
|
||||
final onDoubleTap = widget.onDoubleTap;
|
||||
instance
|
||||
|
@ -87,8 +92,11 @@ class _MagnifierGestureDetectorState extends State<MagnifierGestureDetector> {
|
|||
..onDoubleTapDown = _onDoubleTapDown
|
||||
..onDoubleTap = onDoubleTap != null
|
||||
? () {
|
||||
onDoubleTap(doubleTapDetails.value!);
|
||||
doubleTapDetails.value = null;
|
||||
final details = doubleTapDetails.value;
|
||||
if (details != null) {
|
||||
onDoubleTap(details);
|
||||
doubleTapDetails.value = null;
|
||||
}
|
||||
}
|
||||
: null;
|
||||
},
|
||||
|
@ -103,5 +111,28 @@ class _MagnifierGestureDetectorState extends State<MagnifierGestureDetector> {
|
|||
|
||||
void _onDoubleTapCancel() => doubleTapDetails.value = null;
|
||||
|
||||
void _onDoubleTapDown(TapDownDetails details) => doubleTapDetails.value = details;
|
||||
void _onDoubleTapDown(TapDownDetails details) {
|
||||
if (widget.allowDoubleTap?.call(details.localPosition) ?? true) {
|
||||
doubleTapDetails.value = details;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MagnifierDoubleTapGestureRecognizer extends DoubleTapGestureRecognizer {
|
||||
final MagnifierDoubleTapPredicate allowDoubleTap;
|
||||
|
||||
MagnifierDoubleTapGestureRecognizer({
|
||||
super.debugOwner,
|
||||
super.supportedDevices,
|
||||
super.allowedButtonsFilter,
|
||||
required this.allowDoubleTap,
|
||||
});
|
||||
|
||||
@override
|
||||
bool isPointerAllowed(PointerDownEvent event) {
|
||||
if (!allowDoubleTap(event.localPosition)) {
|
||||
return false;
|
||||
}
|
||||
return super.isPointerAllowed(event);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue