video: fixed gesture handling

This commit is contained in:
Thibault Deckers 2022-03-04 12:24:39 +09:00
parent fe7c2d61f9
commit c761ee013d
4 changed files with 102 additions and 69 deletions

View file

@ -19,6 +19,7 @@ class MagnifierCore extends StatefulWidget {
final bool applyScale; final bool applyScale;
final double panInertia; final double panInertia;
final MagnifierTapCallback? onTap; final MagnifierTapCallback? onTap;
final MagnifierDoubleTapCallback? onDoubleTap;
final Widget child; final Widget child;
const MagnifierCore({ const MagnifierCore({
@ -27,7 +28,8 @@ class MagnifierCore extends StatefulWidget {
required this.scaleStateCycle, required this.scaleStateCycle,
required this.applyScale, required this.applyScale,
this.panInertia = .2, this.panInertia = .2,
required this.onTap, this.onTap,
this.onDoubleTap,
required this.child, required this.child,
}) : super(key: key); }) : super(key: key);
@ -204,6 +206,12 @@ class _MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateM
void onDoubleTap(TapDownDetails details) { void onDoubleTap(TapDownDetails details) {
final viewportTapPosition = details.localPosition; final viewportTapPosition = details.localPosition;
if (widget.onDoubleTap != null) {
final viewportSize = scaleBoundaries.viewportSize;
final alignment = Alignment(viewportTapPosition.dx / viewportSize.width, viewportTapPosition.dy / viewportSize.height);
if (widget.onDoubleTap?.call(alignment) == true) return;
}
final childTapPosition = scaleBoundaries.viewportToChildPosition(controller, viewportTapPosition); final childTapPosition = scaleBoundaries.viewportToChildPosition(controller, viewportTapPosition);
nextScaleState(ChangeSource.gesture, childFocalPoint: childTapPosition); nextScaleState(ChangeSource.gesture, childFocalPoint: childTapPosition);
} }

View file

@ -29,6 +29,7 @@ class Magnifier extends StatelessWidget {
this.scaleStateCycle = defaultScaleStateCycle, this.scaleStateCycle = defaultScaleStateCycle,
this.applyScale = true, this.applyScale = true,
this.onTap, this.onTap,
this.onDoubleTap,
required this.child, required this.child,
}) : super(key: key); }) : super(key: key);
@ -49,6 +50,7 @@ class Magnifier extends StatelessWidget {
final ScaleStateCycle scaleStateCycle; final ScaleStateCycle scaleStateCycle;
final bool applyScale; final bool applyScale;
final MagnifierTapCallback? onTap; final MagnifierTapCallback? onTap;
final MagnifierDoubleTapCallback? onDoubleTap;
final Widget child; final Widget child;
@override @override
@ -68,6 +70,7 @@ class Magnifier extends StatelessWidget {
scaleStateCycle: scaleStateCycle, scaleStateCycle: scaleStateCycle,
applyScale: applyScale, applyScale: applyScale,
onTap: onTap, onTap: onTap,
onDoubleTap: onDoubleTap,
child: child, child: child,
); );
}, },
@ -81,3 +84,7 @@ typedef MagnifierTapCallback = Function(
MagnifierState state, MagnifierState state,
Offset childTapPosition, Offset childTapPosition,
); );
typedef MagnifierDoubleTapCallback = bool Function(
Alignment alignment,
);

View file

@ -57,10 +57,10 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
_togglePlayPause(context, controller); _togglePlayPause(context, controller);
break; break;
case EntryAction.videoReplay10: case EntryAction.videoReplay10:
if (controller.isReady) controller.seekTo(controller.currentPosition - 10000); controller.seekTo(controller.currentPosition - 10000);
break; break;
case EntryAction.videoSkip10: case EntryAction.videoSkip10:
if (controller.isReady) controller.seekTo(controller.currentPosition + 10000); controller.seekTo(controller.currentPosition + 10000);
break; break;
case EntryAction.open: case EntryAction.open:
final entry = controller.entry; final entry = controller.entry;

View file

