// Author: fruit.com.cn // 职责:追剧收藏网格、分页和编辑选择,不承载顶部续播或重复区块标题。 import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../core/data/services/favorite_controller.dart'; import '../../core/formatting/error_text.dart'; import '../../core/widgets/empty_state.dart'; import '../../l10n/app_localizations.dart'; import '../player/open_player.dart'; import 'following_grid_card.dart'; import 'following_paged_grid.dart'; /// 追剧编辑中的选中状态。 class FollowingFavoriteSelection { const FollowingFavoriteSelection({ required this.editing, required this.selected, required this.onToggle, }); final ValueListenable editing; final ValueListenable> selected; final ValueChanged onToggle; } /// 追剧列表。编辑时点卡片只切换选中,不进入播放。 class FollowingFavoritesGrid extends StatelessWidget { const FollowingFavoritesGrid({ super.key, required this.controller, required this.selection, required this.scrollController, required this.onGoHome, }); final FavoriteController controller; final FollowingFavoriteSelection selection; final ScrollController scrollController; final VoidCallback onGoHome; @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: selection.editing, builder: (context, isEditing, _) { return ValueListenableBuilder>( valueListenable: selection.selected, builder: (context, picked, _) { return ListenableBuilder( listenable: controller, builder: (context, _) => _grid(context, isEditing, picked), ); }, ); }, ); } Widget _grid(BuildContext context, bool isEditing, Set picked) { final l10n = AppLocalizations.of(context)!; final hasFavorites = controller.cards.isNotEmpty; final extra = !isEditing && hasFavorites ? 1 : 0; return FollowingPagedGrid( key: const Key('following-favorites-grid'), scrollController: scrollController, loading: controller.loading, loadingMore: controller.loadingMore, hasMore: controller.hasMore, hasItems: hasFavorites, showInitialLoading: false, errorMessage: controller.error == null ? null : errorText(l10n, controller.error, l10n.favoritesError), loadMoreError: controller.loadMoreError == null ? null : errorText(l10n, controller.loadMoreError, l10n.favoritesError), onRefresh: controller.load, onLoadMore: controller.loadMore, itemCount: controller.cards.length + extra, empty: EmptyState( key: const Key('following-favorites-empty'), icon: Icons.bookmark_border_rounded, title: l10n.noFavoritesYet, ), itemBuilder: (context, index) => _item(context, index, isEditing, picked), ); } Widget _item( BuildContext context, int index, bool isEditing, Set picked, ) { if (index == controller.cards.length) { return FollowingAddCard(onTap: onGoHome); } final drama = controller.cards[index]; return FollowingGridCard( dramaId: drama.id, title: drama.localizedTitle(Localizations.localeOf(context).languageCode), posterUrl: drama.posterUrl, viewCount: drama.viewCount, editing: isEditing, selected: isEditing ? picked.contains(drama.id) : null, // TODO(backend): Drama 增加 hasUpdate 字段后,在此传 updateBadge。 onTap: () => _openOrToggle(context, drama.id, isEditing), ); } void _openOrToggle(BuildContext context, String dramaId, bool isEditing) { if (isEditing) { selection.onToggle(dramaId); return; } final drama = controller.cards.firstWhere((item) => item.id == dramaId); unawaited(playDramaFromCatalog(context, drama)); } }