190 lines
5.9 KiB
Dart
190 lines
5.9 KiB
Dart
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';
|
|
import 'interaction_bar.dart';
|
|
import 'video_info_section.dart';
|
|
import 'video_player_view.dart';
|
|
|
|
/// Feed 单页:播放器 + 手势层 + 互动栏 + 信息区 + 双击爱心动画
|
|
/// 手势(《01-开发文档》5.3):单击暂停/继续,双击点赞,长按弹倍速/分享
|
|
class FeedVideoPage extends ConsumerStatefulWidget {
|
|
const FeedVideoPage({
|
|
super.key,
|
|
required this.item,
|
|
required this.slotIndex,
|
|
required this.pool,
|
|
required this.isCurrent,
|
|
required this.onLike,
|
|
this.onFavorite,
|
|
this.onComment,
|
|
});
|
|
|
|
final VideoItem item;
|
|
final int slotIndex;
|
|
final FeedPlayerPool pool;
|
|
final bool isCurrent;
|
|
final void Function(int videoId) onLike;
|
|
final void Function(int videoId)? onFavorite;
|
|
final void Function(VideoItem item)? onComment;
|
|
|
|
@override
|
|
ConsumerState<FeedVideoPage> createState() => _FeedVideoPageState();
|
|
}
|
|
|
|
class _FeedVideoPageState extends ConsumerState<FeedVideoPage> {
|
|
int _likeBurstTrigger = 0;
|
|
|
|
static const _rates = [1.0, 1.25, 1.5, 2.0];
|
|
|
|
void _onDoubleTap() {
|
|
if (!ref.read(authProvider).isLoggedIn) {
|
|
context.go('/login');
|
|
return;
|
|
}
|
|
setState(() => _likeBurstTrigger++);
|
|
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(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
// 底部渐变(提升文字可读性)
|
|
const Positioned(
|
|
left: 0, right: 0, bottom: 0, height: 160,
|
|
child: IgnorePointer(
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [Colors.transparent, Colors.black54],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// 播放器(含抖音风格细进度条)
|
|
VideoPlayerView(
|
|
pool: widget.pool,
|
|
slotIndex: widget.slotIndex,
|
|
coverUrl: widget.item.coverUrl,
|
|
playing: widget.isCurrent && widget.pool.isCurrentPlaying,
|
|
),
|
|
|
|
// 双击爱心动画
|
|
IgnorePointer(child: LikeBurst(trigger: _likeBurstTrigger)),
|
|
|
|
// 右侧互动栏(头像/点赞/评论/收藏 — 分享已移至长按菜单)
|
|
Positioned(
|
|
right: 8, bottom: 96,
|
|
child: InteractionBar(
|
|
item: widget.item,
|
|
onLike: widget.onLike,
|
|
onFavorite: widget.onFavorite,
|
|
onComment: widget.onComment,
|
|
),
|
|
),
|
|
|
|
// 左下信息区
|
|
Positioned(
|
|
left: 12, right: 88, bottom: 24,
|
|
child: VideoInfoSection(item: widget.item),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|