This commit is contained in:
lzcode
2026-10-16 16:05:01 +08:00
parent 24d63e6702
commit 9962136fc8
19 changed files with 1425 additions and 208 deletions
@@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:share_plus/share_plus.dart';
import '../../../core/player/feed_player_pool.dart';
import '../../../core/theme/app_theme.dart';
import '../../../models/video_item.dart';
import '../../../providers/auth_provider.dart';
import 'feed_overlays.dart';
@@ -11,7 +13,7 @@ import 'video_info_section.dart';
import 'video_player_view.dart';
/// Feed 单页:播放器 + 手势层 + 互动栏 + 信息区 + 双击爱心动画
/// 手势(《01-开发文档》5.3):单击暂停/继续,双击点赞
/// 手势(《01-开发文档》5.3):单击暂停/继续,双击点赞,长按弹倍速/分享
class FeedVideoPage extends ConsumerStatefulWidget {
const FeedVideoPage({
super.key,
@@ -22,26 +24,16 @@ class FeedVideoPage extends ConsumerStatefulWidget {
required this.onLike,
this.onFavorite,
this.onComment,
this.onShare,
});
final VideoItem item;
final int slotIndex;
final FeedPlayerPool pool;
final bool isCurrent;
/// 点赞回调(由上层接 feedProvider.toggleLike,内部判登录态)
final void Function(int videoId) onLike;
/// 收藏回调(由上层接 feedProvider.toggleFavorite)
final void Function(int videoId)? onFavorite;
/// 评论入口(打开评论弹层)
final void Function(VideoItem item)? onComment;
/// 分享入口(系统分享面板)
final void Function(VideoItem item)? onShare;
@override
ConsumerState<FeedVideoPage> createState() => _FeedVideoPageState();
}
@@ -49,24 +41,98 @@ class FeedVideoPage extends ConsumerStatefulWidget {
class _FeedVideoPageState extends ConsumerState<FeedVideoPage> {
int _likeBurstTrigger = 0;
static const _rates = [1.0, 1.25, 1.5, 2.0];
void _onDoubleTap() {
// 游客双击:与点赞按钮一致守卫登录(《01-开发文档》2.1.1 游客不可点赞)
if (!ref.read(authProvider).isLoggedIn) {
context.go('/login');
return;
}
setState(() => _likeBurstTrigger++);
// 双击:未点赞等同点赞;已点赞只播动画不改状态
if (!widget.item.isLiked) {
widget.onLike(widget.item.videoId);
}
if (!widget.item.isLiked) widget.onLike(widget.item.videoId);
}
void _onLongPress() {
showModalBottomSheet<void>(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (_) {
final currentRate = widget.pool.slotAt(widget.slotIndex).player.state.rate;
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.speed, color: AppColors.primary),
title: const Text('倍速播放'),
subtitle: Text('当前 ${currentRate.toStringAsFixed(2)}x'),
onTap: () {
Navigator.of(context).pop();
_showRateSheet();
},
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.share_outlined),
title: const Text('分享'),
onTap: () {
Navigator.of(context).pop();
_share();
},
),
const SizedBox(height: 8),
],
),
);
},
);
}
void _showRateSheet() {
final currentRate = widget.pool.slotAt(widget.slotIndex).player.state.rate;
showModalBottomSheet<void>(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (_) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final r in _rates)
ListTile(
title: Center(child: Text('${r}x')),
trailing: (currentRate - r).abs() < 0.01
? const Icon(Icons.check, color: AppColors.primary)
: null,
onTap: () {
widget.pool.slotAt(widget.slotIndex).player.setRate(r);
Navigator.of(context).pop();
},
),
const SizedBox(height: 8),
],
),
),
);
}
Future<void> _share() async {
final text = '${widget.item.title}\n${widget.item.videoUrl}';
await Share.share(text, subject: widget.item.title);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: widget.pool.toggleCurrent,
onDoubleTap: _onDoubleTap,
onLongPress: _onLongPress,
child: Container(
color: Colors.black,
child: Stack(
@@ -74,10 +140,7 @@ class _FeedVideoPageState extends ConsumerState<FeedVideoPage> {
children: [
// 底部渐变(提升文字可读性)
const Positioned(
left: 0,
right: 0,
bottom: 0,
height: 160,
left: 0, right: 0, bottom: 0, height: 160,
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
@@ -91,9 +154,10 @@ class _FeedVideoPageState extends ConsumerState<FeedVideoPage> {
),
),
// 播放器
// 播放器(含抖音风格细进度条)
VideoPlayerView(
controller: widget.pool.controllerAt(widget.slotIndex),
pool: widget.pool,
slotIndex: widget.slotIndex,
coverUrl: widget.item.coverUrl,
playing: widget.isCurrent && widget.pool.isCurrentPlaying,
),
@@ -101,24 +165,20 @@ class _FeedVideoPageState extends ConsumerState<FeedVideoPage> {
// 双击爱心动画
IgnorePointer(child: LikeBurst(trigger: _likeBurstTrigger)),
// 右侧互动栏
// 右侧互动栏(头像/点赞/评论/收藏 — 分享已移至长按菜单)
Positioned(
right: 8,
bottom: 96,
right: 8, bottom: 96,
child: InteractionBar(
item: widget.item,
onLike: widget.onLike,
onFavorite: widget.onFavorite,
onComment: widget.onComment,
onShare: widget.onShare,
),
),
// 左下信息区
Positioned(
left: 12,
right: 88,
bottom: 24,
left: 12, right: 88, bottom: 24,
child: VideoInfoSection(item: widget.item),
),
],