🐛 Fix cursor based loading in timeline cause duplicate data due to not covered the popularity reordered case, close #213
This commit is contained in:
@@ -8,6 +8,10 @@ abstract class PaginationController<T> {
|
|||||||
|
|
||||||
bool get fetchedAll;
|
bool get fetchedAll;
|
||||||
bool get isLoading;
|
bool get isLoading;
|
||||||
|
bool get hasMore;
|
||||||
|
set hasMore(bool value);
|
||||||
|
String? get cursor;
|
||||||
|
set cursor(String? value);
|
||||||
|
|
||||||
FutureOr<List<T>> fetch();
|
FutureOr<List<T>> fetch();
|
||||||
|
|
||||||
@@ -31,19 +35,31 @@ mixin AsyncPaginationController<T> on AsyncNotifier<List<T>>
|
|||||||
int get fetchedCount => state.value?.length ?? 0;
|
int get fetchedCount => state.value?.length ?? 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get fetchedAll => totalCount != null && fetchedCount >= totalCount!;
|
bool get fetchedAll =>
|
||||||
|
!hasMore || (totalCount != null && fetchedCount >= totalCount!);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<List<T>> build() async => fetch();
|
bool hasMore = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? cursor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<List<T>> build() async {
|
||||||
|
cursor = null;
|
||||||
|
return fetch();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
totalCount = null;
|
totalCount = null;
|
||||||
state = AsyncData<List<T>>([]);
|
hasMore = true;
|
||||||
|
cursor = null;
|
||||||
|
state = AsyncLoading<List<T>>();
|
||||||
|
|
||||||
final newState = await AsyncValue.guard<List<T>>(() async {
|
final newState = await AsyncValue.guard<List<T>>(() async {
|
||||||
return await fetch();
|
return await fetch();
|
||||||
@@ -55,6 +71,7 @@ mixin AsyncPaginationController<T> on AsyncNotifier<List<T>>
|
|||||||
@override
|
@override
|
||||||
Future<void> fetchFurther() async {
|
Future<void> fetchFurther() async {
|
||||||
if (fetchedAll) return;
|
if (fetchedAll) return;
|
||||||
|
if (isLoading) return;
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
state = AsyncLoading<List<T>>();
|
state = AsyncLoading<List<T>>();
|
||||||
@@ -77,7 +94,9 @@ mixin AsyncPaginationFilter<F, T> on AsyncPaginationController<T>
|
|||||||
// Reset the data
|
// Reset the data
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
totalCount = null;
|
totalCount = null;
|
||||||
state = AsyncData<List<T>>([]);
|
hasMore = true;
|
||||||
|
cursor = null;
|
||||||
|
state = AsyncLoading<List<T>>();
|
||||||
currentFilter = filter;
|
currentFilter = filter;
|
||||||
|
|
||||||
final newState = await AsyncValue.guard<List<T>>(() async {
|
final newState = await AsyncValue.guard<List<T>>(() async {
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ class ActivityListNotifier extends AsyncNotifier<List<SnTimelineEvent>>
|
|||||||
Future<List<SnTimelineEvent>> fetch() async {
|
Future<List<SnTimelineEvent>> fetch() async {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
|
|
||||||
final cursor = state.value?.lastOrNull?.createdAt.toUtc().toIso8601String();
|
|
||||||
|
|
||||||
final queryParameters = {
|
final queryParameters = {
|
||||||
if (cursor != null) 'cursor': cursor,
|
if (cursor != null) 'cursor': cursor,
|
||||||
'take': pageSize,
|
'take': pageSize,
|
||||||
@@ -37,10 +35,16 @@ class ActivityListNotifier extends AsyncNotifier<List<SnTimelineEvent>>
|
|||||||
.map((e) => SnTimelineEvent.fromJson(e as Map<String, dynamic>))
|
.map((e) => SnTimelineEvent.fromJson(e as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty';
|
hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty';
|
||||||
|
// Find the latest createdAt timestamp from all items for cursor-based pagination
|
||||||
totalCount =
|
// This ensures we get items created before this timestamp, regardless of sort order
|
||||||
(state.value?.length ?? 0) + items.length + (hasMore ? pageSize : 0);
|
if (items.isNotEmpty) {
|
||||||
|
final latestCreatedAt = items
|
||||||
|
.where((e) => e.type.startsWith('posts.'))
|
||||||
|
.map((e) => e.createdAt)
|
||||||
|
.reduce((a, b) => a.isBefore(b) ? a : b);
|
||||||
|
cursor = latestCreatedAt.toUtc().toIso8601String();
|
||||||
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user