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

+109 -1
View File
@@ -1,8 +1,11 @@
import 'package:flutter/material.dart';
import '../../core/data/models/drama.dart';
import '../discover/discover_logic.dart';
import '../drama_detail/drama_detail_view.dart';
import '../mine/mine_logic.dart';
class HomeLogic {
class HomeLogic extends ChangeNotifier {
HomeLogic(List<Drama> dramas) : currentDrama = dramas.first;
final scaffoldKey = GlobalKey<ScaffoldState>();
@@ -16,6 +19,11 @@ class HomeLogic {
bool drawerOpen = false;
bool detailOpen = false;
bool videoImmersive = false;
bool _disposed = false;
void notify() {
if (!_disposed) notifyListeners();
}
void select(
int nextTab, {
@@ -32,5 +40,105 @@ class HomeLogic {
sectionRequest++;
}
if (resume) resumeRequest++;
notify();
}
void openMenu() {
drawerOpen = true;
notify();
scaffoldKey.currentState?.openDrawer();
}
void selectFromMenu(int nextTab, {int? mySection}) {
scaffoldKey.currentState?.closeDrawer();
select(nextTab, mySection: mySection);
}
Future<void> openCurrentSeries(BuildContext context) async {
detailOpen = true;
notify();
scaffoldKey.currentState?.closeDrawer();
await WidgetsBinding.instance.endOfFrame;
if (!context.mounted) return;
try {
await Navigator.of(context).push<void>(
MaterialPageRoute(
builder: (_) => DramaDetailPage(drama: currentDrama),
),
);
} finally {
if (context.mounted) {
detailOpen = false;
notify();
}
}
}
void openDrama(BuildContext context, Drama drama) {
Navigator.of(context).push(
MaterialPageRoute<void>(builder: (_) => DramaDetailPage(drama: drama)),
);
}
void discoverAction(BuildContext context, DiscoverPageAction action) {
switch (action) {
case DiscoverPageAction.home:
select(0);
case DiscoverPageAction.my:
select(2);
case DiscoverPageAction.watchlist:
select(2, watchlist: true);
case DiscoverPageAction.resume:
select(0, resume: true);
case DiscoverPageAction.detail:
openDrama(context, currentDrama);
}
}
void myAction(MyPageAction action) {
switch (action) {
case MyPageAction.home:
select(0);
case MyPageAction.discover:
select(1, search: true);
case MyPageAction.resume:
select(0, resume: true);
}
}
void onSystemBack() {
if (drawerOpen) {
scaffoldKey.currentState?.closeDrawer();
} else if (videoImmersive) {
backRequest++;
notify();
} else if (tab != 0) {
select(0);
} else {
backRequest++;
notify();
}
}
void onDrawerChanged(bool opened) {
if (drawerOpen == opened) return;
drawerOpen = opened;
notify();
}
void onDramaChanged(Drama drama) {
currentDrama = drama;
notify();
}
void onImmersiveChanged(bool value) {
videoImmersive = value;
notify();
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}
+31 -115
View File
@@ -5,7 +5,6 @@ import '../../core/data/models/drama.dart';
import '../../core/data/services/catalog_controller.dart';
import '../../l10n/app_localizations.dart';
import '../discover/discover_view.dart';
import '../drama_detail/drama_detail_view.dart';
import '../mine/mine_view.dart';
import '../player/player_view.dart';
import 'home_binding.dart';
@@ -79,163 +78,80 @@ class _HomeTabsState extends State<_HomeTabs> {
logic = HomeBinding().create(widget.dramas);
}
void _select(
int tab, {
bool search = false,
bool watchlist = false,
bool resume = false,
int? mySection,
}) {
setState(
() => logic.select(
tab,
search: search,
watchlist: watchlist,
resume: resume,
mySection: mySection,
),
);
}
void _openMenu() {
setState(() => logic.drawerOpen = true);
logic.scaffoldKey.currentState?.openDrawer();
}
void _selectFromMenu(int tab, {int? mySection}) {
logic.scaffoldKey.currentState?.closeDrawer();
_select(tab, mySection: mySection);
}
Future<void> _openCurrentSeries() async {
setState(() => logic.detailOpen = true);
logic.scaffoldKey.currentState?.closeDrawer();
await WidgetsBinding.instance.endOfFrame;
if (!mounted) return;
try {
await Navigator.of(context).push<void>(
MaterialPageRoute(
builder: (_) => DramaDetailPage(drama: logic.currentDrama),
),
);
} finally {
if (mounted) setState(() => logic.detailOpen = false);
}
}
void _discoverAction(DiscoverPageAction action) {
switch (action) {
case DiscoverPageAction.home:
_select(0);
case DiscoverPageAction.my:
_select(2);
case DiscoverPageAction.watchlist:
_select(2, watchlist: true);
case DiscoverPageAction.resume:
_select(0, resume: true);
case DiscoverPageAction.detail:
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => DramaDetailPage(drama: logic.currentDrama),
),
);
}
}
void _myAction(MyPageAction action) {
switch (action) {
case MyPageAction.home:
_select(0);
case MyPageAction.discover:
_select(1, search: true);
case MyPageAction.resume:
_select(0, resume: true);
}
@override
void dispose() {
logic.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: logic,
builder: (context, _) => _scaffold(context),
);
}
Widget _scaffold(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;
if (logic.drawerOpen) {
logic.scaffoldKey.currentState?.closeDrawer();
} else if (logic.videoImmersive) {
setState(() => logic.backRequest++);
} else if (logic.tab != 0) {
_select(0);
} else {
setState(() => logic.backRequest++);
}
logic.onSystemBack();
},
child: Scaffold(
key: logic.scaffoldKey,
backgroundColor: const Color(0xFF0B0C10),
drawer: HomeQuickDrawer(
drama: logic.currentDrama,
onOpenSeries: _openCurrentSeries,
onDiscover: () => _selectFromMenu(1),
onHistory: () => _selectFromMenu(2, mySection: 0),
onWatchlist: () => _selectFromMenu(2, mySection: 1),
onLiked: () => _selectFromMenu(2, mySection: 2),
onOpenSeries: () => logic.openCurrentSeries(context),
onDiscover: () => logic.selectFromMenu(1),
onHistory: () => logic.selectFromMenu(2, mySection: 0),
onWatchlist: () => logic.selectFromMenu(2, mySection: 1),
onLiked: () => logic.selectFromMenu(2, mySection: 2),
),
drawerEnableOpenDragGesture: false,
onDrawerChanged: (opened) {
if (logic.drawerOpen != opened) {
setState(() => logic.drawerOpen = opened);
}
},
onDrawerChanged: logic.onDrawerChanged,
body: IndexedStack(
index: logic.tab,
children: [
PlayerPage(
drama: widget.dramas.first,
feedDramas: widget.dramas,
onDramaChanged: (drama) =>
setState(() => logic.currentDrama = drama),
onDramaChanged: logic.onDramaChanged,
initialIndex: 0,
homeMode: true,
embedded: true,
active: logic.tab == 0 && !logic.drawerOpen && !logic.detailOpen,
resumeRequest: logic.resumeRequest,
backRequest: logic.backRequest,
onDiscover: () => _select(1),
onMy: () => _select(2),
onSearch: () => _select(1, search: true),
onMenu: _openMenu,
onImmersiveChanged: (value) =>
setState(() => logic.videoImmersive = value),
onDiscover: () => logic.select(1),
onMy: () => logic.select(2),
onSearch: () => logic.select(1, search: true),
onMenu: logic.openMenu,
onImmersiveChanged: logic.onImmersiveChanged,
),
DiscoverPage(
drama: logic.currentDrama,
dramas: widget.dramas,
onDramaSelected: (drama) => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => DramaDetailPage(drama: drama),
),
),
onDramaSelected: (drama) => logic.openDrama(context, drama),
duration: Duration.zero,
embedded: true,
active: logic.tab == 1,
searchRequest: logic.searchRequest,
onAction: _discoverAction,
onAction: (action) => logic.discoverAction(context, action),
),
MyPage(
drama: logic.currentDrama,
dramas: widget.dramas,
onDramaSelected: (drama) => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => DramaDetailPage(drama: drama),
),
),
onDramaSelected: (drama) => logic.openDrama(context, drama),
duration: Duration.zero,
initialSection: logic.mySection,
sectionRequest: logic.sectionRequest,
embedded: true,
active: logic.tab == 2,
onAction: _myAction,
onAction: logic.myAction,
),
],
),
@@ -258,21 +174,21 @@ class _HomeTabsState extends State<_HomeTabs> {
icon: Icons.play_circle_outline_rounded,
label: l10n.homeTab,
selected: logic.tab == 0,
onTap: () => _select(0),
onTap: () => logic.select(0),
),
_TabButton(
key: const Key('tab-discover'),
icon: Icons.explore_outlined,
label: l10n.discoverTab,
selected: logic.tab == 1,
onTap: () => _select(1),
onTap: () => logic.select(1),
),
_TabButton(
key: const Key('tab-my'),
icon: Icons.video_library_outlined,
label: l10n.myTab,
selected: logic.tab == 2,
onTap: () => _select(2),
onTap: () => logic.select(2),
),
],
),