// Author: fruit.com.cn import 'package:flutter/material.dart'; import '../../core/data/models/drama.dart'; import '../../core/data/repositories/watch_history_repository.dart'; import '../../l10n/app_localizations.dart'; enum DiscoverPageAction { home, my, watchlist, detail, resume } class DiscoverLogic extends ChangeNotifier { DiscoverLogic({required this.history, required this.drama}) { refreshPosition(); } final WatchHistoryRepository history; Drama drama; late Future position; final TextEditingController search = TextEditingController(); final FocusNode searchFocus = FocusNode(); final ScrollController scroll = ScrollController(); int category = 0; bool searchOpen = false; bool _disposed = false; void notify() { if (!_disposed) notifyListeners(); } void refreshPosition() { // 续看位置来自服务端(每剧一条记录);未登录或没有记录时给 null,UI 显示"从头看" position = history .resumePointFor(drama.id) .then((point) => point?.position) .catchError((Object _) => null); } void selectCategory(int index) { category = index; notify(); } void onSearchChanged() => notify(); bool get showSearch => searchOpen || search.text.isNotEmpty; void openSearch() { searchOpen = true; notify(); searchFocus.requestFocus(); } void syncDrama({ required bool active, required bool wasActive, required Drama nextDrama, required bool dramaChanged, required int searchRequest, required int previousSearchRequest, required bool Function() isActive, }) { if (active && (!wasActive || dramaChanged)) { drama = nextDrama; refreshPosition(); notify(); } if (active && searchRequest != previousSearchRequest) { searchOpen = true; notify(); WidgetsBinding.instance.addPostFrameCallback((_) { if (isActive()) searchFocus.requestFocus(); }); } } void act( BuildContext context, DiscoverPageAction action, { required bool embedded, ValueChanged? onAction, }) { if (embedded) { onAction?.call(action); } else { Navigator.pop(context, action); } } Drama? dramaFor(String? dramaId, List dramas) { for (final item in dramas) { if (item.id == dramaId) return item; } return null; } void openCard( BuildContext context, Drama? selected, { required bool embedded, ValueChanged? onDramaSelected, ValueChanged? onAction, }) { if (selected == null) { unavailable(context); } else if (embedded && onDramaSelected != null) { onDramaSelected(selected); } else { act(context, DiscoverPageAction.detail, embedded: embedded, onAction: onAction); } } void unavailable(BuildContext context) { final l10n = AppLocalizations.of(context)!; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(l10n.artworkOnlyNotice))); } void catalogInfo(BuildContext context) { final l10n = AppLocalizations.of(context)!; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(l10n.demoCatalogNotice))); } void showFilters(BuildContext context) { final l10n = AppLocalizations.of(context)!; final labels = [ l10n.forYou, l10n.hot, l10n.newest, l10n.romance, l10n.actionGenre, ]; showModalBottomSheet( context: context, backgroundColor: const Color(0xFF202126), showDragHandle: true, builder: (sheetContext) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile(title: Text(l10n.filter)), for (var index = 0; index < labels.length; index++) ListTile( title: Text(labels[index]), trailing: index == category ? const Icon(Icons.check_rounded, color: Color(0xFF2F7BFF)) : null, onTap: () { selectCategory(index); Navigator.pop(sheetContext); }, ), const SizedBox(height: 8), ], ), ), ); } @override void dispose() { _disposed = true; search.dispose(); searchFocus.dispose(); scroll.dispose(); super.dispose(); } }