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

+91 -1
View File
@@ -1,7 +1,14 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/config/locale_controller.dart';
import '../../core/data/models/drama.dart';
import '../../core/data/repositories/watch_history_repository.dart';
import '../../l10n/app_localizations.dart';
class MineLogic {
enum MyPageAction { home, discover, resume }
class MineLogic extends ChangeNotifier {
MineLogic({
required this.history,
required this.drama,
@@ -15,6 +22,11 @@ class MineLogic {
late Future<Duration?> position;
int section;
int filter = 0;
bool _disposed = false;
void notify() {
if (!_disposed) notifyListeners();
}
void refreshPosition() {
position = history.positionFor(
@@ -23,4 +35,82 @@ class MineLogic {
drama.episodes.first.id,
);
}
void selectSection(int index) {
section = index;
notify();
}
void selectFilter(int index) {
filter = index;
notify();
}
void syncPage({
required bool active,
required bool wasActive,
required Drama nextDrama,
required bool dramaChanged,
required int sectionRequest,
required int previousSectionRequest,
required int initialSection,
}) {
if (active && (!wasActive || dramaChanged)) {
drama = nextDrama;
refreshPosition();
notify();
}
if (sectionRequest != previousSectionRequest) {
section = initialSection;
notify();
}
}
void act(
BuildContext context,
MyPageAction action, {
required bool embedded,
ValueChanged<MyPageAction>? onAction,
}) {
if (embedded) {
onAction?.call(action);
} else {
Navigator.pop(context, action);
}
}
Future<void> showLanguage(BuildContext context) async {
final l10n = AppLocalizations.of(context)!;
await showModalBottomSheet<void>(
context: context,
backgroundColor: const Color(0xFF1C1D22),
showDragHandle: true,
builder: (sheetContext) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(title: Text(l10n.language)),
for (final (code, label) in [
('en', l10n.english),
('zh', l10n.simplifiedChinese),
])
ListTile(
title: Text(label),
onTap: () {
context.read<LocaleController>().select(code);
Navigator.pop(sheetContext);
},
),
const SizedBox(height: 8),
],
),
),
);
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}
+60 -56
View File
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/config/locale_controller.dart';
import '../../core/formatting/localized_content.dart';
import '../../core/data/models/drama.dart';
import '../../core/data/repositories/watch_history_repository.dart';
@@ -10,8 +9,6 @@ import '../../l10n/app_localizations.dart';
import 'mine_binding.dart';
import 'mine_logic.dart';
enum MyPageAction { home, discover, resume }
class MyPage extends StatefulWidget {
const MyPage({
super.key,
@@ -58,55 +55,32 @@ class _MyPageState extends State<MyPage> {
@override
void didUpdateWidget(covariant MyPage oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.active &&
(!oldWidget.active || oldWidget.drama.id != widget.drama.id)) {
logic.drama = widget.drama;
setState(logic.refreshPosition);
}
if (widget.sectionRequest != oldWidget.sectionRequest) {
setState(() => logic.section = widget.initialSection);
}
}
void _act(MyPageAction action) {
if (widget.embedded) {
widget.onAction?.call(action);
} else {
Navigator.pop(context, action);
}
}
Future<void> _showLanguage() async {
final l10n = AppLocalizations.of(context)!;
await showModalBottomSheet<void>(
context: context,
backgroundColor: const Color(0xFF1C1D22),
showDragHandle: true,
builder: (sheetContext) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(title: Text(l10n.language)),
for (final (code, label) in [
('en', l10n.english),
('zh', l10n.simplifiedChinese),
])
ListTile(
title: Text(label),
onTap: () {
context.read<LocaleController>().select(code);
Navigator.pop(sheetContext);
},
),
const SizedBox(height: 8),
],
),
),
logic.syncPage(
active: widget.active,
wasActive: oldWidget.active,
nextDrama: widget.drama,
dramaChanged: oldWidget.drama.id != widget.drama.id,
sectionRequest: widget.sectionRequest,
previousSectionRequest: oldWidget.sectionRequest,
initialSection: widget.initialSection,
);
}
@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 content = SafeArea(
bottom: !widget.embedded,
@@ -179,7 +153,12 @@ class _MyPageState extends State<MyPage> {
const Spacer(),
IconButton(
tooltip: l10n.search,
onPressed: () => _act(MyPageAction.discover),
onPressed: () => logic.act(
context,
MyPageAction.discover,
embedded: widget.embedded,
onAction: widget.onAction,
),
icon: const Icon(Icons.search_rounded, size: 23),
),
const SizedBox(width: 6),
@@ -324,7 +303,12 @@ class _MyPageState extends State<MyPage> {
SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed: () => _act(MyPageAction.resume),
onPressed: () => logic.act(
context,
MyPageAction.resume,
embedded: widget.embedded,
onAction: widget.onAction,
),
style: FilledButton.styleFrom(
backgroundColor: _orange,
foregroundColor: Colors.white,
@@ -368,7 +352,7 @@ class _MyPageState extends State<MyPage> {
children: [
for (var index = 0; index < tabs.length; index++) ...[
InkWell(
onTap: () => setState(() => logic.section = index),
onTap: () => logic.selectSection(index),
child: Text(
tabs[index],
style: TextStyle(
@@ -397,7 +381,7 @@ class _MyPageState extends State<MyPage> {
child: ChoiceChip(
label: Text(filters[index]),
selected: logic.filter == index,
onSelected: (_) => setState(() => logic.filter = index),
onSelected: (_) => logic.selectFilter(index),
showCheckmark: false,
visualDensity: VisualDensity.compact,
backgroundColor: const Color(0xFF242328),
@@ -418,7 +402,12 @@ class _MyPageState extends State<MyPage> {
if (logic.section == 0) const SizedBox(height: 11),
if (logic.section == 0 && hasHistory)
InkWell(
onTap: () => _act(MyPageAction.resume),
onTap: () => logic.act(
context,
MyPageAction.resume,
embedded: widget.embedded,
onAction: widget.onAction,
),
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(9),
@@ -499,7 +488,12 @@ class _MyPageState extends State<MyPage> {
if (widget.onDramaSelected != null) {
widget.onDramaSelected!(drama);
} else if (drama.id == widget.drama.id) {
_act(MyPageAction.resume);
logic.act(
context,
MyPageAction.resume,
embedded: widget.embedded,
onAction: widget.onAction,
);
}
},
borderRadius: BorderRadius.circular(12),
@@ -588,7 +582,7 @@ class _MyPageState extends State<MyPage> {
const Icon(Icons.chevron_right_rounded, size: 18),
],
),
onTap: _showLanguage,
onTap: () => logic.showLanguage(context),
),
),
],
@@ -606,12 +600,22 @@ class _MyPageState extends State<MyPage> {
_NavItem(
icon: Icons.play_circle_outline_rounded,
label: l10n.homeTab,
onTap: () => Navigator.pop(context, MyPageAction.home),
onTap: () => logic.act(
context,
MyPageAction.home,
embedded: widget.embedded,
onAction: widget.onAction,
),
),
_NavItem(
icon: Icons.explore_outlined,
label: l10n.discoverTab,
onTap: () => Navigator.pop(context, MyPageAction.discover),
onTap: () => logic.act(
context,
MyPageAction.discover,
embedded: widget.embedded,
onAction: widget.onAction,
),
),
_NavItem(
icon: Icons.video_library_outlined,