feat(flutter): 增加轮播自动播放与站内通知

This commit is contained in:
Zhou Weidong committed 2026-10-17 16:50:25 +08:00
1 parent b7b1c50427
commit b870956665
24 files changed
+1398 -26

No files matched your search

@@ -0,0 +1,118 @@
// Author: fruit.com.cn
import 'package:flutter/foundation.dart';
import '../models/notice.dart';
import '../remote/api_client.dart';
import '../repositories/notice_repository.dart';
/// Pagination and state controller for the anonymous notices page.
class NoticesController extends ChangeNotifier {
NoticesController(this._repository, {this.pageSize = 20});
final NoticeRepository _repository;
final int pageSize;
final List<AppNotice> _notices = [];
int _pageIndex = 0;
int _total = 0;
bool _disposed = false;
bool loading = false;
bool loadingMore = false;
bool refreshing = false;
String? error;
String? loadMoreError;
/// Loaded notices in server order.
List<AppNotice> get notices => List.unmodifiable(_notices);
/// Whether another page is expected from the latest total.
bool get hasMore => _notices.length < _total;
/// Loads the first page.
Future<void> load() async {
loading = true;
error = null;
loadMoreError = null;
_notify();
await _replaceFirstPage();
loading = false;
_notify();
}
/// Refreshes from page one while keeping the current page visible.
Future<void> refresh() async {
refreshing = true;
error = null;
loadMoreError = null;
_notify();
await _replaceFirstPage();
refreshing = false;
_notify();
}
/// Appends the next page when loaded count is below total.
Future<void> loadMore() async {
if (loading || loadingMore || refreshing || !hasMore) return;
loadingMore = true;
loadMoreError = null;
_notify();
try {
final page = await _repository.page(
pageIndex: _pageIndex + 1,
pageSize: pageSize,
);
_appendUnique(page.notices);
_pageIndex++;
_total = page.total;
} catch (failure, stackTrace) {
debugPrint('[NoticesController] load more failed: $failure');
debugPrintStack(
stackTrace: stackTrace,
label: '[NoticesController] load more failed',
);
loadMoreError = _message(failure) ?? 'noticesLoadFailed';
} finally {
loadingMore = false;
_notify();
}
}
Future<void> _replaceFirstPage() async {
try {
final page = await _repository.page(pageIndex: 1, pageSize: pageSize);
if (_disposed) return;
_notices
..clear()
..addAll(page.notices);
_pageIndex = 1;
_total = page.total;
} catch (failure, stackTrace) {
debugPrint('[NoticesController] load failed: $failure');
debugPrintStack(
stackTrace: stackTrace,
label: '[NoticesController] load failed',
);
if (!_disposed) error = _message(failure) ?? 'noticesLoadFailed';
}
}
void _appendUnique(List<AppNotice> incoming) {
final ids = _notices.map((notice) => notice.id).toSet();
for (final notice in incoming) {
if (ids.add(notice.id)) _notices.add(notice);
}
}
String? _message(Object failure) =>
failure is ApiException ? failure.message : null;
void _notify() {
if (!_disposed) notifyListeners();
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}