From 91e869d589e2f868e58f1b82d677a7d645518572 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Thu, 22 Oct 2026 16:21:05 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(nav):=20=E5=BA=95=E9=83=A8=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E6=94=B9=E4=B8=BA=E9=A6=96=E9=A1=B5=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=E8=BF=BD=E5=89=A7=E6=88=91=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/theme/app_colors.dart | 7 +- lib/core/theme/app_dimens.dart | 2 +- lib/core/theme/app_theme.dart | 4 +- lib/core/widgets/app_bottom_nav.dart | 36 +++--- lib/core/widgets/app_root_tab.dart | 38 ++++++ lib/core/widgets/subpage_header.dart | 12 +- lib/l10n/app_en.arb | 2 +- lib/l10n/app_id.arb | 2 +- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_en.dart | 2 +- lib/l10n/app_localizations_id.dart | 2 +- lib/l10n/app_localizations_zh.dart | 2 +- lib/l10n/app_zh.arb | 2 +- lib/pages/discover/discover_types.dart | 4 +- lib/pages/discover/discover_view.dart | 50 +++----- lib/pages/favorites/favorites_page.dart | 60 +++++---- lib/pages/home/home_logic.dart | 12 +- lib/pages/home/home_view.dart | 58 +++------ lib/pages/mine/mine_types.dart | 2 +- lib/pages/mine/mine_view.dart | 48 +++---- lib/pages/player/player_logic.dart | 4 +- test/discover/discover_logic_test.dart | 4 +- test/discover/filter_page_test.dart | 2 +- test/discover/home_all_dramas_grid_test.dart | 2 +- test/discover/theater_pages_test.dart | 2 +- test/home/app_bottom_nav_test.dart | 54 ++++---- test/home/home_logic_test.dart | 8 +- test/home/home_nav_ui_test.dart | 128 +++++++++++++++---- test/home/k28_a_navigation_test.dart | 40 ++---- test/mine/mine_logic_test.dart | 4 +- test/player/player_fullscreen_test.dart | 4 +- test/theme/app_dimens_test.dart | 2 +- test/theme/app_theme_test.dart | 5 +- test/widget_test.dart | 16 +-- 34 files changed, 359 insertions(+), 263 deletions(-) create mode 100644 lib/core/widgets/app_root_tab.dart diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index 585fc68..c9b764e 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -69,6 +69,11 @@ abstract final class AppColors { /// 底部导航底色。 static const bar = ink; + /// 底部导航专用:纯黑背景、选中白、未选中灰与轻分割线。 + static const navBackground = Color(0xFF000000); + static const navSelected = Color(0xFFFFFFFF); + static const navDivider = Color(0xFF1C1C1E); + /// 卡片阴影(深色几乎不用阴影)。 static const cardShadow = Color(0x00000000); @@ -121,7 +126,7 @@ abstract final class AppColors { static const badgeBeige = raised; /// 导航未选中。 - static const navUnselected = muted; + static const navUnselected = Color(0xFF8A8A8E); /// 播放器次要文字。 static const playerMuted = Color(0xA3FFFFFF); diff --git a/lib/core/theme/app_dimens.dart b/lib/core/theme/app_dimens.dart index 5de3484..fba2ed4 100644 --- a/lib/core/theme/app_dimens.dart +++ b/lib/core/theme/app_dimens.dart @@ -36,7 +36,7 @@ abstract final class AppDimens { static const double previewCardTitle = 13; static const double previewSectionTitle = 14; static const double chipFont = 10; - static const double navLabel = 12; + static const double navLabel = 11; static const double buttonHeight = 48; static const double primaryButtonHeight = 52; diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index dc0003d..8cc1e5c 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -193,8 +193,8 @@ abstract final class AppTheme { ), ), bottomNavigationBarTheme: const BottomNavigationBarThemeData( - backgroundColor: AppColors.bar, - selectedItemColor: AppColors.blue, + backgroundColor: AppColors.navBackground, + selectedItemColor: AppColors.navSelected, unselectedItemColor: AppColors.navUnselected, elevation: 0, type: BottomNavigationBarType.fixed, diff --git a/lib/core/widgets/app_bottom_nav.dart b/lib/core/widgets/app_bottom_nav.dart index 9b29d9d..6578ecc 100644 --- a/lib/core/widgets/app_bottom_nav.dart +++ b/lib/core/widgets/app_bottom_nav.dart @@ -1,7 +1,5 @@ // Author: fruit.com.cn -import 'dart:ui'; - import 'package:flutter/material.dart'; import '../theme/app_colors.dart'; @@ -26,12 +24,12 @@ class AppBottomNavItem { final bool selected; } -/// 四槽底部导航:所有页面统一深色底与浅色图标。 +/// 四槽底部导航:纯黑背景,白色选中态,灰色未选中态。 class AppBottomNav extends StatelessWidget { const AppBottomNav({ super.key, required this.items, - this.backgroundColor = AppColors.bar, + this.backgroundColor = AppColors.navBackground, this.barKey, this.dark = false, }); @@ -47,8 +45,10 @@ class AppBottomNav extends StatelessWidget { key: barKey, height: AppDimens.navHeight, decoration: BoxDecoration( - color: dark ? AppColors.bar : backgroundColor.withValues(alpha: 0.96), - border: const Border(top: BorderSide(color: AppColors.border)), + color: dark ? AppColors.navBackground : backgroundColor, + border: const Border( + top: BorderSide(color: AppColors.navDivider, width: 0.5), + ), ), child: Row( children: [ @@ -60,17 +60,9 @@ class AppBottomNav extends StatelessWidget { ], ), ); - if (dark) { - return ColoredBox( - color: AppColors.bar, - child: SafeArea(top: false, child: bar), - ); - } - return ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), - child: SafeArea(top: false, child: bar), - ), + return ColoredBox( + color: AppColors.navBackground, + child: SafeArea(top: false, child: bar), ); } } @@ -102,7 +94,9 @@ class _NavDestination extends StatelessWidget { child: Icon( item.selected ? item.selectedIcon : item.icon, size: AppDimens.navIcon, - color: item.selected ? AppColors.blue : unselectedColor, + color: item.selected + ? AppColors.navSelected + : unselectedColor, ), ), const SizedBox(height: 3), @@ -112,8 +106,10 @@ class _NavDestination extends StatelessWidget { overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: AppDimens.navLabel, - fontWeight: item.selected ? FontWeight.w700 : FontWeight.w500, - color: item.selected ? AppColors.blue : unselectedColor, + fontWeight: item.selected ? FontWeight.w600 : FontWeight.w400, + color: item.selected + ? AppColors.navSelected + : unselectedColor, letterSpacing: 0, ), ), diff --git a/lib/core/widgets/app_root_tab.dart b/lib/core/widgets/app_root_tab.dart new file mode 100644 index 0000000..2a49059 --- /dev/null +++ b/lib/core/widgets/app_root_tab.dart @@ -0,0 +1,38 @@ +// Author: fruit.com.cn + +import 'package:flutter/material.dart'; + +import '../../l10n/app_localizations.dart'; + +/// 客户端根导航的固定顺序。 +enum AppRootTab { home, recommend, following, mine } + +extension AppRootTabUi on AppRootTab { + Key get tabKey => switch (this) { + AppRootTab.home => const Key('tab-home'), + AppRootTab.recommend => const Key('tab-recommend'), + AppRootTab.following => const Key('tab-follow'), + AppRootTab.mine => const Key('tab-mine'), + }; + + IconData get icon => switch (this) { + AppRootTab.home => Icons.home_outlined, + AppRootTab.recommend => Icons.smart_display_outlined, + AppRootTab.following => Icons.bookmark_border_rounded, + AppRootTab.mine => Icons.person_outline_rounded, + }; + + IconData get selectedIcon => switch (this) { + AppRootTab.home => Icons.home_rounded, + AppRootTab.recommend => Icons.smart_display_rounded, + AppRootTab.following => Icons.bookmark_rounded, + AppRootTab.mine => Icons.person_rounded, + }; + + String label(AppLocalizations l10n) => switch (this) { + AppRootTab.home => l10n.homeTab, + AppRootTab.recommend => l10n.recommendedTab, + AppRootTab.following => l10n.followingTab, + AppRootTab.mine => l10n.myTab, + }; +} diff --git a/lib/core/widgets/subpage_header.dart b/lib/core/widgets/subpage_header.dart index ed8131f..0c57432 100644 --- a/lib/core/widgets/subpage_header.dart +++ b/lib/core/widgets/subpage_header.dart @@ -120,6 +120,7 @@ class AppSubpageHeader extends StatelessWidget { this.actions, this.onBack, this.dark = false, + this.showBack = true, }); final String title; @@ -128,6 +129,7 @@ class AppSubpageHeader extends StatelessWidget { final Widget? actions; final VoidCallback? onBack; final bool dark; + final bool showBack; @override Widget build(BuildContext context) { @@ -140,8 +142,14 @@ class AppSubpageHeader extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: _horizontal), child: Row( children: [ - AppSubpageBackButton(backKey: backKey, dark: dark, onBack: onBack), - const SizedBox(width: _backGap), + if (showBack) ...[ + AppSubpageBackButton( + backKey: backKey, + dark: dark, + onBack: onBack, + ), + const SizedBox(width: _backGap), + ], Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 526f378..20336f1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -162,7 +162,7 @@ "preferences": "Preferences", "followingTab": "Following", "animatedTab": "Animated", - "recommendedTab": "Recommended", + "recommendedTab": "Recommend", "noCategoryDramas": "No dramas in this category yet.", "allDramas": "All Dramas", "allLanguages": "All languages", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index b707d25..684a989 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -139,7 +139,7 @@ "completed": "Selesai", "noSavedShows": "Belum ada tontonan di sini.", "preferences": "Preferensi", - "followingTab": "Mengikuti", + "followingTab": "Daftar Tonton", "animatedTab": "Animasi", "recommendedTab": "Rekomendasi", "noCategoryDramas": "Belum ada drama di kategori ini.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 447651c..5ce83e2 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -685,7 +685,7 @@ abstract class AppLocalizations { /// No description provided for @recommendedTab. /// /// In en, this message translates to: - /// **'Recommended'** + /// **'Recommend'** String get recommendedTab; /// No description provided for @noCategoryDramas. diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index d36f78e..36952cf 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -329,7 +329,7 @@ class AppLocalizationsEn extends AppLocalizations { String get animatedTab => 'Animated'; @override - String get recommendedTab => 'Recommended'; + String get recommendedTab => 'Recommend'; @override String get noCategoryDramas => 'No dramas in this category yet.'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index b5c5ee0..a540d17 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -323,7 +323,7 @@ class AppLocalizationsId extends AppLocalizations { String get preferences => 'Preferensi'; @override - String get followingTab => 'Mengikuti'; + String get followingTab => 'Daftar Tonton'; @override String get animatedTab => 'Animasi'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 3bfa8c4..d57351f 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -318,7 +318,7 @@ class AppLocalizationsZh extends AppLocalizations { String get preferences => '偏好设置'; @override - String get followingTab => '关注'; + String get followingTab => '追剧'; @override String get animatedTab => '漫剧'; diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 32ecfa9..25647d4 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -139,7 +139,7 @@ "completed": "已看完", "noSavedShows": "这里还没有剧集。", "preferences": "偏好设置", - "followingTab": "关注", + "followingTab": "追剧", "animatedTab": "漫剧", "recommendedTab": "推荐", "noCategoryDramas": "这个分类暂时没有剧集。", diff --git a/lib/pages/discover/discover_types.dart b/lib/pages/discover/discover_types.dart index d369cc9..8443cb6 100644 --- a/lib/pages/discover/discover_types.dart +++ b/lib/pages/discover/discover_types.dart @@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart'; import '../../core/data/models/drama.dart'; -/// 剧场页与根导航之间的动作。 -enum DiscoverPageAction { home, create, my } +/// 推荐页与根导航之间的动作。 +enum DiscoverPageAction { home, recommend, following, mine } /// 剧场页导航回调;没有 `onAction` 时按独立页面处理。 class DiscoverPageCallbacks { diff --git a/lib/pages/discover/discover_view.dart b/lib/pages/discover/discover_view.dart index 961f09b..8ec05bd 100644 --- a/lib/pages/discover/discover_view.dart +++ b/lib/pages/discover/discover_view.dart @@ -19,6 +19,7 @@ import '../../core/theme/app_dimens.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/app_bottom_nav.dart'; import '../../core/widgets/app_pull_to_refresh.dart'; +import '../../core/widgets/app_root_tab.dart'; import '../../core/widgets/catalog_debug_info.dart'; import '../../core/widgets/catalog_endpoint_editor.dart'; import '../../core/widgets/empty_state.dart'; @@ -149,7 +150,7 @@ class _DiscoverPageState extends State { key: const Key('theater-page-surface'), color: AppColors.ink, child: SafeArea( - bottom: !embedded, + bottom: false, child: Column( children: [ DiscoverHeader( @@ -613,37 +614,26 @@ class _DiscoverPageState extends State { Widget _bottomNav(AppLocalizations l10n) => AppBottomNav( items: [ - AppBottomNavItem( - tabKey: const Key('discover-nav-home'), - icon: Icons.home_outlined, - selectedIcon: Icons.home_rounded, - label: l10n.homeTab, - onTap: () => _act(DiscoverPageAction.home), - ), - AppBottomNavItem( - tabKey: const Key('discover-nav-current'), - icon: Icons.explore_outlined, - selectedIcon: Icons.explore_rounded, - label: l10n.theaterTab, - selected: true, - onTap: () {}, - ), - AppBottomNavItem( - tabKey: const Key('discover-nav-create'), - icon: Icons.add_box_outlined, - selectedIcon: Icons.add_box_rounded, - label: l10n.createTab, - onTap: () => _act(DiscoverPageAction.create), - ), - AppBottomNavItem( - tabKey: const Key('discover-nav-my'), - icon: Icons.person_outline_rounded, - selectedIcon: Icons.person_rounded, - label: l10n.myTab, - onTap: () => _act(DiscoverPageAction.my), - ), + for (final tab in AppRootTab.values) + AppBottomNavItem( + tabKey: tab.tabKey, + icon: tab.icon, + selectedIcon: tab.selectedIcon, + label: tab.label(l10n), + selected: tab == AppRootTab.recommend, + onTap: tab == AppRootTab.recommend + ? () {} + : () => _act(_actionOf(tab)), + ), ], ); + + DiscoverPageAction _actionOf(AppRootTab tab) => switch (tab) { + AppRootTab.home => DiscoverPageAction.home, + AppRootTab.recommend => DiscoverPageAction.recommend, + AppRootTab.following => DiscoverPageAction.following, + AppRootTab.mine => DiscoverPageAction.mine, + }; } /// 分类 tab 的弧形选中线。 diff --git a/lib/pages/favorites/favorites_page.dart b/lib/pages/favorites/favorites_page.dart index 5333467..5452ec1 100644 --- a/lib/pages/favorites/favorites_page.dart +++ b/lib/pages/favorites/favorites_page.dart @@ -44,14 +44,33 @@ const double _loadMoreThreshold = 240; final RegExp _tagSplitter = RegExp(r'[,/、·]'); /// 独立我的收藏页。 -class FavoritesPage extends StatefulWidget { +class FavoritesPage extends StatelessWidget { const FavoritesPage({super.key}); @override - State createState() => _FavoritesPageState(); + Widget build(BuildContext context) { + return Theme( + data: AppTheme.dark(), + child: Scaffold( + key: const Key('favorites-page'), + backgroundColor: AppColors.ink, + body: const SafeArea(child: FavoritesContent()), + ), + ); + } } -class _FavoritesPageState extends State { +/// 收藏列表主体;可独立使用,也可直接嵌入根导航。 +class FavoritesContent extends StatefulWidget { + const FavoritesContent({super.key, this.showBack = true}); + + final bool showBack; + + @override + State createState() => _FavoritesContentState(); +} + +class _FavoritesContentState extends State { bool _requested = false; @override @@ -74,28 +93,21 @@ class _FavoritesPageState extends State { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final signedIn = context.watch().isSignedIn; - return Theme( - data: AppTheme.dark(), - child: Scaffold( - key: const Key('favorites-page'), - backgroundColor: AppColors.ink, - body: SafeArea( - child: Column( - children: [ - AppSubpageHeader( - title: l10n.myFavorites, - backKey: const Key('favorites-back'), - ), - Expanded( - child: signedIn - ? _FavoritesBody( - favorites: context.watch(), - ) - : const AuthGatePanel(), - ), - ], + return SafeArea( + bottom: false, + child: Column( + children: [ + AppSubpageHeader( + title: l10n.myFavorites, + backKey: const Key('favorites-back'), + showBack: widget.showBack, ), - ), + Expanded( + child: signedIn + ? _FavoritesBody(favorites: context.watch()) + : const AuthGatePanel(), + ), + ], ), ); } diff --git a/lib/pages/home/home_logic.dart b/lib/pages/home/home_logic.dart index 826749e..dfd1f00 100644 --- a/lib/pages/home/home_logic.dart +++ b/lib/pages/home/home_logic.dart @@ -79,9 +79,11 @@ class HomeLogic extends ChangeNotifier { switch (action) { case DiscoverPageAction.home: select(0); - case DiscoverPageAction.create: + case DiscoverPageAction.recommend: + select(1, search: true); + case DiscoverPageAction.following: select(2); - case DiscoverPageAction.my: + case DiscoverPageAction.mine: select(3); } } @@ -102,10 +104,12 @@ class HomeLogic extends ChangeNotifier { switch (action) { case MyPageAction.home: select(0); - case MyPageAction.discover: + case MyPageAction.recommend: select(1, search: true); - case MyPageAction.create: + case MyPageAction.following: select(2); + case MyPageAction.mine: + select(3); case MyPageAction.resume: select(0, resume: true); } diff --git a/lib/pages/home/home_view.dart b/lib/pages/home/home_view.dart index 6a3ab4c..61b52ec 100644 --- a/lib/pages/home/home_view.dart +++ b/lib/pages/home/home_view.dart @@ -17,13 +17,14 @@ 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/app_root_tab.dart'; import '../../core/widgets/catalog_debug_info.dart'; import '../../core/widgets/catalog_endpoint_editor.dart'; import '../../core/widgets/empty_state.dart'; import '../../l10n/app_localizations.dart'; import '../discover/discover_types.dart'; import '../discover/discover_view.dart'; -import '../create/create_view.dart'; +import '../favorites/favorites_page.dart'; import '../mine/mine_types.dart'; import '../player/widgets/home_feed_header.dart'; import '../player/player_view.dart'; @@ -295,7 +296,7 @@ class _HomeTabsState extends State<_HomeTabs> { children: [ _home(context), _discover(context), - const CreatePage(), + const FavoritesContent(key: Key('favorites-page'), showBack: false), _my(context), ], ); @@ -455,10 +456,10 @@ class _HomeTabsState extends State<_HomeTabs> { barKey: const Key('home-tabs-bar'), dark: logic.tab == 0, items: [ - _navItem(context, l10n, 0), - _navItem(context, l10n, 1), - _navItem(context, l10n, 2), - _navItem(context, l10n, 3), + _navItem(context, l10n, AppRootTab.home), + _navItem(context, l10n, AppRootTab.recommend), + _navItem(context, l10n, AppRootTab.following), + _navItem(context, l10n, AppRootTab.mine), ], ); } @@ -466,43 +467,16 @@ class _HomeTabsState extends State<_HomeTabs> { AppBottomNavItem _navItem( BuildContext context, AppLocalizations l10n, - int index, + AppRootTab tab, ) { - final selected = logic.tab == index; - return switch (index) { - 0 => AppBottomNavItem( - tabKey: const Key('tab-home'), - icon: Icons.home_outlined, - selectedIcon: Icons.home_rounded, - label: l10n.homeTab, - selected: selected, - onTap: () => _selectTab(context, index), - ), - 1 => AppBottomNavItem( - tabKey: const Key('tab-theater'), - icon: Icons.explore_outlined, - selectedIcon: Icons.explore_rounded, - label: l10n.theaterTab, - selected: selected, - onTap: () => _selectTab(context, index), - ), - 2 => AppBottomNavItem( - tabKey: const Key('tab-create'), - icon: Icons.add_box_outlined, - selectedIcon: Icons.add_box_rounded, - label: l10n.createTab, - selected: selected, - onTap: () => _selectTab(context, index), - ), - _ => AppBottomNavItem( - tabKey: const Key('tab-mine'), - icon: Icons.person_outline_rounded, - selectedIcon: Icons.person_rounded, - label: l10n.myTab, - selected: selected, - onTap: () => _selectTab(context, index), - ), - }; + return AppBottomNavItem( + tabKey: tab.tabKey, + icon: tab.icon, + selectedIcon: tab.selectedIcon, + label: tab.label(l10n), + selected: logic.tab == tab.index, + onTap: () => _selectTab(context, tab.index), + ); } void _selectTab(BuildContext context, int tab) { diff --git a/lib/pages/mine/mine_types.dart b/lib/pages/mine/mine_types.dart index a823e8b..b7da322 100644 --- a/lib/pages/mine/mine_types.dart +++ b/lib/pages/mine/mine_types.dart @@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart'; import '../../core/data/models/drama.dart'; /// 「我的」页与根导航之间的返回动作。 -enum MyPageAction { home, discover, create, resume } +enum MyPageAction { home, recommend, following, mine, resume } /// 「我的」页可进入的功能目的地。 enum MineDestination { diff --git a/lib/pages/mine/mine_view.dart b/lib/pages/mine/mine_view.dart index cb3ac65..bba996f 100644 --- a/lib/pages/mine/mine_view.dart +++ b/lib/pages/mine/mine_view.dart @@ -13,6 +13,7 @@ import '../../core/theme/app_colors.dart'; import '../../core/theme/app_dimens.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/app_bottom_nav.dart'; +import '../../core/widgets/app_root_tab.dart'; import '../../core/widgets/auth_gate.dart'; import '../../core/widgets/plain_page_scaffold.dart'; import '../../l10n/app_localizations.dart'; @@ -81,7 +82,7 @@ class _MyPageState extends State { Widget _page(BuildContext context) { final l10n = AppLocalizations.of(context)!; final content = SafeArea( - bottom: !embedded, + bottom: false, child: Column( children: [ _topBar(l10n), @@ -240,39 +241,26 @@ class _MyPageState extends State { Widget _bottomNav(AppLocalizations l10n) { return AppBottomNav( items: [ - AppBottomNavItem( - tabKey: const Key('mine-nav-home'), - icon: Icons.home_outlined, - selectedIcon: Icons.home_rounded, - label: l10n.homeTab, - onTap: () => _act(MyPageAction.home), - ), - AppBottomNavItem( - tabKey: const Key('mine-nav-discover'), - icon: Icons.explore_outlined, - selectedIcon: Icons.explore_rounded, - label: l10n.theaterTab, - onTap: () => _act(MyPageAction.discover), - ), - AppBottomNavItem( - tabKey: const Key('mine-nav-create'), - icon: Icons.add_box_outlined, - selectedIcon: Icons.add_box_rounded, - label: l10n.createTab, - onTap: () => _act(MyPageAction.create), - ), - AppBottomNavItem( - tabKey: const Key('mine-nav-current'), - icon: Icons.person_outline_rounded, - selectedIcon: Icons.person_rounded, - label: l10n.myTab, - selected: true, - onTap: () {}, - ), + for (final tab in AppRootTab.values) + AppBottomNavItem( + tabKey: tab.tabKey, + icon: tab.icon, + selectedIcon: tab.selectedIcon, + label: tab.label(l10n), + selected: tab == AppRootTab.mine, + onTap: tab == AppRootTab.mine ? () {} : () => _act(_actionOf(tab)), + ), ], ); } + MyPageAction _actionOf(AppRootTab tab) => switch (tab) { + AppRootTab.home => MyPageAction.home, + AppRootTab.recommend => MyPageAction.recommend, + AppRootTab.following => MyPageAction.following, + AppRootTab.mine => MyPageAction.mine, + }; + void _act(MyPageAction action) { logic.act( context, diff --git a/lib/pages/player/player_logic.dart b/lib/pages/player/player_logic.dart index 97b90a3..a9c44e8 100644 --- a/lib/pages/player/player_logic.dart +++ b/lib/pages/player/player_logic.dart @@ -1085,7 +1085,7 @@ class PlayerLogic extends ChangeNotifier { context, ).push(MaterialPageRoute(builder: (_) => const MyPage())); if (!context.mounted) return; - if (action == MyPageAction.discover) { + if (action == MyPageAction.recommend) { await showDiscover(context); } else { session.setRouteVisible(true); @@ -1117,7 +1117,7 @@ class PlayerLogic extends ChangeNotifier { MaterialPageRoute(builder: (_) => DiscoverPage(focusSearch: focusSearch)), ); if (!context.mounted) return; - if (action == DiscoverPageAction.my) { + if (action == DiscoverPageAction.mine) { await showMy(context); } else { session.setRouteVisible(true); diff --git a/test/discover/discover_logic_test.dart b/test/discover/discover_logic_test.dart index bd84546..80bcfe8 100644 --- a/test/discover/discover_logic_test.dart +++ b/test/discover/discover_logic_test.dart @@ -99,9 +99,9 @@ void main() { ); expect(forwarded, [DiscoverPageAction.home]); - logic.act(pageContext, DiscoverPageAction.my, embedded: false); + logic.act(pageContext, DiscoverPageAction.mine, embedded: false); await tester.pumpAndSettle(); - await expectLater(popped.future, completion(DiscoverPageAction.my)); + await expectLater(popped.future, completion(DiscoverPageAction.mine)); logic.dispose(); }); diff --git a/test/discover/filter_page_test.dart b/test/discover/filter_page_test.dart index 10fe4c2..91d82dc 100644 --- a/test/discover/filter_page_test.dart +++ b/test/discover/filter_page_test.dart @@ -151,7 +151,7 @@ Future _pumpFilterPage(WidgetTester tester) async { ), ); await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('theater-toolbar-filter'))); await tester.pumpAndSettle(); diff --git a/test/discover/home_all_dramas_grid_test.dart b/test/discover/home_all_dramas_grid_test.dart index 960a0d2..5ed4eb0 100644 --- a/test/discover/home_all_dramas_grid_test.dart +++ b/test/discover/home_all_dramas_grid_test.dart @@ -455,7 +455,7 @@ Future _pumpTheater( ), ); await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); } diff --git a/test/discover/theater_pages_test.dart b/test/discover/theater_pages_test.dart index 4bb3437..1e1877c 100644 --- a/test/discover/theater_pages_test.dart +++ b/test/discover/theater_pages_test.dart @@ -303,7 +303,7 @@ Future _pumpTheater( ), ); await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); } diff --git a/test/home/app_bottom_nav_test.dart b/test/home/app_bottom_nav_test.dart index 2ddf85a..ecf53c2 100644 --- a/test/home/app_bottom_nav_test.dart +++ b/test/home/app_bottom_nav_test.dart @@ -7,7 +7,7 @@ import 'package:fruitshorts/core/theme/app_dimens.dart'; import 'package:fruitshorts/core/widgets/app_bottom_nav.dart'; void main() { - testWidgets('should_render_four_slots_with_orange_selected_state', ( + testWidgets('should_render_four_slots_with_white_selected_state', ( tester, ) async { var theaterTaps = 0; @@ -25,17 +25,17 @@ void main() { onTap: () {}, ), AppBottomNavItem( - tabKey: const Key('tab-theater'), - icon: Icons.explore_outlined, - selectedIcon: Icons.explore_rounded, - label: 'Theater', + tabKey: const Key('tab-recommend'), + icon: Icons.smart_display_outlined, + selectedIcon: Icons.smart_display_rounded, + label: 'Recommend', onTap: () => theaterTaps++, ), AppBottomNavItem( - tabKey: const Key('tab-create'), - icon: Icons.add_box_outlined, - selectedIcon: Icons.add_box_rounded, - label: 'Create', + tabKey: const Key('tab-follow'), + icon: Icons.bookmark_border_rounded, + selectedIcon: Icons.bookmark_rounded, + label: 'Following', onTap: () {}, ), AppBottomNavItem( @@ -52,15 +52,21 @@ void main() { ); expect(find.byKey(const Key('tab-home')), findsOneWidget); - expect(find.byKey(const Key('tab-theater')), findsOneWidget); - expect(find.byKey(const Key('tab-create')), findsOneWidget); + expect(find.byKey(const Key('tab-recommend')), findsOneWidget); + expect(find.byKey(const Key('tab-follow')), findsOneWidget); expect(find.byKey(const Key('tab-mine')), findsOneWidget); expect(find.byKey(const Key('nav-center-action')), findsNothing); expect(find.text('Home'), findsOneWidget); + final selectedLabel = tester.widget(find.text('Home')); + expect(selectedLabel.style?.color, AppColors.navSelected); + expect(selectedLabel.style?.fontWeight, FontWeight.w600); + final unselectedLabel = tester.widget(find.text('Recommend')); + expect(unselectedLabel.style?.color, AppColors.navUnselected); + expect(unselectedLabel.style?.fontWeight, FontWeight.w400); final centers = [ Key('tab-home'), - Key('tab-theater'), - Key('tab-create'), + Key('tab-recommend'), + Key('tab-follow'), Key('tab-mine'), ].map((key) => tester.getCenter(find.byKey(key)).dx).toList(); expect(centers, orderedEquals(centers.toList()..sort())); @@ -77,9 +83,9 @@ void main() { ), ) .color, - AppColors.blue, + AppColors.navSelected, ); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); expect(theaterTaps, 1); }); @@ -105,17 +111,17 @@ void main() { onTap: () {}, ), AppBottomNavItem( - tabKey: const Key('tab-theater'), - icon: Icons.explore_outlined, - selectedIcon: Icons.explore_rounded, - label: 'Theater', + tabKey: const Key('tab-recommend'), + icon: Icons.smart_display_outlined, + selectedIcon: Icons.smart_display_rounded, + label: 'Recommend', onTap: () {}, ), AppBottomNavItem( - tabKey: const Key('tab-create'), - icon: Icons.add_box_outlined, - selectedIcon: Icons.add_box_rounded, - label: 'Create', + tabKey: const Key('tab-follow'), + icon: Icons.bookmark_border_rounded, + selectedIcon: Icons.bookmark_rounded, + label: 'Following', onTap: () {}, ), AppBottomNavItem( @@ -144,6 +150,6 @@ void main() { matching: find.byType(ColoredBox), ), ); - expect(coloredBox.color, AppColors.bar); + expect(coloredBox.color, AppColors.navBackground); }); } diff --git a/test/home/home_logic_test.dart b/test/home/home_logic_test.dart index c23a029..478bdad 100644 --- a/test/home/home_logic_test.dart +++ b/test/home/home_logic_test.dart @@ -159,11 +159,11 @@ void main() { test('should_map_onto_root_tabs_when_my_page_actions_fire', () { final logic = HomeLogic(dramas); - logic.myAction(MyPageAction.discover); + logic.myAction(MyPageAction.recommend); expect(logic.tab, 1); expect(logic.searchRequest, 1); - logic.myAction(MyPageAction.create); + logic.myAction(MyPageAction.following); expect(logic.tab, 2); logic.myAction(MyPageAction.resume); @@ -179,10 +179,10 @@ void main() { test('should_map_onto_root_tabs_when_discover_actions_fire', () { final logic = HomeLogic(dramas); - logic.discoverAction(DiscoverPageAction.my); + logic.discoverAction(DiscoverPageAction.mine); expect(logic.tab, 3); - logic.discoverAction(DiscoverPageAction.create); + logic.discoverAction(DiscoverPageAction.following); expect(logic.tab, 2); logic.discoverAction(DiscoverPageAction.home); diff --git a/test/home/home_nav_ui_test.dart b/test/home/home_nav_ui_test.dart index 03bd987..1e080fe 100644 --- a/test/home/home_nav_ui_test.dart +++ b/test/home/home_nav_ui_test.dart @@ -7,6 +7,8 @@ import 'package:fruitshorts/core/data/repositories/favorite_repository.dart'; import 'package:fruitshorts/core/data/repositories/playback_repository.dart'; import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart'; import 'package:fruitshorts/core/theme/app_colors.dart'; +import 'package:fruitshorts/core/widgets/app_bottom_nav.dart'; +import 'package:fruitshorts/pages/favorites/favorites_page.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; @@ -19,8 +21,8 @@ import '../support/widget_player.dart'; const _keys = [ Key('home-tabs-bar'), Key('tab-home'), - Key('tab-theater'), - Key('tab-create'), + Key('tab-recommend'), + Key('tab-follow'), Key('tab-mine'), ]; @@ -47,20 +49,54 @@ void main() { for (final key in _keys.skip(1)) { expect(tester.getSize(find.byKey(key)).height, greaterThanOrEqualTo(48)); } + final tabCenters = _keys + .skip(1) + .map((key) => tester.getCenter(find.byKey(key)).dx) + .toList(); + expect(tabCenters, orderedEquals(tabCenters.toList()..sort())); + expect( + find.descendant( + of: find.byKey(const Key('tab-home')), + matching: find.text('Home'), + ), + findsOneWidget, + ); + expect( + find.descendant( + of: find.byKey(const Key('tab-recommend')), + matching: find.text('Recommend'), + ), + findsOneWidget, + ); + expect( + find.descendant( + of: find.byKey(const Key('tab-follow')), + matching: find.text('Following'), + ), + findsOneWidget, + ); + expect( + find.descendant( + of: find.byKey(const Key('tab-mine')), + matching: find.text('My'), + ), + findsOneWidget, + ); final barFinder = find.byKey(const Key('home-tabs-bar')); final bar = tester.widget(barFinder); final decoration = bar.decoration as BoxDecoration; final border = decoration.border as Border; // 所有页面统一深色导航。 - expect(decoration.color, AppColors.bar); - expect(border.top.color, AppColors.border); + expect(decoration.color, AppColors.navBackground); + expect(border.top.color, AppColors.navDivider); + expect(border.top.width, 0.5); expect( tester .widget( find.descendant( - of: find.byKey(const Key('tab-theater')), - matching: find.text('Theater'), + of: find.byKey(const Key('tab-recommend')), + matching: find.text('Recommend'), ), ) .style @@ -85,6 +121,27 @@ void main() { ), findsOneWidget, ); + expect( + find.descendant( + of: find.byKey(const Key('tab-recommend')), + matching: find.byIcon(Icons.smart_display_outlined), + ), + findsOneWidget, + ); + expect( + find.descendant( + of: find.byKey(const Key('tab-follow')), + matching: find.byIcon(Icons.bookmark_border_rounded), + ), + findsOneWidget, + ); + expect( + find.descendant( + of: find.byKey(const Key('tab-mine')), + matching: find.byIcon(Icons.person_outline_rounded), + ), + findsOneWidget, + ); expect( tester .widget( @@ -94,23 +151,26 @@ void main() { ), ) .color, - AppColors.blue, + AppColors.navSelected, ); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); expect(haptics.selectionClicks, 1); final theaterBar = tester.widget(barFinder); final theaterDecoration = theaterBar.decoration as BoxDecoration; - expect(theaterDecoration.color, AppColors.bar.withValues(alpha: 0.96)); - expect((theaterDecoration.border as Border).top.color, AppColors.border); - await tester.tap(find.byKey(const Key('tab-theater'))); + expect(theaterDecoration.color, AppColors.navBackground); + expect( + (theaterDecoration.border as Border).top.color, + AppColors.navDivider, + ); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); expect(haptics.selectionClicks, 1); expect( find.descendant( - of: find.byKey(const Key('tab-theater')), - matching: find.byIcon(Icons.explore_rounded), + of: find.byKey(const Key('tab-recommend')), + matching: find.byIcon(Icons.smart_display_rounded), ), findsOneWidget, ); @@ -118,13 +178,13 @@ void main() { tester .widget( find.descendant( - of: find.byKey(const Key('tab-theater')), - matching: find.text('Theater'), + of: find.byKey(const Key('tab-recommend')), + matching: find.text('Recommend'), ), ) .style ?.color, - AppColors.blue, + AppColors.navSelected, ); expect( tester @@ -147,32 +207,56 @@ void main() { ); }); - testWidgets('should_map_four_tabs_to_home_theater_create_and_me', ( + testWidgets('should_map_four_tabs_to_home_recommend_following_and_me', ( tester, ) async { await _pumpApp(tester); expect(find.byKey(const Key('tab-home')), findsOneWidget); - expect(find.byKey(const Key('tab-theater')), findsOneWidget); - expect(find.byKey(const Key('tab-create')), findsOneWidget); + expect(find.byKey(const Key('tab-recommend')), findsOneWidget); + expect(find.byKey(const Key('tab-follow')), findsOneWidget); expect(find.byKey(const Key('tab-mine')), findsOneWidget); + expect(find.byKey(const Key('tab-create')), findsNothing); expect(find.byKey(const Key('home-feed-title')), findsOneWidget); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('home-all-dramas-grid')), findsOneWidget); - await tester.tap(find.byKey(const Key('tab-create'))); + await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('create-page')), findsOneWidget); + final favorites = find.byType(FavoritesContent); + expect(favorites, findsOneWidget); + expect(find.byKey(const Key('favorites-page')), findsOneWidget); + expect(find.byKey(const Key('favorites-back')), findsNothing); + expect(find.byType(AppBottomNav), findsOneWidget); + expect( + find.descendant(of: favorites, matching: find.byType(Scaffold)), + findsNothing, + ); + expect(Navigator.canPop(tester.element(favorites)), isFalse); + expect( + find.descendant( + of: find.byKey(const Key('tab-follow')), + matching: find.byIcon(Icons.bookmark_rounded), + ), + findsOneWidget, + ); await tester.tap(find.byKey(const Key('tab-mine'))); await tester.pumpAndSettle(); expect(find.text('Viewer'), findsOneWidget); expect(find.byKey(const Key('mine-menu')), findsOneWidget); expect(find.byKey(const Key('mine-watchlist')), findsOneWidget); + expect(find.byKey(const Key('mine-creator-center')), findsOneWidget); expect(find.byKey(const Key('mine-library-tab-0')), findsNothing); + await tester.tap(find.byKey(const Key('tab-follow'))); + await tester.pumpAndSettle(); + expect(favorites, findsOneWidget); + expect(find.byType(AppBottomNav), findsOneWidget); + expect(Navigator.canPop(tester.element(favorites)), isFalse); + await tester.tap(find.byKey(const Key('tab-home'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('home-feed-title')), findsOneWidget); diff --git a/test/home/k28_a_navigation_test.dart b/test/home/k28_a_navigation_test.dart index ab70046..4b3c6cf 100644 --- a/test/home/k28_a_navigation_test.dart +++ b/test/home/k28_a_navigation_test.dart @@ -10,6 +10,7 @@ import 'package:fruitshorts/core/data/models/feed_page.dart'; import 'package:fruitshorts/core/data/repositories/favorite_repository.dart'; import 'package:fruitshorts/core/data/repositories/playback_repository.dart'; import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart'; +import 'package:fruitshorts/core/widgets/app_bottom_nav.dart'; import 'package:fruitshorts/pages/home/home_feed_channel.dart'; import 'package:fruitshorts/pages/player/player_view.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; @@ -27,7 +28,7 @@ void main() { InMemorySharedPreferencesAsync.empty(); }); - testWidgets('should_render_four_tabs_and_open_create_placeholder', ( + testWidgets('should_render_four_tabs_and_open_following_page', ( tester, ) async { await _pumpApp(tester); @@ -35,27 +36,14 @@ void main() { for (final key in _tabKeys) { expect(find.byKey(key), findsOneWidget); } + expect(find.byKey(const Key('tab-create')), findsNothing); - await tester.tap(find.byKey(const Key('tab-create'))); + await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('create-page')), findsOneWidget); - expect(find.text('Create'), findsWidgets); - expect(find.text('Stories become short dramas'), findsOneWidget); - expect(find.byKey(const Key('create-ai')), findsOneWidget); - expect(find.byKey(const Key('create-upload')), findsOneWidget); - expect(tester.getSize(find.byKey(const Key('create-ai'))).height, 196); - expect(find.byKey(const Key('create-motto-primary')), findsOneWidget); - expect(find.byKey(const Key('create-motto-secondary')), findsOneWidget); - expect( - tester.getSize(find.byKey(const Key('create-motto-line'))), - const Size(96, 2), - ); - - await tester.tap(find.byKey(const Key('create-ai'))); - await tester.pumpAndSettle(); - expect(find.byKey(const Key('create-placeholder-page')), findsOneWidget); - expect(find.text('Coming soon'), findsOneWidget); + expect(find.byKey(const Key('favorites-page')), findsOneWidget); + expect(find.byKey(const Key('favorites-back')), findsNothing); + expect(find.byType(AppBottomNav), findsOneWidget); }); testWidgets('should_start_home_on_immersive_feed', (tester) async { @@ -334,7 +322,7 @@ void main() { await _pumpApp(tester); expect(_hasLightStatusBar(tester), isTrue); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('home-page-gradient')), findsOneWidget); @@ -395,7 +383,7 @@ void main() { await _pumpApp(tester); expect(find.byKey(const Key('home-feed-title')), findsOneWidget); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('home-feed-title')), findsNothing); @@ -418,7 +406,7 @@ void main() { testWidgets('should_not_render_theater_language_filters', (tester) async { await _pumpApp(tester); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('theater-language-chips')), findsNothing); @@ -435,7 +423,7 @@ void main() { tester, ) async { await _pumpApp(tester); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); final card = find.byKey(const Key('home-drama-card-the-signal-demo')); @@ -464,7 +452,7 @@ void main() { expect(find.byKey(const Key('home-feed-title')), findsOneWidget); expect(tester.takeException(), isNull); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('theater-category-chips')), findsOneWidget); expect(find.byKey(const Key('theater-language-chips')), findsNothing); @@ -474,8 +462,8 @@ void main() { const _tabKeys = [ Key('tab-home'), - Key('tab-theater'), - Key('tab-create'), + Key('tab-recommend'), + Key('tab-follow'), Key('tab-mine'), ]; diff --git a/test/mine/mine_logic_test.dart b/test/mine/mine_logic_test.dart index 30cb8d5..b6105d8 100644 --- a/test/mine/mine_logic_test.dart +++ b/test/mine/mine_logic_test.dart @@ -91,11 +91,11 @@ void main() { logic.act( pageContext, - MyPageAction.discover, + MyPageAction.recommend, embedded: true, onAction: forwarded.add, ); - expect(forwarded, [MyPageAction.discover]); + expect(forwarded, [MyPageAction.recommend]); logic.act(pageContext, MyPageAction.resume, embedded: false); await tester.pumpAndSettle(); diff --git a/test/player/player_fullscreen_test.dart b/test/player/player_fullscreen_test.dart index bc1bd8c..53d0b23 100644 --- a/test/player/player_fullscreen_test.dart +++ b/test/player/player_fullscreen_test.dart @@ -39,7 +39,7 @@ void main() { tester, ) async { await _pumpApp(tester); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('home-drama-card-city-lights-demo'))); await tester.pumpAndSettle(); @@ -1331,7 +1331,7 @@ Future _openSeriesPlayer( WidgetTester tester, { Key cardKey = const Key('home-drama-card-city-lights-demo'), }) async { - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); final card = find.byKey(cardKey); await tester.scrollUntilVisible( diff --git a/test/theme/app_dimens_test.dart b/test/theme/app_dimens_test.dart index d48b70d..c7187bc 100644 --- a/test/theme/app_dimens_test.dart +++ b/test/theme/app_dimens_test.dart @@ -18,7 +18,7 @@ void main() { expect(AppDimens.listTitle, 15); expect(AppDimens.sectionTitle, 16); expect(AppDimens.pageTitle, 20); - expect(AppDimens.navLabel, 12); + expect(AppDimens.navLabel, 11); expect(AppDimens.navIcon, 24); expect(AppDimens.buttonHeight, 48); expect(AppDimens.primaryButtonHeight, 52); diff --git a/test/theme/app_theme_test.dart b/test/theme/app_theme_test.dart index 1cd52c2..2f94760 100644 --- a/test/theme/app_theme_test.dart +++ b/test/theme/app_theme_test.dart @@ -63,7 +63,10 @@ void main() { expect(theme.appBarTheme.systemOverlayStyle, SystemUiOverlayStyle.light); expect(theme.cardTheme.elevation, 0); expect(theme.dialogTheme.backgroundColor, AppColors.raised); - expect(theme.bottomNavigationBarTheme.backgroundColor, AppColors.bar); + expect( + theme.bottomNavigationBarTheme.backgroundColor, + AppColors.navBackground, + ); expect(theme.inputDecorationTheme.fillColor, AppColors.input); }); diff --git a/test/widget_test.dart b/test/widget_test.dart index 26fb796..16f61ed 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -120,9 +120,9 @@ void main() { await tester.pumpAndSettle(); expect(find.byKey(const Key('home-feed-title')), findsOneWidget); - await tester.tap(find.byKey(const Key('tab-create'))); + await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('create-page')), findsOneWidget); + expect(find.byKey(const Key('favorites-page')), findsOneWidget); await tester.tap(find.byKey(const Key('tab-mine'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('mine-menu')), findsOneWidget); @@ -144,9 +144,9 @@ void main() { expect(find.byKey(const Key('mine-language')), findsOneWidget); expect(find.byKey(const Key('mine-wallet')), findsOneWidget); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); - expect(find.text('Theater'), findsWidgets); + expect(find.text('Recommend'), findsWidgets); }); testWidgets('should_switch_app_language_from_mine_menu', (tester) async { @@ -168,9 +168,9 @@ void main() { expect(find.text('我的'), findsWidgets); expect(find.text('语言'), findsOneWidget); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); - expect(find.text('剧场'), findsWidgets); + expect(find.text('推荐'), findsWidgets); }); testWidgets('should_fit_compact_viewport_when_player_is_opened', ( @@ -207,7 +207,7 @@ void main() { await tester.pumpWidget(appUnderTest()); await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('discover-search')), findsOneWidget); @@ -316,7 +316,7 @@ void main() { } Future _openTheaterPlayer(WidgetTester tester) async { - await tester.tap(find.byKey(const Key('tab-theater'))); + await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); final card = find.byKey(const Key('home-drama-card-city-lights-demo')); await tester.scrollUntilVisible( From 729c226c95966cea5229dbc1c22cf5dd4078cbd6 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Thu, 22 Oct 2026 16:21:30 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs(nav):=20=E5=9B=9E=E5=A1=AB=E5=9B=9B=20?= =?UTF-8?q?Tab=20=E5=BA=95=E9=83=A8=E5=AF=BC=E8=88=AA=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/PROGRESS.md | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index 0213a52..627a97a 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -785,3 +785,49 @@ Running Gradle task 'assembleDebug'... 解读:静态分析零告警,644 项测试全部通过,debug APK 构建成功;测试与构建后再次确认 `pubspec.lock` 无改动,未启停服务、未操作真机。 + +## 2026-10-22 客户端底部导航改为首页/推荐/追剧/我的 + +- 分支:`feature/client-bottom-nav-4tabs` +- 基线:`develop` / `origin/develop` = `ec3cb8f` +- 计划:`docs/plans/2026-10-22-client-bottom-nav-4tabs.md` +- 实现提交:`91e869d feat(nav): 底部导航改为首页推荐追剧我的` + +### 问题 + +根导航仍为首页/剧场/创作/我的,底栏选中态使用品牌橙。追剧若通过新路由打开, +会叠加第二层底栏并产生返回栈,不符合参考图和单根导航交互。 + +### 改动 + +- 新增 `AppRootTab` 统一四个 tab 的顺序、key、图标和 l10n 标签。 +- `AppBottomNav` 改为纯黑底、白色选中、`#8A8A8E` 未选中,图标 24、文字 11, + 保留 SafeArea 与轻震动。 +- 首页 `IndexedStack` 第三槽直接使用 `FavoritesContent`,不再 push 收藏路由。 + 追剧保留顶部标题区、隐藏返回按钮,只有一个根底栏,不创建嵌套 Scaffold。 +- `FavoritesPage` 保留独立 Scaffold 和返回行为,继续供“我的”入口使用。 +- 推荐、追剧、我的三个根页面统一调用 `HomeLogic.select` 切换,不进入返回栈。 +- 创作页保留,`mine-creator-center` 入口保留;创作不再出现在根底栏。 +- 中文使用“追剧”,英文使用 `Home / Recommend / Following / My`, + 印尼语使用 `Beranda / Rekomendasi / Daftar Tonton / Saya`。 +- `FavoritesContent` 内置顶部 SafeArea,避免嵌入首页后被状态栏遮挡。 + +### 门禁命令与原始输出 + +```text +$ dart analyze lib test +Analyzing lib, test... +No issues found! +``` + +```text +$ flutter test --no-pub +04:38 +644: All tests passed! +``` + +导航测试覆盖:四个 tab 数量与顺序、纯白/灰色状态、点击追剧进入 +`FavoritesContent`、仅一个 `AppBottomNav`、追剧内部无嵌套 Scaffold、 +`Navigator.canPop == false`、创作不再出现在底栏且我的入口保留。 + +解读:静态分析零告警,644 项测试全部通过;追剧与首页/推荐/我的均在同一根页面 +内切换,无叠加导航或返回栈。`pubspec.lock` 未提交,未启动或停止服务,未操作真机。