From 972e542e5c661daf817a07113bc103e8953a30e5 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Thu, 22 Oct 2026 18:30:33 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(home):=20=E8=B0=83=E6=95=B4=E5=B0=81?= =?UTF-8?q?=E9=9D=A2=E6=92=AD=E6=94=BE=E9=87=8F=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/PROGRESS.md | 2 +- lib/core/theme/app_colors.dart | 2 + lib/core/theme/app_dimens.dart | 2 + lib/core/widgets/cover_play_count.dart | 60 +++++++++++++++ lib/pages/following/following_grid_card.dart | 72 +++++++----------- lib/pages/home/widgets/home_feed_card.dart | 75 +++++++------------ .../widgets/recommend_section_card.dart | 72 +++++++----------- test/following/following_page_test.dart | 14 +++- test/home/home_card_style_test.dart | 12 +++ test/recommend/recommend_sections_test.dart | 15 +++- 10 files changed, 179 insertions(+), 147 deletions(-) create mode 100644 lib/core/widgets/cover_play_count.dart diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index 61d99e4..3fefd21 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -1115,5 +1115,5 @@ $ flutter test - 四类短剧卡片移除底板/描边,封面统一 8 圆角,网格行距 10、封底到标题 6;热剧/新剧/漫剧角标移至右上角异形丝带。 - 标题下方恢复左对齐题材文字,使用 `|` 分隔、灰色小字,与封面状态角标保持独立。 - 首页搜索栏到一级、一级到二级、二级到网格间距为 8/8/10;非精选 tab 直接以 10 衔接网格。 -- 播放量移至标题行最右,使用 spark 图标与 12/w400 纯文字、`w` 格式,整数无小数;四类列表共用。 +- 播放量回到封面右下角,使用 spark 图标与 12/w400 纯文字、`w` 格式,并加极淡底部渐变;四类列表共用。 - 全部短剧的地区、字幕、配音筛选 UI 已占位;以下接口待补:地区、字幕/配音筛选数据。 diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index c43328a..5a49010 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -159,6 +159,8 @@ abstract final class AppColors { /// 播放器控件胶囊与圆形底色。 static const playerOverlayPill = Color(0xB3000000); + static const cardBottomScrimStart = Color(0x00000000); + static const cardBottomScrimEnd = Color(0x40000000); /// 进度条未激活轨道。 static const inactiveTrack = Color(0x24FFFFFF); diff --git a/lib/core/theme/app_dimens.dart b/lib/core/theme/app_dimens.dart index 1bddc13..1ed7c4e 100644 --- a/lib/core/theme/app_dimens.dart +++ b/lib/core/theme/app_dimens.dart @@ -89,6 +89,8 @@ abstract final class AppDimens { static const double cardViewsIcon = 11; static const double cardPlayCountFont = 12; static const double cardPlayCountGap = 2; + static const double cardPlayCountBottom = 6; + static const double cardPlayCountScrimHeight = 32; static const double cardGenreRibbonHeight = 20; static const double cardGenreRibbonRadius = 10; static const double cardGenreRibbonLeftInset = 8; diff --git a/lib/core/widgets/cover_play_count.dart b/lib/core/widgets/cover_play_count.dart new file mode 100644 index 0000000..73205b1 --- /dev/null +++ b/lib/core/widgets/cover_play_count.dart @@ -0,0 +1,60 @@ +// Author: fruit.com.cn + +import 'package:flutter/material.dart'; + +import '../formatting/count_format.dart'; +import '../theme/app_colors.dart'; +import '../theme/app_dimens.dart'; + +class CoverPlayCount extends StatelessWidget { + const CoverPlayCount({super.key, required this.count}); + + final int count; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.auto_awesome_rounded, + color: AppColors.textPrimary, + size: AppDimens.cardViewsIcon, + ), + const SizedBox(width: AppDimens.cardPlayCountGap), + Text( + formatPlayCount(count), + style: const TextStyle( + color: AppColors.textPrimary, + fontSize: AppDimens.cardPlayCountFont, + fontWeight: FontWeight.w400, + ), + ), + ], + ); + } +} + +class CoverBottomScrim extends StatelessWidget { + const CoverBottomScrim({super.key}); + + @override + Widget build(BuildContext context) { + return const SizedBox( + height: AppDimens.cardPlayCountScrimHeight, + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.cardBottomScrimStart, + AppColors.cardBottomScrimEnd, + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/following/following_grid_card.dart b/lib/pages/following/following_grid_card.dart index a525d69..c5f43fd 100644 --- a/lib/pages/following/following_grid_card.dart +++ b/lib/pages/following/following_grid_card.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; -import '../../core/formatting/count_format.dart'; import '../../core/theme/app_colors.dart'; import '../../core/theme/app_dimens.dart'; +import '../../core/widgets/cover_play_count.dart'; import '../../core/widgets/remote_image.dart'; class FollowingGridCard extends StatelessWidget { @@ -80,31 +80,35 @@ class FollowingGridCard extends StatelessWidget { ), ), ), + const Positioned( + left: 0, + right: 0, + bottom: 0, + child: CoverBottomScrim(), + ), + Positioned( + right: 0, + bottom: AppDimens.cardPlayCountBottom, + child: CoverPlayCount( + key: Key('following-views-$dramaId'), + count: viewCount, + ), + ), ], ), ), const SizedBox(height: AppDimens.cardCoverTitleGap), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.left, - style: const TextStyle( - color: AppColors.textPrimary, - fontSize: AppDimens.cardTitleFont, - fontWeight: FontWeight.w500, - height: AppDimens.cardTitleLineHeight, - ), - ), - ), - const SizedBox(width: AppDimens.spaceXs), - _views(), - ], + Text( + title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + style: const TextStyle( + color: AppColors.textPrimary, + fontSize: AppDimens.cardTitleFont, + fontWeight: FontWeight.w500, + height: AppDimens.cardTitleLineHeight, + ), ), if (tags.isNotEmpty) ...[ const SizedBox(height: AppDimens.cardTitleTagGap), @@ -140,30 +144,6 @@ class FollowingGridCard extends StatelessWidget { ); } - Widget _views() { - return Row( - key: Key('following-views-$dramaId'), - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.auto_awesome_rounded, - color: AppColors.textPrimary, - size: AppDimens.cardViewsIcon, - ), - const SizedBox(width: AppDimens.cardPlayCountGap), - Text( - formatPlayCount(viewCount), - style: const TextStyle( - color: AppColors.textPrimary, - fontSize: AppDimens.cardPlayCountFont, - fontWeight: FontWeight.w400, - ), - ), - ], - ); - } - Widget _tags() { final visible = tags.take(3).toList(growable: false); return Text( diff --git a/lib/pages/home/widgets/home_feed_card.dart b/lib/pages/home/widgets/home_feed_card.dart index 7b7d9b3..6a4c58f 100644 --- a/lib/pages/home/widgets/home_feed_card.dart +++ b/lib/pages/home/widgets/home_feed_card.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import '../../../core/data/models/drama.dart'; -import '../../../core/formatting/count_format.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; +import '../../../core/widgets/cover_play_count.dart'; import '../../../core/widgets/drama_cover_badge_ribbon.dart'; import '../../../core/widgets/remote_image.dart'; import '../../../l10n/app_localizations.dart'; @@ -50,28 +50,18 @@ class HomeFeedCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: 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(width: AppDimens.spaceXs), - _views(), - ], + 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(), @@ -112,6 +102,20 @@ class HomeFeedCard extends StatelessWidget { keyPrefix: '${drama.id}-${badge.name}', ), ), + const Positioned( + left: 0, + right: 0, + bottom: 0, + child: CoverBottomScrim(), + ), + Positioned( + right: 0, + bottom: AppDimens.cardPlayCountBottom, + child: CoverPlayCount( + key: Key('home-feed-views-${drama.id}'), + count: drama.viewCount, + ), + ), ], ), ), @@ -145,31 +149,6 @@ class HomeFeedCard extends StatelessWidget { }; } - Widget _views() { - return Row( - key: Key('home-feed-views-${drama.id}'), - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.auto_awesome_rounded, - size: AppDimens.cardViewsIcon, - color: AppColors.navSelected, - ), - const SizedBox(width: AppDimens.cardPlayCountGap), - Text( - formatPlayCount(drama.viewCount), - style: const TextStyle( - color: AppColors.navSelected, - fontSize: AppDimens.cardPlayCountFont, - fontWeight: FontWeight.w400, - letterSpacing: 0, - ), - ), - ], - ); - } - Widget _tags() { final tags = _cardTags(); if (tags.isEmpty) return const SizedBox.shrink(); diff --git a/lib/pages/recommend/widgets/recommend_section_card.dart b/lib/pages/recommend/widgets/recommend_section_card.dart index 58d1187..c10894c 100644 --- a/lib/pages/recommend/widgets/recommend_section_card.dart +++ b/lib/pages/recommend/widgets/recommend_section_card.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import '../../../core/data/models/drama.dart'; -import '../../../core/formatting/count_format.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; +import '../../../core/widgets/cover_play_count.dart'; import '../../../core/widgets/drama_cover_badge_ribbon.dart'; import '../../../core/widgets/remote_image.dart'; import '../../../l10n/app_localizations.dart'; @@ -62,32 +62,36 @@ class RecommendSectionCard extends StatelessWidget { keyPrefix: '$sectionKey-${drama.id}-hot', ), ), + const Positioned( + left: 0, + right: 0, + bottom: 0, + child: CoverBottomScrim(), + ), + Positioned( + right: 0, + bottom: AppDimens.cardPlayCountBottom, + child: CoverPlayCount( + key: Key('recommend-views-$sectionKey-${drama.id}'), + count: drama.viewCount, + ), + ), ], ), ), ), const SizedBox(height: AppDimens.cardCoverTitleGap), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - drama.title, - key: Key('recommend-title-$sectionKey-${drama.id}'), - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: AppColors.textPrimary, - fontSize: AppDimens.cardTitleFont, - fontWeight: FontWeight.w500, - height: AppDimens.cardTitleLineHeight, - ), - ), - ), - const SizedBox(width: AppDimens.spaceXs), - _views(), - ], + Text( + drama.title, + key: Key('recommend-title-$sectionKey-${drama.id}'), + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: AppColors.textPrimary, + fontSize: AppDimens.cardTitleFont, + fontWeight: FontWeight.w500, + height: AppDimens.cardTitleLineHeight, + ), ), const SizedBox(height: AppDimens.cardTitleTagGap), _tags(), @@ -97,30 +101,6 @@ class RecommendSectionCard extends StatelessWidget { ); } - Widget _views() { - return Row( - key: Key('recommend-views-$sectionKey-${drama.id}'), - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.auto_awesome_rounded, - color: AppColors.textPrimary, - size: AppDimens.cardViewsIcon, - ), - const SizedBox(width: AppDimens.cardPlayCountGap), - Text( - formatPlayCount(drama.viewCount), - style: const TextStyle( - color: AppColors.textPrimary, - fontSize: AppDimens.cardPlayCountFont, - fontWeight: FontWeight.w400, - ), - ), - ], - ); - } - List _cardTags() { final seen = {}; return [drama.genre, drama.category] diff --git a/test/following/following_page_test.dart b/test/following/following_page_test.dart index bf298b8..1c0adc7 100644 --- a/test/following/following_page_test.dart +++ b/test/following/following_page_test.dart @@ -64,9 +64,10 @@ void main() { 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')), + final coverFinder = find.byKey( + const Key('following-cover-the-signal-demo'), ); + final cover = tester.widget(coverFinder); expect(cover.aspectRatio, AppDimens.cardCoverAspectRatio); final coverClip = tester.widget( find.descendant( @@ -89,6 +90,13 @@ void main() { ); expect(views.style?.fontSize, AppDimens.cardPlayCountFont); expect(views.style?.fontWeight, FontWeight.w400); + expect( + find.descendant( + of: coverFinder, + matching: find.byKey(const Key('following-views-the-signal-demo')), + ), + findsOneWidget, + ); expect(views.style?.color, AppColors.textPrimary); expect( find.descendant( @@ -103,7 +111,7 @@ void main() { 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(title).dx, tester.getTopLeft(coverFinder).dx); expect(tester.getTopLeft(tags).dx, tester.getTopLeft(cover).dx); await tester.tap(find.byKey(const Key('following-edit'))); diff --git a/test/home/home_card_style_test.dart b/test/home/home_card_style_test.dart index c09896d..4721472 100644 --- a/test/home/home_card_style_test.dart +++ b/test/home/home_card_style_test.dart @@ -79,6 +79,18 @@ void main() { expect(viewsText.style?.color, AppColors.navSelected); expect( find.descendant(of: cover, matching: find.byKey(views)), + findsOneWidget, + ); + expect( + tester.getTopRight(views).dx, + closeTo(tester.getTopRight(cover).dx, 1), + ); + expect( + tester.getBottomLeft(cover).dy - tester.getBottomLeft(views).dy, + AppDimens.cardPlayCountBottom, + ); + expect( + find.descendant(of: views, matching: find.byType(Container)), findsNothing, ); expect( diff --git a/test/recommend/recommend_sections_test.dart b/test/recommend/recommend_sections_test.dart index d80ef66..0aaa7e6 100644 --- a/test/recommend/recommend_sections_test.dart +++ b/test/recommend/recommend_sections_test.dart @@ -92,9 +92,11 @@ void main() { coverClip.borderRadius, BorderRadius.circular(AppDimens.cardCoverRadius), ); - final cover = tester.widget( - find.descendant(of: card, matching: find.byType(AspectRatio)), + final coverFinder = find.descendant( + of: card, + matching: find.byType(AspectRatio), ); + final cover = tester.widget(coverFinder); expect(cover.aspectRatio, AppDimens.cardCoverAspectRatio); final cardTitle = tester.widget( find.byKey(const Key('recommend-title-hits-the-signal-demo')), @@ -110,6 +112,13 @@ void main() { ); expect(views.style?.fontSize, AppDimens.cardPlayCountFont); expect(views.style?.fontWeight, FontWeight.w400); + expect( + find.descendant( + of: coverFinder, + matching: find.byKey(const Key('recommend-views-hits-the-signal-demo')), + ), + findsOneWidget, + ); expect(views.style?.color, AppColors.textPrimary); final viewIcon = find.descendant( of: find.byKey(const Key('recommend-views-hits-the-signal-demo')), @@ -132,7 +141,7 @@ void main() { 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(cardTitle).dx, tester.getTopLeft(coverFinder).dx); expect(tester.getTopLeft(tags).dx, tester.getTopLeft(cover).dx); }); From 8375d8b25581086973adb995e2929f4e71002780 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Thu, 22 Oct 2026 18:31:03 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(home):=20=E6=94=B6=E7=B4=A7=E5=B0=81?= =?UTF-8?q?=E9=9D=A2=E7=8A=B6=E6=80=81=E8=A7=92=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/PROGRESS.md | 2 +- lib/core/theme/app_dimens.dart | 10 ++++------ lib/core/widgets/drama_cover_badge_ribbon.dart | 7 ++----- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index 3fefd21..6f90aee 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -1112,7 +1112,7 @@ $ flutter test ## 2026-10-22 首页布局对齐修正 - 一级分类页内切换 6 个列表,未选 18/w400 `#B0B0B0`、选中 20/w700 白字,无下划线;二级 chips 仅精选显示。 -- 四类短剧卡片移除底板/描边,封面统一 8 圆角,网格行距 10、封底到标题 6;热剧/新剧/漫剧角标移至右上角异形丝带。 +- 四类卡片去底板/描边,封面 8 圆角、行距 10、封底标题间距 6;热剧/新剧/漫剧角标为右上角 16 高紧凑异形丝带。 - 标题下方恢复左对齐题材文字,使用 `|` 分隔、灰色小字,与封面状态角标保持独立。 - 首页搜索栏到一级、一级到二级、二级到网格间距为 8/8/10;非精选 tab 直接以 10 衔接网格。 - 播放量回到封面右下角,使用 spark 图标与 12/w400 纯文字、`w` 格式,并加极淡底部渐变;四类列表共用。 diff --git a/lib/core/theme/app_dimens.dart b/lib/core/theme/app_dimens.dart index 1ed7c4e..cec1b67 100644 --- a/lib/core/theme/app_dimens.dart +++ b/lib/core/theme/app_dimens.dart @@ -91,13 +91,11 @@ abstract final class AppDimens { static const double cardPlayCountGap = 2; static const double cardPlayCountBottom = 6; static const double cardPlayCountScrimHeight = 32; - static const double cardGenreRibbonHeight = 20; - static const double cardGenreRibbonRadius = 10; - static const double cardGenreRibbonLeftInset = 8; - static const double cardGenreRibbonMaxWidth = 86; + static const double cardGenreRibbonHeight = 16; + static const double cardGenreRibbonLeftInset = 4; + static const double coverBadgeRibbonHorizontalPadding = 4; static const double cardTagFont = 11; - static const double cardTagHorizontalPadding = 6; - static const double cardBadgeFont = 10; + static const double cardBadgeFont = 9; static const double cardCoverTitleGap = 6; static const double cardTitleTagGap = 6; static const double cardCoverAspectRatio = 5 / 6; diff --git a/lib/core/widgets/drama_cover_badge_ribbon.dart b/lib/core/widgets/drama_cover_badge_ribbon.dart index 95a5048..f69d09f 100644 --- a/lib/core/widgets/drama_cover_badge_ribbon.dart +++ b/lib/core/widgets/drama_cover_badge_ribbon.dart @@ -24,15 +24,12 @@ class DramaCoverBadgeRibbon extends StatelessWidget { key: Key('drama-cover-badge-ribbon-$keyPrefix'), clipper: const _SlantedRibbonClipper(), child: Container( - constraints: const BoxConstraints( - maxWidth: AppDimens.cardGenreRibbonMaxWidth, - ), height: AppDimens.cardGenreRibbonHeight, padding: const EdgeInsets.only( left: - AppDimens.cardTagHorizontalPadding + + AppDimens.coverBadgeRibbonHorizontalPadding + AppDimens.cardGenreRibbonLeftInset, - right: AppDimens.cardTagHorizontalPadding, + right: AppDimens.coverBadgeRibbonHorizontalPadding, ), alignment: Alignment.center, color: color,