merge: 固定全部短剧筛选区并统一间距
This commit is contained in:
commit
3289012f64
4 files changed
+144
-64
No files matched your search
@@ -1212,3 +1212,4 @@ $ flutter test
|
||||
- 四行筛选统一 28 高、8 行距、24 项目间距;选中 chip 改为 `#4A1E1E`、白字 13/w600、圆角 14。
|
||||
- 最热/最新改为纯文字选中态,三横线筛选固定在右端;网格改为左右 12、列距 6、行距 12。
|
||||
- 第三行筛选完整展示,卡片继续复用首页组件;`flutter analyze`:No issues found。
|
||||
- 筛选项字号提升到 15、圆角 15;筛选区移出滚动容器并固定吸顶,下拉刷新仅作用于网格。
|
||||
@@ -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;
|
||||
|
||||
@@ -55,72 +55,84 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
|
||||
final itemExtent =
|
||||
itemWidth / AppDimens.cardCoverAspectRatio +
|
||||
AppDimens.homeCardBodyExtent;
|
||||
return NotificationListener<ScrollNotification>(
|
||||
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<ScrollNotification>(
|
||||
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<AllDramasFilterGrid> {
|
||||
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(
|
||||
|
||||
@@ -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<Container>(
|
||||
find
|
||||
|
||||
Reference in new issue
Block a user