// Author: fruit.com.cn import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../../core/data/models/category_ranking.dart'; import '../../../core/data/models/drama.dart'; import '../../../core/data/services/category_controller.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../../core/widgets/app_pull_to_refresh.dart'; import '../../../core/widgets/drama_grid_skeleton.dart'; import '../../../core/widgets/empty_state.dart'; import '../../../l10n/app_localizations.dart'; import '../../ranking/widgets/ranking_drama_row.dart'; /// 榜单页顶部暗红光晕,向下淡入纯黑(与剧场榜单页一致)。 const BoxDecoration _rankingBackdrop = BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ AppColors.followingGlow, AppColors.followingGlowMid, AppColors.appBackground, ], stops: [0, 0.14, 0.3], ), ); /// 推荐榜频道:服务端排名快照,统一行布局,名次角标压在封面左上角。 class HomeRankingDramas extends StatefulWidget { const HomeRankingDramas({ super.key, required this.categoryId, required this.onOpenDrama, this.topPadding = AppDimens.spaceLg, }); final String categoryId; final ValueChanged onOpenDrama; final double topPadding; @override State createState() => _HomeRankingDramasState(); } class _HomeRankingDramasState extends State { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _ensureLoaded()); } @override void didUpdateWidget(HomeRankingDramas oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.categoryId != widget.categoryId) _ensureLoaded(); } void _ensureLoaded() { if (!mounted) return; final controller = context.read(); final slot = controller.rankingSlotOf(widget.categoryId); if (slot.loading || slot.ranking != null) return; unawaited(controller.openRanking(widget.categoryId)); } @override Widget build(BuildContext context) { final controller = context.watch(); final slot = controller.rankingSlotOf(widget.categoryId); final entries = slot.ranking?.entries ?? const []; if (slot.loading && entries.isEmpty) { return const DecoratedBox( key: Key('home-ranking-surface'), decoration: _rankingBackdrop, child: DramaGridSkeleton( key: Key('home-ranking-skeleton'), columns: 3, horizontalPadding: AppDimens.feedPageHorizontal, crossAxisSpacing: AppDimens.homeGridGap, mainAxisSpacing: AppDimens.homeGridRowGap, ), ); } return DecoratedBox( key: const Key('home-ranking-surface'), decoration: _rankingBackdrop, child: Material( type: MaterialType.transparency, child: AppPullToRefresh( key: const Key('home-ranking-refresh'), onRefresh: () => controller.openRanking(widget.categoryId), child: _content(context, entries, slot.error), ), ), ); } Widget _content( BuildContext context, List entries, Object? error, ) { final l10n = AppLocalizations.of(context)!; if (entries.isEmpty) { return CustomScrollView( key: PageStorageKey('home-ranking-scroll-${widget.categoryId}'), physics: AppPullToRefresh.physics, slivers: [ SliverFillRemaining( hasScrollBody: false, child: EmptyState( key: Key( error == null ? 'home-ranking-empty' : 'home-ranking-error', ), icon: error == null ? Icons.leaderboard_outlined : Icons.cloud_off_rounded, title: error == null ? l10n.emptyContent : l10n.catalogError, actionLabel: error == null ? null : l10n.retry, onAction: error == null ? null : () => unawaited( context.read().openRanking( widget.categoryId, ), ), stretchContent: true, ), ), ], ); } return CustomScrollView( key: PageStorageKey('home-ranking-scroll-${widget.categoryId}'), physics: AppPullToRefresh.physics, slivers: [ SliverPadding( padding: EdgeInsets.fromLTRB( AppDimens.feedPageHorizontal, widget.topPadding, AppDimens.feedPageHorizontal, AppDimens.spaceXl, ), sliver: SliverList.separated( itemCount: entries.length, itemBuilder: (context, index) => _row(entries[index]), separatorBuilder: (_, _) => const Divider( height: AppDimens.mineGridDividerWidth, thickness: AppDimens.mineGridDividerWidth, color: AppColors.border, ), ), ), ], ); } Widget _row(CategoryRankingEntry entry) { return Padding( padding: const EdgeInsets.symmetric(vertical: AppDimens.spaceSm), child: RankingDramaRow( rank: entry.rank, drama: entry.drama, onTap: () => widget.onOpenDrama(entry.drama), ), ); } }