diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index 8ae87b4..8bbcd43 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -1218,3 +1218,5 @@ $ flutter test - 第 4 行复用选中胶囊样式,右侧改为 44×44 圆形深灰按钮和 22 三横线图标;`flutter analyze` 无问题。 - 筛选行距收紧到 6、项距 10,chip 左右 padding 12;四行均可横向回弹滑动。 - 第 4 行仅最热/最新在滚动区内,44 圆形筛选按钮固定在滚动区外;`flutter analyze` 无问题。 +- 四行统一复用 `_filterRow` 构建,筛选区仅保留一层左右 12 padding。 +- 新增量化测试断言四行首项 x 均为 12、相邻 chip 间距均为 10;`flutter analyze` 无问题。 diff --git a/lib/pages/recommend/widgets/all_dramas_filter_grid.dart b/lib/pages/recommend/widgets/all_dramas_filter_grid.dart index 75086a8..5ddcda2 100644 --- a/lib/pages/recommend/widgets/all_dramas_filter_grid.dart +++ b/lib/pages/recommend/widgets/all_dramas_filter_grid.dart @@ -142,9 +142,9 @@ class _AllDramasFilterGridState extends State { final genres = _availableGenres(l10n); return Padding( padding: const EdgeInsets.fromLTRB( - AppDimens.feedPageHorizontal, + AppDimens.allDramasHorizontal, 0, - AppDimens.allDramasFilterRight, + AppDimens.allDramasHorizontal, AppDimens.allDramasFilterToGridGap, ), child: Column( @@ -184,37 +184,45 @@ class _AllDramasFilterGridState extends State { required String selected, required ValueChanged onSelected, }) { - return Padding( - key: Key('all-filter-row-$row'), - padding: const EdgeInsets.only(bottom: AppDimens.filterRowGap), - child: SizedBox( - height: AppDimens.filterChipHeight, - child: SingleChildScrollView( - key: Key('all-filter-scroll-$row'), - scrollDirection: Axis.horizontal, - physics: const BouncingScrollPhysics(), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - for (var index = 0; index < options.length; index++) ...[ - _chip( - key: Key('all-filter-${options[index].id}'), - label: options[index].label, - selected: selected == options[index].id, - onTap: () => onSelected(options[index].id), - ), - if (index < options.length - 1) - const SizedBox(width: AppDimens.filterItemGap), - ], + return _filterRow( + row: row, + child: SingleChildScrollView( + key: Key('all-filter-scroll-$row'), + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + for (var index = 0; index < options.length; index++) ...[ + _chip( + key: Key('all-filter-${options[index].id}'), + label: options[index].label, + selected: selected == options[index].id, + onTap: () => onSelected(options[index].id), + ), + if (index < options.length - 1) + const SizedBox(width: AppDimens.filterItemGap), ], - ), + ], ), ), ); } + Widget _filterRow({ + required int row, + required Widget child, + double height = AppDimens.filterChipHeight, + }) { + return Padding( + key: Key('all-filter-row-$row'), + padding: EdgeInsets.only(bottom: row < 3 ? AppDimens.filterRowGap : 0), + child: SizedBox(height: height, child: child), + ); + } + Widget _genreRow( int row, List<({String id, String label})> options, @@ -270,8 +278,8 @@ class _AllDramasFilterGridState extends State { } Widget _sortRow(AppLocalizations l10n) { - return SizedBox( - key: const Key('all-filter-row-3'), + return _filterRow( + row: 3, height: AppDimens.allDramasFilterButton, child: Row( mainAxisAlignment: MainAxisAlignment.start, diff --git a/test/recommend/all_dramas_filter_alignment_test.dart b/test/recommend/all_dramas_filter_alignment_test.dart new file mode 100644 index 0000000..4f35d15 --- /dev/null +++ b/test/recommend/all_dramas_filter_alignment_test.dart @@ -0,0 +1,110 @@ +// Author: fruit.com.cn + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fruitshorts/core/data/services/home_feed_controller.dart'; +import 'package:fruitshorts/core/theme/app_dimens.dart'; +import 'package:fruitshorts/l10n/app_localizations.dart'; +import 'package:fruitshorts/pages/recommend/widgets/all_dramas_filter_grid.dart'; + +import '../support/fake_catalog.dart'; + +void main() { + testWidgets('all filter rows start at the same left edge', (tester) async { + final catalog = FakeCatalogRepository(); + final controller = HomeFeedController(catalog) + ..seedRecommend(catalog.dramas); + addTearDown(controller.dispose); + + await tester.pumpWidget( + MaterialApp( + locale: const Locale('en'), + supportedLocales: AppLocalizations.supportedLocales, + localizationsDelegates: AppLocalizations.localizationsDelegates, + home: Scaffold( + body: AllDramasFilterGrid( + feedController: controller, + onOpenDrama: (_) {}, + onOpenFilter: () {}, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + expect(AppDimens.allDramasHorizontal, 12); + final firstChips = [ + find.descendant( + of: find.byKey(const Key('all-filter-row-0')), + matching: find.byKey(const Key('all-filter-all')), + ), + find.descendant( + of: find.byKey(const Key('all-filter-row-1')), + matching: find.byKey(const Key('all-filter-all')), + ), + find.descendant( + of: find.byKey(const Key('all-filter-row-2')), + matching: find.byKey(const Key('all-filter-all')), + ), + find.byKey(const Key('all-sort-hot')), + ]; + final starts = firstChips + .map((finder) => tester.getTopLeft(finder).dx) + .toList(growable: false); + + for (final start in starts) { + expect(start, closeTo(12, 0.5)); + expect(start, closeTo(starts.first, 0.5)); + } + + final rowZeroChips = [ + find.byKey(const Key('all-filter-all')).first, + find.byKey(const Key('all-filter-local')), + find.byKey(const Key('all-filter-asia')), + ]; + final rowZeroRects = rowZeroChips.map(tester.getRect).toList(); + final rowZeroGaps = [ + for (var index = 1; index < rowZeroRects.length; index++) + rowZeroRects[index].left - rowZeroRects[index - 1].right, + ]; + expect(rowZeroGaps[0], closeTo(10, 0.5)); + expect(rowZeroGaps[1], closeTo(rowZeroGaps[0], 0.5)); + + final rowOneAll = find.descendant( + of: find.byKey(const Key('all-filter-row-1')), + matching: find.byKey(const Key('all-filter-all')), + ); + final rowOneSubtitles = find.byKey(const Key('all-filter-subtitles')); + final rowOneDubbed = find.byKey(const Key('all-filter-dubbed')); + final rowOneGap = + tester.getTopLeft(rowOneSubtitles).dx - + tester.getTopRight(rowOneAll).dx; + expect(rowOneGap, closeTo(10, 0.5)); + expect( + tester.getTopLeft(rowOneDubbed).dx - + tester.getTopRight(rowOneSubtitles).dx, + closeTo(rowOneGap, 0.5), + ); + + final rowTwoAll = find.descendant( + of: find.byKey(const Key('all-filter-row-2')), + matching: find.byKey(const Key('all-filter-all')), + ); + final rowTwoReversal = find.byKey(const Key('all-filter-reversal')); + final rowTwoUplifting = find.byKey(const Key('all-filter-uplifting')); + final rowTwoGap = + tester.getTopLeft(rowTwoReversal).dx - tester.getTopRight(rowTwoAll).dx; + expect(rowTwoGap, closeTo(10, 0.5)); + expect( + tester.getTopLeft(rowTwoUplifting).dx - + tester.getTopRight(rowTwoReversal).dx, + closeTo(rowTwoGap, 0.5), + ); + + expect( + tester.getTopLeft(find.byKey(const Key('all-sort-new'))).dx - + tester.getTopRight(find.byKey(const Key('all-sort-hot'))).dx, + closeTo(10, 0.5), + ); + }); +} diff --git a/test/recommend/recommend_sections_test.dart b/test/recommend/recommend_sections_test.dart index 6d981ce..2503f52 100644 --- a/test/recommend/recommend_sections_test.dart +++ b/test/recommend/recommend_sections_test.dart @@ -259,12 +259,15 @@ void main() { ]; expect( filterStarts, - everyElement(closeTo(AppDimens.feedPageHorizontal, 0.1)), + everyElement(closeTo(AppDimens.allDramasHorizontal, 0.1)), ); final pageRect = tester.getRect(find.byKey(const Key('recommend-page'))); for (var row = 0; row < 4; row++) { final rowRect = tester.getRect(find.byKey(Key('all-filter-row-$row'))); - expect(rowRect.left, closeTo(pageRect.left + 14, 0.1)); + expect( + rowRect.left, + closeTo(pageRect.left + AppDimens.allDramasHorizontal, 0.1), + ); expect(rowRect.right, closeTo(pageRect.right - 12, 0.1)); } final firstTexts = [ @@ -293,7 +296,7 @@ void main() { firstTextX.first, closeTo( pageRect.left + - AppDimens.feedPageHorizontal + + AppDimens.allDramasHorizontal + AppDimens.filterChipHorizontalPadding, 0.1, ), @@ -362,7 +365,7 @@ void main() { ); expect( tester.getTopLeft(rowZeroAll).dx, - closeTo(pageRect.left + AppDimens.feedPageHorizontal, 0.1), + closeTo(pageRect.left + AppDimens.allDramasHorizontal, 0.1), ); expect( tester.getTopLeft(rowZeroLocal).dx - tester.getTopRight(rowZeroAll).dx,