Files
ReelShort/lib/pages/drama_detail/drama_detail_logic.dart

157 lines
4.6 KiB
Dart

// Author: fruit.com.cn
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/data/models/drama.dart';
import '../../core/data/models/watch_progress.dart';
import '../../core/data/remote/api_client.dart';
import '../../core/data/repositories/watch_history_repository.dart';
import '../../core/data/repositories/playback_repository.dart';
import '../../core/data/services/favorite_controller.dart';
import '../../core/data/services/download_controller.dart';
import '../../core/data/services/playback_position_cache.dart';
import '../../core/data/services/wallet_controller.dart';
import '../player/player_view.dart';
class DramaDetailLogic extends ChangeNotifier {
DramaDetailLogic({
required this.drama,
required this.history,
required this.playback,
required this.favorites,
required this.positionCache,
});
/// 先用列表卡片渲染,取到详情后换成带完整剧集的版本。
Drama drama;
final WatchHistoryRepository history;
final PlaybackRepository playback;
final FavoriteController favorites;
final PlaybackPositionCache positionCache;
bool loading = false;
bool failed = false;
/// 接口给出的失败原因;为空时界面回退到本地文案。
String? failureMessage;
int? continueIndex;
WatchProgress? progress;
String? progressError;
bool _disposed = false;
void notify() {
if (!_disposed) notifyListeners();
}
/// 列表卡片只带一集可播预览,进详情时补一次完整剧集列表。
Future<void> load(Future<Drama> Function() fetch) async {
favorites.applyServerCount(drama.id, drama.favoriteCount, notify: false);
if (drama.detailLoaded) {
await loadContinue();
return;
}
loading = true;
failed = false;
failureMessage = null;
notify();
try {
drama = await fetch();
favorites.applyServerCount(drama.id, drama.favoriteCount);
await loadContinue();
} on ApiException catch (exception) {
failed = true;
failureMessage = exception.message;
} on DioException {
// 断网/超时:交给 l10n 文案,不把 HTTP 细节丢给用户
failed = true;
failureMessage = 'networkError';
} catch (_) {
failed = true;
} finally {
loading = false;
notify();
}
}
Future<void> loadContinue() async {
final cached = positionCache.latestForDrama(drama.id);
if (cached != null) {
final index = _indexOf(cached.episodeId);
if (index != null) {
progress = WatchProgress(
dramaId: cached.dramaId,
episodeId: cached.episodeId,
position: cached.position,
duration: cached.duration,
updatedAt: cached.updatedAt,
);
continueIndex = index;
progressError = null;
notify();
return;
}
}
try {
final record = await history.progressFor(drama.id);
if (_disposed) return;
progress = record;
progressError = null;
continueIndex = record == null
? null
: _indexOf(record.episodeId) ?? continueIndex;
notify();
} catch (exception) {
if (_disposed) return;
// 拿不到续看位置不影响看第一集,但要让用户知道(可重试)
progressError = exception is ApiException
? exception.message
: 'progressError';
notify();
}
}
int? _indexOf(String episodeId) {
final index = drama.episodes.indexWhere(
(episode) => episode.id == episodeId,
);
return index >= 0 ? index : null;
}
/// 该集的续看进度(0~1),用于剧集行上的进度条;没有记录返回 null。
double? progressFractionFor(String episodeId) {
final record = progress;
if (record == null || record.episodeId != episodeId) return null;
if (!record.hasPosition) return null;
return record.fraction;
}
Future<void> openPlayer(BuildContext context, int index) async {
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => PlayerPage(
drama: drama,
initialIndex: index,
wallet: context.read<WalletController>(),
downloads: context.read<DownloadController>(),
),
),
);
await loadContinue();
}
/// 详情页展示用的收藏数,统一从全局 FavoriteController 读取。
int? get adjustedFavoriteCount => favorites.favoriteCountFor(drama);
/// 收藏切换:状态与计数均由全局控制器维护。
Future<bool> toggleFavorite() async {
return favorites.toggle(drama.id, card: drama);
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}