From d7a59e5a27bd061642f71a83bb4d7d26f857f466 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Thu, 22 Oct 2026 18:06:03 +0800 Subject: [PATCH] =?UTF-8?q?style(client):=20=E5=AF=B9=E9=BD=90=E7=9F=AD?= =?UTF-8?q?=E5=89=A7=E5=88=97=E8=A1=A8=E5=AD=97=E5=8F=B7=E4=B8=8E=E9=97=B4?= =?UTF-8?q?=E8=B7=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/PROGRESS.md | 38 ++++++ lib/core/theme/app_colors.dart | 1 + lib/core/theme/app_dimens.dart | 48 ++++++-- lib/pages/following/following_grid_card.dart | 64 ++++++++-- lib/pages/following/following_page.dart | 113 +++++++++++------- lib/pages/home/widgets/home_feed_card.dart | 35 +++--- lib/pages/home/widgets/home_feed_grid.dart | 6 +- lib/pages/home/widgets/home_grid_header.dart | 34 +++--- lib/pages/recommend/recommend_page.dart | 8 +- .../widgets/all_dramas_filter_grid.dart | 24 ++-- .../widgets/recommend_section_card.dart | 82 ++++++++++--- .../widgets/recommend_sections_view.dart | 13 +- test/following/following_page_test.dart | 73 +++++++++++ test/home/home_card_style_test.dart | 41 +++++-- test/home/home_preview_layout_test.dart | 72 ++++++++++- test/mine/mine_preview_layout_test.dart | 8 ++ test/recommend/recommend_sections_test.dart | 64 ++++++++++ 17 files changed, 576 insertions(+), 148 deletions(-) diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index 0a3327d..be697c7 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -1070,3 +1070,41 @@ $ flutter test 解读:登录且钱包快照存在时钱包红标仍显示;首页四个 tab、推荐三个子 tab、 追剧页和我的页均断言不存在两侧悬浮组件。`pubspec.lock` 未提交, 未操作真机。 + +## 2026-10-22 首页与短剧列表视觉字号对齐 + +- 分支:`feature/visual-type-scale` +- 基线:`develop` / `origin/develop` = `d03dcf1` + +### Token 与组件 + +- 新增 `textTertiary = #9E9E9E`,以及搜索、页签、筛选 chip、分区标题、 + 卡片标题/播放量/标签/角标、封面比例和网格间距的公共 token。 +- 搜索框统一为高 40、圆角 8、placeholder 13;一级分类 16,选中 + 白字 w700,未选中 `#9E9E9E` w400;二级标签 13 并采用参考图选中/未选中 + 底色。 +- 三列首页网格与全部短剧网格统一左右边距 14、列间距 6、行间距 18; + 封面 5:6、圆角 6。 +- 首页、剧单横滑、追剧双列卡片统一标题 15/w500/1.3、播放量 11 白字、 + 标签 11/高 18/左右 6,封面到标题 8、标题到标签 6。 +- 剧单分区标题 17/w700,副标题 11 `#8E8E93`,更多 12 `#8E8E93`; + 分区间距保持 24。 +- 全部短剧筛选 chip 统一高 30、圆角 15、字号 13;我的页标题 17, + 追剧顶部页签 16。 + +### 门禁命令与原始输出 + +```text +$ flutter analyze +Analyzing reelshort-visual-type... +No issues found! (ran in 1.8s) +``` + +```text +$ flutter test +00:57 +648: All tests passed! +``` + +解读:首页搜索/页签/二级标签、三类卡片字体与标签、5:6 封面、网格间距、 +筛选 chip、我的标题和追剧双列 320 宽度适配均有测试覆盖。目标组件均低于 +500 行;`pubspec.lock` 未提交,未操作真机。 diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index 08aa77e..70f8447 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -79,6 +79,7 @@ abstract final class AppColors { /// 次级文字与列表 chevron。 static const textPrimary = Color(0xFFFFFFFF); static const textSecondary = Color(0xFF8E8E93); + static const textTertiary = Color(0xFF9E9E9E); /// 原生开屏与冷启动过渡底色。 static const splashInk = ink; diff --git a/lib/core/theme/app_dimens.dart b/lib/core/theme/app_dimens.dart index c752040..780df01 100644 --- a/lib/core/theme/app_dimens.dart +++ b/lib/core/theme/app_dimens.dart @@ -54,7 +54,9 @@ abstract final class AppDimens { static const double progressRadius = 2; static const double progressLineHeight = 3; - static const double searchHeight = 44; + static const double searchHeight = 40; + static const double searchRadius = 8; + static const double searchPlaceholderFont = 13; static const double navDot = 3; static const double glassBlur = 24; @@ -75,23 +77,51 @@ abstract final class AppDimens { static const double cardShadowY = 0; /// 首页探索网格。 - static const double homeGridGap = 8; - static const double homeCardRadius = 8; + static const double feedPageHorizontal = 14; + static const double homeGridGap = 6; + static const double homeGridRowGap = 18; + static const double homeCardRadius = 6; static const double homeCardPadding = 8; - static const double homeCardTitle = 13; - static const double homeCardMeta = 10; - static const double homeCardBadge = 9; - static const double homeTagFont = 10; + static const double cardTitleFont = 15; + static const double cardTitleLineHeight = 1.3; + static const double cardViewsFont = 11; + static const double cardViewsIcon = 11; + static const double cardTagFont = 11; + static const double cardTagHeight = 18; + static const double cardTagRadius = 9; + static const double cardTagHorizontalPadding = 6; + static const double cardBadgeFont = 10; + static const double cardBadgeRadius = 3; + static const double cardBadgeHorizontalPadding = 6; + static const double cardBadgeVerticalPadding = 2; + static const double cardCoverTitleGap = 8; + static const double cardTitleTagGap = 6; + static const double cardCoverAspectRatio = 5 / 6; static const double homeCardBodyExtent = 84; - static const double homeSectionFont = 14; + static const double homePrimaryTabFont = 16; + static const double homeSecondaryTagFont = 13; static const double homeSectionIndicator = 2; static const double homeIconSlot = 44; static const double homeHeaderIcon = 22; + /// 推荐分区。 + static const double recommendSectionTitle = 17; + static const double recommendSectionSubtitle = 11; + static const double recommendSectionMore = 12; + static const double recommendRowHeight = 252; + static const double recommendCardWidth = 122; + + /// 全部短剧筛选与追剧双列。 + static const double filterChipFont = 13; + static const double filterChipHeight = 30; + static const double filterChipRadius = 15; + static const double followingTabFont = 16; + static const double followingGridAspectRatio = 0.5; + /// 「我的」页中性视觉。 static const double mineCardRadius = 16; static const double mineRowHeight = 56; - static const double minePageTitle = 21; + static const double minePageTitle = 17; static const double mineSectionTitle = 15; static const double mineAvatar = 56; static const double mineUserName = 20; diff --git a/lib/pages/following/following_grid_card.dart b/lib/pages/following/following_grid_card.dart index 3bcb88d..d9175bb 100644 --- a/lib/pages/following/following_grid_card.dart +++ b/lib/pages/following/following_grid_card.dart @@ -14,6 +14,7 @@ class FollowingGridCard extends StatelessWidget { required this.title, required this.posterUrl, required this.viewCount, + required this.tags, required this.subtitle, required this.onTap, this.progress, @@ -25,6 +26,7 @@ class FollowingGridCard extends StatelessWidget { final String title; final String posterUrl; final int viewCount; + final List tags; final String subtitle; final VoidCallback onTap; final double? progress; @@ -40,7 +42,9 @@ class FollowingGridCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( + AspectRatio( + key: Key('following-cover-$dramaId'), + aspectRatio: AppDimens.cardCoverAspectRatio, child: Stack( clipBehavior: Clip.none, children: [ @@ -75,13 +79,14 @@ class FollowingGridCard extends StatelessWidget { const Icon( Icons.play_arrow_rounded, color: AppColors.textPrimary, - size: AppDimens.homeCardMeta, + size: AppDimens.cardViewsIcon, ), Text( formatInteractionCount(viewCount), + key: Key('following-views-$dramaId'), style: const TextStyle( color: AppColors.textPrimary, - fontSize: AppDimens.homeCardMeta, + fontSize: AppDimens.cardViewsFont, ), ), ], @@ -109,24 +114,30 @@ class FollowingGridCard extends StatelessWidget { ], ), ), - const SizedBox(height: AppDimens.spaceXs), + const SizedBox(height: AppDimens.cardCoverTitleGap), Text( title, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.textPrimary, - fontSize: AppDimens.homeCardTitle, - fontWeight: FontWeight.w600, + fontSize: AppDimens.cardTitleFont, + fontWeight: FontWeight.w500, + height: AppDimens.cardTitleLineHeight, ), ), + if (tags.isNotEmpty) ...[ + const SizedBox(height: AppDimens.cardTitleTagGap), + _tags(), + ], + const SizedBox(height: AppDimens.spaceXs), Text( subtitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: AppColors.textSecondary, - fontSize: AppDimens.homeCardMeta, + color: AppColors.textTertiary, + fontSize: AppDimens.cardViewsFont, ), ), if (progress != null) ...[ @@ -148,4 +159,41 @@ 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, + ), + ), + ), + ), + ), + ], + ); + } } diff --git a/lib/pages/following/following_page.dart b/lib/pages/following/following_page.dart index c201220..2f01832 100644 --- a/lib/pages/following/following_page.dart +++ b/lib/pages/following/following_page.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import '../../core/data/models/drama.dart'; import '../../core/data/models/watch_record.dart'; import '../../core/data/repositories/catalog_repository.dart'; import '../../core/data/repositories/watch_history_repository.dart'; @@ -22,6 +23,8 @@ import '../../l10n/app_localizations.dart'; import '../player/open_player.dart'; import 'following_grid_card.dart'; +final RegExp _tagSplitter = RegExp(r'[,/、·]'); + enum FollowingTab { favorites, history, reserve } class FollowingPage extends StatefulWidget { @@ -117,51 +120,56 @@ class _FollowingPageState extends State { return SizedBox( key: const Key('following-header'), height: 52, - child: Row( - children: [ - for (final tab in tabs) - Expanded( - child: InkWell( - key: Key('following-tab-${tab.$3}'), - onTap: () => setState(() { - _tab = tab.$1; - _editing = false; - }), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - tab.$2, - maxLines: 1, - style: TextStyle( - color: _tab == tab.$1 - ? AppColors.textPrimary - : AppColors.textSecondary, - fontSize: 16, - fontWeight: _tab == tab.$1 - ? FontWeight.w700 - : FontWeight.w500, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppDimens.feedPageHorizontal, + ), + child: Row( + children: [ + for (final tab in tabs) + Expanded( + child: InkWell( + key: Key('following-tab-${tab.$3}'), + onTap: () => setState(() { + _tab = tab.$1; + _editing = false; + }), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + tab.$2, + maxLines: 1, + style: TextStyle( + color: _tab == tab.$1 + ? AppColors.textPrimary + : AppColors.textTertiary, + fontSize: AppDimens.followingTabFont, + fontWeight: _tab == tab.$1 + ? FontWeight.w700 + : FontWeight.w400, + ), ), - ), - const SizedBox(height: 3), - Container( - width: _tab == tab.$1 ? 20 : 0, - height: 2, - color: AppColors.textPrimary, - ), - ], + const SizedBox(height: 3), + Container( + width: _tab == tab.$1 ? 20 : 0, + height: 2, + color: AppColors.textPrimary, + ), + ], + ), ), ), + IconButton( + key: const Key('following-edit'), + tooltip: _editing ? l10n.done : l10n.edit, + onPressed: () => setState(() => _editing = !_editing), + color: AppColors.textPrimary, + iconSize: 24, + icon: Icon(_editing ? Icons.check_rounded : Icons.edit_outlined), ), - IconButton( - key: const Key('following-edit'), - tooltip: _editing ? l10n.done : l10n.edit, - onPressed: () => setState(() => _editing = !_editing), - color: AppColors.textPrimary, - iconSize: 24, - icon: Icon(_editing ? Icons.check_rounded : Icons.edit_outlined), - ), - ], + ], + ), ), ); } @@ -233,6 +241,7 @@ class _FavoritesGrid extends StatelessWidget { title: drama.title, posterUrl: drama.posterUrl, viewCount: drama.viewCount, + tags: _dramaTags(drama), editing: editing, subtitle: l10n.favoriteEpisodeProgress( episode, @@ -304,6 +313,7 @@ class _HistoryGrid extends StatelessWidget { title: title, posterUrl: record.displayCover, viewCount: record.drama?.viewCount ?? 0, + tags: _dramaTags(record.drama), progress: record.progress.fraction, editing: editing, subtitle: l10n.watchedToEpisode(record.episodeNumber), @@ -322,6 +332,17 @@ class _HistoryGrid extends StatelessWidget { } } +List _dramaTags(Drama? drama) { + if (drama == null) return const []; + final seen = {}; + return [drama.genre, drama.category] + .expand((value) => value.split(_tagSplitter)) + .map((value) => value.trim()) + .where((value) => value.isNotEmpty && seen.add(value)) + .take(2) + .toList(growable: false); +} + Future _openHistory( BuildContext context, WatchRecordsController controller, @@ -416,17 +437,17 @@ class _PagedGrid extends StatelessWidget { else ...[ SliverPadding( padding: const EdgeInsets.fromLTRB( - AppDimens.pageHorizontal, + AppDimens.feedPageHorizontal, AppDimens.spaceMd, - AppDimens.pageHorizontal, + AppDimens.feedPageHorizontal, AppDimens.spaceSm, ), sliver: SliverGrid( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, - crossAxisSpacing: AppDimens.spaceMd, - mainAxisSpacing: AppDimens.spaceMd, - childAspectRatio: 0.56, + crossAxisSpacing: AppDimens.homeGridGap, + mainAxisSpacing: AppDimens.homeGridRowGap, + childAspectRatio: AppDimens.followingGridAspectRatio, ), delegate: SliverChildBuilderDelegate( itemBuilder, diff --git a/lib/pages/home/widgets/home_feed_card.dart b/lib/pages/home/widgets/home_feed_card.dart index 9b3870d..fd7c6be 100644 --- a/lib/pages/home/widgets/home_feed_card.dart +++ b/lib/pages/home/widgets/home_feed_card.dart @@ -10,7 +10,6 @@ import '../../../core/widgets/remote_image.dart'; import '../../../l10n/app_localizations.dart'; import 'home_drama_classification.dart'; -const double _coverAspectRatio = 5 / 6; final RegExp _tagSplitter = RegExp(r'[,/、·]'); enum HomeDramaBadge { hot, newRelease, anime } @@ -60,12 +59,12 @@ class HomeFeedCard extends StatelessWidget { overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.navSelected, - fontSize: AppDimens.homeCardTitle, - fontWeight: FontWeight.w600, - height: 1.25, + fontSize: AppDimens.cardTitleFont, + fontWeight: FontWeight.w500, + height: AppDimens.cardTitleLineHeight, ), ), - const SizedBox(height: AppDimens.spaceXs), + const SizedBox(height: AppDimens.cardTitleTagGap), _tags(), ], ), @@ -81,7 +80,7 @@ class HomeFeedCard extends StatelessWidget { Widget _cover(AppLocalizations l10n) { final badge = _badge(); return AspectRatio( - aspectRatio: _coverAspectRatio, + aspectRatio: AppDimens.cardCoverAspectRatio, child: Stack( fit: StackFit.expand, children: [ @@ -128,18 +127,18 @@ class HomeFeedCard extends StatelessWidget { return Container( key: Key('home-feed-badge-${badge.name}-${drama.id}'), padding: const EdgeInsets.symmetric( - horizontal: AppDimens.spaceSm, - vertical: 2, + horizontal: AppDimens.cardBadgeHorizontalPadding, + vertical: AppDimens.cardBadgeVerticalPadding, ), decoration: BoxDecoration( color: color, - borderRadius: BorderRadius.circular(AppDimens.chipRadius), + borderRadius: BorderRadius.circular(AppDimens.cardBadgeRadius), ), child: Text( label, style: const TextStyle( color: AppColors.navSelected, - fontSize: AppDimens.homeCardBadge, + fontSize: AppDimens.cardBadgeFont, fontWeight: FontWeight.w700, letterSpacing: 0, ), @@ -160,7 +159,7 @@ class HomeFeedCard extends StatelessWidget { children: [ const Icon( Icons.play_arrow_rounded, - size: AppDimens.homeCardMeta, + size: AppDimens.cardViewsIcon, color: AppColors.navSelected, ), const SizedBox(width: 1), @@ -168,7 +167,7 @@ class HomeFeedCard extends StatelessWidget { formatInteractionCount(drama.viewCount), style: const TextStyle( color: AppColors.navSelected, - fontSize: AppDimens.homeCardMeta, + fontSize: AppDimens.cardViewsFont, fontWeight: FontWeight.w600, letterSpacing: 0, ), @@ -191,18 +190,22 @@ class HomeFeedCard extends StatelessWidget { ), child: Container( key: Key('home-feed-tag-${drama.id}-$index'), - padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), + height: AppDimens.cardTagHeight, + alignment: Alignment.center, + padding: const EdgeInsets.symmetric( + horizontal: AppDimens.cardTagHorizontalPadding, + ), decoration: BoxDecoration( color: AppColors.contentSurface, - borderRadius: BorderRadius.circular(AppDimens.chipRadius), + borderRadius: BorderRadius.circular(AppDimens.cardTagRadius), ), child: Text( tag, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: AppColors.textSecondary, - fontSize: AppDimens.homeTagFont, + color: AppColors.textTertiary, + fontSize: AppDimens.cardTagFont, letterSpacing: 0, ), ), diff --git a/lib/pages/home/widgets/home_feed_grid.dart b/lib/pages/home/widgets/home_feed_grid.dart index cd76a56..64043be 100644 --- a/lib/pages/home/widgets/home_feed_grid.dart +++ b/lib/pages/home/widgets/home_feed_grid.dart @@ -73,7 +73,7 @@ class _HomeFeedGridState extends State { builder: (context, constraints) { final itemWidth = (constraints.maxWidth - - AppDimens.pageHorizontal * 2 - + AppDimens.feedPageHorizontal * 2 - AppDimens.homeGridGap * 2) / 3; final itemExtent = itemWidth * 6 / 5 + AppDimens.homeCardBodyExtent; @@ -117,7 +117,7 @@ class _HomeFeedGridState extends State { else SliverPadding( padding: const EdgeInsets.symmetric( - horizontal: AppDimens.pageHorizontal, + horizontal: AppDimens.feedPageHorizontal, ), sliver: SliverGrid( key: const Key('home-grid-cards'), @@ -125,7 +125,7 @@ class _HomeFeedGridState extends State { SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: AppDimens.homeGridGap, - mainAxisSpacing: AppDimens.homeGridGap, + mainAxisSpacing: AppDimens.homeGridRowGap, mainAxisExtent: itemExtent, ), delegate: SliverChildBuilderDelegate( diff --git a/lib/pages/home/widgets/home_grid_header.dart b/lib/pages/home/widgets/home_grid_header.dart index 830330c..a12a7f1 100644 --- a/lib/pages/home/widgets/home_grid_header.dart +++ b/lib/pages/home/widgets/home_grid_header.dart @@ -78,9 +78,9 @@ class HomeGridHeader extends StatelessWidget { Widget _searchRow(AppLocalizations l10n) { return Padding( padding: const EdgeInsets.fromLTRB( - AppDimens.pageHorizontal, + AppDimens.feedPageHorizontal, AppDimens.spaceSm, - AppDimens.pageHorizontal, + AppDimens.feedPageHorizontal, AppDimens.spaceSm, ), child: Row( @@ -88,7 +88,7 @@ class HomeGridHeader extends StatelessWidget { Expanded( child: InkWell( key: const Key('home-search-entry'), - borderRadius: BorderRadius.circular(AppDimens.inputRadius), + borderRadius: BorderRadius.circular(AppDimens.searchRadius), onTap: callbacks.onSearch, child: Container( height: AppDimens.searchHeight, @@ -97,7 +97,7 @@ class HomeGridHeader extends StatelessWidget { ), decoration: BoxDecoration( color: AppColors.contentSurface, - borderRadius: BorderRadius.circular(AppDimens.inputRadius), + borderRadius: BorderRadius.circular(AppDimens.searchRadius), ), child: Row( children: [ @@ -114,7 +114,7 @@ class HomeGridHeader extends StatelessWidget { overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.textSecondary, - fontSize: AppDimens.body, + fontSize: AppDimens.searchPlaceholderFont, ), ), ), @@ -191,7 +191,7 @@ class HomeGridHeader extends StatelessWidget { formatInteractionCount(badge), style: const TextStyle( color: AppColors.navSelected, - fontSize: AppDimens.homeCardBadge, + fontSize: AppDimens.cardBadgeFont, fontWeight: FontWeight.w700, ), ), @@ -206,7 +206,9 @@ class HomeGridHeader extends StatelessWidget { return SingleChildScrollView( key: const Key('home-primary-sections'), scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: AppDimens.pageHorizontal), + padding: const EdgeInsets.symmetric( + horizontal: AppDimens.feedPageHorizontal, + ), child: Row( children: [ _primaryItem( @@ -264,9 +266,9 @@ class HomeGridHeader extends StatelessWidget { style: TextStyle( color: selected ? AppColors.navSelected - : AppColors.textSecondary, - fontSize: AppDimens.homeSectionFont, - fontWeight: selected ? FontWeight.w700 : FontWeight.w500, + : AppColors.textTertiary, + fontSize: AppDimens.homePrimaryTabFont, + fontWeight: selected ? FontWeight.w700 : FontWeight.w400, letterSpacing: 0, ), ), @@ -288,9 +290,9 @@ class HomeGridHeader extends StatelessWidget { Widget _tagRow(AppLocalizations l10n) { return Padding( padding: const EdgeInsets.fromLTRB( - AppDimens.pageHorizontal, + AppDimens.feedPageHorizontal, AppDimens.spaceMd, - AppDimens.pageHorizontal, + AppDimens.feedPageHorizontal, AppDimens.spaceSm, ), child: Row( @@ -347,15 +349,15 @@ class HomeGridHeader extends StatelessWidget { vertical: AppDimens.spaceSm, ), decoration: BoxDecoration( - color: selected ? AppColors.contentChip : AppColors.contentSurface, + color: selected ? AppColors.contentSurface : AppColors.transparent, borderRadius: BorderRadius.circular(AppDimens.pillRadius), ), child: Text( tag.label, style: TextStyle( - color: selected ? AppColors.navSelected : AppColors.textSecondary, - fontSize: AppDimens.homeSectionFont - 2, - fontWeight: selected ? FontWeight.w600 : FontWeight.w500, + color: selected ? AppColors.navSelected : AppColors.textTertiary, + fontSize: AppDimens.homeSecondaryTagFont, + fontWeight: selected ? FontWeight.w700 : FontWeight.w400, letterSpacing: 0, ), ), diff --git a/lib/pages/recommend/recommend_page.dart b/lib/pages/recommend/recommend_page.dart index 464ce9d..341ea03 100644 --- a/lib/pages/recommend/recommend_page.dart +++ b/lib/pages/recommend/recommend_page.dart @@ -134,7 +134,7 @@ class _RecommendPageState extends State { height: 56, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: AppDimens.pageHorizontal, + horizontal: AppDimens.feedPageHorizontal, ), child: Row( children: [ @@ -189,9 +189,9 @@ class _RecommendPageState extends State { style: TextStyle( color: selected ? AppColors.textPrimary - : AppColors.textSecondary, - fontSize: AppDimens.listTitle, - fontWeight: selected ? FontWeight.w700 : FontWeight.w500, + : AppColors.textTertiary, + fontSize: AppDimens.homePrimaryTabFont, + fontWeight: selected ? FontWeight.w700 : FontWeight.w400, ), ), const SizedBox(height: 3), diff --git a/lib/pages/recommend/widgets/all_dramas_filter_grid.dart b/lib/pages/recommend/widgets/all_dramas_filter_grid.dart index 554dfdd..0a09e9a 100644 --- a/lib/pages/recommend/widgets/all_dramas_filter_grid.dart +++ b/lib/pages/recommend/widgets/all_dramas_filter_grid.dart @@ -47,7 +47,7 @@ class _AllDramasFilterGridState extends State { builder: (context, constraints) { final itemWidth = (constraints.maxWidth - - AppDimens.pageHorizontal * 2 - + AppDimens.feedPageHorizontal * 2 - AppDimens.homeGridGap * 2) / 3; final itemExtent = itemWidth * 6 / 5 + AppDimens.homeCardBodyExtent; @@ -77,14 +77,14 @@ class _AllDramasFilterGridState extends State { else SliverPadding( padding: const EdgeInsets.symmetric( - horizontal: AppDimens.pageHorizontal, + horizontal: AppDimens.feedPageHorizontal, ), sliver: SliverGrid( key: const Key('all-dramas-grid'), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: AppDimens.homeGridGap, - mainAxisSpacing: AppDimens.homeGridGap, + mainAxisSpacing: AppDimens.homeGridRowGap, mainAxisExtent: itemExtent, ), delegate: SliverChildBuilderDelegate( @@ -122,9 +122,9 @@ class _AllDramasFilterGridState extends State { final genres = _availableGenres(l10n); return Padding( padding: const EdgeInsets.fromLTRB( - AppDimens.pageHorizontal, + AppDimens.feedPageHorizontal, AppDimens.spaceSm, - AppDimens.pageHorizontal, + AppDimens.feedPageHorizontal, AppDimens.spaceMd, ), child: Column( @@ -207,21 +207,21 @@ class _AllDramasFilterGridState extends State { return InkWell( key: key, onTap: onTap, - borderRadius: BorderRadius.circular(AppDimens.pillRadius), + borderRadius: BorderRadius.circular(AppDimens.filterChipRadius), child: Container( - height: 32, + height: AppDimens.filterChipHeight, padding: const EdgeInsets.symmetric(horizontal: AppDimens.spaceMd), alignment: Alignment.center, decoration: BoxDecoration( - color: selected ? AppColors.contentChip : AppColors.contentSurface, - borderRadius: BorderRadius.circular(AppDimens.pillRadius), + color: selected ? AppColors.contentSurface : AppColors.transparent, + borderRadius: BorderRadius.circular(AppDimens.filterChipRadius), ), child: Text( label, style: TextStyle( - color: selected ? AppColors.textPrimary : AppColors.textSecondary, - fontSize: AppDimens.caption, - fontWeight: selected ? FontWeight.w700 : FontWeight.w500, + color: selected ? AppColors.textPrimary : AppColors.textTertiary, + fontSize: AppDimens.filterChipFont, + fontWeight: selected ? FontWeight.w700 : FontWeight.w400, ), ), ), diff --git a/lib/pages/recommend/widgets/recommend_section_card.dart b/lib/pages/recommend/widgets/recommend_section_card.dart index d2974a8..be9b282 100644 --- a/lib/pages/recommend/widgets/recommend_section_card.dart +++ b/lib/pages/recommend/widgets/recommend_section_card.dart @@ -10,6 +10,8 @@ import '../../../core/widgets/remote_image.dart'; import '../../../l10n/app_localizations.dart'; import '../../home/widgets/home_drama_classification.dart'; +final RegExp _tagSplitter = RegExp(r'[,/、·]'); + class RecommendSectionCard extends StatelessWidget { const RecommendSectionCard({ super.key, @@ -26,7 +28,7 @@ class RecommendSectionCard extends StatelessWidget { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return SizedBox( - width: 122, + width: AppDimens.recommendCardWidth, child: InkWell( key: Key('recommend-card-$sectionKey-${drama.id}'), borderRadius: BorderRadius.circular(AppDimens.homeCardRadius), @@ -38,7 +40,7 @@ class RecommendSectionCard extends StatelessWidget { ClipRRect( borderRadius: BorderRadius.circular(AppDimens.homeCardRadius), child: AspectRatio( - aspectRatio: 3 / 4, + aspectRatio: AppDimens.cardCoverAspectRatio, child: Stack( fit: StackFit.expand, children: [ @@ -55,20 +57,20 @@ class RecommendSectionCard extends StatelessWidget { child: Container( key: Key('recommend-hot-$sectionKey-${drama.id}'), padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 2, + horizontal: AppDimens.cardBadgeHorizontalPadding, + vertical: AppDimens.cardBadgeVerticalPadding, ), decoration: BoxDecoration( color: AppColors.badgeHot, borderRadius: BorderRadius.circular( - AppDimens.chipRadius, + AppDimens.cardBadgeRadius, ), ), child: Text( l10n.homeBadgeHot, style: const TextStyle( color: AppColors.textPrimary, - fontSize: AppDimens.homeCardBadge, + fontSize: AppDimens.cardBadgeFont, fontWeight: FontWeight.w700, ), ), @@ -78,19 +80,20 @@ class RecommendSectionCard extends StatelessWidget { ), ), ), - const SizedBox(height: AppDimens.spaceXs), + const SizedBox(height: AppDimens.cardCoverTitleGap), Row( + key: Key('recommend-views-$sectionKey-${drama.id}'), children: [ const Icon( Icons.play_arrow_rounded, - color: AppColors.textSecondary, - size: AppDimens.homeCardMeta, + color: AppColors.textPrimary, + size: AppDimens.cardViewsIcon, ), Text( formatInteractionCount(drama.viewCount), style: const TextStyle( - color: AppColors.textSecondary, - fontSize: AppDimens.homeCardMeta, + color: AppColors.textPrimary, + fontSize: AppDimens.cardViewsFont, ), ), ], @@ -103,14 +106,65 @@ class RecommendSectionCard extends StatelessWidget { overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.textPrimary, - fontSize: AppDimens.homeCardTitle, - fontWeight: FontWeight.w600, - height: 1.25, + fontSize: AppDimens.cardTitleFont, + fontWeight: FontWeight.w500, + height: AppDimens.cardTitleLineHeight, ), ), + if (_cardTags().isNotEmpty) ...[ + const SizedBox(height: AppDimens.cardTitleTagGap), + _tags(), + ], ], ), ), ); } + + 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, + ), + ), + ), + ), + ), + ], + ); + } + + List _cardTags() { + final seen = {}; + return [drama.genre, drama.category] + .expand((value) => value.split(_tagSplitter)) + .map((value) => value.trim()) + .where((value) => value.isNotEmpty && seen.add(value)) + .take(2) + .toList(growable: false); + } } diff --git a/lib/pages/recommend/widgets/recommend_sections_view.dart b/lib/pages/recommend/widgets/recommend_sections_view.dart index 704c9a1..ac1a0bb 100644 --- a/lib/pages/recommend/widgets/recommend_sections_view.dart +++ b/lib/pages/recommend/widgets/recommend_sections_view.dart @@ -132,7 +132,7 @@ class RecommendSectionsView extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.symmetric( - horizontal: AppDimens.pageHorizontal, + horizontal: AppDimens.feedPageHorizontal, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -145,7 +145,7 @@ class RecommendSectionsView extends StatelessWidget { title, style: const TextStyle( color: AppColors.textPrimary, - fontSize: AppDimens.sectionTitle, + fontSize: AppDimens.recommendSectionTitle, fontWeight: FontWeight.w700, ), ), @@ -154,7 +154,7 @@ class RecommendSectionsView extends StatelessWidget { subtitle, style: const TextStyle( color: AppColors.textSecondary, - fontSize: AppDimens.cardViews, + fontSize: AppDimens.recommendSectionSubtitle, ), ), ], @@ -173,7 +173,8 @@ class RecommendSectionsView extends StatelessWidget { Text( l10n.more, style: const TextStyle( - fontSize: AppDimens.caption, + color: AppColors.textSecondary, + fontSize: AppDimens.recommendSectionMore, fontWeight: FontWeight.w500, ), ), @@ -186,12 +187,12 @@ class RecommendSectionsView extends StatelessWidget { ), const SizedBox(height: AppDimens.spaceMd), SizedBox( - height: 218, + height: AppDimens.recommendRowHeight, child: ListView.separated( key: Key('recommend-row-$key'), scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric( - horizontal: AppDimens.pageHorizontal, + horizontal: AppDimens.feedPageHorizontal, ), itemCount: dramas.length, separatorBuilder: (_, _) => diff --git a/test/following/following_page_test.dart b/test/following/following_page_test.dart index a7bf640..0c9f16d 100644 --- a/test/following/following_page_test.dart +++ b/test/following/following_page_test.dart @@ -8,6 +8,8 @@ import 'package:fruitshorts/core/data/models/watch_progress.dart'; import 'package:fruitshorts/core/data/repositories/favorite_repository.dart'; import 'package:fruitshorts/core/data/repositories/playback_repository.dart'; import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart'; +import 'package:fruitshorts/core/theme/app_colors.dart'; +import 'package:fruitshorts/core/theme/app_dimens.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'; @@ -41,6 +43,50 @@ void main() { expect(find.byKey(const Key('following-tab-reserve')), findsOneWidget); expect(find.byKey(const Key('following-favorites-grid')), findsOneWidget); expect(find.byType(CustomScrollView), findsWidgets); + final tab = tester.widget( + find.descendant( + of: find.byKey(const Key('following-tab-favorites')), + matching: find.text('Following'), + ), + ); + expect(tab.style?.fontSize, AppDimens.followingTabFont); + expect(tab.style?.fontWeight, FontWeight.w700); + expect(tab.style?.color, AppColors.textPrimary); + final grid = tester.widget( + find.descendant( + of: find.byKey(const Key('following-favorites-grid')), + matching: find.byType(SliverGrid), + ), + ); + final delegate = + grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount; + expect(delegate.crossAxisSpacing, AppDimens.homeGridGap); + expect(delegate.mainAxisSpacing, AppDimens.homeGridRowGap); + expect(delegate.childAspectRatio, AppDimens.followingGridAspectRatio); + final card = find.byKey(const Key('following-card-the-signal-demo')); + final cover = tester.widget( + find.byKey(const Key('following-cover-the-signal-demo')), + ); + expect(cover.aspectRatio, AppDimens.cardCoverAspectRatio); + final title = tester.widget( + find.descendant(of: card, matching: find.text('The Signal')), + ); + expect(title.style?.fontSize, AppDimens.cardTitleFont); + expect(title.style?.fontWeight, FontWeight.w500); + expect(title.style?.height, AppDimens.cardTitleLineHeight); + final views = tester.widget( + find.byKey(const Key('following-views-the-signal-demo')), + ); + 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)), + ); + expect(tagText.style?.fontSize, AppDimens.cardTagFont); + expect(tagText.style?.color, AppColors.textTertiary); await tester.tap(find.byKey(const Key('following-edit'))); await tester.pumpAndSettle(); @@ -171,6 +217,33 @@ void main() { ); expect(find.byIcon(Icons.edit_outlined), findsOneWidget); }); + + testWidgets('should_fit_following_cards_on_compact_viewport', (tester) async { + tester.view.physicalSize = const Size(320, 640); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + _signIn(); + final catalog = FakeCatalogRepository(); + await tester.pumpWidget( + _app( + catalog: catalog, + favorites: InMemoryFavoriteRepository( + cards: catalog.dramas.take(1).toList(), + saved: {catalog.dramas.first.id}, + ), + ), + ); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key('tab-follow'))); + await tester.pumpAndSettle(); + + expect( + find.byKey(const Key('following-card-the-signal-demo')), + findsOneWidget, + ); + expect(tester.takeException(), isNull); + }); } void _signIn() { diff --git a/test/home/home_card_style_test.dart b/test/home/home_card_style_test.dart index 3744549..749d48f 100644 --- a/test/home/home_card_style_test.dart +++ b/test/home/home_card_style_test.dart @@ -40,9 +40,19 @@ void main() { final cover = find.byKey(const Key('home-feed-cover-anime-card')); final coverSize = tester.getSize(cover); expect(coverSize.height / coverSize.width, closeTo(6 / 5, 0.02)); + final badge = find.byKey(const Key('home-feed-badge-anime-anime-card')); + expect(badge, findsOneWidget); + final badgeContainer = tester.widget(badge); expect( - find.byKey(const Key('home-feed-badge-anime-anime-card')), - findsOneWidget, + (badgeContainer.decoration! as BoxDecoration).borderRadius, + BorderRadius.circular(AppDimens.cardBadgeRadius), + ); + expect( + tester + .widget(find.descendant(of: badge, matching: find.byType(Text))) + .style + ?.fontSize, + AppDimens.cardBadgeFont, ); expect(find.text('Anime'), findsOneWidget); expect(find.byKey(const Key('home-feed-views-anime-card')), findsOneWidget); @@ -51,16 +61,31 @@ void main() { find.descendant(of: views, matching: find.byType(Text)), ); expect(viewsText.data, startsWith('44.')); - expect( - tester - .widget(find.byKey(const Key('home-feed-title-anime-card'))) - .maxLines, - 2, + expect(viewsText.style?.fontSize, AppDimens.cardViewsFont); + expect(viewsText.style?.color, AppColors.navSelected); + final title = tester.widget( + find.byKey(const Key('home-feed-title-anime-card')), ); - expect(find.byKey(const Key('home-feed-tag-anime-card-0')), findsOneWidget); + expect(title.maxLines, 2); + 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 tagText = tester.widget( + find.descendant(of: firstTag, matching: find.byType(Text)), + ); + expect(tagText.style?.fontSize, AppDimens.cardTagFont); + expect(tagText.style?.color, AppColors.textTertiary); await tester.tap(card); expect(taps, 1); diff --git a/test/home/home_preview_layout_test.dart b/test/home/home_preview_layout_test.dart index dd58684..86eaee8 100644 --- a/test/home/home_preview_layout_test.dart +++ b/test/home/home_preview_layout_test.dart @@ -16,6 +16,8 @@ import 'package:fruitshorts/core/data/repositories/favorite_repository.dart'; import 'package:fruitshorts/core/data/repositories/playback_repository.dart'; import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart'; import 'package:fruitshorts/core/data/services/playback_thumbnail_service.dart'; +import 'package:fruitshorts/core/theme/app_colors.dart'; +import 'package:fruitshorts/core/theme/app_dimens.dart'; import 'package:fruitshorts/core/theme/app_theme.dart'; import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/home/home_feed_channel.dart'; @@ -82,8 +84,8 @@ void main() { final delegate = grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount; expect(delegate.crossAxisCount, 3); - expect(delegate.crossAxisSpacing, 8); - expect(delegate.mainAxisSpacing, 8); + expect(delegate.crossAxisSpacing, AppDimens.homeGridGap); + expect(delegate.mainAxisSpacing, AppDimens.homeGridRowGap); expect(find.byKey(const Key('home-section-featured')), findsOneWidget); expect(find.byKey(const Key('home-section-new')), findsOneWidget); expect(find.byKey(const Key('home-section-categories')), findsOneWidget); @@ -92,6 +94,67 @@ void main() { expect(find.byKey(const Key('home-section-category-社群')), findsOneWidget); expect(find.byKey(const Key('home-secondary-tags')), findsOneWidget); expect(find.byKey(const Key('home-grid-filter')), findsOneWidget); + final searchBox = find.descendant( + of: find.byKey(const Key('home-search-entry')), + matching: find.byType(Container), + ); + expect(tester.getSize(searchBox).height, AppDimens.searchHeight); + expect( + (tester.widget(searchBox).decoration! as BoxDecoration) + .borderRadius, + BorderRadius.circular(AppDimens.searchRadius), + ); + final placeholder = tester.widget( + find.text('Search dramas, actors, genres...'), + ); + expect(placeholder.style?.fontSize, AppDimens.searchPlaceholderFont); + expect(placeholder.style?.color, AppColors.textSecondary); + final selectedPrimary = tester.widget( + find.descendant( + of: find.byKey(const Key('home-section-featured')), + matching: find.text('Featured'), + ), + ); + 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')), + matching: find.text('Hot'), + ), + ); + expect(secondaryTag.style?.fontSize, AppDimens.homeSecondaryTagFont); + expect(secondaryTag.style?.fontWeight, FontWeight.w400); + expect(secondaryTag.style?.color, AppColors.textTertiary); + final unselectedTagChip = tester.widget( + find.descendant( + of: find.byKey(const Key('home-tag-hot')), + matching: find.byType(Container), + ), + ); + expect( + (unselectedTagChip.decoration! as BoxDecoration).color, + AppColors.transparent, + ); + 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')), + matching: find.text('Hot'), + ), + ); + expect(selectedTag.style?.color, AppColors.navSelected); + expect(selectedTag.style?.fontWeight, FontWeight.w700); expect(find.byType(PageView), findsNothing); expect(tester.takeException(), isNull); }); @@ -128,10 +191,7 @@ void main() { expect(find.byKey(const Key('recommend-page')), findsOneWidget); expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget); expect(find.byType(PlayerPage), findsOneWidget); - expect( - tester.widget(find.byType(PlayerPage)).homeMode, - isTrue, - ); + expect(tester.widget(find.byType(PlayerPage)).homeMode, isTrue); expect(find.byKey(const Key('recommend-section-hits')), findsNothing); await tester.tap(find.byKey(const Key('recommend-tab-playlist'))); diff --git a/test/mine/mine_preview_layout_test.dart b/test/mine/mine_preview_layout_test.dart index a8396cb..5f63599 100644 --- a/test/mine/mine_preview_layout_test.dart +++ b/test/mine/mine_preview_layout_test.dart @@ -41,6 +41,14 @@ void main() { expect(find.byKey(const Key('mine-vip')), findsOneWidget); expect(find.byKey(const Key('mine-vip-open')), findsOneWidget); expect(find.byKey(const Key('mine-menu')), findsOneWidget); + final title = tester.widget( + find.descendant( + of: find.byKey(const Key('mine-page-surface')), + matching: find.text('My'), + ), + ); + expect(title.style?.fontSize, AppDimens.minePageTitle); + expect(title.style?.fontWeight, FontWeight.w700); expect(find.byKey(const Key('mine-mini-player')), findsNothing); for (final oldShortcut in const [ Key('mine-watchlist'), diff --git a/test/recommend/recommend_sections_test.dart b/test/recommend/recommend_sections_test.dart index b5029ef..e488434 100644 --- a/test/recommend/recommend_sections_test.dart +++ b/test/recommend/recommend_sections_test.dart @@ -6,6 +6,8 @@ import 'package:fruitshorts/app.dart'; import 'package:fruitshorts/core/data/repositories/favorite_repository.dart'; import 'package:fruitshorts/core/data/repositories/playback_repository.dart'; import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart'; +import 'package:fruitshorts/core/theme/app_colors.dart'; +import 'package:fruitshorts/core/theme/app_dimens.dart'; import 'package:fruitshorts/core/widgets/app_bottom_nav.dart'; import 'package:fruitshorts/pages/player/player_view.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; @@ -66,6 +68,53 @@ void main() { expect(find.text('New This Week'), findsOneWidget); expect(find.text('Costume Drama Awards'), findsOneWidget); expect(find.byType(AppBottomNav), findsOneWidget); + final sectionTitle = tester.widget(find.text("This Week's Hits")); + expect(sectionTitle.style?.fontSize, AppDimens.recommendSectionTitle); + expect(sectionTitle.style?.fontWeight, FontWeight.w700); + final sectionSubtitle = tester.widget( + find.text('Binge the hottest stories of the week!'), + ); + expect(sectionSubtitle.style?.fontSize, AppDimens.recommendSectionSubtitle); + expect(sectionSubtitle.style?.color, AppColors.textSecondary); + final moreText = tester.widget( + find.descendant( + of: find.byKey(const Key('recommend-more-hits')), + matching: find.text('More'), + ), + ); + expect(moreText.style?.fontSize, AppDimens.recommendSectionMore); + expect(moreText.style?.color, AppColors.textSecondary); + final card = find.byKey(const Key('recommend-card-hits-the-signal-demo')); + final cover = tester.widget( + find.descendant(of: card, matching: find.byType(AspectRatio)), + ); + expect(cover.aspectRatio, AppDimens.cardCoverAspectRatio); + final cardTitle = tester.widget( + find.byKey(const Key('recommend-title-hits-the-signal-demo')), + ); + expect(cardTitle.style?.fontSize, AppDimens.cardTitleFont); + expect(cardTitle.style?.fontWeight, FontWeight.w500); + expect(cardTitle.style?.height, AppDimens.cardTitleLineHeight); + final views = tester.widget( + find.descendant( + of: find.byKey(const Key('recommend-views-hits-the-signal-demo')), + matching: find.byType(Text), + ), + ); + 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), + ); }); testWidgets('should_switch_inner_tabs_without_losing_grid_filter_state', ( @@ -88,6 +137,21 @@ void main() { await tester.tap(find.byKey(const Key('all-sort-new'))); await tester.pump(); expect(_textStyle(tester, 'New').fontWeight, FontWeight.w700); + expect(_textStyle(tester, 'New').fontSize, AppDimens.filterChipFont); + expect( + tester.getSize(find.byKey(const Key('all-sort-new'))).height, + AppDimens.filterChipHeight, + ); + final chipContainer = tester.widget( + find.descendant( + of: find.byKey(const Key('all-sort-new')), + matching: find.byType(Container), + ), + ); + expect( + (chipContainer.decoration! as BoxDecoration).borderRadius, + BorderRadius.circular(AppDimens.filterChipRadius), + ); await tester.tap(find.byKey(const Key('recommend-tab-playlist'))); await tester.pumpAndSettle();