fix: 使用点击序号防止双击误暂停
This commit is contained in:
1 parent
9ddd7fc833
commit
bbc759473c
7 files changed
+113
-61
No files matched your search
@@ -120,8 +120,7 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
bool _scrubbing = false;
|
||||
double? _speedBeforeBoost;
|
||||
bool? _chromeBeforeBoost;
|
||||
bool _doubleTapHandling = false;
|
||||
Timer? _doubleTapGuardTimer;
|
||||
DateTime? _lastDoubleTapAt;
|
||||
|
||||
/// 收藏切换后的本地增量:基准是详情快照,切换成功按服务端状态 ±1。
|
||||
final Map<String, int> _favoriteDelta = {};
|
||||
@@ -263,7 +262,6 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
appRouteObserver.unsubscribe(this);
|
||||
_spriteAxisService.dispose();
|
||||
_thumbnailService.dispose();
|
||||
_doubleTapGuardTimer?.cancel();
|
||||
logic.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -336,7 +334,7 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
}
|
||||
|
||||
void _handleFeedSurfaceTap() {
|
||||
if (_scrubbing || _doubleTapHandling) return;
|
||||
if (_scrubbing || _doubleTapGuardActive) return;
|
||||
if (!_chromeVisible) {
|
||||
_restoreFeedChrome();
|
||||
return;
|
||||
@@ -345,20 +343,23 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
}
|
||||
|
||||
void _togglePlayback() {
|
||||
if (_doubleTapHandling) return;
|
||||
if (_doubleTapGuardActive) return;
|
||||
logic.session.setUserWantsPlay(!logic.session.userWantsPlay);
|
||||
}
|
||||
|
||||
bool get _doubleTapGuardActive {
|
||||
final lastDoubleTapAt = _lastDoubleTapAt;
|
||||
return lastDoubleTapAt != null &&
|
||||
DateTime.now().difference(lastDoubleTapAt) <
|
||||
const Duration(milliseconds: 350);
|
||||
}
|
||||
|
||||
void _handleDoubleTapLike(
|
||||
BuildContext context,
|
||||
InteractionController interactions,
|
||||
Episode episode,
|
||||
) {
|
||||
_doubleTapHandling = true;
|
||||
_doubleTapGuardTimer?.cancel();
|
||||
_doubleTapGuardTimer = Timer(const Duration(milliseconds: 300), () {
|
||||
_doubleTapHandling = false;
|
||||
});
|
||||
_lastDoubleTapAt = DateTime.now();
|
||||
unawaited(_toggleLike(context, interactions, episode, likeOnly: true));
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ class HomeFeedOverlay extends StatefulWidget {
|
||||
required this.onBoostEnd,
|
||||
required this.onMore,
|
||||
this.onDoubleTapLike,
|
||||
this.now,
|
||||
this.likeCount,
|
||||
this.favoriteCount,
|
||||
this.commentCount,
|
||||
@@ -94,6 +95,7 @@ class HomeFeedOverlay extends StatefulWidget {
|
||||
final VoidCallback onBoostEnd;
|
||||
final VoidCallback onMore;
|
||||
final ValueChanged<Offset>? onDoubleTapLike;
|
||||
final DateTime Function()? now;
|
||||
final VoidCallback? onScrubStart;
|
||||
final ValueChanged<bool>? onScrubEnd;
|
||||
final ValueChanged<bool>? onScrubVisibilityChanged;
|
||||
@@ -107,12 +109,13 @@ class HomeFeedOverlay extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
static const Duration _singleTapDelay = Duration(milliseconds: 280);
|
||||
static const Duration _singleTapDelay = Duration(milliseconds: 320);
|
||||
|
||||
bool _scrubbing = false;
|
||||
bool _wasVisibleBeforeScrub = true;
|
||||
Timer? _singleTapTimer;
|
||||
DateTime? _lastTapAt;
|
||||
int _tapSeq = 0;
|
||||
int _nextHeartBurstId = 0;
|
||||
final List<({int id, Offset position})> _heartBursts = [];
|
||||
|
||||
@@ -126,6 +129,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tapSeq++;
|
||||
_singleTapTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -249,11 +253,11 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
}
|
||||
|
||||
void _handleTapUp(TapUpDetails details) {
|
||||
final now = DateTime.now();
|
||||
final tapSeq = ++_tapSeq;
|
||||
final now = widget.now?.call() ?? DateTime.now();
|
||||
final lastTap = _lastTapAt;
|
||||
if (_singleTapTimer != null &&
|
||||
lastTap != null &&
|
||||
now.difference(lastTap) <= _singleTapDelay) {
|
||||
if (lastTap != null && now.difference(lastTap) <= _singleTapDelay) {
|
||||
_tapSeq++;
|
||||
_singleTapTimer?.cancel();
|
||||
_singleTapTimer = null;
|
||||
_lastTapAt = null;
|
||||
@@ -262,11 +266,22 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
}
|
||||
_lastTapAt = now;
|
||||
_singleTapTimer?.cancel();
|
||||
_singleTapTimer = Timer(_singleTapDelay, () {
|
||||
_singleTapTimer = null;
|
||||
late final Timer pending;
|
||||
pending = Timer(_singleTapDelay, () {
|
||||
if (identical(_singleTapTimer, pending)) {
|
||||
_singleTapTimer = null;
|
||||
}
|
||||
if (tapSeq != _tapSeq) return;
|
||||
final latestTap = _lastTapAt;
|
||||
if (latestTap != null &&
|
||||
(widget.now?.call() ?? DateTime.now()).difference(latestTap) <
|
||||
_singleTapDelay) {
|
||||
return;
|
||||
}
|
||||
_lastTapAt = null;
|
||||
if (mounted) widget.onTogglePlay();
|
||||
});
|
||||
_singleTapTimer = pending;
|
||||
}
|
||||
|
||||
void _showHearts(Offset position) {
|
||||
@@ -281,6 +296,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
}
|
||||
|
||||
void _handleLongPressStart(LongPressStartDetails details, double width) {
|
||||
_tapSeq++;
|
||||
_singleTapTimer?.cancel();
|
||||
_singleTapTimer = null;
|
||||
_lastTapAt = null;
|
||||
|
||||
@@ -17,13 +17,11 @@ class PlayerVideoSurface extends StatefulWidget {
|
||||
required this.controller,
|
||||
required this.videoUrl,
|
||||
required this.theater,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final VideoPlayerController? controller;
|
||||
final String videoUrl;
|
||||
final bool theater;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
State<PlayerVideoSurface> createState() => _PlayerVideoSurfaceState();
|
||||
@@ -69,7 +67,6 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface> {
|
||||
controller: controller,
|
||||
value: value!,
|
||||
theater: widget.theater,
|
||||
onTap: widget.onTap,
|
||||
)
|
||||
: const SizedBox.expand(),
|
||||
),
|
||||
@@ -141,17 +138,15 @@ class _VideoLayer extends StatelessWidget {
|
||||
required this.controller,
|
||||
required this.value,
|
||||
required this.theater,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final VideoPlayerController controller;
|
||||
final VideoPlayerValue value;
|
||||
final bool theater;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = FittedBox(
|
||||
return FittedBox(
|
||||
fit: theater ? BoxFit.contain : BoxFit.cover,
|
||||
child: SizedBox(
|
||||
width: value.size.width,
|
||||
@@ -159,12 +154,5 @@ class _VideoLayer extends StatelessWidget {
|
||||
child: VideoPlayer(controller, key: ValueKey(controller)),
|
||||
),
|
||||
);
|
||||
final tap = onTap;
|
||||
if (tap == null) return child;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: tap,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user