diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index be697c7..fdcf24d 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -1108,3 +1108,10 @@ $ flutter test 解读:首页搜索/页签/二级标签、三类卡片字体与标签、5:6 封面、网格间距、 筛选 chip、我的标题和追剧双列 320 宽度适配均有测试覆盖。目标组件均低于 500 行;`pubspec.lock` 未提交,未操作真机。 + +## 2026-10-22 首页布局对齐修正 + +- 一级分类改为页内切换 6 个列表,选中 18/w700 白字、未选中 15/w400 灰字,无下划线。 +- 二级 chips 与一级行左对齐可滑动;未选项纯文字,选中项深灰胶囊。 +- 首页、剧单、全部短剧、追剧卡片移除底板/描边,标题与题材从封面左边起始,题材改为 `|` 分隔纯文字。 +- 全部短剧的地区、字幕、配音筛选 UI 已占位;以下接口待补:地区、字幕/配音筛选数据。 diff --git a/lib/core/theme/app_dimens.dart b/lib/core/theme/app_dimens.dart index 780df01..7f62d8f 100644 --- a/lib/core/theme/app_dimens.dart +++ b/lib/core/theme/app_dimens.dart @@ -98,9 +98,15 @@ abstract final class AppDimens { static const double cardTitleTagGap = 6; static const double cardCoverAspectRatio = 5 / 6; static const double homeCardBodyExtent = 84; - static const double homePrimaryTabFont = 16; + static const double homePrimaryTabFont = 15; + static const double homePrimaryTabSelectedFont = 18; + static const double homePrimaryRowHeight = 30; + static const Duration homePrimaryTabAnimationDuration = Duration( + milliseconds: 180, + ); static const double homeSecondaryTagFont = 13; - static const double homeSectionIndicator = 2; + static const double homePrimaryCategoryGap = 24; + static const double homeSecondaryTagGap = 18; static const double homeIconSlot = 44; static const double homeHeaderIcon = 22; diff --git a/lib/pages/following/following_grid_card.dart b/lib/pages/following/following_grid_card.dart index d9175bb..7c63162 100644 --- a/lib/pages/following/following_grid_card.dart +++ b/lib/pages/following/following_grid_card.dart @@ -41,6 +41,7 @@ class FollowingGridCard extends StatelessWidget { onTap: onTap, child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ AspectRatio( key: Key('following-cover-$dramaId'), @@ -119,6 +120,7 @@ class FollowingGridCard extends StatelessWidget { title, maxLines: 2, overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, style: const TextStyle( color: AppColors.textPrimary, fontSize: AppDimens.cardTitleFont, @@ -161,39 +163,17 @@ class FollowingGridCard extends StatelessWidget { } Widget _tags() { - final visible = tags.take(2).toList(growable: false); - return Row( - children: [ - for (final (index, tag) in visible.indexed) - Flexible( - child: Padding( - padding: EdgeInsets.only( - right: index == visible.length - 1 ? 0 : AppDimens.spaceXs, - ), - child: Container( - key: Key('following-tag-$dramaId-$index'), - height: AppDimens.cardTagHeight, - alignment: Alignment.center, - padding: const EdgeInsets.symmetric( - horizontal: AppDimens.cardTagHorizontalPadding, - ), - decoration: BoxDecoration( - color: AppColors.contentSurface, - borderRadius: BorderRadius.circular(AppDimens.cardTagRadius), - ), - child: Text( - tag, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: AppColors.textTertiary, - fontSize: AppDimens.cardTagFont, - ), - ), - ), - ), - ), - ], + final visible = tags.take(3).toList(growable: false); + return Text( + visible.join(' | '), + key: Key('following-tags-$dramaId'), + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + style: const TextStyle( + color: AppColors.textTertiary, + fontSize: AppDimens.cardTagFont, + ), ); } } diff --git a/lib/pages/home/home_view.dart b/lib/pages/home/home_view.dart index f719c42..2630cf2 100644 --- a/lib/pages/home/home_view.dart +++ b/lib/pages/home/home_view.dart @@ -26,8 +26,6 @@ import '../discover/discover_view.dart'; import '../following/following_page.dart'; import '../filter/filter_page.dart'; import '../mine/mine_types.dart'; -import '../new_releases/new_releases_page.dart'; -import '../ranking/ranking_page.dart'; import '../recommend/recommend_page.dart'; import '../mine/mine_view.dart'; import '../store/vip_page.dart'; @@ -284,9 +282,6 @@ class _HomeTabsState extends State<_HomeTabs> { onSearch: () => _openSearch(context), onOpenVip: () => unawaited(_openVip(context)), onOpenWallet: () => unawaited(_openWallet(context)), - onOpenNewReleases: () => _pushPage(context, const NewReleasesPage()), - onOpenCategories: () => _pushPage(context, const FilterPage()), - onOpenRanking: () => _pushPage(context, const RankingPage()), onOpenFilter: () => _pushPage(context, const FilterPage()), ), onOpenDrama: (drama) => unawaited(logic.playDrama(context, drama)), @@ -324,9 +319,8 @@ class _HomeTabsState extends State<_HomeTabs> { Future _openWallet(BuildContext context) async { if (!await ensureSignedIn(context) || !context.mounted) return; - await Navigator.of( - context, - ).push(MaterialPageRoute(builder: (_) => const WalletPage())); + await Navigator.of(context) + .push(MaterialPageRoute(builder: (_) => const WalletPage())); } Widget _my(BuildContext context) { diff --git a/lib/pages/home/widgets/home_feed_card.dart b/lib/pages/home/widgets/home_feed_card.dart index fd7c6be..54fc807 100644 --- a/lib/pages/home/widgets/home_feed_card.dart +++ b/lib/pages/home/widgets/home_feed_card.dart @@ -29,49 +29,46 @@ class HomeFeedCard extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; - return Container( + return Material( key: Key('home-feed-card-${drama.id}'), - decoration: BoxDecoration( - color: AppColors.contentCard, + color: AppColors.transparent, + child: InkWell( borderRadius: BorderRadius.circular(AppDimens.homeCardRadius), - border: Border.all(color: AppColors.contentBorder), - ), - child: Material( - color: AppColors.transparent, - borderRadius: BorderRadius.circular(AppDimens.homeCardRadius), - clipBehavior: Clip.antiAlias, - child: InkWell( - onTap: onTap, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _cover(l10n), - Expanded( - child: Padding( - padding: const EdgeInsets.all(AppDimens.homeCardPadding), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - drama.title, - key: Key('home-feed-title-${drama.id}'), - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: AppColors.navSelected, - fontSize: AppDimens.cardTitleFont, - fontWeight: FontWeight.w500, - height: AppDimens.cardTitleLineHeight, - ), + onTap: onTap, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _cover(l10n), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: AppDimens.cardCoverTitleGap, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + drama.title, + key: Key('home-feed-title-${drama.id}'), + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + style: const TextStyle( + color: AppColors.navSelected, + fontSize: AppDimens.cardTitleFont, + fontWeight: FontWeight.w500, + height: AppDimens.cardTitleLineHeight, ), - const SizedBox(height: AppDimens.cardTitleTagGap), - _tags(), - ], - ), + ), + const SizedBox(height: AppDimens.cardTitleTagGap), + _tags(), + ], ), ), - ], - ), + ), + ], ), ), ); @@ -180,39 +177,17 @@ class HomeFeedCard extends StatelessWidget { Widget _tags() { final tags = _cardTags(); if (tags.isEmpty) return const SizedBox.shrink(); - return Row( - children: [ - for (final (index, tag) in tags.indexed) - Flexible( - child: Padding( - padding: EdgeInsets.only( - right: index == tags.length - 1 ? 0 : AppDimens.spaceXs, - ), - child: Container( - key: Key('home-feed-tag-${drama.id}-$index'), - height: AppDimens.cardTagHeight, - alignment: Alignment.center, - padding: const EdgeInsets.symmetric( - horizontal: AppDimens.cardTagHorizontalPadding, - ), - decoration: BoxDecoration( - color: AppColors.contentSurface, - borderRadius: BorderRadius.circular(AppDimens.cardTagRadius), - ), - child: Text( - tag, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: AppColors.textTertiary, - fontSize: AppDimens.cardTagFont, - letterSpacing: 0, - ), - ), - ), - ), - ), - ], + return Text( + tags.join(' | '), + key: Key('home-feed-tags-${drama.id}'), + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + style: const TextStyle( + color: AppColors.textTertiary, + fontSize: AppDimens.cardTagFont, + letterSpacing: 0, + ), ); } @@ -222,7 +197,7 @@ class HomeFeedCard extends StatelessWidget { .expand((value) => value.split(_tagSplitter)) .map((value) => value.trim()) .where((value) => value.isNotEmpty && seen.add(value)) - .take(2) + .take(3) .toList(growable: false); } } diff --git a/lib/pages/home/widgets/home_feed_grid.dart b/lib/pages/home/widgets/home_feed_grid.dart index 64043be..238ad04 100644 --- a/lib/pages/home/widgets/home_feed_grid.dart +++ b/lib/pages/home/widgets/home_feed_grid.dart @@ -42,8 +42,27 @@ class HomeFeedGrid extends StatefulWidget { } class _HomeFeedGridState extends State { - String? _selectedCategory; - String? _selectedTag; + HomePrimaryTab _selectedPrimary = HomePrimaryTab.featured; + late final Map _scrollControllers; + final Map _selectedTags = { + for (final tab in HomePrimaryTab.values) tab: null, + }; + + @override + void initState() { + super.initState(); + _scrollControllers = { + for (final tab in HomePrimaryTab.values) tab: ScrollController(), + }; + } + + @override + void dispose() { + for (final controller in _scrollControllers.values) { + controller.dispose(); + } + super.dispose(); + } @override Widget build(BuildContext context) { @@ -53,14 +72,10 @@ class _HomeFeedGridState extends State { final wallet = context.watch(); final shortcuts = _categoryShortcuts(categories, l10n); final tags = _availableTags(l10n); - final activeCategory = - shortcuts.any((shortcut) => shortcut.categoryName == _selectedCategory) - ? _selectedCategory + final activeTag = + tags.any((tag) => tag.id == _selectedTags[_selectedPrimary]) + ? _selectedTags[_selectedPrimary] : null; - final activeTag = tags.any((tag) => tag.id == _selectedTag) - ? _selectedTag - : null; - final dramas = _filteredDramas(activeCategory, activeTag); final walletBadge = auth.isSignedIn && wallet.snapshot != null ? wallet.balance : null; @@ -69,102 +84,122 @@ class _HomeFeedGridState extends State { color: AppColors.appBackground, child: SafeArea( bottom: false, - child: LayoutBuilder( - builder: (context, constraints) { - final itemWidth = - (constraints.maxWidth - - AppDimens.feedPageHorizontal * 2 - - AppDimens.homeGridGap * 2) / - 3; - final itemExtent = itemWidth * 6 / 5 + AppDimens.homeCardBodyExtent; - return NotificationListener( - onNotification: _onScroll, - child: AppPullToRefresh( - key: const Key('home-grid-refresh'), - onRefresh: _refresh, - child: CustomScrollView( - key: const Key('home-grid-scroll'), - physics: AppPullToRefresh.physics, - slivers: [ - SliverToBoxAdapter( - child: HomeGridHeader( - callbacks: widget.callbacks, - categories: shortcuts, - tags: tags, - selectedCategory: activeCategory, - selectedTag: activeTag, - onCategorySelected: _selectCategory, - onTagSelected: (tag) => - setState(() => _selectedTag = tag), - walletBadge: walletBadge, - ), - ), - if (widget.feedController.loadingFirstPage) - const SliverFillRemaining( - hasScrollBody: false, - child: Center( - child: SizedBox.square( - dimension: 24, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ), - ) - else if (dramas.isEmpty) - SliverFillRemaining( - hasScrollBody: false, - child: _emptyState(l10n, activeCategory, activeTag), - ) - else - SliverPadding( - padding: const EdgeInsets.symmetric( - horizontal: AppDimens.feedPageHorizontal, - ), - sliver: SliverGrid( - key: const Key('home-grid-cards'), - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: AppDimens.homeGridGap, - mainAxisSpacing: AppDimens.homeGridRowGap, - mainAxisExtent: itemExtent, - ), - delegate: SliverChildBuilderDelegate( - (context, index) => HomeFeedCard( - drama: dramas[index], - onTap: () => widget.onOpenDrama(dramas[index]), - ), - childCount: dramas.length, - ), - ), - ), - if (dramas.isNotEmpty) - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.only( - bottom: AppDimens.spaceXl, - ), - child: PagedListFooter( - loading: widget.feedController.loadingMore, - hasMore: widget.feedController.hasMore, - hasItems: dramas.isNotEmpty, - errorMessage: - widget.feedController.lastError == null - ? null - : l10n.catalogError, - onRetry: widget.feedController.loadMore, - ), - ), - ), - ], - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + HomeGridHeader( + callbacks: widget.callbacks, + categories: shortcuts, + tags: tags, + selectedPrimary: _selectedPrimary, + selectedTag: activeTag, + onPrimarySelected: (tab) => + setState(() => _selectedPrimary = tab), + onTagSelected: (tag) => + setState(() => _selectedTags[_selectedPrimary] = tag), + walletBadge: walletBadge, + ), + Expanded( + child: IndexedStack( + index: _selectedPrimary.index, + children: [ + for (final tab in HomePrimaryTab.values) + _tabGrid(context, l10n, tab: tab, shortcuts: shortcuts), + ], ), - ); - }, + ), + ], ), ), ); } + Widget _tabGrid( + BuildContext context, + AppLocalizations l10n, { + required HomePrimaryTab tab, + required List shortcuts, + }) { + final tag = _selectedTags[tab]; + final dramas = _dramasFor(tab, tag, shortcuts); + return LayoutBuilder( + builder: (context, constraints) { + final itemWidth = + (constraints.maxWidth - + AppDimens.feedPageHorizontal * 2 - + AppDimens.homeGridGap * 2) / + 3; + final itemExtent = itemWidth * 6 / 5 + AppDimens.homeCardBodyExtent; + return NotificationListener( + onNotification: _onScroll, + child: AppPullToRefresh( + key: const Key('home-grid-refresh'), + onRefresh: _refresh, + child: CustomScrollView( + key: const Key('home-grid-scroll'), + controller: _scrollControllers[tab], + physics: AppPullToRefresh.physics, + slivers: [ + if (widget.feedController.loadingFirstPage) + const SliverFillRemaining( + hasScrollBody: false, + child: Center( + child: SizedBox.square( + dimension: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ) + else if (dramas.isEmpty) + SliverFillRemaining( + hasScrollBody: false, + child: _emptyState(l10n, tab, tag), + ) + else + SliverPadding( + padding: const EdgeInsets.symmetric( + horizontal: AppDimens.feedPageHorizontal, + ), + sliver: SliverGrid( + key: const Key('home-grid-cards'), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: AppDimens.homeGridGap, + mainAxisSpacing: AppDimens.homeGridRowGap, + mainAxisExtent: itemExtent, + ), + delegate: SliverChildBuilderDelegate( + (context, index) => HomeFeedCard( + drama: dramas[index], + onTap: () => widget.onOpenDrama(dramas[index]), + ), + childCount: dramas.length, + ), + ), + ), + if (dramas.isNotEmpty) + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(bottom: AppDimens.spaceXl), + child: PagedListFooter( + loading: widget.feedController.loadingMore, + hasMore: widget.feedController.hasMore, + hasItems: dramas.isNotEmpty, + errorMessage: widget.feedController.lastError == null + ? null + : l10n.catalogError, + onRetry: widget.feedController.loadMore, + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + Future _refresh() async { await widget.feedController.refreshCurrent(); } @@ -182,37 +217,24 @@ class _HomeFeedGridState extends State { return false; } - void _selectCategory(String? category) { - setState(() { - _selectedCategory = category; - _selectedTag = null; - }); - } - List _categoryShortcuts( List categories, AppLocalizations l10n, ) { - final shortcuts = []; final costume = _findCategory(categories, _costumeAliases); final community = _findCategory(categories, _communityAliases); - if (costume != null) { - shortcuts.add( - HomeGridCategoryShortcut( - label: l10n.homeSectionCostume, - categoryName: costume.name, - ), - ); - } - if (community != null) { - shortcuts.add( - HomeGridCategoryShortcut( - label: l10n.homeSectionCommunity, - categoryName: community.name, - ), - ); - } - return shortcuts; + return [ + HomeGridCategoryShortcut( + label: l10n.homeSectionCostume, + categoryName: costume?.name ?? '', + tab: HomePrimaryTab.costume, + ), + HomeGridCategoryShortcut( + label: l10n.homeSectionCommunity, + categoryName: community?.name ?? '', + tab: HomePrimaryTab.community, + ), + ]; } ContentCategory? _findCategory( @@ -227,46 +249,59 @@ class _HomeFeedGridState extends State { } List _availableTags(AppLocalizations l10n) { - final definitions = [ + // UI 先完整展示筛选维度;后续接口补齐题材字段时只替换数据来源。 + return [ HomeGridTagOption(id: 'hot', label: l10n.homeTagHot), HomeGridTagOption(id: 'uplifting', label: l10n.homeTagUplifting), HomeGridTagOption(id: 'sweet', label: l10n.homeTagSweet), HomeGridTagOption(id: 'faceSlap', label: l10n.homeTagFaceSlap), HomeGridTagOption(id: 'angst', label: l10n.homeTagAngst), ]; - final categoryText = context - .read() - .categories - .map((category) => category.name) - .toList(growable: false); - return definitions - .where((option) { - if (option.id == 'hot') { - return widget.feedController.dramas.any(isHotDrama); - } - return categoryText.any((text) => tagMatchesText(option.id, text)) || - widget.feedController.dramas.any( - (drama) => matchesHomeTag(drama, option.id), - ); - }) - .toList(growable: false); } - List _filteredDramas(String? category, String? tag) { - final normalizedCategory = category?.toLowerCase(); - return widget.feedController.dramas - .where((drama) { - final categoryMatches = - normalizedCategory == null || - drama.category.toLowerCase().contains(normalizedCategory) || - drama.genre.toLowerCase().contains(normalizedCategory); - final tagMatches = tag == null || matchesHomeTag(drama, tag); - return categoryMatches && tagMatches; - }) - .toList(growable: false); + List _dramasFor( + HomePrimaryTab tab, + String? tag, + List shortcuts, + ) { + Iterable source = widget.feedController.dramas; + if (tab == HomePrimaryTab.costume || tab == HomePrimaryTab.community) { + final categoryName = shortcuts + .firstWhere((shortcut) => shortcut.tab == tab) + .categoryName + .toLowerCase(); + if (categoryName.isEmpty) return const []; + source = source.where( + (drama) => + drama.category.toLowerCase().contains(categoryName) || + drama.genre.toLowerCase().contains(categoryName), + ); + } + if (tag != null) { + source = source.where((drama) => matchesHomeTag(drama, tag)); + } + final dramas = source.toList(growable: false); + switch (tab) { + case HomePrimaryTab.newReleases: + return [...dramas]..sort((a, b) { + final left = publishedAtForDrama(a); + final right = publishedAtForDrama(b); + if (left == null && right == null) return b.id.compareTo(a.id); + if (left == null) return 1; + if (right == null) return -1; + return right.compareTo(left); + }); + case HomePrimaryTab.ranking: + return [...dramas]..sort((a, b) => b.viewCount.compareTo(a.viewCount)); + case HomePrimaryTab.featured: + case HomePrimaryTab.categories: + case HomePrimaryTab.costume: + case HomePrimaryTab.community: + return dramas; + } } - Widget _emptyState(AppLocalizations l10n, String? category, String? tag) { + Widget _emptyState(AppLocalizations l10n, HomePrimaryTab tab, String? tag) { if (widget.feedController.lastError != null) { return EmptyState( key: const Key('home-grid-error'), @@ -276,7 +311,10 @@ class _HomeFeedGridState extends State { onAction: () => unawaited(widget.feedController.refreshCurrent()), ); } - final filtered = category != null || tag != null; + final filtered = + tab == HomePrimaryTab.costume || + tab == HomePrimaryTab.community || + tag != null; return EmptyState( key: const Key('home-grid-empty'), icon: Icons.movie_filter_outlined, @@ -284,10 +322,7 @@ class _HomeFeedGridState extends State { subtitle: filtered ? null : l10n.emptyHint, actionLabel: filtered ? l10n.homeGridClearFilter : null, onAction: filtered - ? () => setState(() { - _selectedCategory = null; - _selectedTag = null; - }) + ? () => setState(() => _selectedTags[tab] = null) : null, ); } diff --git a/lib/pages/home/widgets/home_grid_header.dart b/lib/pages/home/widgets/home_grid_header.dart index a12a7f1..76b3988 100644 --- a/lib/pages/home/widgets/home_grid_header.dart +++ b/lib/pages/home/widgets/home_grid_header.dart @@ -7,14 +7,25 @@ import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../../l10n/app_localizations.dart'; +enum HomePrimaryTab { + featured, + newReleases, + categories, + ranking, + costume, + community, +} + class HomeGridCategoryShortcut { const HomeGridCategoryShortcut({ required this.label, required this.categoryName, + required this.tab, }); final String label; final String categoryName; + final HomePrimaryTab tab; } class HomeGridTagOption { @@ -29,18 +40,12 @@ class HomeGridCallbacks { required this.onSearch, required this.onOpenVip, required this.onOpenWallet, - required this.onOpenNewReleases, - required this.onOpenCategories, - required this.onOpenRanking, required this.onOpenFilter, }); final VoidCallback onSearch; final VoidCallback onOpenVip; final VoidCallback onOpenWallet; - final VoidCallback onOpenNewReleases; - final VoidCallback onOpenCategories; - final VoidCallback onOpenRanking; final VoidCallback onOpenFilter; } @@ -50,9 +55,9 @@ class HomeGridHeader extends StatelessWidget { required this.callbacks, required this.categories, required this.tags, - required this.selectedCategory, + required this.selectedPrimary, required this.selectedTag, - required this.onCategorySelected, + required this.onPrimarySelected, required this.onTagSelected, this.walletBadge, }); @@ -60,9 +65,9 @@ class HomeGridHeader extends StatelessWidget { final HomeGridCallbacks callbacks; final List categories; final List tags; - final String? selectedCategory; + final HomePrimaryTab selectedPrimary; final String? selectedTag; - final ValueChanged onCategorySelected; + final ValueChanged onPrimarySelected; final ValueChanged onTagSelected; final int? walletBadge; @@ -71,6 +76,8 @@ class HomeGridHeader extends StatelessWidget { final l10n = AppLocalizations.of(context)!; return Column( key: const Key('home-grid-header'), + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [_searchRow(l10n), _primaryRow(l10n), _tagRow(l10n)], ); } @@ -84,6 +91,8 @@ class HomeGridHeader extends StatelessWidget { AppDimens.spaceSm, ), child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( child: InkWell( @@ -210,37 +219,38 @@ class HomeGridHeader extends StatelessWidget { horizontal: AppDimens.feedPageHorizontal, ), child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ _primaryItem( key: const Key('home-section-featured'), label: l10n.homeSectionFeatured, - selected: selectedCategory == null, - onTap: () => onCategorySelected(null), + tab: HomePrimaryTab.featured, ), _primaryItem( key: const Key('home-section-new'), label: l10n.homeSectionNew, - selected: false, - onTap: callbacks.onOpenNewReleases, + tab: HomePrimaryTab.newReleases, ), _primaryItem( key: const Key('home-section-categories'), label: l10n.homeSectionCategories, - selected: false, - onTap: callbacks.onOpenCategories, + tab: HomePrimaryTab.categories, ), _primaryItem( key: const Key('home-section-ranking'), label: l10n.homeSectionRanking, - selected: false, - onTap: callbacks.onOpenRanking, + tab: HomePrimaryTab.ranking, ), for (final category in categories) _primaryItem( - key: Key('home-section-category-${category.categoryName}'), + key: Key( + category.categoryName.isEmpty + ? 'home-section-${category.tab.name}' + : 'home-section-category-${category.categoryName}', + ), label: category.label, - selected: selectedCategory == category.categoryName, - onTap: () => onCategorySelected(category.categoryName), + tab: category.tab, ), ], ), @@ -250,38 +260,35 @@ class HomeGridHeader extends StatelessWidget { Widget _primaryItem({ required Key key, required String label, - required bool selected, - required VoidCallback onTap, + required HomePrimaryTab tab, }) { + final selected = selectedPrimary == tab; return InkWell( key: key, - onTap: onTap, + onTap: () => onPrimarySelected(tab), child: Padding( - padding: const EdgeInsets.only(right: AppDimens.spaceXl), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - label, + padding: const EdgeInsets.only(right: AppDimens.homePrimaryCategoryGap), + child: SizedBox( + height: AppDimens.homePrimaryRowHeight, + child: Align( + alignment: Alignment.bottomLeft, + child: AnimatedDefaultTextStyle( + key: Key('home-primary-label-${tab.name}'), + duration: AppDimens.homePrimaryTabAnimationDuration, + curve: Curves.easeOut, style: TextStyle( color: selected ? AppColors.navSelected : AppColors.textTertiary, - fontSize: AppDimens.homePrimaryTabFont, + fontSize: selected + ? AppDimens.homePrimaryTabSelectedFont + : AppDimens.homePrimaryTabFont, fontWeight: selected ? FontWeight.w700 : FontWeight.w400, letterSpacing: 0, ), + child: Text(label, textAlign: TextAlign.left), ), - const SizedBox(height: AppDimens.spaceXs), - Container( - height: AppDimens.homeSectionIndicator, - width: selected ? 20 : 0, - decoration: BoxDecoration( - color: AppColors.navSelected, - borderRadius: BorderRadius.circular(AppDimens.pillRadius), - ), - ), - ], + ), ), ), ); @@ -296,16 +303,20 @@ class HomeGridHeader extends StatelessWidget { AppDimens.spaceSm, ), child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( child: SingleChildScrollView( key: const Key('home-secondary-tags'), scrollDirection: Axis.horizontal, child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ for (final tag in tags) ...[ _tagChip(tag), - const SizedBox(width: AppDimens.spaceSm), + const SizedBox(width: AppDimens.homeSecondaryTagGap), ], ], ), @@ -339,29 +350,35 @@ class HomeGridHeader extends StatelessWidget { Widget _tagChip(HomeGridTagOption tag) { final selected = selectedTag == tag.id; + final label = Text( + tag.label, + style: TextStyle( + color: selected ? AppColors.navSelected : AppColors.textTertiary, + fontSize: AppDimens.homeSecondaryTagFont, + fontWeight: selected ? FontWeight.w700 : FontWeight.w400, + letterSpacing: 0, + ), + ); return InkWell( key: Key('home-tag-${tag.id}'), borderRadius: BorderRadius.circular(AppDimens.pillRadius), onTap: () => onTagSelected(selected ? null : tag.id), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: AppDimens.spaceMd, - vertical: AppDimens.spaceSm, - ), - decoration: BoxDecoration( - color: selected ? AppColors.contentSurface : AppColors.transparent, - borderRadius: BorderRadius.circular(AppDimens.pillRadius), - ), - child: Text( - tag.label, - style: TextStyle( - color: selected ? AppColors.navSelected : AppColors.textTertiary, - fontSize: AppDimens.homeSecondaryTagFont, - fontWeight: selected ? FontWeight.w700 : FontWeight.w400, - letterSpacing: 0, - ), - ), - ), + child: selected + ? Container( + padding: const EdgeInsets.symmetric( + horizontal: AppDimens.spaceMd, + vertical: AppDimens.spaceSm, + ), + decoration: BoxDecoration( + color: AppColors.contentSurface, + borderRadius: BorderRadius.circular(AppDimens.pillRadius), + ), + child: label, + ) + : Padding( + padding: const EdgeInsets.symmetric(vertical: AppDimens.spaceSm), + child: label, + ), ); } } diff --git a/lib/pages/recommend/widgets/all_dramas_filter_grid.dart b/lib/pages/recommend/widgets/all_dramas_filter_grid.dart index 0a09e9a..8ce56e4 100644 --- a/lib/pages/recommend/widgets/all_dramas_filter_grid.dart +++ b/lib/pages/recommend/widgets/all_dramas_filter_grid.dart @@ -36,6 +36,8 @@ class AllDramasFilterGrid extends StatefulWidget { } class _AllDramasFilterGridState extends State { + String _region = 'all'; + String _audio = 'all'; String? _genre; bool _newest = false; @@ -71,7 +73,11 @@ class _AllDramasFilterGridState extends State { icon: Icons.movie_filter_outlined, title: l10n.homeGridFilterEmpty, actionLabel: l10n.homeGridClearFilter, - onAction: () => setState(() => _genre = null), + onAction: () => setState(() { + _region = 'all'; + _audio = 'all'; + _genre = null; + }), ), ) else @@ -130,12 +136,28 @@ class _AllDramasFilterGridState extends State { child: Column( key: const Key('all-dramas-filters'), children: [ - _chipRow(0, [l10n.all]), - _chipRow(1, [l10n.all]), - _chipRow(2, [ - (id: null, label: l10n.all), - for (final genre in genres) (id: genre.id, label: genre.label), - ]), + _selectableRow( + 0, + [ + (id: 'all', label: l10n.all), + (id: 'local', label: l10n.localFilter), + (id: 'asia', label: l10n.asiaFilter), + (id: 'western', label: l10n.westernFilter), + ], + selected: _region, + onSelected: (value) => setState(() => _region = value), + ), + _selectableRow( + 1, + [ + (id: 'all', label: l10n.all), + (id: 'subtitles', label: l10n.subtitlesOnlyFilter), + (id: 'dubbed', label: l10n.dubbedFilter), + ], + selected: _audio, + onSelected: (value) => setState(() => _audio = value), + ), + _genreRow(2, genres, l10n), Row( key: const Key('all-filter-row-3'), children: [ @@ -173,7 +195,12 @@ class _AllDramasFilterGridState extends State { ); } - Widget _chipRow(int row, List options) { + Widget _selectableRow( + int row, + List<({String id, String label})> options, { + required String selected, + required ValueChanged onSelected, + }) { return Padding( key: Key('all-filter-row-$row'), padding: const EdgeInsets.only(bottom: AppDimens.spaceSm), @@ -182,14 +209,12 @@ class _AllDramasFilterGridState extends State { child: Row( children: [ for (final option in options) ...[ - if (option is String) - _chip(label: option, selected: true, onTap: () {}), - if (option is ({String? id, String label})) - _chip( - label: option.label, - selected: _genre == option.id, - onTap: () => setState(() => _genre = option.id), - ), + _chip( + key: Key('all-filter-${option.id}'), + label: option.label, + selected: selected == option.id, + onTap: () => onSelected(option.id), + ), const SizedBox(width: AppDimens.spaceSm), ], ], @@ -198,6 +223,23 @@ class _AllDramasFilterGridState extends State { ); } + Widget _genreRow( + int row, + List<({String id, String label})> options, + AppLocalizations l10n, + ) { + return _selectableRow( + row, + [ + (id: 'all', label: l10n.all), + for (final genre in options) (id: genre.id, label: genre.label), + ], + selected: _genre ?? 'all', + onSelected: (value) => + setState(() => _genre = value == 'all' ? null : value), + ); + } + Widget _chip({ Key? key, required String label, @@ -248,6 +290,7 @@ class _AllDramasFilterGridState extends State { } List _filtered() { + if (_region != 'all' || _audio != 'all') return const []; final dramas = widget.feedController.dramas .where((drama) { return _genre == null || _matchesGenre(drama, _genre!, const []); diff --git a/lib/pages/recommend/widgets/recommend_section_card.dart b/lib/pages/recommend/widgets/recommend_section_card.dart index be9b282..376bf4b 100644 --- a/lib/pages/recommend/widgets/recommend_section_card.dart +++ b/lib/pages/recommend/widgets/recommend_section_card.dart @@ -35,6 +35,7 @@ class RecommendSectionCard extends StatelessWidget { onTap: onTap, child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ ClipRRect( @@ -83,6 +84,8 @@ class RecommendSectionCard extends StatelessWidget { const SizedBox(height: AppDimens.cardCoverTitleGap), Row( key: Key('recommend-views-$sectionKey-${drama.id}'), + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ const Icon( Icons.play_arrow_rounded, @@ -123,38 +126,16 @@ class RecommendSectionCard extends StatelessWidget { Widget _tags() { final tags = _cardTags(); - return Row( - children: [ - for (final (index, tag) in tags.indexed) - Flexible( - child: Padding( - padding: EdgeInsets.only( - right: index == tags.length - 1 ? 0 : AppDimens.spaceXs, - ), - child: Container( - key: Key('recommend-tag-$sectionKey-${drama.id}-$index'), - height: AppDimens.cardTagHeight, - alignment: Alignment.center, - padding: const EdgeInsets.symmetric( - horizontal: AppDimens.cardTagHorizontalPadding, - ), - decoration: BoxDecoration( - color: AppColors.contentSurface, - borderRadius: BorderRadius.circular(AppDimens.cardTagRadius), - ), - child: Text( - tag, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: AppColors.textTertiary, - fontSize: AppDimens.cardTagFont, - ), - ), - ), - ), - ), - ], + return Text( + tags.join(' | '), + key: Key('recommend-tags-$sectionKey-${drama.id}'), + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + style: const TextStyle( + color: AppColors.textTertiary, + fontSize: AppDimens.cardTagFont, + ), ); } @@ -164,7 +145,7 @@ class RecommendSectionCard extends StatelessWidget { .expand((value) => value.split(_tagSplitter)) .map((value) => value.trim()) .where((value) => value.isNotEmpty && seen.add(value)) - .take(2) + .take(3) .toList(growable: false); } } diff --git a/test/following/following_page_test.dart b/test/following/following_page_test.dart index 0c9f16d..b5e3a81 100644 --- a/test/following/following_page_test.dart +++ b/test/following/following_page_test.dart @@ -79,14 +79,14 @@ void main() { ); expect(views.style?.fontSize, AppDimens.cardViewsFont); expect(views.style?.color, AppColors.textPrimary); - final tag = find.byKey(const Key('following-tag-the-signal-demo-0')); - expect(tag, findsOneWidget); - expect(tester.getSize(tag).height, AppDimens.cardTagHeight); - final tagText = tester.widget( - find.descendant(of: tag, matching: find.byType(Text)), + final tags = tester.widget( + find.byKey(const Key('following-tags-the-signal-demo')), ); - expect(tagText.style?.fontSize, AppDimens.cardTagFont); - expect(tagText.style?.color, AppColors.textTertiary); + expect(tags.data, 'Suspense | Mystery'); + expect(tags.style?.fontSize, AppDimens.cardTagFont); + expect(tags.style?.color, AppColors.textTertiary); + expect(tester.getTopLeft(title).dx, tester.getTopLeft(cover).dx); + expect(tester.getTopLeft(tags).dx, tester.getTopLeft(cover).dx); await tester.tap(find.byKey(const Key('following-edit'))); await tester.pumpAndSettle(); diff --git a/test/home/home_card_style_test.dart b/test/home/home_card_style_test.dart index 749d48f..0c4c945 100644 --- a/test/home/home_card_style_test.dart +++ b/test/home/home_card_style_test.dart @@ -26,16 +26,17 @@ void main() { ); final card = find.byKey(const Key('home-feed-card-anime-card')); - final container = tester.widget(card); - final decoration = container.decoration! as BoxDecoration; - expect(decoration.color, AppColors.contentCard); - expect(decoration.boxShadow, isNull); + expect(tester.widget(card).color, AppColors.transparent); expect( - decoration.borderRadius, - BorderRadius.circular(AppDimens.homeCardRadius), + tester + .widgetList( + find.descendant(of: card, matching: find.byType(Container)), + ) + .map((container) => container.decoration) + .whereType() + .every((decoration) => decoration.border == null), + isTrue, ); - expect((decoration.border! as Border).top.width, 1); - expect((decoration.border! as Border).top.color, AppColors.contentBorder); final cover = find.byKey(const Key('home-feed-cover-anime-card')); final coverSize = tester.getSize(cover); @@ -70,22 +71,14 @@ void main() { expect(title.style?.fontSize, AppDimens.cardTitleFont); expect(title.style?.fontWeight, FontWeight.w500); expect(title.style?.height, AppDimens.cardTitleLineHeight); - final firstTag = find.byKey(const Key('home-feed-tag-anime-card-0')); - expect(firstTag, findsOneWidget); - expect(find.byKey(const Key('home-feed-tag-anime-card-1')), findsOneWidget); - expect(find.text('Romance'), findsOneWidget); - expect(find.text('Urban'), findsOneWidget); - final tagContainer = tester.widget(firstTag); - expect(tester.getSize(firstTag).height, AppDimens.cardTagHeight); - expect( - (tagContainer.decoration! as BoxDecoration).borderRadius, - BorderRadius.circular(AppDimens.cardTagRadius), + final tags = tester.widget( + find.byKey(const Key('home-feed-tags-anime-card')), ); - final tagText = tester.widget( - find.descendant(of: firstTag, matching: find.byType(Text)), - ); - expect(tagText.style?.fontSize, AppDimens.cardTagFont); - expect(tagText.style?.color, AppColors.textTertiary); + expect(tags.data, 'Romance | Urban'); + expect(tags.style?.fontSize, AppDimens.cardTagFont); + expect(tags.style?.color, AppColors.textTertiary); + expect(tester.getTopLeft(title).dx, tester.getTopLeft(cover).dx); + expect(tester.getTopLeft(tags).dx, tester.getTopLeft(cover).dx); await tester.tap(card); expect(taps, 1); diff --git a/test/home/home_grid_layout_test.dart b/test/home/home_grid_layout_test.dart index e0f4d35..179a10b 100644 --- a/test/home/home_grid_layout_test.dart +++ b/test/home/home_grid_layout_test.dart @@ -11,6 +11,7 @@ import 'package:fruitshorts/core/data/repositories/catalog_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/watch_history_repository.dart'; +import 'package:fruitshorts/pages/home/widgets/home_feed_card.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/shared_preferences_async_platform_interface.dart'; @@ -88,29 +89,82 @@ void main() { expect(find.byKey(const Key('home-tag-uplifting')), findsNothing); }); - testWidgets('should_open_existing_pages_from_home_sections', (tester) async { + testWidgets('should_switch_home_sections_without_pushing_pages', ( + tester, + ) async { final repository = _PagedCatalogRepository(); await _pumpApp(tester, repository); + String firstDramaId() { + return tester + .widget(find.byType(HomeFeedCard).first) + .drama + .id; + } + + expect(find.byKey(const Key('home-grid')), findsOneWidget); + expect(firstDramaId(), 'page1-0'); + await tester.tap(find.byKey(const Key('home-section-new'))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('new-releases-page')), findsOneWidget); - await tester.pageBack(); - await tester.pumpAndSettle(); + expect(find.byKey(const Key('new-releases-page')), findsNothing); + expect(firstDramaId(), 'page1-9'); - await tester.ensureVisible(find.byKey(const Key('home-section-ranking'))); await tester.tap(find.byKey(const Key('home-section-ranking'))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('ranking-page')), findsOneWidget); - await tester.pageBack(); - await tester.pumpAndSettle(); + expect(find.byKey(const Key('ranking-page')), findsNothing); + expect(firstDramaId(), 'page1-0'); - await tester.ensureVisible( - find.byKey(const Key('home-section-categories')), - ); await tester.tap(find.byKey(const Key('home-section-categories'))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('filter-page')), findsOneWidget); + expect(find.byKey(const Key('filter-page')), findsNothing); + expect(find.byKey(const Key('home-grid-cards')), findsOneWidget); + + await tester.tap(find.byKey(const Key('home-section-category-古装'))); + await tester.pumpAndSettle(); + expect(firstDramaId(), 'page1-0'); + + await tester.tap(find.byKey(const Key('home-section-category-社群'))); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('home-grid-empty')), findsOneWidget); + expect(find.byKey(const Key('filter-page')), findsNothing); + }); + + testWidgets('should_keep_scroll_position_when_switching_sections', ( + tester, + ) async { + final repository = _PagedCatalogRepository(); + await _pumpApp(tester, repository); + + await tester.drag( + find.byKey(const Key('home-grid-scroll')), + const Offset(0, -320), + ); + await tester.pumpAndSettle(); + final scrollable = find + .descendant( + of: find.byKey(const Key('home-grid-scroll')), + matching: find.byType(Scrollable), + ) + .first; + final before = tester.state(scrollable).position.pixels; + expect(before, greaterThan(0)); + + await tester.tap(find.byKey(const Key('home-section-new'))); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key('home-section-featured'))); + await tester.pumpAndSettle(); + + final afterScrollable = find + .descendant( + of: find.byKey(const Key('home-grid-scroll')), + matching: find.byType(Scrollable), + ) + .first; + expect( + tester.state(afterScrollable).position.pixels, + closeTo(before, 0.1), + ); }); testWidgets('should_load_next_page_without_expanding_page_size', ( @@ -232,14 +286,16 @@ class _PagedCatalogRepository implements CatalogRepository { } Drama _drama(String id) { + final index = int.tryParse(id.split('-').last) ?? 0; return Drama( id: id, title: 'Grid Drama $id', synopsis: 'synopsis', posterUrl: '', genre: 'Romance', - category: 'Urban', - viewCount: 200000, + category: index.isEven ? '古装' : 'Urban', + viewCount: (10 - index) * 10000, + createdAt: DateTime(2026, 10, 1).add(Duration(days: index)), detailLoaded: true, episodes: [ Episode( diff --git a/test/home/home_preview_layout_test.dart b/test/home/home_preview_layout_test.dart index 86eaee8..152ffe7 100644 --- a/test/home/home_preview_layout_test.dart +++ b/test/home/home_preview_layout_test.dart @@ -109,23 +109,29 @@ void main() { ); expect(placeholder.style?.fontSize, AppDimens.searchPlaceholderFont); expect(placeholder.style?.color, AppColors.textSecondary); - final selectedPrimary = tester.widget( + final selectedPrimary = tester.widget( + find.byKey(const Key('home-primary-label-featured')), + ); + final unselectedPrimary = tester.widget( + find.byKey(const Key('home-primary-label-newReleases')), + ); + expect( + selectedPrimary.style.fontSize, + AppDimens.homePrimaryTabSelectedFont, + ); + expect(selectedPrimary.style.fontWeight, FontWeight.w700); + expect(selectedPrimary.style.color, AppColors.navSelected); + expect(selectedPrimary.duration, AppDimens.homePrimaryTabAnimationDuration); + expect(unselectedPrimary.style.fontSize, AppDimens.homePrimaryTabFont); + expect(unselectedPrimary.style.fontWeight, FontWeight.w400); + expect(unselectedPrimary.style.color, AppColors.textTertiary); + expect( find.descendant( of: find.byKey(const Key('home-section-featured')), - matching: find.text('Featured'), + matching: find.byType(Container), ), + findsNothing, ); - final unselectedPrimary = tester.widget( - find.descendant( - of: find.byKey(const Key('home-section-new')), - matching: find.text('New'), - ), - ); - expect(selectedPrimary.style?.fontSize, AppDimens.homePrimaryTabFont); - expect(selectedPrimary.style?.fontWeight, FontWeight.w700); - expect(selectedPrimary.style?.color, AppColors.navSelected); - expect(unselectedPrimary.style?.fontWeight, FontWeight.w400); - expect(unselectedPrimary.style?.color, AppColors.textTertiary); final secondaryTag = tester.widget( find.descendant( of: find.byKey(const Key('home-tag-hot')), @@ -135,18 +141,38 @@ void main() { expect(secondaryTag.style?.fontSize, AppDimens.homeSecondaryTagFont); expect(secondaryTag.style?.fontWeight, FontWeight.w400); expect(secondaryTag.style?.color, AppColors.textTertiary); - final unselectedTagChip = tester.widget( + expect( + find.descendant( + of: find.byKey(const Key('home-tag-hot')), + matching: find.byType(Container), + ), + findsNothing, + ); + final primaryStart = tester.getTopLeft(find.text('Featured')); + final pageStart = tester.getTopLeft(searchBox); + expect(primaryStart.dx, pageStart.dx); + final primaryGap = + tester.getTopLeft(find.text('New')).dx - + tester.getBottomRight(find.text('Featured')).dx; + expect(primaryGap, closeTo(AppDimens.homePrimaryCategoryGap, 0.1)); + final tagStart = tester.getTopLeft(find.text('Hot')); + expect(tagStart.dx, pageStart.dx); + final tagGap = + tester.getTopLeft(find.text('Uplifting')).dx - + tester.getBottomRight(find.text('Hot')).dx; + expect(tagGap, closeTo(AppDimens.homeSecondaryTagGap, 0.1)); + await tester.tap(find.byKey(const Key('home-tag-hot'))); + await tester.pump(); + final selectedTagChip = tester.widget( find.descendant( of: find.byKey(const Key('home-tag-hot')), matching: find.byType(Container), ), ); expect( - (unselectedTagChip.decoration! as BoxDecoration).color, - AppColors.transparent, + (selectedTagChip.decoration! as BoxDecoration).color, + AppColors.contentSurface, ); - await tester.tap(find.byKey(const Key('home-tag-hot'))); - await tester.pump(); final selectedTag = tester.widget( find.descendant( of: find.byKey(const Key('home-tag-hot')), diff --git a/test/recommend/recommend_sections_test.dart b/test/recommend/recommend_sections_test.dart index e488434..1527c04 100644 --- a/test/recommend/recommend_sections_test.dart +++ b/test/recommend/recommend_sections_test.dart @@ -103,18 +103,14 @@ void main() { ); expect(views.style?.fontSize, AppDimens.cardViewsFont); expect(views.style?.color, AppColors.textPrimary); - final tag = find.byKey(const Key('recommend-tag-hits-the-signal-demo-0')); - expect(tag, findsOneWidget); - expect(tester.getSize(tag).height, AppDimens.cardTagHeight); - final tagText = tester.widget( - find.descendant(of: tag, matching: find.byType(Text)), - ); - expect(tagText.style?.fontSize, AppDimens.cardTagFont); - expect(tagText.style?.color, AppColors.textTertiary); - expect( - (tester.widget(tag).decoration! as BoxDecoration).borderRadius, - BorderRadius.circular(AppDimens.cardTagRadius), + final tags = tester.widget( + find.byKey(const Key('recommend-tags-hits-the-signal-demo')), ); + expect(tags.data, 'Suspense | Mystery'); + expect(tags.style?.fontSize, AppDimens.cardTagFont); + expect(tags.style?.color, AppColors.textTertiary); + expect(tester.getTopLeft(cardTitle).dx, tester.getTopLeft(cover).dx); + expect(tester.getTopLeft(tags).dx, tester.getTopLeft(cover).dx); }); testWidgets('should_switch_inner_tabs_without_losing_grid_filter_state', ( @@ -132,8 +128,13 @@ void main() { for (var row = 0; row < 4; row++) { expect(find.byKey(Key('all-filter-row-$row')), findsOneWidget); } - expect(find.byKey(const Key('all-filter-local')), findsNothing); - expect(find.byKey(const Key('all-filter-subtitles')), findsNothing); + expect(find.byKey(const Key('all-filter-local')), findsOneWidget); + expect(find.byKey(const Key('all-filter-subtitles')), findsOneWidget); + await tester.tap(find.byKey(const Key('all-filter-local'))); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('all-dramas-empty')), findsOneWidget); + await tester.tap(find.byKey(const Key('all-filter-all')).first); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('all-sort-new'))); await tester.pump(); expect(_textStyle(tester, 'New').fontWeight, FontWeight.w700);