⚗️ Testing out new own pagination utils
This commit is contained in:
@@ -2,14 +2,24 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/file_list_item.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/paging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
|
||||
part 'file_list.g.dart';
|
||||
|
||||
@riverpod
|
||||
class CloudFileListNotifier extends _$CloudFileListNotifier
|
||||
with CursorPagingNotifierMixin<FileListItem> {
|
||||
Future<Map<String, dynamic>?> billingUsage(Ref ref) async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
final response = await client.get('/drive/billing/usage');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
final indexedCloudFileListNotifierProvider = AsyncNotifierProvider(
|
||||
IndexedCloudFileListNotifier.new,
|
||||
);
|
||||
|
||||
class IndexedCloudFileListNotifier extends AsyncNotifier<List<FileListItem>>
|
||||
with AsyncPaginationController<FileListItem> {
|
||||
String _currentPath = '/';
|
||||
String? _poolId;
|
||||
String? _query;
|
||||
@@ -42,12 +52,7 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<FileListItem>> fetch({
|
||||
required String? cursor,
|
||||
}) async {
|
||||
Future<List<FileListItem>> fetch() async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
|
||||
final queryParameters = <String, String>{'path': _currentPath};
|
||||
@@ -83,21 +88,16 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
|
||||
...files.map((file) => FileListItem.file(file)),
|
||||
];
|
||||
|
||||
// The new API returns all files in the path, no pagination
|
||||
return CursorPagingData(items: items, hasMore: false, nextCursor: null);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<Map<String, dynamic>?> billingUsage(Ref ref) async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
final response = await client.get('/drive/billing/usage');
|
||||
return response.data;
|
||||
}
|
||||
final unindexedFileListNotifierProvider = AsyncNotifierProvider(
|
||||
UnindexedFileListNotifier.new,
|
||||
);
|
||||
|
||||
@riverpod
|
||||
class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
||||
with CursorPagingNotifierMixin<FileListItem> {
|
||||
class UnindexedFileListNotifier extends AsyncNotifier<List<FileListItem>>
|
||||
with AsyncPaginationController<FileListItem> {
|
||||
String? _poolId;
|
||||
bool _recycled = false;
|
||||
String? _query;
|
||||
@@ -129,21 +129,15 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
|
||||
static const int pageSize = 20;
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<FileListItem>> fetch({
|
||||
required String? cursor,
|
||||
}) async {
|
||||
Future<List<FileListItem>> fetch() async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
|
||||
final offset = cursor != null ? int.tryParse(cursor) ?? 0 : 0;
|
||||
const take = 50; // Default page size
|
||||
|
||||
final queryParameters = <String, String>{
|
||||
'take': take.toString(),
|
||||
'offset': offset.toString(),
|
||||
'take': pageSize.toString(),
|
||||
'offset': fetchedCount.toString(),
|
||||
};
|
||||
|
||||
if (_poolId != null) {
|
||||
@@ -169,7 +163,7 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
||||
queryParameters: queryParameters,
|
||||
);
|
||||
|
||||
final total = int.tryParse(response.headers.value('x-total') ?? '0') ?? 0;
|
||||
totalCount = int.tryParse(response.headers.value('x-total') ?? '0') ?? 0;
|
||||
|
||||
final List<SnCloudFile> files =
|
||||
(response.data as List)
|
||||
@@ -179,14 +173,7 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
||||
final List<FileListItem> items =
|
||||
files.map((file) => FileListItem.unindexedFile(file)).toList();
|
||||
|
||||
final hasMore = offset + take < total;
|
||||
final nextCursor = hasMore ? (offset + take).toString() : null;
|
||||
|
||||
return CursorPagingData(
|
||||
items: items,
|
||||
hasMore: hasMore,
|
||||
nextCursor: nextCursor,
|
||||
);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,47 +44,5 @@ final billingQuotaProvider =
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef BillingQuotaRef = AutoDisposeFutureProviderRef<Map<String, dynamic>?>;
|
||||
String _$cloudFileListNotifierHash() =>
|
||||
r'533dfa86f920b60cf7491fb4aeb95ece19e428af';
|
||||
|
||||
/// See also [CloudFileListNotifier].
|
||||
@ProviderFor(CloudFileListNotifier)
|
||||
final cloudFileListNotifierProvider = AutoDisposeAsyncNotifierProvider<
|
||||
CloudFileListNotifier,
|
||||
CursorPagingData<FileListItem>
|
||||
>.internal(
|
||||
CloudFileListNotifier.new,
|
||||
name: r'cloudFileListNotifierProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$cloudFileListNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$CloudFileListNotifier =
|
||||
AutoDisposeAsyncNotifier<CursorPagingData<FileListItem>>;
|
||||
String _$unindexedFileListNotifierHash() =>
|
||||
r'afa487d7b956b71b21ca1b073a01364a34ede1d5';
|
||||
|
||||
/// See also [UnindexedFileListNotifier].
|
||||
@ProviderFor(UnindexedFileListNotifier)
|
||||
final unindexedFileListNotifierProvider = AutoDisposeAsyncNotifierProvider<
|
||||
UnindexedFileListNotifier,
|
||||
CursorPagingData<FileListItem>
|
||||
>.internal(
|
||||
UnindexedFileListNotifier.new,
|
||||
name: r'unindexedFileListNotifierProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$unindexedFileListNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$UnindexedFileListNotifier =
|
||||
AutoDisposeAsyncNotifier<CursorPagingData<FileListItem>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
82
lib/pods/paging.dart
Normal file
82
lib/pods/paging.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
abstract class PaginationController<T> {
|
||||
int? get totalCount;
|
||||
int get fetchedCount;
|
||||
|
||||
bool get fetchedAll;
|
||||
|
||||
FutureOr<List<T>> fetch();
|
||||
|
||||
Future<void> refresh();
|
||||
|
||||
Future<void> fetchFurther();
|
||||
}
|
||||
|
||||
abstract class PaginationFiltered<F> {
|
||||
late F currentFilter;
|
||||
|
||||
Future<void> applyFilter(F filter);
|
||||
}
|
||||
|
||||
mixin AsyncPaginationController<T> on AsyncNotifier<List<T>>
|
||||
implements PaginationController<T> {
|
||||
@override
|
||||
int? totalCount;
|
||||
|
||||
@override
|
||||
int fetchedCount = 0;
|
||||
|
||||
@override
|
||||
bool get fetchedAll => totalCount != null && fetchedCount >= totalCount!;
|
||||
|
||||
@override
|
||||
FutureOr<List<T>> build() async => fetch();
|
||||
|
||||
@override
|
||||
Future<void> refresh() async {
|
||||
totalCount = null;
|
||||
fetchedCount = 0;
|
||||
state = AsyncLoading<List<T>>();
|
||||
|
||||
final newState = await AsyncValue.guard<List<T>>(() async {
|
||||
return await fetch();
|
||||
});
|
||||
state = newState;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> fetchFurther() async {
|
||||
if (!fetchedAll) return;
|
||||
|
||||
state = AsyncLoading<List<T>>();
|
||||
|
||||
final newState = await AsyncValue.guard<List<T>>(() async {
|
||||
final elements = await fetch();
|
||||
return [...?state.valueOrNull, ...elements];
|
||||
});
|
||||
|
||||
state = newState;
|
||||
fetchedCount = newState.value?.length ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
mixin AsyncPaginationFilter<F, T> on AsyncPaginationController<T>
|
||||
implements PaginationFiltered<F> {
|
||||
@override
|
||||
Future<void> applyFilter(F filter) async {
|
||||
// Reset the data
|
||||
totalCount = null;
|
||||
fetchedCount = 0;
|
||||
currentFilter = filter;
|
||||
|
||||
state = AsyncLoading<List<T>>();
|
||||
|
||||
final newState = await AsyncValue.guard<List<T>>(() async {
|
||||
return await fetch();
|
||||
});
|
||||
state = newState;
|
||||
}
|
||||
}
|
||||
65
lib/pods/timeline.dart
Normal file
65
lib/pods/timeline.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/activity.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/paging.dart';
|
||||
|
||||
final activityListNotifierProvider =
|
||||
AsyncNotifierProvider<ActivityListNotifier, List<SnTimelineEvent>>(
|
||||
ActivityListNotifier.new,
|
||||
);
|
||||
|
||||
class ActivityListNotifier extends AsyncNotifier<List<SnTimelineEvent>>
|
||||
with
|
||||
AsyncPaginationController<SnTimelineEvent>,
|
||||
AsyncPaginationFilter<String?, SnTimelineEvent> {
|
||||
static const int pageSize = 20;
|
||||
|
||||
@override
|
||||
String? currentFilter;
|
||||
|
||||
@override
|
||||
Future<List<SnTimelineEvent>> fetch() async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
|
||||
final cursor =
|
||||
state.valueOrNull?.lastOrNull?.createdAt.toUtc().toIso8601String();
|
||||
|
||||
final queryParameters = {
|
||||
if (cursor != null) 'cursor': cursor,
|
||||
'take': pageSize,
|
||||
if (currentFilter != null) 'filter': currentFilter,
|
||||
if (kDebugMode)
|
||||
'debugInclude': 'realms,publishers,articles,shuffledPosts',
|
||||
};
|
||||
|
||||
final response = await client.get(
|
||||
'/sphere/timeline',
|
||||
queryParameters: queryParameters,
|
||||
);
|
||||
|
||||
final List<SnTimelineEvent> items =
|
||||
(response.data as List)
|
||||
.map((e) => SnTimelineEvent.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty';
|
||||
|
||||
totalCount =
|
||||
(state.valueOrNull?.length ?? 0) +
|
||||
items.length +
|
||||
(hasMore ? pageSize : 0);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
void updateOne(int index, SnTimelineEvent activity) {
|
||||
final currentState = state.valueOrNull;
|
||||
if (currentState == null) return;
|
||||
|
||||
final updatedItems = [...currentState];
|
||||
updatedItems[index] = activity;
|
||||
|
||||
state = AsyncData(updatedItems);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user