Files
ReelShort/lib/pages/following/following_paged_grid.dart
T

160 lines
5.5 KiB
Dart

// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/material.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/widgets/app_pull_to_refresh.dart';
import '../../core/widgets/empty_state.dart';
import '../../core/widgets/paged_list_footer.dart';
import '../../l10n/app_localizations.dart';
class FollowingPagedGrid extends StatelessWidget {
const FollowingPagedGrid({
super.key,
required this.scrollController,
required this.loading,
required this.loadingMore,
required this.hasMore,
required this.hasItems,
required this.itemCount,
required this.itemBuilder,
required this.empty,
required this.onRefresh,
required this.onLoadMore,
this.showInitialLoading = true,
this.singleColumn = false,
this.errorMessage,
this.loadMoreError,
});
static const double _loadMoreThreshold = 240;
final ScrollController scrollController;
final bool loading;
final bool loadingMore;
final bool hasMore;
final bool hasItems;
final int itemCount;
final IndexedWidgetBuilder itemBuilder;
final Widget empty;
final String? errorMessage;
final String? loadMoreError;
final bool showInitialLoading;
final bool singleColumn;
final Future<void> Function() onRefresh;
final Future<void> Function() onLoadMore;
@override
Widget build(BuildContext context) {
if (showInitialLoading && loading && !hasItems) {
return const Center(child: CircularProgressIndicator());
}
return LayoutBuilder(
builder: (context, constraints) {
final gridWidth =
constraints.maxWidth - AppDimens.feedPageHorizontal * 2;
final itemWidth = (gridWidth - AppDimens.followingGridGap * 2) / 3;
final itemExtent =
itemWidth / AppDimens.cardCoverAspectRatio +
AppDimens.followingCardTextExtent;
final gridDelegate = singleColumn
? const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
mainAxisSpacing: AppDimens.followingHistoryRowGap,
mainAxisExtent: AppDimens.followingHistoryCoverHeight,
)
: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: AppDimens.followingGridGap,
mainAxisSpacing: AppDimens.followingGridRowGap,
mainAxisExtent: itemExtent,
);
return NotificationListener<ScrollNotification>(
onNotification: _onScroll,
child: AppPullToRefresh(
onRefresh: onRefresh,
child: CustomScrollView(
key: ValueKey<String>('following-scroll-$runtimeType'),
controller: scrollController,
physics: AppPullToRefresh.physics,
slivers: [
if (itemCount == 0)
SliverFillRemaining(
hasScrollBody: false,
child: errorMessage == null
? empty
: EmptyState(
icon: Icons.cloud_off_rounded,
title: errorMessage!,
actionLabel: AppLocalizations.of(context)!.retry,
onAction: () => unawaited(onRefresh()),
),
)
else ...[
if (errorMessage != null)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(
AppDimens.feedPageHorizontal,
AppDimens.spaceSm,
AppDimens.feedPageHorizontal,
0,
),
child: Text(
errorMessage!,
style: const TextStyle(
color: AppColors.textTertiary,
fontSize: AppDimens.followingSubtitleFont,
),
),
),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(
AppDimens.feedPageHorizontal,
0,
AppDimens.feedPageHorizontal,
AppDimens.spaceSm,
),
sliver: SliverGrid(
gridDelegate: gridDelegate,
delegate: SliverChildBuilderDelegate(
itemBuilder,
childCount: itemCount,
),
),
),
SliverToBoxAdapter(
child: PagedListFooter(
loading: loadingMore,
errorMessage: loadMoreError,
onRetry: onLoadMore,
),
),
const SliverToBoxAdapter(
child: SizedBox(height: AppDimens.spaceXl),
),
],
],
),
),
);
},
);
}
bool _onScroll(ScrollNotification notification) {
if (notification.depth != 0) return false;
if (notification.metrics.extentAfter > _loadMoreThreshold) return false;
if (notification is ScrollUpdateNotification ||
notification is ScrollEndNotification ||
notification is OverscrollNotification) {
unawaited(onLoadMore());
}
return false;
}
}