feat(flutter): 增加榜单新剧分页与预约刷新

This commit is contained in:
Zhou Weidong committed 2026-10-20 16:53:07 +08:00
1 parent 805762f761
commit c4dcb714ab
12 files changed
+611 -96

No files matched your search

+116
View File
@@ -0,0 +1,116 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../theme/app_colors.dart';
const int _defaultPageSize = 20;
const Duration _defaultLoadDelay = Duration(milliseconds: 300);
/// 客户端本地列表分页状态。
class PagedLocalListController<T> extends ChangeNotifier {
PagedLocalListController({
List<T> items = const [],
this.pageSize = _defaultPageSize,
this.minimumLoadDelay = _defaultLoadDelay,
}) : _items = List<T>.unmodifiable(items);
final int pageSize;
final Duration minimumLoadDelay;
List<T> _items;
int _pageCount = 1;
int _generation = 0;
bool _loading = false;
bool _disposed = false;
List<T> get items => _items;
bool get loading => _loading;
bool get hasMore => visibleItems.length < _items.length;
List<T> get visibleItems =>
_items.take(_pageCount * pageSize).toList(growable: false);
/// 用最新目录替换本地数据,并回到第一页。
void replaceAll(List<T> items) {
_generation++;
_items = List<T>.unmodifiable(items);
_pageCount = 1;
_loading = false;
_notify();
}
/// 追加下一页;已加载完或正在加载时不重复触发。
Future<void> loadMore() async {
if (_loading || !hasMore) return;
final generation = _generation;
_loading = true;
_notify();
await Future<void>.delayed(minimumLoadDelay);
if (_disposed || generation != _generation) return;
_pageCount++;
_loading = false;
_notify();
}
void _notify() {
if (!_disposed) notifyListeners();
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}
/// 分页列表底部加载态与到底提示。
class PagedLoadFooter extends StatelessWidget {
const PagedLoadFooter({
super.key,
required this.loading,
required this.hasMore,
required this.hasItems,
required this.loadMoreKey,
required this.noMoreKey,
required this.noMoreLabel,
});
final bool loading;
final bool hasMore;
final bool hasItems;
final Key loadMoreKey;
final Key noMoreKey;
final String noMoreLabel;
@override
Widget build(BuildContext context) {
if (loading) {
return SizedBox(
key: loadMoreKey,
height: 56,
child: const Center(
child: SizedBox.square(
dimension: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: AppColors.blue,
),
),
),
);
}
if (hasItems && !hasMore) {
return Padding(
key: noMoreKey,
padding: const EdgeInsets.symmetric(vertical: 14),
child: Text(
noMoreLabel,
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.muted, fontSize: 12),
),
);
}
return const SizedBox(height: 16);
}
}