video: fixed gesture handling
This commit is contained in:
parent
fe7c2d61f9
commit
c761ee013d
4 changed files with 102 additions and 69 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,19 +220,31 @@ 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;
|
||||||
|
} else if (x > .75) {
|
||||||
|
_applyAction(EntryAction.videoSkip10);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (playGesture) {
|
||||||
|
_applyAction(
|
||||||
|
EntryAction.videoTogglePlay,
|
||||||
|
icon: () => videoController.isPlaying ? AIcons.pause : AIcons.play,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
|
@ -239,6 +252,7 @@ class _EntryPageViewState extends State<EntryPageView> {
|
||||||
children: [
|
children: [
|
||||||
_buildMagnifier(
|
_buildMagnifier(
|
||||||
displaySize: videoDisplaySize,
|
displaySize: videoDisplaySize,
|
||||||
|
onDoubleTap: _onDoubleTap,
|
||||||
child: VideoView(
|
child: VideoView(
|
||||||
entry: entry,
|
entry: entry,
|
||||||
controller: videoController,
|
controller: videoController,
|
||||||
|
@ -254,15 +268,6 @@ class _EntryPageViewState extends State<EntryPageView> {
|
||||||
viewStateNotifier: _viewStateNotifier,
|
viewStateNotifier: _viewStateNotifier,
|
||||||
debugMode: true,
|
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)
|
if (useActionGesture)
|
||||||
ValueListenableBuilder<Widget?>(
|
ValueListenableBuilder<Widget?>(
|
||||||
valueListenable: _actionFeedbackChildNotifier,
|
valueListenable: _actionFeedbackChildNotifier,
|
||||||
|
@ -272,14 +277,24 @@ class _EntryPageViewState extends State<EntryPageView> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
_buildVideoCover(videoController, videoDisplaySize),
|
_buildVideoCover(
|
||||||
|
videoController: videoController,
|
||||||
|
videoDisplaySize: videoDisplaySize,
|
||||||
|
onDoubleTap: _onDoubleTap,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue