diff --git a/lib/pages/home/widgets/home_drama_grid.dart b/lib/pages/home/widgets/home_drama_grid.dart index ce650a8..677d848 100644 --- a/lib/pages/home/widgets/home_drama_grid.dart +++ b/lib/pages/home/widgets/home_drama_grid.dart @@ -3,6 +3,7 @@ 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/theme/app_theme.dart'; @@ -12,9 +13,12 @@ import '../../../l10n/app_localizations.dart'; const int _columns = 2; const double _columnGap = 10; const double _cardGap = 10; -const double _coverAspectRatio = 3 / 4; -const double _addSize = 28; -const double _addIconSize = 18; +const double _coverAspectRatio = 5 / 6; +const double _cardPadding = 8; +const double _coverTitleGap = 6; +const double _lineGap = 2; +const double _viewsIconSize = 10; +const double _viewsFontSize = 9; /// 首页浏览态使用的双列剧目网格。 class HomeDramaGrid extends StatelessWidget { @@ -89,16 +93,16 @@ class HomeDramaCard extends StatelessWidget { key: Key('home-drama-card-${drama.id}'), onTap: onTap, child: Padding( - padding: const EdgeInsets.all(AppDimens.spaceSm), + padding: const EdgeInsets.all(_cardPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _cover(), - const SizedBox(height: AppDimens.spaceSm), + const SizedBox(height: _coverTitleGap), _title(), - const SizedBox(height: AppDimens.spaceXs), + const SizedBox(height: _lineGap), _genre(), - const SizedBox(height: AppDimens.spaceXs), + const SizedBox(height: _lineGap), _metadata(), ], ), @@ -124,6 +128,7 @@ class HomeDramaCard extends StatelessWidget { Positioned(top: 8, left: 8, child: _tagBadge()), if (_lockedCount > 0) Positioned(top: 8, right: 8, child: _lockedBadge()), + Positioned(left: 8, bottom: 8, child: _viewsBadge()), Positioned(right: 8, bottom: 8, child: _episodeBadge()), ], ), @@ -210,6 +215,37 @@ class HomeDramaCard extends StatelessWidget { ); } + /// 播放次数角标:深色半透明胶囊 + 白色小播放图标。 + Widget _viewsBadge() { + return Container( + key: Key('home-drama-views-${drama.id}'), + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: AppColors.playerOverlayPill, + borderRadius: BorderRadius.circular(AppDimens.pillRadius), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.play_arrow_rounded, + color: AppColors.onPrimary, + size: _viewsIconSize, + ), + const SizedBox(width: 2), + Text( + formatInteractionCount(drama.viewCount), + style: const TextStyle( + color: AppColors.onPrimary, + fontSize: _viewsFontSize, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ); + } + Widget _title() { return Text( drama.title, @@ -239,47 +275,13 @@ class HomeDramaCard extends StatelessWidget { } Widget _metadata() { - return Row( - children: [ - Expanded( - child: Wrap( - spacing: AppDimens.spaceSm, - runSpacing: AppDimens.spaceXs, - children: [ - Text( - l10n.episodeCount(drama.episodeCount), - key: Key('home-drama-episode-count-${drama.id}'), - style: const TextStyle( - color: AppColors.subtle, - fontSize: AppDimens.cardViews, - ), - ), - Text( - l10n.viewCountLabel(drama.viewCount), - key: Key('home-drama-views-${drama.id}'), - style: const TextStyle( - color: AppColors.subtle, - fontSize: AppDimens.cardViews, - ), - ), - ], - ), - ), - Container( - key: Key('home-drama-add-${drama.id}'), - width: _addSize, - height: _addSize, - decoration: const BoxDecoration( - color: AppColors.blue, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.add_rounded, - color: AppColors.onPrimary, - size: _addIconSize, - ), - ), - ], + return Text( + l10n.episodeCount(drama.episodeCount), + key: Key('home-drama-episode-count-${drama.id}'), + style: const TextStyle( + color: AppColors.subtle, + fontSize: AppDimens.cardViews, + ), ); } } diff --git a/test/home/home_card_style_test.dart b/test/home/home_card_style_test.dart index 1859799..04f0495 100644 --- a/test/home/home_card_style_test.dart +++ b/test/home/home_card_style_test.dart @@ -10,7 +10,7 @@ import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/home/widgets/home_drama_grid.dart'; void main() { - testWidgets('should_render_warm_orange_cover_badges_and_plus_action', ( + testWidgets('should_render_compact_cover_badges_without_plus_action', ( tester, ) async { await tester.pumpWidget( @@ -20,13 +20,15 @@ void main() { supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: AppLocalizations.localizationsDelegates, home: Scaffold( - body: Builder( - builder: (context) => SizedBox( - width: 180, - child: HomeDramaCard( - l10n: AppLocalizations.of(context)!, - drama: _drama, - onTap: () {}, + body: SingleChildScrollView( + child: Builder( + builder: (context) => SizedBox( + width: 180, + child: HomeDramaCard( + l10n: AppLocalizations.of(context)!, + drama: _drama, + onTap: () {}, + ), ), ), ), @@ -54,7 +56,29 @@ void main() { expect(find.text('Free'), findsOneWidget); expect(find.text('18 locked'), findsOneWidget); expect(find.text('All 24'), findsOneWidget); - expect(find.byKey(const Key('home-drama-add-style-drama')), findsOneWidget); + expect(find.byKey(const Key('home-drama-add-style-drama')), findsNothing); + + // 播放次数角标落在封面内,并按万位格式化。 + final views = find.byKey(const Key('home-drama-views-style-drama')); + expect( + find.descendant( + of: find.byKey(const Key('home-drama-cover-style-drama')), + matching: views, + ), + findsOneWidget, + ); + expect( + find.descendant(of: views, matching: find.text('1.2万')), + findsOneWidget, + ); + + // 压缩后卡片高度接近「卡宽 × 1.5」,明显矮于旧版 3:4 封面。 + final cardSize = tester.getSize(card); + expect(cardSize.height, lessThan(cardSize.width * 1.6)); + final coverSize = tester.getSize( + find.byKey(const Key('home-drama-cover-style-drama')), + ); + expect(coverSize.height / coverSize.width, closeTo(6 / 5, 0.02)); }); } @@ -66,4 +90,5 @@ const _drama = Drama( genre: 'Romance', totalEpisodes: 24, freeEpisodes: 6, + viewCount: 12400, ); diff --git a/test/home/k28_a_navigation_test.dart b/test/home/k28_a_navigation_test.dart index 57820c3..8429c79 100644 --- a/test/home/k28_a_navigation_test.dart +++ b/test/home/k28_a_navigation_test.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/app.dart'; +import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/core/data/models/home_feed.dart'; import 'package:fruitshorts/core/data/models/watch_progress.dart'; import 'package:fruitshorts/core/data/repositories/favorite_repository.dart'; @@ -104,7 +105,23 @@ void main() { }); testWidgets('should_pin_home_channels_when_scrolling', (tester) async { - await _pumpApp(tester); + // 多放几部剧,保证内容高度足够滚过吸顶阈值。 + await _pumpApp( + tester, + withBanner: false, + dramas: [ + for (var index = 0; index < 10; index++) + Drama( + id: 'scroll-$index', + title: 'Scroll Drama $index', + synopsis: 'synopsis', + posterUrl: '/files/scroll-$index.png', + genre: 'Drama', + totalEpisodes: 10, + freeEpisodes: 10, + ), + ], + ); final before = tester.getRect(find.byKey(const Key('home-channel-hot'))); expect(find.byKey(const Key('home-channels-pinned')), findsNothing); @@ -278,12 +295,14 @@ Future _pumpApp( WatchHistoryRepository? history, Size size = const Size(390, 844), bool withBanner = true, + List? dramas, }) async { tester.view.physicalSize = size; tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final catalog = FakeCatalogRepository( + dramas: dramas, banners: withBanner ? const [ FeedBanner(