// Author: fruit.com.cn import 'package:flutter/material.dart'; import '../../core/data/models/drama.dart'; import '../../core/data/models/watch_progress.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; WatchProgress? resumeRecord; final TextEditingController search = TextEditingController(); final FocusNode searchFocus = FocusNode(); final ScrollController scroll = ScrollController(); 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); refreshResume(); } Future refreshResume() async { try { final recent = await history.recent(limit: 1); if (recent.isEmpty || recent.first.finished) { resumeRecord = null; } else { resumeRecord = recent.first; } } catch (_) { resumeRecord = null; } notify(); } void onSearchChanged() => notify(); List filtered(List dramas) { final query = search.text.trim().toLowerCase(); if (query.isEmpty) return dramas; return dramas.where((drama) { final haystack = [ drama.title, drama.genre, drama.category, drama.synopsis, ].join(' ').toLowerCase(); return haystack.contains(query); }).toList(); } Drama? resumeDrama(List dramas) { final record = resumeRecord; if (record == null) return null; for (final drama in dramas) { if (drama.id == record.dramaId) return drama; } return null; } 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))); } @override void dispose() { _disposed = true; search.dispose(); searchFocus.dispose(); scroll.dispose(); super.dispose(); } }