// 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/theme/app_dimens.dart'; import '../../core/widgets/empty_state.dart'; import '../../l10n/app_localizations.dart'; import '../player/player_view.dart'; import 'widgets/all_dramas_filter_grid.dart'; import 'widgets/recommend_sections_view.dart'; enum RecommendInnerTab { feed, playlists, allDramas } class RecommendPage extends StatefulWidget { const RecommendPage({ super.key, required this.feedController, required this.active, required this.onSearch, required this.onOpenFilter, }); final HomeFeedController feedController; final bool active; final VoidCallback onSearch; final VoidCallback onOpenFilter; @override State createState() => _RecommendPageState(); } class _RecommendPageState extends State { RecommendInnerTab _tab = RecommendInnerTab.feed; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final categories = context.watch(); return ColoredBox( key: const Key('recommend-page'), color: AppColors.appBackground, child: SafeArea( bottom: false, child: Column( children: [ _header(l10n), Expanded( child: IndexedStack( index: _tab.index, children: [ _recommendFeed(context), RecommendSectionsView( feedController: widget.feedController, categories: categories, onOpenDrama: _openDrama, onMore: _handleMore, ), AllDramasFilterGrid( feedController: widget.feedController, onOpenDrama: _openDrama, onOpenFilter: widget.onOpenFilter, ), ], ), ), ], ), ), ); } 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, ); } Widget _header(AppLocalizations l10n) { final items = [ (RecommendInnerTab.feed, l10n.recommendTopTab, 'recommend'), (RecommendInnerTab.playlists, l10n.playlistTopTab, 'playlist'), (RecommendInnerTab.allDramas, l10n.allDramasTopTab, 'all'), ]; return SizedBox( key: const Key('recommend-top-nav'), height: 56, child: Padding( padding: const EdgeInsets.symmetric( horizontal: AppDimens.feedPageHorizontal, ), child: Row( children: [ Expanded( child: Row( children: [ for (final item in items) Expanded( child: _tabButton( key: Key('recommend-tab-${item.$3}'), label: item.$2, selected: _tab == item.$1, onTap: () => setState(() => _tab = item.$1), ), ), ], ), ), const SizedBox(width: AppDimens.spaceSm), IconButton( key: const Key('recommend-search'), tooltip: l10n.search, onPressed: widget.onSearch, iconSize: 24, color: AppColors.textPrimary, icon: const Icon(Icons.search_rounded), ), ], ), ), ); } Widget _tabButton({ required Key key, required String label, required bool selected, required VoidCallback onTap, }) { return InkWell( key: key, onTap: onTap, child: Padding( padding: const EdgeInsets.only(right: AppDimens.spaceSm), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: selected ? AppColors.textPrimary : AppColors.textTertiary, fontSize: AppDimens.homePrimaryTabFont, fontWeight: selected ? FontWeight.w700 : FontWeight.w400, ), ), const SizedBox(height: 3), Container( width: selected ? 20 : 0, height: 2, decoration: BoxDecoration( color: AppColors.textPrimary, borderRadius: BorderRadius.circular(AppDimens.pillRadius), ), ), ], ), ), ); } 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, ), ), ); } }