@ -30,6 +30,7 @@ import 'package:collection/collection.dart';
import 'package:decorated_icon/decorated_icon.dart'; import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
class EntryPageView extends StatefulWidget { class EntryPageView extends StatefulWidget {
final AvesEntry mainEntry, pageEntry; final AvesEntry mainEntry, pageEntry;
@ -173,7 +174,7 @@ class _EntryPageViewState extends State<EntryPageView> {
} }
Widget _buildSvgView() { Widget _buildSvgView() {
var child = _buildMagnifier( return _buildMagnifier(
maxScale: const ScaleLevel(factor: 25), maxScale: const ScaleLevel(factor: 25),
scaleStateCycle: _vectorScaleStateCycle, scaleStateCycle: _vectorScaleStateCycle,
applyScale: false, applyScale: false,
@ -186,25 +187,25 @@ class _EntryPageViewState extends State<EntryPageView> {
), ),
), ),
); );
return child;
} }
Widget _buildVideoView() { Widget _buildVideoView() {
final videoController = context.read<VideoConductor>().getController(entry); final videoController = context.read<VideoConductor>().getController(entry);
if (videoController == null) return const SizedBox(); if (videoController == null) return const SizedBox();
Positioned _buildDoubleTapDetector( return ValueListenableBuilder<double>(
EntryAction action, { valueListenable: videoController.sarNotifier,
double widthFactor = 1, builder: (context, sar, child) {
AlignmentGeometry alignment = Alignment.center, final videoDisplaySize = entry.videoDisplaySize(sar);
IconData? Function()? icon,
}) { return Selector<Settings, Tuple2<bool, bool>>(
return Positioned.fill( selector: (context, s) => Tuple2(s.videoGestureDoubleTapTogglePlay, s.videoGestureSideDoubleTapSeek),
child: FractionallySizedBox( builder: (context, s, child) {
alignment: alignment, final playGesture = s.item1;
widthFactor: widthFactor, final seekGesture = s.item2;
child: GestureDetector( final useActionGesture = playGesture || seekGesture;
onDoubleTap: () {
void _applyAction(EntryAction action, {IconData? Function()? icon}) {
_actionFeedbackChildNotifier.value = DecoratedIcon( _actionFeedbackChildNotifier.value = DecoratedIcon(
icon?.call() ?? action.getIconData(), icon?.call() ?? action.getIconData(),
size: 48, size: 48,
@ -219,67 +220,81 @@ class _EntryPageViewState extends State<EntryPageView> {
controller: videoController, controller: videoController,
action: action, action: action,
).dispatch(context); ).dispatch(context);
}, }
),
),
);
}
return ValueListenableBuilder<double>( MagnifierDoubleTapCallback? _onDoubleTap = useActionGesture
valueListenable: videoController.sarNotifier, ? (alignment) {
builder: (context, sar, child) { final x = alignment.x;
final videoDisplaySize = entry.videoDisplaySize(sar); if (seekGesture) {
final playGesture = settings.videoGestureDoubleTapTogglePlay; if (x < .25) {
final seekGesture = settings.videoGestureSideDoubleTapSeek; _applyAction(EntryAction.videoReplay10);
final useActionGesture = playGesture || seekGesture; return true;
return Stack( } else if (x > .75) {
fit: StackFit.expand, _applyAction(EntryAction.videoSkip10);
children: [ return true;
Stack( }
}
if (playGesture) {
_applyAction(
EntryAction.videoTogglePlay,
icon: () => videoController.isPlaying ? AIcons.pause : AIcons.play,
);
return true;
}
return false;
}
: null;
return Stack(
fit: StackFit.expand,
children: [ children: [
_buildMagnifier( Stack(
displaySize: videoDisplaySize, children: [
child: VideoView( _buildMagnifier(
entry: entry, displaySize: videoDisplaySize,
controller: videoController, onDoubleTap: _onDoubleTap,
), child: VideoView(
), entry: entry,
VideoSubtitles( controller: videoController,
controller: videoController, ),
viewStateNotifier: _viewStateNotifier,
),
if (settings.videoShowRawTimedText)
VideoSubtitles(
controller: videoController,
viewStateNotifier: _viewStateNotifier,
debugMode: true,
),
if (playGesture)
_buildDoubleTapDetector(
EntryAction.videoTogglePlay,
icon: () => videoController.isPlaying ? AIcons.pause : AIcons.play,
),
if (seekGesture) ...[
_buildDoubleTapDetector(EntryAction.videoReplay10, widthFactor: .25, alignment: Alignment.centerLeft),
_buildDoubleTapDetector(EntryAction.videoSkip10, widthFactor: .25, alignment: Alignment.centerRight),
],
if (useActionGesture)
ValueListenableBuilder<Widget?>(
valueListenable: _actionFeedbackChildNotifier,
builder: (context, feedbackChild, child) => ActionFeedback(
child: feedbackChild,
), ),
), VideoSubtitles(
controller: videoController,
viewStateNotifier: _viewStateNotifier,
),
if (settings.videoShowRawTimedText)
VideoSubtitles(
controller: videoController,
viewStateNotifier: _viewStateNotifier,
debugMode: true,
),
if (useActionGesture)
ValueListenableBuilder<Widget?>(
valueListenable: _actionFeedbackChildNotifier,
builder: (context, feedbackChild, child) => ActionFeedback(
child: feedbackChild,
),
),
],
),
_buildVideoCover(
videoController: videoController,
videoDisplaySize: videoDisplaySize,
onDoubleTap: _onDoubleTap,
),
], ],
), );
_buildVideoCover(videoController, videoDisplaySize), },
],
); );
}, },
); );
} }
StreamBuilder<VideoStatus> _buildVideoCover(AvesVideoController videoController, Size videoDisplaySize) { StreamBuilder<VideoStatus> _buildVideoCover({
required AvesVideoController videoController,
required Size videoDisplaySize,
required MagnifierDoubleTapCallback? onDoubleTap,
}) {
// fade out image to ease transition with the player // fade out image to ease transition with the player
return StreamBuilder<VideoStatus>( return StreamBuilder<VideoStatus>(
stream: videoController.statusStream, stream: videoController.statusStream,
@ -307,6 +322,7 @@ class _EntryPageViewState extends State<EntryPageView> {
return _buildMagnifier( return _buildMagnifier(
controller: coverController, controller: coverController,
displaySize: coverSize, displaySize: coverSize,
onDoubleTap: onDoubleTap,
child: Image( child: Image(
image: videoCoverUriImage, image: videoCoverUriImage,
), ),
@ -342,6 +358,7 @@ class _EntryPageViewState extends State<EntryPageView> {
ScaleLevel maxScale = maxScale, ScaleLevel maxScale = maxScale,
ScaleStateCycle scaleStateCycle = defaultScaleStateCycle, ScaleStateCycle scaleStateCycle = defaultScaleStateCycle,
bool applyScale = true, bool applyScale = true,
MagnifierDoubleTapCallback? onDoubleTap,
required Widget child, required Widget child,
}) { }) {
return Magnifier( return Magnifier(
@ -355,6 +372,7 @@ class _EntryPageViewState extends State<EntryPageView> {
scaleStateCycle: scaleStateCycle, scaleStateCycle: scaleStateCycle,
applyScale: applyScale, applyScale: applyScale,
onTap: (c, d, s, o) => _onTap(), onTap: (c, d, s, o) => _onTap(),
onDoubleTap: onDoubleTap,
child: child, child: child,
); );
} }