import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_theme.dart'; import '../../models/video_item.dart'; import '../../providers/feed_provider.dart'; import 'widgets/comment_sheet.dart'; import 'widgets/feed_overlays.dart'; import 'widgets/feed_video_page.dart'; /// 首页 Feed:全屏竖向 PageView 沉浸式播放(《01-开发文档》5.2.1) /// - 顶部:策略 Tab(推荐/最新/热度)+ 搜索入口 /// - 底部:游客/非会员免费剩余次数提示条 /// - 滑出暂停、滑入自动播放并上报观看计费 class FeedScreen extends ConsumerStatefulWidget { const FeedScreen({super.key}); @override ConsumerState createState() => _FeedScreenState(); } class _FeedScreenState extends ConsumerState with WidgetsBindingObserver { FeedStrategy _strategy = FeedStrategy.recommend; final PageController _pageController = PageController(); StreamSubscription? _eventSub; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); // 监听计费/UI 事件(弹窗与 Toast 由 Notifier 事件流驱动) _eventSub = ref .read(feedProvider(_strategy).notifier) .events .listen(_handleEvent); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _eventSub?.cancel(); _pageController.dispose(); super.dispose(); } /// App 退后台暂停、回前台恢复播放 @override void didChangeAppLifecycleState(AppLifecycleState state) { final notifier = ref.read(feedProvider(_strategy).notifier); if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) { notifier.pool.pauseCurrent(); } else if (state == AppLifecycleState.resumed) { notifier.pool.playCurrent(); } } void _handleEvent(FeedEvent event) { if (!mounted) return; switch (event) { case FeedToastEvent(:final message): showTopToast(context, message); case FeedGuestLimitEvent(): showGuestLimitDialog(context); case FeedPointsInsufficientEvent(:final points): showPointsInsufficientDialog(context, points); } } void _switchStrategy(FeedStrategy next) { if (next == _strategy) return; // 暂停旧策略播放,切换后新策略独立维护状态与播放器池 ref.read(feedProvider(_strategy).notifier).pool.pauseCurrent(); setState(() => _strategy = next); _eventSub?.cancel(); _eventSub = ref .read(feedProvider(_strategy).notifier) .events .listen(_handleEvent); _pageController.jumpToPage(0); } /// 打开评论弹层:打开期间暂停当前播放,关闭后恢复 Future _openComments(VideoItem item) async { final notifier = ref.read(feedProvider(_strategy).notifier); notifier.pool.pauseCurrent(); await showCommentSheet( context, videoId: item.videoId, onCountChanged: (delta) => notifier.adjustCommentCount(item.videoId, delta), ); if (mounted) notifier.pool.playCurrent(); } @override Widget build(BuildContext context) { final state = ref.watch(feedProvider(_strategy)); final notifier = ref.read(feedProvider(_strategy).notifier); return Scaffold( backgroundColor: Colors.black, body: Stack( children: [ // 内容区 if (state.isRefreshing && state.items.isEmpty) const Center( child: CircularProgressIndicator(color: AppColors.primary), ) else if (state.loadFailed && state.items.isEmpty) _buildRetry(notifier) else if (state.items.isEmpty) const Center( child: Text('暂无视频', style: TextStyle(color: AppColors.textSecondary)), ) else PageView.builder( controller: _pageController, scrollDirection: Axis.vertical, itemCount: state.items.length, onPageChanged: notifier.onPageChanged, itemBuilder: (context, index) { final item = state.items[index]; return FeedVideoPage( key: ValueKey(item.videoId), item: item, slotIndex: notifier.pool.slotOf(index), pool: notifier.pool, isCurrent: index == state.currentIndex, onLike: notifier.toggleLike, onFavorite: notifier.toggleFavorite, onComment: _openComments, ); }, ), // 顶部:策略 Tab + 搜索入口 SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: Row( children: [ const Spacer(), for (final s in FeedStrategy.values) ...[ _StrategyTab( label: s.label, selected: s == _strategy, onTap: () => _switchStrategy(s), ), const SizedBox(width: 16), ], IconButton( icon: const Icon(Icons.search, color: Colors.white), onPressed: () => context.push('/search'), ), ], ), ), ), // 底部:免费剩余次数提示条(≤3 时显示) Positioned( left: 0, right: 0, bottom: 0, child: FreeQuotaBanner(remaining: state.freeRemainingToday), ), ], ), ); } Widget _buildRetry(FeedNotifier notifier) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('网络异常', style: TextStyle(color: AppColors.textSecondary)), const SizedBox(height: 12), TextButton( onPressed: notifier.refresh, child: const Text('点击重试', style: TextStyle(color: AppColors.primary)), ), ], ), ); } } /// 顶部策略 Tab class _StrategyTab extends StatelessWidget { const _StrategyTab({ required this.label, required this.selected, required this.onTap, }); final String label; final bool selected; final VoidCallback onTap; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Text( label, style: TextStyle( fontSize: 16, fontWeight: selected ? FontWeight.w700 : FontWeight.w400, color: selected ? Colors.white : AppColors.textSecondary, shadows: const [Shadow(color: Colors.black54, blurRadius: 4)], ), ), ); } }