124 lines
3.9 KiB
Dart
124 lines
3.9 KiB
Dart
// 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<bool> editing;
|
|
final ValueListenable<Set<String>> selected;
|
|
final ValueChanged<String> 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<bool>(
|
|
valueListenable: selection.editing,
|
|
builder: (context, isEditing, _) {
|
|
return ValueListenableBuilder<Set<String>>(
|
|
valueListenable: selection.selected,
|
|
builder: (context, picked, _) {
|
|
return ListenableBuilder(
|
|
listenable: controller,
|
|
builder: (context, _) => _grid(context, isEditing, picked),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _grid(BuildContext context, bool isEditing, Set<String> 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<String> 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));
|
|
}
|
|
}
|