fix: 修正双击点赞与心形联动
This commit is contained in:
1 parent
78d0166eb7
commit
68ccfa21c0
8 files changed
+200
-64
No files matched your search
@@ -516,16 +516,8 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
controller: videoPort?.controller,
|
||||
videoUrl: videoPort?.controller.dataSource ?? episode.videoUrl,
|
||||
theater: theater,
|
||||
onTap: () => _handleFeedSurfaceTap(),
|
||||
onTap: widget.homeMode ? () {} : _handleFeedSurfaceTap,
|
||||
),
|
||||
if (widget.homeMode && _chromeVisible && !logic.chromeHidden)
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
key: const Key('home-feed-video-toggle'),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _handleFeedSurfaceTap,
|
||||
),
|
||||
),
|
||||
// 票据三态之一:起播前正在向后端要签名地址
|
||||
if (selected && logic.ticketLoading)
|
||||
const Center(
|
||||
@@ -640,24 +632,11 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
commentCount: _commentCounts[episode.id] ?? episode.commentCount,
|
||||
saved: context.watch<FavoriteController>().isSaved(drama.id),
|
||||
onLike: () async {
|
||||
if (await ensureSignedIn(context)) {
|
||||
if (!context.mounted) return;
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
final result = await interactions.toggleEpisodeLike(episode.id);
|
||||
if (!result.isSuccess && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
result.errorMessage ??
|
||||
AppLocalizations.of(context)!.likeFailed,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
await _toggleLike(context, interactions, episode);
|
||||
},
|
||||
onDoubleTapLike: (_) =>
|
||||
unawaited(_likeFromDoubleTap(context, interactions, episode)),
|
||||
onDoubleTapLike: (_) => unawaited(
|
||||
_toggleLike(context, interactions, episode, likeOnly: true),
|
||||
),
|
||||
onSave: () => unawaited(_toggleSave(context, drama)),
|
||||
onComments: () => unawaited(_openComments(context, drama, episode)),
|
||||
onShare: () => logic.shareDrama(context, drama),
|
||||
|
||||
@@ -81,34 +81,17 @@ extension _PlayerViewActions on _PlayerPageState {
|
||||
Future<void> _toggleLike(
|
||||
BuildContext context,
|
||||
InteractionController interactions,
|
||||
Episode episode,
|
||||
) async {
|
||||
if (!await ensureSignedIn(context) || !context.mounted) return;
|
||||
Episode episode, {
|
||||
bool likeOnly = false,
|
||||
}) async {
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
final result = await interactions.toggleEpisodeLike(episode.id);
|
||||
if (result.isSuccess || !context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
result.errorMessage ?? AppLocalizations.of(context)!.likeFailed,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _likeFromDoubleTap(
|
||||
BuildContext context,
|
||||
InteractionController interactions,
|
||||
Episode episode,
|
||||
) async {
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
if (interactions.isEpisodeLiked(episode.id) ||
|
||||
interactions.isEpisodeBusy(episode.id)) {
|
||||
if (interactions.isEpisodeBusy(episode.id) ||
|
||||
(likeOnly && interactions.isEpisodeLiked(episode.id))) {
|
||||
return;
|
||||
}
|
||||
if (!await ensureSignedIn(context) || !context.mounted) return;
|
||||
if (interactions.isEpisodeLiked(episode.id) ||
|
||||
interactions.isEpisodeBusy(episode.id)) {
|
||||
if (interactions.isEpisodeBusy(episode.id) ||
|
||||
(likeOnly && interactions.isEpisodeLiked(episode.id))) {
|
||||
return;
|
||||
}
|
||||
final result = await interactions.toggleEpisodeLike(episode.id);
|
||||
|
||||
@@ -107,9 +107,12 @@ class HomeFeedOverlay extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
static const Duration _singleTapDelay = Duration(milliseconds: 280);
|
||||
|
||||
bool _scrubbing = false;
|
||||
bool _wasVisibleBeforeScrub = true;
|
||||
Offset? _doubleTapPosition;
|
||||
Timer? _singleTapTimer;
|
||||
DateTime? _lastTapAt;
|
||||
int _nextHeartBurstId = 0;
|
||||
final List<({int id, Offset position})> _heartBursts = [];
|
||||
|
||||
@@ -121,6 +124,12 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_singleTapTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
@@ -135,10 +144,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
child: GestureDetector(
|
||||
key: const Key('home-feed-gesture'),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _handleSurfaceTap,
|
||||
onDoubleTapDown: (details) =>
|
||||
_doubleTapPosition = details.localPosition,
|
||||
onDoubleTap: _handleDoubleTap,
|
||||
onTapUp: _handleTapUp,
|
||||
onLongPressStart: (details) =>
|
||||
_handleLongPressStart(details, constraints.maxWidth),
|
||||
onLongPressEnd: (_) => widget.onBoostEnd(),
|
||||
@@ -242,13 +248,28 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
widget.onScrubEnd?.call(wasVisible);
|
||||
}
|
||||
|
||||
void _handleSurfaceTap() {
|
||||
widget.onTogglePlay();
|
||||
void _handleTapUp(TapUpDetails details) {
|
||||
final now = DateTime.now();
|
||||
final lastTap = _lastTapAt;
|
||||
if (_singleTapTimer != null &&
|
||||
lastTap != null &&
|
||||
now.difference(lastTap) <= _singleTapDelay) {
|
||||
_singleTapTimer?.cancel();
|
||||
_singleTapTimer = null;
|
||||
_lastTapAt = null;
|
||||
_showHearts(details.localPosition);
|
||||
return;
|
||||
}
|
||||
_lastTapAt = now;
|
||||
_singleTapTimer?.cancel();
|
||||
_singleTapTimer = Timer(_singleTapDelay, () {
|
||||
_singleTapTimer = null;
|
||||
_lastTapAt = null;
|
||||
if (mounted) widget.onTogglePlay();
|
||||
});
|
||||
}
|
||||
|
||||
void _handleDoubleTap() {
|
||||
final position = _doubleTapPosition;
|
||||
if (position == null) return;
|
||||
void _showHearts(Offset position) {
|
||||
final id = _nextHeartBurstId++;
|
||||
setState(() => _heartBursts.add((id: id, position: position)));
|
||||
widget.onDoubleTapLike?.call(position);
|
||||
@@ -260,6 +281,9 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
}
|
||||
|
||||
void _handleLongPressStart(LongPressStartDetails details, double width) {
|
||||
_singleTapTimer?.cancel();
|
||||
_singleTapTimer = null;
|
||||
_lastTapAt = null;
|
||||
final third = width / 3;
|
||||
final dx = details.localPosition.dx;
|
||||
if (dx < third || dx > width - third) {
|
||||
|
||||
@@ -371,6 +371,9 @@ class HomeFeedActionRail extends StatelessWidget {
|
||||
icon: liked
|
||||
? Icons.favorite_rounded
|
||||
: Icons.favorite_border_rounded,
|
||||
iconColor: liked
|
||||
? AppColors.doubleTapHeartRed
|
||||
: AppColors.textPrimary,
|
||||
label: likeCount == null
|
||||
? l10n.like
|
||||
: formatInteractionCount(likeCount!),
|
||||
@@ -397,6 +400,7 @@ class HomeFeedActionRail extends StatelessWidget {
|
||||
required String label,
|
||||
required Key labelKey,
|
||||
required VoidCallback onTap,
|
||||
Color iconColor = AppColors.textPrimary,
|
||||
double labelFontSize = 12,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
@@ -409,7 +413,7 @@ class HomeFeedActionRail extends StatelessWidget {
|
||||
Icon(
|
||||
icon,
|
||||
size: _actionIcon,
|
||||
color: AppColors.textPrimary,
|
||||
color: iconColor,
|
||||
shadows: const [
|
||||
Shadow(
|
||||
color: AppColors.shadowSoft,
|
||||
|
||||
@@ -19,6 +19,7 @@ class InteractionRailItem {
|
||||
this.count,
|
||||
this.countKey,
|
||||
this.selected = false,
|
||||
this.selectedColor = AppColors.blue,
|
||||
});
|
||||
|
||||
final Key key;
|
||||
@@ -28,6 +29,7 @@ class InteractionRailItem {
|
||||
final int? count;
|
||||
final Key? countKey;
|
||||
final bool selected;
|
||||
final Color selectedColor;
|
||||
}
|
||||
|
||||
/// 播放页与首页 feed 共用的右侧互动栏:收藏/评论/喜欢/分享。
|
||||
@@ -71,7 +73,7 @@ class _RailButton extends StatelessWidget {
|
||||
width: _iconSize,
|
||||
height: _iconSize,
|
||||
fit: BoxFit.contain,
|
||||
color: item.selected ? AppColors.blue : AppColors.onPrimary,
|
||||
color: item.selected ? item.selectedColor : AppColors.onPrimary,
|
||||
colorBlendMode: BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -170,6 +170,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
|
||||
count: data.likeCount,
|
||||
countKey: const Key('player-rail-like-count'),
|
||||
selected: data.liked,
|
||||
selectedColor: AppColors.doubleTapHeartRed,
|
||||
onTap: actions.onLike,
|
||||
),
|
||||
InteractionRailItem(
|
||||
|
||||
Reference in new issue
Block a user