diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index 7146751..f109384 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -1212,3 +1212,4 @@ $ flutter test - 四行筛选统一 28 高、8 行距、24 项目间距;选中 chip 改为 `#4A1E1E`、白字 13/w600、圆角 14。 - 最热/最新改为纯文字选中态,三横线筛选固定在右端;网格改为左右 12、列距 6、行距 12。 - 第三行筛选完整展示,卡片继续复用首页组件;`flutter analyze`:No issues found。 +- 筛选项字号提升到 15、圆角 15;筛选区移出滚动容器并固定吸顶,下拉刷新仅作用于网格。 diff --git a/lib/core/theme/app_dimens.dart b/lib/core/theme/app_dimens.dart index 2e6cb4a..f767148 100644 --- a/lib/core/theme/app_dimens.dart +++ b/lib/core/theme/app_dimens.dart @@ -137,9 +137,9 @@ abstract final class AppDimens { static const double recommendCardWidth = 122; /// 全部短剧筛选与追剧双列。 - static const double filterChipFont = 13; + static const double filterChipFont = 15; static const double filterChipHeight = 28; - static const double filterChipRadius = 14; + static const double filterChipRadius = 15; static const double filterChipHorizontalPadding = 12; static const double filterRowGap = 8; static const double filterItemGap = 24; diff --git a/lib/pages/recommend/widgets/all_dramas_filter_grid.dart b/lib/pages/recommend/widgets/all_dramas_filter_grid.dart index 102f05a..e1317f6 100644 --- a/lib/pages/recommend/widgets/all_dramas_filter_grid.dart +++ b/lib/pages/recommend/widgets/all_dramas_filter_grid.dart @@ -55,72 +55,84 @@ class _AllDramasFilterGridState extends State { final itemExtent = itemWidth / AppDimens.cardCoverAspectRatio + AppDimens.homeCardBodyExtent; - return NotificationListener( - onNotification: _onScroll, - child: AppPullToRefresh( - key: const Key('all-dramas-refresh'), - onRefresh: () async { - await widget.feedController.refreshCurrent(); - }, - child: CustomScrollView( - key: const Key('all-dramas-scroll'), - physics: AppPullToRefresh.physics, - slivers: [ - SliverToBoxAdapter(child: _filters(l10n)), - if (dramas.isEmpty) - SliverFillRemaining( - hasScrollBody: false, - child: EmptyState( - key: const Key('all-dramas-empty'), - icon: Icons.movie_filter_outlined, - title: l10n.homeGridFilterEmpty, - actionLabel: l10n.homeGridClearFilter, - onAction: () => setState(() { - _region = 'all'; - _audio = 'all'; - _genre = null; - }), - ), - ) - else - SliverPadding( - padding: const EdgeInsets.symmetric( - horizontal: AppDimens.allDramasHorizontal, - ), - sliver: SliverGrid( - key: const Key('all-dramas-grid'), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: AppDimens.allDramasGridGap, - mainAxisSpacing: AppDimens.allDramasGridRowGap, - mainAxisExtent: itemExtent, - ), - delegate: SliverChildBuilderDelegate( - (context, index) => HomeFeedCard( - drama: dramas[index], - onTap: () => widget.onOpenDrama(dramas[index]), + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _filters(l10n), + Expanded( + child: NotificationListener( + onNotification: _onScroll, + child: AppPullToRefresh( + key: const Key('all-dramas-refresh'), + onRefresh: () async { + await widget.feedController.refreshCurrent(); + }, + child: CustomScrollView( + key: const Key('all-dramas-scroll'), + physics: AppPullToRefresh.physics, + slivers: [ + if (dramas.isEmpty) + SliverFillRemaining( + hasScrollBody: false, + child: EmptyState( + key: const Key('all-dramas-empty'), + icon: Icons.movie_filter_outlined, + title: l10n.homeGridFilterEmpty, + actionLabel: l10n.homeGridClearFilter, + onAction: () => setState(() { + _region = 'all'; + _audio = 'all'; + _genre = null; + }), + ), + ) + else + SliverPadding( + padding: const EdgeInsets.symmetric( + horizontal: AppDimens.allDramasHorizontal, + ), + sliver: SliverGrid( + key: const Key('all-dramas-grid'), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: AppDimens.allDramasGridGap, + mainAxisSpacing: + AppDimens.allDramasGridRowGap, + mainAxisExtent: itemExtent, + ), + delegate: SliverChildBuilderDelegate( + (context, index) => HomeFeedCard( + drama: dramas[index], + onTap: () => widget.onOpenDrama(dramas[index]), + ), + childCount: dramas.length, + ), + ), + ), + 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, + ), ), - childCount: dramas.length, ), - ), - ), - 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, - ), + ], ), ), - ], + ), ), - ), + ], ); }, ); @@ -304,7 +316,11 @@ class _AllDramasFilterGridState extends State { onTap: onTap, child: SizedBox( height: AppDimens.filterChipHeight, - child: Center( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: AppDimens.filterChipHorizontalPadding, + ), + alignment: Alignment.center, child: Text( label, style: TextStyle( diff --git a/test/recommend/recommend_sections_test.dart b/test/recommend/recommend_sections_test.dart index 132301a..5851add 100644 --- a/test/recommend/recommend_sections_test.dart +++ b/test/recommend/recommend_sections_test.dart @@ -248,6 +248,55 @@ void main() { for (var row = 0; row < 4; row++) { expect(find.byKey(Key('all-filter-row-$row')), findsOneWidget); } + final filterStarts = [ + for (var row = 0; row < 4; row++) + tester.getRect(find.byKey(Key('all-filter-row-$row'))).left, + ]; + expect( + filterStarts, + everyElement(closeTo(AppDimens.feedPageHorizontal, 0.1)), + ); + for (var row = 0; row < 3; row++) { + expect( + tester.getSize(find.byKey(Key('all-filter-row-$row'))).height, + AppDimens.filterChipHeight + AppDimens.filterRowGap, + ); + } + expect( + tester.getSize(find.byKey(const Key('all-filter-row-3'))).height, + AppDimens.filterChipHeight, + ); + expect( + tester.getRect(find.byKey(const Key('all-dramas-scroll'))).top - + tester.getRect(find.byKey(const Key('all-dramas-filters'))).bottom, + closeTo(AppDimens.allDramasFilterToGridGap, 0.1), + ); + expect( + find.descendant( + of: find.byKey(const Key('all-dramas-refresh')), + matching: find.byKey(const Key('all-dramas-filters')), + ), + findsNothing, + ); + expect( + find.descendant( + of: find.byKey(const Key('all-dramas-scroll')), + matching: find.byKey(const Key('all-dramas-grid')), + ), + findsOneWidget, + ); + final fixedFilterTop = tester + .getRect(find.byKey(const Key('all-dramas-filters'))) + .top; + await tester.drag( + find.byKey(const Key('all-dramas-scroll')), + const Offset(0, -120), + ); + await tester.pump(); + expect( + tester.getRect(find.byKey(const Key('all-dramas-filters'))).top, + closeTo(fixedFilterTop, 0.1), + ); expect(find.byKey(const Key('all-filter-local')), findsOneWidget); expect(find.byKey(const Key('all-filter-subtitles')), findsOneWidget); for (final key in [ @@ -259,10 +308,24 @@ void main() { ]) { expect(find.byKey(Key(key)), findsOneWidget); } + final rowZero = find.byKey(const Key('all-filter-row-0')); + final rowZeroAll = find.descendant( + of: rowZero, + matching: find.byKey(const Key('all-filter-all')), + ); + final rowZeroLocal = find.descendant( + of: rowZero, + matching: find.byKey(const Key('all-filter-local')), + ); + expect( + tester.getTopLeft(rowZeroLocal).dx - tester.getTopRight(rowZeroAll).dx, + AppDimens.filterItemGap, + ); await tester.tap(find.byKey(const Key('all-filter-local'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('all-dramas-empty')), findsOneWidget); expect(_textStyle(tester, 'Local').fontWeight, FontWeight.w600); + expect(_textStyle(tester, 'Local').fontSize, AppDimens.filterChipFont); expect(_textStyle(tester, 'Local').color, AppColors.textPrimary); final selectedChip = tester.widget( find