merge: 固定全部短剧筛选区并统一间距

This commit is contained in:
Zhou Weidong committed 2026-10-22 20:43:22 +08:00
commit 3289012f64
4 files changed
+144 -64

No files matched your search

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