feat: 逻辑与页面布局分离

This commit is contained in:
Vicky committed 2026-10-13 16:52:14 +08:00
1 parent b1f6ce4bf9
commit 1e61ae7a0b
11 files changed
+1233 -907

No files matched your search

+34 -6
View File
@@ -1,19 +1,47 @@
import 'package:flutter/material.dart';
import '../../core/data/models/drama.dart';
import '../../core/data/repositories/watch_history_repository.dart';
import '../player/player_view.dart';
class DramaDetailLogic {
class DramaDetailLogic extends ChangeNotifier {
DramaDetailLogic({required this.drama, required this.history});
final Drama drama;
final WatchHistoryRepository history;
int? continueIndex;
bool _disposed = false;
void notify() {
if (!_disposed) notifyListeners();
}
Future<void> loadContinue() async {
final id = await history.lastEpisodeId(demoProfileId, drama.id);
if (id == null) return;
final index = drama.episodes.indexWhere((episode) => episode.id == id);
if (index >= 0 && drama.episodes[index].isFree) {
continueIndex = index;
try {
final id = await history.lastEpisodeId(demoProfileId, drama.id);
if (id == null || _disposed) return;
final index = drama.episodes.indexWhere((episode) => episode.id == id);
if (index >= 0 && drama.episodes[index].isFree) {
continueIndex = index;
notify();
}
} catch (_) {
// Keep the first episode available if preferences cannot be read.
}
}
Future<void> openPlayer(BuildContext context, int index) async {
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => PlayerPage(drama: drama, initialIndex: index),
),
);
await loadContinue();
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}
+15 -20
View File
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import '../../core/data/models/drama.dart';
import '../../core/formatting/localized_content.dart';
import '../../l10n/app_localizations.dart';
import '../player/player_view.dart';
import 'drama_detail_binding.dart';
import 'drama_detail_logic.dart';
@@ -22,29 +21,24 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
void initState() {
super.initState();
logic = DramaDetailBinding().create(drama: widget.drama);
_loadContinue();
logic.loadContinue();
}
Future<void> _loadContinue() async {
try {
await logic.loadContinue();
if (mounted) setState(() {});
} catch (_) {
// Keep the first episode available if preferences cannot be read.
}
}
Future<void> _openPlayer(BuildContext context, int index) async {
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => PlayerPage(drama: logic.drama, initialIndex: index),
),
);
await _loadContinue();
@override
void dispose() {
logic.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: logic,
builder: (context, _) => _page(context),
);
}
Widget _page(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final drama = logic.drama;
return Scaffold(
@@ -87,7 +81,8 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
),
const SizedBox(height: 20),
FilledButton.icon(
onPressed: () => _openPlayer(context, logic.continueIndex ?? 0),
onPressed: () =>
logic.openPlayer(context, logic.continueIndex ?? 0),
icon: const Icon(Icons.play_arrow_rounded),
label: Text(
logic.continueIndex == null
@@ -124,7 +119,7 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
title: Text(l10n.episodeLabel(episode.number)),
subtitle: Text(l10n.freeTestMedia),
trailing: const Icon(Icons.play_circle_outline_rounded),
onTap: () => _openPlayer(context, index),
onTap: () => logic.openPlayer(context, index),
),
);
},