♻️ Rebuilt fetching state machine

This commit is contained in:
2026-01-01 11:40:28 +08:00
parent eea56a742e
commit 38dffa414f
34 changed files with 665 additions and 430 deletions

View File

@@ -2,6 +2,42 @@ import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class PaginationState<T> {
final List<T> items;
final bool isLoading;
final bool isReloading;
final int? totalCount;
final bool hasMore;
final String? cursor;
const PaginationState({
required this.items,
required this.isLoading,
required this.isReloading,
required this.totalCount,
required this.hasMore,
required this.cursor,
});
PaginationState<T> copyWith({
List<T>? items,
bool? isLoading,
bool? isReloading,
int? totalCount,
bool? hasMore,
String? cursor,
}) {
return PaginationState<T>(
items: items ?? this.items,
isLoading: isLoading ?? this.isLoading,
isReloading: isReloading ?? this.isReloading,
totalCount: totalCount ?? this.totalCount,
hasMore: hasMore ?? this.hasMore,
cursor: cursor ?? this.cursor,
);
}
}
abstract class PaginationController<T> {
int? get totalCount;
int get fetchedCount;
@@ -27,51 +63,84 @@ abstract class PaginationFiltered<F> {
Future<void> applyFilter(F filter);
}
mixin AsyncPaginationController<T> on AsyncNotifier<List<T>>
mixin AsyncPaginationController<T> on AsyncNotifier<PaginationState<T>>
implements PaginationController<T> {
@override
int? totalCount;
@override
int get fetchedCount => isReloading ? 0 : state.value?.length ?? 0;
int get fetchedCount =>
state.value?.isReloading == true ? 0 : state.value?.items.length ?? 0;
@override
bool get fetchedAll =>
!hasMore || (totalCount != null && fetchedCount >= totalCount!);
!(state.value?.hasMore ?? true) ||
((state.value?.totalCount != null &&
fetchedCount >= state.value!.totalCount!));
@override
bool isLoading = false;
bool get isLoading => state.value?.isLoading ?? false;
@override
bool isReloading = false;
bool get isReloading => state.value?.isReloading ?? false;
@override
bool hasMore = true;
bool get hasMore => state.value?.hasMore ?? true;
@override
String? cursor;
String? get cursor => state.value?.cursor;
@override
FutureOr<List<T>> build() async {
cursor = null;
return fetch();
set hasMore(bool value) {
if (state is AsyncData) {
state = AsyncData((state as AsyncData).value.copyWith(hasMore: value));
}
}
@override
set cursor(String? value) {
if (state is AsyncData) {
state = AsyncData((state as AsyncData).value.copyWith(cursor: value));
}
}
@override
FutureOr<PaginationState<T>> build() async {
final items = await fetch();
return PaginationState(
items: items,
isLoading: false,
isReloading: false,
totalCount: totalCount,
hasMore: hasMore,
cursor: cursor,
);
}
@override
Future<void> refresh() async {
isLoading = true;
isReloading = true;
totalCount = null;
hasMore = true;
cursor = null;
state = AsyncLoading<List<T>>();
state = AsyncData(
state.value!.copyWith(
isLoading: true,
isReloading: true,
totalCount: null,
hasMore: true,
cursor: null,
),
);
final newState = await AsyncValue.guard<List<T>>(() async {
return await fetch();
});
isReloading = false;
isLoading = false;
state = newState;
final newItems = await fetch();
state = AsyncData(
state.value!.copyWith(
items: newItems,
isLoading: false,
isReloading: false,
totalCount: totalCount,
hasMore: hasMore,
cursor: cursor,
),
);
}
@override
@@ -79,16 +148,16 @@ mixin AsyncPaginationController<T> on AsyncNotifier<List<T>>
if (fetchedAll) return;
if (isLoading) return;
isLoading = true;
state = AsyncLoading<List<T>>();
state = AsyncData(state.value!.copyWith(isLoading: true));
final newState = await AsyncValue.guard<List<T>>(() async {
final elements = await fetch();
return [...?state.value, ...elements];
});
final newItems = await fetch();
isLoading = false;
state = newState;
state = AsyncData(
state.value!.copyWith(
items: [...state.value!.items, ...newItems],
isLoading: false,
),
);
}
}
@@ -97,20 +166,29 @@ mixin AsyncPaginationFilter<F, T> on AsyncPaginationController<T>
@override
Future<void> applyFilter(F filter) async {
if (currentFilter == filter) return;
// Reset the data
isReloading = true;
isLoading = true;
totalCount = null;
hasMore = true;
cursor = null;
state = AsyncLoading<List<T>>();
state = AsyncData(
state.value!.copyWith(
isReloading: true,
isLoading: true,
totalCount: null,
hasMore: true,
cursor: null,
),
);
currentFilter = filter;
final newState = await AsyncValue.guard<List<T>>(() async {
return await fetch();
});
isLoading = false;
isReloading = false;
state = newState;
final newItems = await fetch();
state = AsyncData(
state.value!.copyWith(
items: newItems,
isLoading: false,
isReloading: false,
totalCount: totalCount,
hasMore: hasMore,
cursor: cursor,
),
);
}
}