feat(app): 剧场页九宫格与极简我的页

This commit is contained in:
Zhou Weidong committed 2026-10-20 05:28:51 +08:00
1 parent dc8ca7b743
commit 35338cbfc8
46 files changed
+2353 -4358

No files matched your search

+105 -113
View File
@@ -6,8 +6,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/data/models/drama.dart';
import '../../core/data/models/home_feed.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/data/services/catalog_controller.dart';
import '../../core/data/services/download_controller.dart';
import '../../core/data/services/wallet_controller.dart';
@@ -15,10 +13,11 @@ import '../../core/platform/haptics.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/app_bottom_nav.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/empty_state.dart';
import '../../l10n/app_localizations.dart';
import '../discover/discover_types.dart';
import '../discover/discover_view.dart';
import '../mine/mine_types.dart';
import '../splash/splash_page.dart';
import '../mine/mine_view.dart';
import '../player/player_view.dart';
@@ -74,7 +73,6 @@ class HomePage extends StatelessWidget {
return _HomeTabs(
key: ValueKey(catalog.dramas.first.id),
dramas: catalog.dramas,
banners: catalog.banners,
);
}
}
@@ -99,14 +97,9 @@ class HomeFailureView extends StatelessWidget {
/// The three primary destinations share a single route and keep their state.
class _HomeTabs extends StatefulWidget {
const _HomeTabs({
super.key,
required this.dramas,
required this.banners,
});
const _HomeTabs({super.key, required this.dramas});
final List<Drama> dramas;
final List<FeedBanner> banners;
@override
State<_HomeTabs> createState() => _HomeTabsState();
@@ -137,8 +130,6 @@ class _HomeTabsState extends State<_HomeTabs> {
Widget _scaffold(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
// 登录态统一从 AuthController 取(原来是 home_logic 里的本地演示开关)
final signedIn = context.watch<AuthController>().isSignedIn;
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
@@ -152,111 +143,113 @@ class _HomeTabsState extends State<_HomeTabs> {
child: Scaffold(
key: logic.scaffoldKey,
backgroundColor: AppColors.ink,
body: IndexedStack(
index: logic.tab,
children: [
PlayerPage(
drama: widget.dramas.first,
feedDramas: widget.dramas,
onDramaChanged: logic.onDramaChanged,
initialIndex: 0,
homeMode: true,
embedded: true,
active: logic.tab == 0 && !logic.drawerOpen && !logic.detailOpen,
resumeRequest: logic.resumeRequest,
backRequest: logic.backRequest,
onDiscover: () => logic.select(1),
onMy: logic.openProfile,
onSearch: () => logic.select(1, search: true),
onMenu: () => _openMenu(context),
onImmersiveChanged: logic.onImmersiveChanged,
wallet: context.read<WalletController>(),
downloads: context.read<DownloadController>(),
),
DiscoverPage(
drama: logic.currentDrama,
dramas: widget.dramas,
onDramaSelected: (drama) => logic.playDrama(context, drama),
duration: Duration.zero,
embedded: true,
active: logic.tab == 1,
searchRequest: logic.searchRequest,
signedIn: signedIn,
onSignIn: () => openAuthPage(context),
onCoins: logic.openProfile,
onRewards: logic.openProfile,
banners: widget.banners,
onBannerSelected: (banner) {
final dramaId = banner.dramaId;
if (dramaId != null) {
unawaited(logic.openDramaById(context, dramaId));
}
},
onAction: (action) => logic.discoverAction(context, action),
),
MyPage(
drama: logic.currentDrama,
dramas: widget.dramas,
onDramaSelected: (drama) => logic.openDrama(context, drama),
duration: Duration.zero,
initialSection: logic.mySection,
profile: logic.profile,
sectionRequest: logic.sectionRequest,
embedded: true,
active: logic.tab == 2,
onAction: logic.myAction,
signedIn: signedIn,
onSignIn: () => openAuthPage(context),
onSignOut: () => context.read<AuthController>().signOut(),
wallet: context.read<WalletController>(),
),
],
),
bottomNavigationBar: logic.videoImmersive
? null
: AppBottomNav(
barKey: const Key('home-tabs-bar'),
items: [
AppBottomNavItem(
tabKey: const Key('tab-home'),
icon: Icons.play_circle_outline_rounded,
selectedIcon: Icons.play_circle_fill_rounded,
label: l10n.homeTab,
selected: logic.tab == 0,
onTap: () => _selectTab(context, 0),
),
AppBottomNavItem(
tabKey: const Key('tab-discover'),
icon: Icons.explore_outlined,
selectedIcon: Icons.explore_rounded,
label: l10n.discoverTab,
selected: logic.tab == 1,
onTap: () => _selectTab(context, 1),
),
AppBottomNavItem(
tabKey: const Key('tab-my'),
icon: Icons.video_library_outlined,
selectedIcon: Icons.video_library_rounded,
label: l10n.myTab,
selected: logic.tab == 2,
onTap: () => _selectTab(context, 2, myProfile: true),
),
],
),
body: _body(context),
bottomNavigationBar: logic.videoImmersive ? null : _nav(context, l10n),
),
);
}
void _selectTab(BuildContext context, int tab, {bool myProfile = false}) {
Widget _body(BuildContext context) {
return IndexedStack(
index: logic.tab,
children: [_player(context), _discover(context), _my(context)],
);
}
Widget _player(BuildContext context) {
return PlayerPage(
drama: widget.dramas.first,
feedDramas: widget.dramas,
onDramaChanged: logic.onDramaChanged,
initialIndex: 0,
homeMode: true,
embedded: true,
active: logic.tab == 0 && !logic.drawerOpen && !logic.detailOpen,
resumeRequest: logic.resumeRequest,
backRequest: logic.backRequest,
onDiscover: () => logic.select(1),
onMy: logic.openProfile,
onSearch: () => logic.select(1, search: true),
onMenu: () => _openMenu(context),
onImmersiveChanged: logic.onImmersiveChanged,
wallet: context.read<WalletController>(),
downloads: context.read<DownloadController>(),
);
}
Widget _discover(BuildContext context) {
return DiscoverPage(
active: logic.tab == 1,
searchRequest: logic.searchRequest,
callbacks: DiscoverPageCallbacks(
onAction: logic.discoverAction,
onDramaSelected: (drama) => logic.playDrama(context, drama),
onProfile: logic.openProfile,
),
);
}
Widget _my(BuildContext context) {
return MyPage(
active: logic.tab == 2,
callbacks: MyPageCallbacks(
onAction: logic.myAction,
onDramaSelected: (drama) => logic.openDrama(context, drama),
),
);
}
Widget _nav(BuildContext context, AppLocalizations l10n) {
return AppBottomNav(
barKey: const Key('home-tabs-bar'),
items: [
_navItem(context, l10n, 0),
_navItem(context, l10n, 1),
_navItem(context, l10n, 2),
],
);
}
AppBottomNavItem _navItem(
BuildContext context,
AppLocalizations l10n,
int index,
) {
final selected = logic.tab == index;
return switch (index) {
0 => AppBottomNavItem(
tabKey: const Key('tab-home'),
icon: Icons.play_circle_outline_rounded,
selectedIcon: Icons.play_circle_fill_rounded,
label: l10n.homeTab,
selected: selected,
onTap: () => _selectTab(context, index),
),
1 => AppBottomNavItem(
tabKey: const Key('tab-discover'),
icon: Icons.explore_outlined,
selectedIcon: Icons.explore_rounded,
label: l10n.discoverTab,
selected: selected,
onTap: () => _selectTab(context, index),
),
_ => AppBottomNavItem(
tabKey: const Key('tab-my'),
icon: Icons.video_library_outlined,
selectedIcon: Icons.video_library_rounded,
label: l10n.myTab,
selected: selected,
onTap: () => _selectTab(context, index),
),
};
}
void _selectTab(BuildContext context, int tab) {
final same = logic.tab == tab;
if (!same) {
unawaited(context.read<Haptics>().selectionClick());
}
if (tab == 2) {
myProfile ? logic.openProfile() : logic.openLibrary();
} else {
logic.select(tab);
}
logic.select(tab);
}
Future<void> _openMenu(BuildContext context) async {
@@ -269,10 +262,9 @@ class _HomeTabsState extends State<_HomeTabs> {
builder: (_) => HomeMenuSheet(
drama: logic.currentDrama,
onOpenSeries: () => logic.openCurrentSeries(context),
onDiscover: () => logic.selectFromMenu(1),
onHistory: () => logic.openLibrary(section: 0),
onWatchlist: logic.openLibrary,
onLiked: () => logic.selectFromMenu(2, mySection: 2),
onDiscover: () => logic.select(1),
onHistory: () => logic.openHistory(context),
onWatchlist: () => logic.openFavorites(context),
),
);
logic.setMenuOpen(false);