merge: 合并四 Tab 底部导航

This commit is contained in:
Zhou Weidong committed 2026-10-22 16:21:39 +08:00
commit 667fdc5f0a
35 files changed
+405 -263

No files matched your search

+46
View File
@@ -785,3 +785,49 @@ Running Gradle task 'assembleDebug'...
解读:静态分析零告警,644 项测试全部通过,debug APK 构建成功;测试与构建后再次确认 解读:静态分析零告警,644 项测试全部通过,debug APK 构建成功;测试与构建后再次确认
`pubspec.lock` 无改动,未启停服务、未操作真机。 `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` 未提交,未启动或停止服务,未操作真机。
+6 -1
View File
@@ -69,6 +69,11 @@ abstract final class AppColors {
/// 底部导航底色。 /// 底部导航底色。
static const bar = ink; static const bar = ink;
/// 底部导航专用:纯黑背景、选中白、未选中灰与轻分割线。
static const navBackground = Color(0xFF000000);
static const navSelected = Color(0xFFFFFFFF);
static const navDivider = Color(0xFF1C1C1E);
/// 卡片阴影(深色几乎不用阴影)。 /// 卡片阴影(深色几乎不用阴影)。
static const cardShadow = Color(0x00000000); static const cardShadow = Color(0x00000000);
@@ -121,7 +126,7 @@ abstract final class AppColors {
static const badgeBeige = raised; static const badgeBeige = raised;
/// 导航未选中。 /// 导航未选中。
static const navUnselected = muted; static const navUnselected = Color(0xFF8A8A8E);
/// 播放器次要文字。 /// 播放器次要文字。
static const playerMuted = Color(0xA3FFFFFF); static const playerMuted = Color(0xA3FFFFFF);
+1 -1
View File
@@ -36,7 +36,7 @@ abstract final class AppDimens {
static const double previewCardTitle = 13; static const double previewCardTitle = 13;
static const double previewSectionTitle = 14; static const double previewSectionTitle = 14;
static const double chipFont = 10; static const double chipFont = 10;
static const double navLabel = 12; static const double navLabel = 11;
static const double buttonHeight = 48; static const double buttonHeight = 48;
static const double primaryButtonHeight = 52; static const double primaryButtonHeight = 52;
+2 -2
View File
@@ -193,8 +193,8 @@ abstract final class AppTheme {
), ),
), ),
bottomNavigationBarTheme: const BottomNavigationBarThemeData( bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.bar, backgroundColor: AppColors.navBackground,
selectedItemColor: AppColors.blue, selectedItemColor: AppColors.navSelected,
unselectedItemColor: AppColors.navUnselected, unselectedItemColor: AppColors.navUnselected,
elevation: 0, elevation: 0,
type: BottomNavigationBarType.fixed, type: BottomNavigationBarType.fixed,
+16 -20
View File
@@ -1,7 +1,5 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../theme/app_colors.dart'; import '../theme/app_colors.dart';
@@ -26,12 +24,12 @@ class AppBottomNavItem {
final bool selected; final bool selected;
} }
/// 四槽底部导航:所有页面统一深色底与浅色图标。 /// 四槽底部导航:纯黑背景,白色选中态,灰色未选中态。
class AppBottomNav extends StatelessWidget { class AppBottomNav extends StatelessWidget {
const AppBottomNav({ const AppBottomNav({
super.key, super.key,
required this.items, required this.items,
this.backgroundColor = AppColors.bar, this.backgroundColor = AppColors.navBackground,
this.barKey, this.barKey,
this.dark = false, this.dark = false,
}); });
@@ -47,8 +45,10 @@ class AppBottomNav extends StatelessWidget {
key: barKey, key: barKey,
height: AppDimens.navHeight, height: AppDimens.navHeight,
decoration: BoxDecoration( decoration: BoxDecoration(
color: dark ? AppColors.bar : backgroundColor.withValues(alpha: 0.96), color: dark ? AppColors.navBackground : backgroundColor,
border: const Border(top: BorderSide(color: AppColors.border)), border: const Border(
top: BorderSide(color: AppColors.navDivider, width: 0.5),
),
), ),
child: Row( child: Row(
children: [ children: [
@@ -60,17 +60,9 @@ class AppBottomNav extends StatelessWidget {
], ],
), ),
); );
if (dark) { return ColoredBox(
return ColoredBox( color: AppColors.navBackground,
color: AppColors.bar, child: SafeArea(top: false, child: bar),
child: SafeArea(top: false, child: bar),
);
}
return ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: SafeArea(top: false, child: bar),
),
); );
} }
} }
@@ -102,7 +94,9 @@ class _NavDestination extends StatelessWidget {
child: Icon( child: Icon(
item.selected ? item.selectedIcon : item.icon, item.selected ? item.selectedIcon : item.icon,
size: AppDimens.navIcon, size: AppDimens.navIcon,
color: item.selected ? AppColors.blue : unselectedColor, color: item.selected
? AppColors.navSelected
: unselectedColor,
), ),
), ),
const SizedBox(height: 3), const SizedBox(height: 3),
@@ -112,8 +106,10 @@ class _NavDestination extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: AppDimens.navLabel, fontSize: AppDimens.navLabel,
fontWeight: item.selected ? FontWeight.w700 : FontWeight.w500, fontWeight: item.selected ? FontWeight.w600 : FontWeight.w400,
color: item.selected ? AppColors.blue : unselectedColor, color: item.selected
? AppColors.navSelected
: unselectedColor,
letterSpacing: 0, letterSpacing: 0,
), ),
), ),
+38
View File
@@ -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,
};
}
+10 -2
View File
@@ -120,6 +120,7 @@ class AppSubpageHeader extends StatelessWidget {
this.actions, this.actions,
this.onBack, this.onBack,
this.dark = false, this.dark = false,
this.showBack = true,
}); });
final String title; final String title;
@@ -128,6 +129,7 @@ class AppSubpageHeader extends StatelessWidget {
final Widget? actions; final Widget? actions;
final VoidCallback? onBack; final VoidCallback? onBack;
final bool dark; final bool dark;
final bool showBack;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -140,8 +142,14 @@ class AppSubpageHeader extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: _horizontal), padding: const EdgeInsets.symmetric(horizontal: _horizontal),
child: Row( child: Row(
children: [ children: [
AppSubpageBackButton(backKey: backKey, dark: dark, onBack: onBack), if (showBack) ...[
const SizedBox(width: _backGap), AppSubpageBackButton(
backKey: backKey,
dark: dark,
onBack: onBack,
),
const SizedBox(width: _backGap),
],
Expanded( Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
+1 -1
View File
@@ -162,7 +162,7 @@
"preferences": "Preferences", "preferences": "Preferences",
"followingTab": "Following", "followingTab": "Following",
"animatedTab": "Animated", "animatedTab": "Animated",
"recommendedTab": "Recommended", "recommendedTab": "Recommend",
"noCategoryDramas": "No dramas in this category yet.", "noCategoryDramas": "No dramas in this category yet.",
"allDramas": "All Dramas", "allDramas": "All Dramas",
"allLanguages": "All languages", "allLanguages": "All languages",
+1 -1
View File
@@ -139,7 +139,7 @@
"completed": "Selesai", "completed": "Selesai",
"noSavedShows": "Belum ada tontonan di sini.", "noSavedShows": "Belum ada tontonan di sini.",
"preferences": "Preferensi", "preferences": "Preferensi",
"followingTab": "Mengikuti", "followingTab": "Daftar Tonton",
"animatedTab": "Animasi", "animatedTab": "Animasi",
"recommendedTab": "Rekomendasi", "recommendedTab": "Rekomendasi",
"noCategoryDramas": "Belum ada drama di kategori ini.", "noCategoryDramas": "Belum ada drama di kategori ini.",
+1 -1
View File
@@ -685,7 +685,7 @@ abstract class AppLocalizations {
/// No description provided for @recommendedTab. /// No description provided for @recommendedTab.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Recommended'** /// **'Recommend'**
String get recommendedTab; String get recommendedTab;
/// No description provided for @noCategoryDramas. /// No description provided for @noCategoryDramas.
+1 -1
View File
@@ -329,7 +329,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get animatedTab => 'Animated'; String get animatedTab => 'Animated';
@override @override
String get recommendedTab => 'Recommended'; String get recommendedTab => 'Recommend';
@override @override
String get noCategoryDramas => 'No dramas in this category yet.'; String get noCategoryDramas => 'No dramas in this category yet.';
+1 -1
View File
@@ -323,7 +323,7 @@ class AppLocalizationsId extends AppLocalizations {
String get preferences => 'Preferensi'; String get preferences => 'Preferensi';
@override @override
String get followingTab => 'Mengikuti'; String get followingTab => 'Daftar Tonton';
@override @override
String get animatedTab => 'Animasi'; String get animatedTab => 'Animasi';
+1 -1
View File
@@ -318,7 +318,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get preferences => '偏好设置'; String get preferences => '偏好设置';
@override @override
String get followingTab => '关注'; String get followingTab => '追剧';
@override @override
String get animatedTab => '漫剧'; String get animatedTab => '漫剧';
+1 -1
View File
@@ -139,7 +139,7 @@
"completed": "已看完", "completed": "已看完",
"noSavedShows": "这里还没有剧集。", "noSavedShows": "这里还没有剧集。",
"preferences": "偏好设置", "preferences": "偏好设置",
"followingTab": "关注", "followingTab": "追剧",
"animatedTab": "漫剧", "animatedTab": "漫剧",
"recommendedTab": "推荐", "recommendedTab": "推荐",
"noCategoryDramas": "这个分类暂时没有剧集。", "noCategoryDramas": "这个分类暂时没有剧集。",
+2 -2
View File
@@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart';
import '../../core/data/models/drama.dart'; import '../../core/data/models/drama.dart';
/// 剧场页与根导航之间的动作。 /// 推荐页与根导航之间的动作。
enum DiscoverPageAction { home, create, my } enum DiscoverPageAction { home, recommend, following, mine }
/// 剧场页导航回调;没有 `onAction` 时按独立页面处理。 /// 剧场页导航回调;没有 `onAction` 时按独立页面处理。
class DiscoverPageCallbacks { class DiscoverPageCallbacks {
+20 -30
View File
@@ -19,6 +19,7 @@ import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart'; import '../../core/theme/app_theme.dart';
import '../../core/widgets/app_bottom_nav.dart'; import '../../core/widgets/app_bottom_nav.dart';
import '../../core/widgets/app_pull_to_refresh.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_debug_info.dart';
import '../../core/widgets/catalog_endpoint_editor.dart'; import '../../core/widgets/catalog_endpoint_editor.dart';
import '../../core/widgets/empty_state.dart'; import '../../core/widgets/empty_state.dart';
@@ -149,7 +150,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
key: const Key('theater-page-surface'), key: const Key('theater-page-surface'),
color: AppColors.ink, color: AppColors.ink,
child: SafeArea( child: SafeArea(
bottom: !embedded, bottom: false,
child: Column( child: Column(
children: [ children: [
DiscoverHeader( DiscoverHeader(
@@ -613,37 +614,26 @@ class _DiscoverPageState extends State<DiscoverPage> {
Widget _bottomNav(AppLocalizations l10n) => AppBottomNav( Widget _bottomNav(AppLocalizations l10n) => AppBottomNav(
items: [ items: [
AppBottomNavItem( for (final tab in AppRootTab.values)
tabKey: const Key('discover-nav-home'), AppBottomNavItem(
icon: Icons.home_outlined, tabKey: tab.tabKey,
selectedIcon: Icons.home_rounded, icon: tab.icon,
label: l10n.homeTab, selectedIcon: tab.selectedIcon,
onTap: () => _act(DiscoverPageAction.home), label: tab.label(l10n),
), selected: tab == AppRootTab.recommend,
AppBottomNavItem( onTap: tab == AppRootTab.recommend
tabKey: const Key('discover-nav-current'), ? () {}
icon: Icons.explore_outlined, : () => _act(_actionOf(tab)),
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),
),
], ],
); );
DiscoverPageAction _actionOf(AppRootTab tab) => switch (tab) {
AppRootTab.home => DiscoverPageAction.home,
AppRootTab.recommend => DiscoverPageAction.recommend,
AppRootTab.following => DiscoverPageAction.following,
AppRootTab.mine => DiscoverPageAction.mine,
};
} }
/// 分类 tab 的弧形选中线。 /// 分类 tab 的弧形选中线。
+36 -24
View File
@@ -44,14 +44,33 @@ const double _loadMoreThreshold = 240;
final RegExp _tagSplitter = RegExp(r'[,/、·]'); final RegExp _tagSplitter = RegExp(r'[,/、·]');
/// 独立我的收藏页。 /// 独立我的收藏页。
class FavoritesPage extends StatefulWidget { class FavoritesPage extends StatelessWidget {
const FavoritesPage({super.key}); const FavoritesPage({super.key});
@override @override
State<FavoritesPage> 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<FavoritesPage> { /// 收藏列表主体;可独立使用,也可直接嵌入根导航。
class FavoritesContent extends StatefulWidget {
const FavoritesContent({super.key, this.showBack = true});
final bool showBack;
@override
State<FavoritesContent> createState() => _FavoritesContentState();
}
class _FavoritesContentState extends State<FavoritesContent> {
bool _requested = false; bool _requested = false;
@override @override
@@ -74,28 +93,21 @@ class _FavoritesPageState extends State<FavoritesPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final signedIn = context.watch<AuthController>().isSignedIn; final signedIn = context.watch<AuthController>().isSignedIn;
return Theme( return SafeArea(
data: AppTheme.dark(), bottom: false,
child: Scaffold( child: Column(
key: const Key('favorites-page'), children: [
backgroundColor: AppColors.ink, AppSubpageHeader(
body: SafeArea( title: l10n.myFavorites,
child: Column( backKey: const Key('favorites-back'),
children: [ showBack: widget.showBack,
AppSubpageHeader(
title: l10n.myFavorites,
backKey: const Key('favorites-back'),
),
Expanded(
child: signedIn
? _FavoritesBody(
favorites: context.watch<FavoriteController>(),
)
: const AuthGatePanel(),
),
],
), ),
), Expanded(
child: signedIn
? _FavoritesBody(favorites: context.watch<FavoriteController>())
: const AuthGatePanel(),
),
],
), ),
); );
} }
+8 -4
View File
@@ -79,9 +79,11 @@ class HomeLogic extends ChangeNotifier {
switch (action) { switch (action) {
case DiscoverPageAction.home: case DiscoverPageAction.home:
select(0); select(0);
case DiscoverPageAction.create: case DiscoverPageAction.recommend:
select(1, search: true);
case DiscoverPageAction.following:
select(2); select(2);
case DiscoverPageAction.my: case DiscoverPageAction.mine:
select(3); select(3);
} }
} }
@@ -102,10 +104,12 @@ class HomeLogic extends ChangeNotifier {
switch (action) { switch (action) {
case MyPageAction.home: case MyPageAction.home:
select(0); select(0);
case MyPageAction.discover: case MyPageAction.recommend:
select(1, search: true); select(1, search: true);
case MyPageAction.create: case MyPageAction.following:
select(2); select(2);
case MyPageAction.mine:
select(3);
case MyPageAction.resume: case MyPageAction.resume:
select(0, resume: true); select(0, resume: true);
} }
+16 -42
View File
@@ -17,13 +17,14 @@ import '../../core/platform/haptics.dart';
import '../../core/theme/app_colors.dart'; import '../../core/theme/app_colors.dart';
import '../../core/theme/app_theme.dart'; import '../../core/theme/app_theme.dart';
import '../../core/widgets/app_bottom_nav.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_debug_info.dart';
import '../../core/widgets/catalog_endpoint_editor.dart'; import '../../core/widgets/catalog_endpoint_editor.dart';
import '../../core/widgets/empty_state.dart'; import '../../core/widgets/empty_state.dart';
import '../../l10n/app_localizations.dart'; import '../../l10n/app_localizations.dart';
import '../discover/discover_types.dart'; import '../discover/discover_types.dart';
import '../discover/discover_view.dart'; import '../discover/discover_view.dart';
import '../create/create_view.dart'; import '../favorites/favorites_page.dart';
import '../mine/mine_types.dart'; import '../mine/mine_types.dart';
import '../player/widgets/home_feed_header.dart'; import '../player/widgets/home_feed_header.dart';
import '../player/player_view.dart'; import '../player/player_view.dart';
@@ -295,7 +296,7 @@ class _HomeTabsState extends State<_HomeTabs> {
children: [ children: [
_home(context), _home(context),
_discover(context), _discover(context),
const CreatePage(), const FavoritesContent(key: Key('favorites-page'), showBack: false),
_my(context), _my(context),
], ],
); );
@@ -455,10 +456,10 @@ class _HomeTabsState extends State<_HomeTabs> {
barKey: const Key('home-tabs-bar'), barKey: const Key('home-tabs-bar'),
dark: logic.tab == 0, dark: logic.tab == 0,
items: [ items: [
_navItem(context, l10n, 0), _navItem(context, l10n, AppRootTab.home),
_navItem(context, l10n, 1), _navItem(context, l10n, AppRootTab.recommend),
_navItem(context, l10n, 2), _navItem(context, l10n, AppRootTab.following),
_navItem(context, l10n, 3), _navItem(context, l10n, AppRootTab.mine),
], ],
); );
} }
@@ -466,43 +467,16 @@ class _HomeTabsState extends State<_HomeTabs> {
AppBottomNavItem _navItem( AppBottomNavItem _navItem(
BuildContext context, BuildContext context,
AppLocalizations l10n, AppLocalizations l10n,
int index, AppRootTab tab,
) { ) {
final selected = logic.tab == index; return AppBottomNavItem(
return switch (index) { tabKey: tab.tabKey,
0 => AppBottomNavItem( icon: tab.icon,
tabKey: const Key('tab-home'), selectedIcon: tab.selectedIcon,
icon: Icons.home_outlined, label: tab.label(l10n),
selectedIcon: Icons.home_rounded, selected: logic.tab == tab.index,
label: l10n.homeTab, onTap: () => _selectTab(context, tab.index),
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),
),
};
} }
void _selectTab(BuildContext context, int tab) { void _selectTab(BuildContext context, int tab) {
+1 -1
View File
@@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart';
import '../../core/data/models/drama.dart'; import '../../core/data/models/drama.dart';
/// 「我的」页与根导航之间的返回动作。 /// 「我的」页与根导航之间的返回动作。
enum MyPageAction { home, discover, create, resume } enum MyPageAction { home, recommend, following, mine, resume }
/// 「我的」页可进入的功能目的地。 /// 「我的」页可进入的功能目的地。
enum MineDestination { enum MineDestination {
+18 -30
View File
@@ -13,6 +13,7 @@ import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart'; import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart'; import '../../core/theme/app_theme.dart';
import '../../core/widgets/app_bottom_nav.dart'; import '../../core/widgets/app_bottom_nav.dart';
import '../../core/widgets/app_root_tab.dart';
import '../../core/widgets/auth_gate.dart'; import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/plain_page_scaffold.dart'; import '../../core/widgets/plain_page_scaffold.dart';
import '../../l10n/app_localizations.dart'; import '../../l10n/app_localizations.dart';
@@ -81,7 +82,7 @@ class _MyPageState extends State<MyPage> {
Widget _page(BuildContext context) { Widget _page(BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final content = SafeArea( final content = SafeArea(
bottom: !embedded, bottom: false,
child: Column( child: Column(
children: [ children: [
_topBar(l10n), _topBar(l10n),
@@ -240,39 +241,26 @@ class _MyPageState extends State<MyPage> {
Widget _bottomNav(AppLocalizations l10n) { Widget _bottomNav(AppLocalizations l10n) {
return AppBottomNav( return AppBottomNav(
items: [ items: [
AppBottomNavItem( for (final tab in AppRootTab.values)
tabKey: const Key('mine-nav-home'), AppBottomNavItem(
icon: Icons.home_outlined, tabKey: tab.tabKey,
selectedIcon: Icons.home_rounded, icon: tab.icon,
label: l10n.homeTab, selectedIcon: tab.selectedIcon,
onTap: () => _act(MyPageAction.home), label: tab.label(l10n),
), selected: tab == AppRootTab.mine,
AppBottomNavItem( onTap: tab == AppRootTab.mine ? () {} : () => _act(_actionOf(tab)),
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: () {},
),
], ],
); );
} }
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) { void _act(MyPageAction action) {
logic.act( logic.act(
context, context,
+2 -2
View File
@@ -1085,7 +1085,7 @@ class PlayerLogic extends ChangeNotifier {
context, context,
).push<MyPageAction>(MaterialPageRoute(builder: (_) => const MyPage())); ).push<MyPageAction>(MaterialPageRoute(builder: (_) => const MyPage()));
if (!context.mounted) return; if (!context.mounted) return;
if (action == MyPageAction.discover) { if (action == MyPageAction.recommend) {
await showDiscover(context); await showDiscover(context);
} else { } else {
session.setRouteVisible(true); session.setRouteVisible(true);
@@ -1117,7 +1117,7 @@ class PlayerLogic extends ChangeNotifier {
MaterialPageRoute(builder: (_) => DiscoverPage(focusSearch: focusSearch)), MaterialPageRoute(builder: (_) => DiscoverPage(focusSearch: focusSearch)),
); );
if (!context.mounted) return; if (!context.mounted) return;
if (action == DiscoverPageAction.my) { if (action == DiscoverPageAction.mine) {
await showMy(context); await showMy(context);
} else { } else {
session.setRouteVisible(true); session.setRouteVisible(true);
+2 -2
View File
@@ -99,9 +99,9 @@ void main() {
); );
expect(forwarded, [DiscoverPageAction.home]); expect(forwarded, [DiscoverPageAction.home]);
logic.act(pageContext, DiscoverPageAction.my, embedded: false); logic.act(pageContext, DiscoverPageAction.mine, embedded: false);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await expectLater(popped.future, completion(DiscoverPageAction.my)); await expectLater(popped.future, completion(DiscoverPageAction.mine));
logic.dispose(); logic.dispose();
}); });
+1 -1
View File
@@ -151,7 +151,7 @@ Future<void> _pumpFilterPage(WidgetTester tester) async {
), ),
); );
await tester.pumpAndSettle(); 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.pumpAndSettle();
await tester.tap(find.byKey(const Key('theater-toolbar-filter'))); await tester.tap(find.byKey(const Key('theater-toolbar-filter')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
+1 -1
View File
@@ -455,7 +455,7 @@ Future<void> _pumpTheater(
), ),
); );
await tester.pumpAndSettle(); 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.pumpAndSettle();
} }
+1 -1
View File
@@ -303,7 +303,7 @@ Future<void> _pumpTheater(
), ),
); );
await tester.pumpAndSettle(); 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.pumpAndSettle();
} }
+30 -24
View File
@@ -7,7 +7,7 @@ import 'package:fruitshorts/core/theme/app_dimens.dart';
import 'package:fruitshorts/core/widgets/app_bottom_nav.dart'; import 'package:fruitshorts/core/widgets/app_bottom_nav.dart';
void main() { void main() {
testWidgets('should_render_four_slots_with_orange_selected_state', ( testWidgets('should_render_four_slots_with_white_selected_state', (
tester, tester,
) async { ) async {
var theaterTaps = 0; var theaterTaps = 0;
@@ -25,17 +25,17 @@ void main() {
onTap: () {}, onTap: () {},
), ),
AppBottomNavItem( AppBottomNavItem(
tabKey: const Key('tab-theater'), tabKey: const Key('tab-recommend'),
icon: Icons.explore_outlined, icon: Icons.smart_display_outlined,
selectedIcon: Icons.explore_rounded, selectedIcon: Icons.smart_display_rounded,
label: 'Theater', label: 'Recommend',
onTap: () => theaterTaps++, onTap: () => theaterTaps++,
), ),
AppBottomNavItem( AppBottomNavItem(
tabKey: const Key('tab-create'), tabKey: const Key('tab-follow'),
icon: Icons.add_box_outlined, icon: Icons.bookmark_border_rounded,
selectedIcon: Icons.add_box_rounded, selectedIcon: Icons.bookmark_rounded,
label: 'Create', label: 'Following',
onTap: () {}, onTap: () {},
), ),
AppBottomNavItem( AppBottomNavItem(
@@ -52,15 +52,21 @@ void main() {
); );
expect(find.byKey(const Key('tab-home')), findsOneWidget); expect(find.byKey(const Key('tab-home')), findsOneWidget);
expect(find.byKey(const Key('tab-theater')), findsOneWidget); expect(find.byKey(const Key('tab-recommend')), findsOneWidget);
expect(find.byKey(const Key('tab-create')), findsOneWidget); expect(find.byKey(const Key('tab-follow')), findsOneWidget);
expect(find.byKey(const Key('tab-mine')), findsOneWidget); expect(find.byKey(const Key('tab-mine')), findsOneWidget);
expect(find.byKey(const Key('nav-center-action')), findsNothing); expect(find.byKey(const Key('nav-center-action')), findsNothing);
expect(find.text('Home'), findsOneWidget); expect(find.text('Home'), findsOneWidget);
final selectedLabel = tester.widget<Text>(find.text('Home'));
expect(selectedLabel.style?.color, AppColors.navSelected);
expect(selectedLabel.style?.fontWeight, FontWeight.w600);
final unselectedLabel = tester.widget<Text>(find.text('Recommend'));
expect(unselectedLabel.style?.color, AppColors.navUnselected);
expect(unselectedLabel.style?.fontWeight, FontWeight.w400);
final centers = [ final centers = [
Key('tab-home'), Key('tab-home'),
Key('tab-theater'), Key('tab-recommend'),
Key('tab-create'), Key('tab-follow'),
Key('tab-mine'), Key('tab-mine'),
].map((key) => tester.getCenter(find.byKey(key)).dx).toList(); ].map((key) => tester.getCenter(find.byKey(key)).dx).toList();
expect(centers, orderedEquals(centers.toList()..sort())); expect(centers, orderedEquals(centers.toList()..sort()));
@@ -77,9 +83,9 @@ void main() {
), ),
) )
.color, .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); expect(theaterTaps, 1);
}); });
@@ -105,17 +111,17 @@ void main() {
onTap: () {}, onTap: () {},
), ),
AppBottomNavItem( AppBottomNavItem(
tabKey: const Key('tab-theater'), tabKey: const Key('tab-recommend'),
icon: Icons.explore_outlined, icon: Icons.smart_display_outlined,
selectedIcon: Icons.explore_rounded, selectedIcon: Icons.smart_display_rounded,
label: 'Theater', label: 'Recommend',
onTap: () {}, onTap: () {},
), ),
AppBottomNavItem( AppBottomNavItem(
tabKey: const Key('tab-create'), tabKey: const Key('tab-follow'),
icon: Icons.add_box_outlined, icon: Icons.bookmark_border_rounded,
selectedIcon: Icons.add_box_rounded, selectedIcon: Icons.bookmark_rounded,
label: 'Create', label: 'Following',
onTap: () {}, onTap: () {},
), ),
AppBottomNavItem( AppBottomNavItem(
@@ -144,6 +150,6 @@ void main() {
matching: find.byType(ColoredBox), matching: find.byType(ColoredBox),
), ),
); );
expect(coloredBox.color, AppColors.bar); expect(coloredBox.color, AppColors.navBackground);
}); });
} }
+4 -4
View File
@@ -159,11 +159,11 @@ void main() {
test('should_map_onto_root_tabs_when_my_page_actions_fire', () { test('should_map_onto_root_tabs_when_my_page_actions_fire', () {
final logic = HomeLogic(dramas); final logic = HomeLogic(dramas);
logic.myAction(MyPageAction.discover); logic.myAction(MyPageAction.recommend);
expect(logic.tab, 1); expect(logic.tab, 1);
expect(logic.searchRequest, 1); expect(logic.searchRequest, 1);
logic.myAction(MyPageAction.create); logic.myAction(MyPageAction.following);
expect(logic.tab, 2); expect(logic.tab, 2);
logic.myAction(MyPageAction.resume); logic.myAction(MyPageAction.resume);
@@ -179,10 +179,10 @@ void main() {
test('should_map_onto_root_tabs_when_discover_actions_fire', () { test('should_map_onto_root_tabs_when_discover_actions_fire', () {
final logic = HomeLogic(dramas); final logic = HomeLogic(dramas);
logic.discoverAction(DiscoverPageAction.my); logic.discoverAction(DiscoverPageAction.mine);
expect(logic.tab, 3); expect(logic.tab, 3);
logic.discoverAction(DiscoverPageAction.create); logic.discoverAction(DiscoverPageAction.following);
expect(logic.tab, 2); expect(logic.tab, 2);
logic.discoverAction(DiscoverPageAction.home); logic.discoverAction(DiscoverPageAction.home);
+106 -22
View File
@@ -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/playback_repository.dart';
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart'; import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
import 'package:fruitshorts/core/theme/app_colors.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/in_memory_shared_preferences_async.dart';
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
@@ -19,8 +21,8 @@ import '../support/widget_player.dart';
const _keys = [ const _keys = [
Key('home-tabs-bar'), Key('home-tabs-bar'),
Key('tab-home'), Key('tab-home'),
Key('tab-theater'), Key('tab-recommend'),
Key('tab-create'), Key('tab-follow'),
Key('tab-mine'), Key('tab-mine'),
]; ];
@@ -47,20 +49,54 @@ void main() {
for (final key in _keys.skip(1)) { for (final key in _keys.skip(1)) {
expect(tester.getSize(find.byKey(key)).height, greaterThanOrEqualTo(48)); 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 barFinder = find.byKey(const Key('home-tabs-bar'));
final bar = tester.widget<Container>(barFinder); final bar = tester.widget<Container>(barFinder);
final decoration = bar.decoration as BoxDecoration; final decoration = bar.decoration as BoxDecoration;
final border = decoration.border as Border; final border = decoration.border as Border;
// 所有页面统一深色导航。 // 所有页面统一深色导航。
expect(decoration.color, AppColors.bar); expect(decoration.color, AppColors.navBackground);
expect(border.top.color, AppColors.border); expect(border.top.color, AppColors.navDivider);
expect(border.top.width, 0.5);
expect( expect(
tester tester
.widget<Text>( .widget<Text>(
find.descendant( find.descendant(
of: find.byKey(const Key('tab-theater')), of: find.byKey(const Key('tab-recommend')),
matching: find.text('Theater'), matching: find.text('Recommend'),
), ),
) )
.style .style
@@ -85,6 +121,27 @@ void main() {
), ),
findsOneWidget, 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( expect(
tester tester
.widget<Icon>( .widget<Icon>(
@@ -94,23 +151,26 @@ void main() {
), ),
) )
.color, .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(); await tester.pumpAndSettle();
expect(haptics.selectionClicks, 1); expect(haptics.selectionClicks, 1);
final theaterBar = tester.widget<Container>(barFinder); final theaterBar = tester.widget<Container>(barFinder);
final theaterDecoration = theaterBar.decoration as BoxDecoration; final theaterDecoration = theaterBar.decoration as BoxDecoration;
expect(theaterDecoration.color, AppColors.bar.withValues(alpha: 0.96)); expect(theaterDecoration.color, AppColors.navBackground);
expect((theaterDecoration.border as Border).top.color, AppColors.border); expect(
await tester.tap(find.byKey(const Key('tab-theater'))); (theaterDecoration.border as Border).top.color,
AppColors.navDivider,
);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(haptics.selectionClicks, 1); expect(haptics.selectionClicks, 1);
expect( expect(
find.descendant( find.descendant(
of: find.byKey(const Key('tab-theater')), of: find.byKey(const Key('tab-recommend')),
matching: find.byIcon(Icons.explore_rounded), matching: find.byIcon(Icons.smart_display_rounded),
), ),
findsOneWidget, findsOneWidget,
); );
@@ -118,13 +178,13 @@ void main() {
tester tester
.widget<Text>( .widget<Text>(
find.descendant( find.descendant(
of: find.byKey(const Key('tab-theater')), of: find.byKey(const Key('tab-recommend')),
matching: find.text('Theater'), matching: find.text('Recommend'),
), ),
) )
.style .style
?.color, ?.color,
AppColors.blue, AppColors.navSelected,
); );
expect( expect(
tester 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, tester,
) async { ) async {
await _pumpApp(tester); await _pumpApp(tester);
expect(find.byKey(const Key('tab-home')), findsOneWidget); expect(find.byKey(const Key('tab-home')), findsOneWidget);
expect(find.byKey(const Key('tab-theater')), findsOneWidget); expect(find.byKey(const Key('tab-recommend')), findsOneWidget);
expect(find.byKey(const Key('tab-create')), findsOneWidget); expect(find.byKey(const Key('tab-follow')), findsOneWidget);
expect(find.byKey(const Key('tab-mine')), 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); 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(); await tester.pumpAndSettle();
expect(find.byKey(const Key('home-all-dramas-grid')), findsOneWidget); 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(); 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.tap(find.byKey(const Key('tab-mine')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('Viewer'), findsOneWidget); expect(find.text('Viewer'), findsOneWidget);
expect(find.byKey(const Key('mine-menu')), findsOneWidget); expect(find.byKey(const Key('mine-menu')), findsOneWidget);
expect(find.byKey(const Key('mine-watchlist')), 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); 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.tap(find.byKey(const Key('tab-home')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byKey(const Key('home-feed-title')), findsOneWidget); expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
+14 -26
View File
@@ -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/favorite_repository.dart';
import 'package:fruitshorts/core/data/repositories/playback_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/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/home/home_feed_channel.dart';
import 'package:fruitshorts/pages/player/player_view.dart'; import 'package:fruitshorts/pages/player/player_view.dart';
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
@@ -27,7 +28,7 @@ void main() {
InMemorySharedPreferencesAsync.empty(); InMemorySharedPreferencesAsync.empty();
}); });
testWidgets('should_render_four_tabs_and_open_create_placeholder', ( testWidgets('should_render_four_tabs_and_open_following_page', (
tester, tester,
) async { ) async {
await _pumpApp(tester); await _pumpApp(tester);
@@ -35,27 +36,14 @@ void main() {
for (final key in _tabKeys) { for (final key in _tabKeys) {
expect(find.byKey(key), findsOneWidget); 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(); await tester.pumpAndSettle();
expect(find.byKey(const Key('create-page')), findsOneWidget); expect(find.byKey(const Key('favorites-page')), findsOneWidget);
expect(find.text('Create'), findsWidgets); expect(find.byKey(const Key('favorites-back')), findsNothing);
expect(find.text('Stories become short dramas'), findsOneWidget); expect(find.byType(AppBottomNav), 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);
}); });
testWidgets('should_start_home_on_immersive_feed', (tester) async { testWidgets('should_start_home_on_immersive_feed', (tester) async {
@@ -334,7 +322,7 @@ void main() {
await _pumpApp(tester); await _pumpApp(tester);
expect(_hasLightStatusBar(tester), isTrue); 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(); await tester.pumpAndSettle();
expect(find.byKey(const Key('home-page-gradient')), findsOneWidget); expect(find.byKey(const Key('home-page-gradient')), findsOneWidget);
@@ -395,7 +383,7 @@ void main() {
await _pumpApp(tester); await _pumpApp(tester);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget); 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(); await tester.pumpAndSettle();
expect(find.byKey(const Key('home-feed-title')), findsNothing); expect(find.byKey(const Key('home-feed-title')), findsNothing);
@@ -418,7 +406,7 @@ void main() {
testWidgets('should_not_render_theater_language_filters', (tester) async { testWidgets('should_not_render_theater_language_filters', (tester) async {
await _pumpApp(tester); 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.pumpAndSettle();
expect(find.byKey(const Key('theater-language-chips')), findsNothing); expect(find.byKey(const Key('theater-language-chips')), findsNothing);
@@ -435,7 +423,7 @@ void main() {
tester, tester,
) async { ) async {
await _pumpApp(tester); 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.pumpAndSettle();
final card = find.byKey(const Key('home-drama-card-the-signal-demo')); 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(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(tester.takeException(), isNull); 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(); await tester.pumpAndSettle();
expect(find.byKey(const Key('theater-category-chips')), findsOneWidget); expect(find.byKey(const Key('theater-category-chips')), findsOneWidget);
expect(find.byKey(const Key('theater-language-chips')), findsNothing); expect(find.byKey(const Key('theater-language-chips')), findsNothing);
@@ -474,8 +462,8 @@ void main() {
const _tabKeys = [ const _tabKeys = [
Key('tab-home'), Key('tab-home'),
Key('tab-theater'), Key('tab-recommend'),
Key('tab-create'), Key('tab-follow'),
Key('tab-mine'), Key('tab-mine'),
]; ];
+2 -2
View File
@@ -91,11 +91,11 @@ void main() {
logic.act( logic.act(
pageContext, pageContext,
MyPageAction.discover, MyPageAction.recommend,
embedded: true, embedded: true,
onAction: forwarded.add, onAction: forwarded.add,
); );
expect(forwarded, [MyPageAction.discover]); expect(forwarded, [MyPageAction.recommend]);
logic.act(pageContext, MyPageAction.resume, embedded: false); logic.act(pageContext, MyPageAction.resume, embedded: false);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
+2 -2
View File
@@ -39,7 +39,7 @@ void main() {
tester, tester,
) async { ) async {
await _pumpApp(tester); 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.pumpAndSettle();
await tester.tap(find.byKey(const Key('home-drama-card-city-lights-demo'))); await tester.tap(find.byKey(const Key('home-drama-card-city-lights-demo')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
@@ -1331,7 +1331,7 @@ Future<void> _openSeriesPlayer(
WidgetTester tester, { WidgetTester tester, {
Key cardKey = const Key('home-drama-card-city-lights-demo'), Key cardKey = const Key('home-drama-card-city-lights-demo'),
}) async { }) async {
await tester.tap(find.byKey(const Key('tab-theater'))); await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final card = find.byKey(cardKey); final card = find.byKey(cardKey);
await tester.scrollUntilVisible( await tester.scrollUntilVisible(
+1 -1
View File
@@ -18,7 +18,7 @@ void main() {
expect(AppDimens.listTitle, 15); expect(AppDimens.listTitle, 15);
expect(AppDimens.sectionTitle, 16); expect(AppDimens.sectionTitle, 16);
expect(AppDimens.pageTitle, 20); expect(AppDimens.pageTitle, 20);
expect(AppDimens.navLabel, 12); expect(AppDimens.navLabel, 11);
expect(AppDimens.navIcon, 24); expect(AppDimens.navIcon, 24);
expect(AppDimens.buttonHeight, 48); expect(AppDimens.buttonHeight, 48);
expect(AppDimens.primaryButtonHeight, 52); expect(AppDimens.primaryButtonHeight, 52);
+4 -1
View File
@@ -63,7 +63,10 @@ void main() {
expect(theme.appBarTheme.systemOverlayStyle, SystemUiOverlayStyle.light); expect(theme.appBarTheme.systemOverlayStyle, SystemUiOverlayStyle.light);
expect(theme.cardTheme.elevation, 0); expect(theme.cardTheme.elevation, 0);
expect(theme.dialogTheme.backgroundColor, AppColors.raised); expect(theme.dialogTheme.backgroundColor, AppColors.raised);
expect(theme.bottomNavigationBarTheme.backgroundColor, AppColors.bar); expect(
theme.bottomNavigationBarTheme.backgroundColor,
AppColors.navBackground,
);
expect(theme.inputDecorationTheme.fillColor, AppColors.input); expect(theme.inputDecorationTheme.fillColor, AppColors.input);
}); });
+8 -8
View File
@@ -120,9 +120,9 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byKey(const Key('home-feed-title')), findsOneWidget); 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(); 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.tap(find.byKey(const Key('tab-mine')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byKey(const Key('mine-menu')), findsOneWidget); 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-language')), findsOneWidget);
expect(find.byKey(const Key('mine-wallet')), 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(); await tester.pumpAndSettle();
expect(find.text('Theater'), findsWidgets); expect(find.text('Recommend'), findsWidgets);
}); });
testWidgets('should_switch_app_language_from_mine_menu', (tester) async { testWidgets('should_switch_app_language_from_mine_menu', (tester) async {
@@ -168,9 +168,9 @@ void main() {
expect(find.text('我的'), findsWidgets); expect(find.text('我的'), findsWidgets);
expect(find.text('语言'), findsOneWidget); 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(); await tester.pumpAndSettle();
expect(find.text('剧场'), findsWidgets); expect(find.text('推荐'), findsWidgets);
}); });
testWidgets('should_fit_compact_viewport_when_player_is_opened', ( testWidgets('should_fit_compact_viewport_when_player_is_opened', (
@@ -207,7 +207,7 @@ void main() {
await tester.pumpWidget(appUnderTest()); await tester.pumpWidget(appUnderTest());
await tester.pumpAndSettle(); 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.pumpAndSettle();
expect(find.byKey(const Key('discover-search')), findsOneWidget); expect(find.byKey(const Key('discover-search')), findsOneWidget);
@@ -316,7 +316,7 @@ void main() {
} }
Future<void> _openTheaterPlayer(WidgetTester tester) async { Future<void> _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(); await tester.pumpAndSettle();
final card = find.byKey(const Key('home-drama-card-city-lights-demo')); final card = find.byKey(const Key('home-drama-card-city-lights-demo'));
await tester.scrollUntilVisible( await tester.scrollUntilVisible(