// Author: fruit.com.cn import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../core/data/models/drama.dart'; import '../../core/data/services/catalog_controller.dart'; import '../../core/data/services/category_controller.dart'; import '../../core/data/services/download_controller.dart'; import '../../core/data/services/home_feed_controller.dart'; import '../../core/data/services/wallet_controller.dart'; import '../../core/navigation/fade_page_route.dart'; import '../../core/theme/app_colors.dart'; import '../../core/widgets/empty_state.dart'; import '../../l10n/app_localizations.dart'; import '../player/player_view.dart'; import 'recommend_types.dart'; import 'widgets/all_dramas_filter_grid.dart'; import 'widgets/recommend_sections_view.dart'; import 'widgets/recommend_top_nav.dart'; class RecommendPage extends StatefulWidget { const RecommendPage({ super.key, required this.feedController, required this.active, required this.onSearch, required this.onOpenFilter, required this.onScrubVisibilityChanged, }); final HomeFeedController feedController; final bool active; final VoidCallback onSearch; final VoidCallback onOpenFilter; final ValueChanged onScrubVisibilityChanged; @override State createState() => _RecommendPageState(); } class _RecommendPageState extends State { RecommendInnerTab _tab = RecommendInnerTab.feed; bool _scrubbing = false; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final categories = context.read(); return ColoredBox( key: const Key('recommend-page'), color: AppColors.appBackground, child: Stack( children: [ Positioned.fill( child: ListenableBuilder( listenable: Listenable.merge([widget.feedController, categories]), builder: (context, _) => IndexedStack( index: _tab.index, children: [ _recommendFeed(context), _withTopNavSpace( RecommendSectionsView( feedController: widget.feedController, categories: categories, onOpenDrama: _openDrama, onMore: _handleMore, ), ), _withTopNavSpace( AllDramasFilterGrid( feedController: widget.feedController, onOpenDrama: _openDrama, onOpenFilter: widget.onOpenFilter, ), ), ], ), ), ), if (!_scrubbing) Positioned( top: 0, left: 0, right: 0, child: SafeArea( bottom: false, child: RecommendTopNav( selected: _tab, onVideo: _tab == RecommendInnerTab.feed, onSelected: (tab) => setState(() => _tab = tab), onSearch: widget.onSearch, ), ), ), ], ), ); } Widget _recommendFeed(BuildContext context) { final dramas = widget.feedController.dramas; if (dramas.isEmpty) { final l10n = AppLocalizations.of(context)!; if (widget.feedController.loadingFirstPage) { return const ColoredBox( color: AppColors.mediaBlack, child: Center( child: CircularProgressIndicator( key: Key('recommend-feed-loading'), color: AppColors.muted, ), ), ); } return ColoredBox( color: AppColors.mediaBlack, child: EmptyState( key: const Key('recommend-feed-empty'), icon: Icons.movie_outlined, title: l10n.noStories, actionLabel: l10n.retry, onAction: () => unawaited(widget.feedController.refreshCurrent()), ), ); } return PlayerPage( key: const Key('recommend-feed-player'), homeMode: true, embedded: true, active: widget.active && _tab == RecommendInnerTab.feed, showHomeFeedHeader: false, drama: dramas.first, initialIndex: 0, feedDramas: dramas, feedChannel: widget.feedController.channel, onOpenFullPlayer: (drama, episode) => _openDrama(drama, episodeId: episode.id), onRefreshFeed: widget.feedController.refreshCurrent, onLoadMore: widget.feedController.loadMore, onScrubVisibilityChanged: _setScrubbing, ); } void _setScrubbing(bool value) { if (_scrubbing == value) return; setState(() => _scrubbing = value); widget.onScrubVisibilityChanged(value); } Widget _withTopNavSpace(Widget child) { return SafeArea( bottom: false, child: Padding( padding: const EdgeInsets.only(top: RecommendTopNav.height), child: child, ), ); } void _handleMore(RecommendSectionKind section) { setState(() => _tab = RecommendInnerTab.allDramas); } Future _openDrama(Drama drama, {String? episodeId}) async { final catalog = context.read(); final wallet = context.read(); final downloads = context.read(); var playable = drama; try { playable = await catalog.requireDetail(drama); } catch (_) { // 卡片预览仍可直接进入播放器。 } if (!mounted) return; final episodeIndex = episodeId == null ? 0 : playable.episodes.indexWhere((episode) => episode.id == episodeId); await Navigator.of(context).push( FadePageRoute( builder: (_) => PlayerPage( drama: playable, initialIndex: episodeIndex < 0 ? 0 : episodeIndex, homeMode: true, feedDramas: [playable], wallet: wallet, downloads: downloads, ), ), ); } }