fix(home): 优化短剧列表下拉刷新体验

This commit is contained in:
Zhou Weidong committed 2026-10-22 19:11:30 +08:00
1 parent f69a27bb80
commit 7945f3ffed
8 files changed
+247 -201

No files matched your search

@@ -44,7 +44,6 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final dramas = _filtered();
return LayoutBuilder(
builder: (context, constraints) {
final itemWidth =
@@ -66,55 +65,69 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
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.feedPageHorizontal,
),
sliver: SliverGrid(
key: const Key('all-dramas-grid'),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: AppDimens.homeGridGap,
mainAxisSpacing: AppDimens.homeGridRowGap,
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,
child: ListenableBuilder(
listenable: widget.feedController,
builder: (context, _) => _filters(l10n),
),
),
ListenableBuilder(
listenable: widget.feedController,
builder: (context, _) {
final dramas = _filtered();
if (dramas.isEmpty) {
return 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;
}),
),
);
}
return SliverPadding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.feedPageHorizontal,
),
sliver: SliverGrid(
key: const Key('all-dramas-grid'),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: AppDimens.homeGridGap,
mainAxisSpacing: AppDimens.homeGridRowGap,
mainAxisExtent: itemExtent,
),
delegate: SliverChildBuilderDelegate(
(context, index) => HomeFeedCard(
drama: dramas[index],
onTap: () => widget.onOpenDrama(dramas[index]),
),
childCount: dramas.length,
),
),
);
},
),
ListenableBuilder(
listenable: widget.feedController,
builder: (context, _) => SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(bottom: AppDimens.spaceXl),
child: PagedListFooter(
loading: widget.feedController.loadingMore,
hasMore: widget.feedController.hasMore,
hasItems: _filtered().isNotEmpty,
errorMessage: widget.feedController.lastError == null
? null
: l10n.catalogError,
onRetry: widget.feedController.loadMore,
),
),
),
),
@@ -33,60 +33,68 @@ class RecommendSectionsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final source = feedController.dramas;
final hits = [...source]
..sort((a, b) => b.viewCount.compareTo(a.viewCount));
final newest = [...source]
..sort((a, b) {
final left = publishedAtForDrama(a);
final right = publishedAtForDrama(b);
if (left == null && right == null) return b.id.compareTo(a.id);
if (left == null) return 1;
if (right == null) return -1;
return right.compareTo(left);
});
return AppPullToRefresh(
key: const Key('recommend-sections-refresh'),
onRefresh: () async {
await Future.wait([feedController.refreshCurrent(), categories.load()]);
return ListenableBuilder(
listenable: Listenable.merge([feedController, categories]),
builder: (context, _) {
final l10n = AppLocalizations.of(context)!;
final source = feedController.dramas;
final hits = [...source]
..sort((a, b) => b.viewCount.compareTo(a.viewCount));
final newest = [...source]
..sort((a, b) {
final left = publishedAtForDrama(a);
final right = publishedAtForDrama(b);
if (left == null && right == null) return b.id.compareTo(a.id);
if (left == null) return 1;
if (right == null) return -1;
return right.compareTo(left);
});
return AppPullToRefresh(
key: const Key('recommend-sections-refresh'),
onRefresh: () async {
await Future.wait([
feedController.refreshCurrent(),
categories.load(),
]);
},
child: ListView(
key: const Key('recommend-sections-scroll'),
physics: AppPullToRefresh.physics,
padding: const EdgeInsets.only(bottom: AppDimens.spaceXl),
children: [
_section(
context,
l10n,
key: 'hits',
title: l10n.weeklyHitsTitle,
subtitle: l10n.weeklyHitsSubtitle,
dramas: hits.take(10).toList(),
onMore: () => onMore(RecommendSectionKind.hits),
),
const SizedBox(height: AppDimens.spaceXl),
_section(
context,
l10n,
key: 'new',
title: l10n.weeklyNewTitle,
subtitle: l10n.weeklyNewSubtitle,
dramas: newest.take(10).toList(),
onMore: () => onMore(RecommendSectionKind.newReleases),
),
const SizedBox(height: AppDimens.spaceXl),
_section(
context,
l10n,
key: 'costume',
title: l10n.costumeAwardsTitle,
subtitle: l10n.costumeAwardsSubtitle,
dramas: _costumeDramas(source),
onMore: () => onMore(RecommendSectionKind.costume),
),
],
),
);
},
child: ListView(
key: const Key('recommend-sections-scroll'),
physics: AppPullToRefresh.physics,
padding: const EdgeInsets.only(bottom: AppDimens.spaceXl),
children: [
_section(
context,
l10n,
key: 'hits',
title: l10n.weeklyHitsTitle,
subtitle: l10n.weeklyHitsSubtitle,
dramas: hits.take(10).toList(),
onMore: () => onMore(RecommendSectionKind.hits),
),
const SizedBox(height: AppDimens.spaceXl),
_section(
context,
l10n,
key: 'new',
title: l10n.weeklyNewTitle,
subtitle: l10n.weeklyNewSubtitle,
dramas: newest.take(10).toList(),
onMore: () => onMore(RecommendSectionKind.newReleases),
),
const SizedBox(height: AppDimens.spaceXl),
_section(
context,
l10n,
key: 'costume',
title: l10n.costumeAwardsTitle,
subtitle: l10n.costumeAwardsSubtitle,
dramas: _costumeDramas(source),
onMore: () => onMore(RecommendSectionKind.costume),
),
],
),
);
}