diff --git a/lib/pods/drive/file_list.dart b/lib/pods/drive/file_list.dart index 8994b0af..ebccf151 100644 --- a/lib/pods/drive/file_list.dart +++ b/lib/pods/drive/file_list.dart @@ -18,7 +18,8 @@ final indexedCloudFileListProvider = AsyncNotifierProvider.autoDispose( IndexedCloudFileListNotifier.new, ); -class IndexedCloudFileListNotifier extends AsyncNotifier> +class IndexedCloudFileListNotifier + extends AsyncNotifier> with AsyncPaginationController { String _currentPath = '/'; String? _poolId; @@ -51,6 +52,19 @@ class IndexedCloudFileListNotifier extends AsyncNotifier> ref.invalidateSelf(); } + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: null, + hasMore: false, + cursor: null, + ); + } + @override Future> fetch() async { final client = ref.read(apiClientProvider); @@ -96,7 +110,8 @@ final unindexedFileListProvider = AsyncNotifierProvider.autoDispose( UnindexedFileListNotifier.new, ); -class UnindexedFileListNotifier extends AsyncNotifier> +class UnindexedFileListNotifier + extends AsyncNotifier> with AsyncPaginationController { String? _poolId; bool _recycled = false; @@ -131,6 +146,19 @@ class UnindexedFileListNotifier extends AsyncNotifier> static const int pageSize = 20; + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override Future> fetch() async { final client = ref.read(apiClientProvider); diff --git a/lib/pods/paging.dart b/lib/pods/paging.dart index 6782cf02..024c7e27 100644 --- a/lib/pods/paging.dart +++ b/lib/pods/paging.dart @@ -2,6 +2,42 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +class PaginationState { + final List 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 copyWith({ + List? items, + bool? isLoading, + bool? isReloading, + int? totalCount, + bool? hasMore, + String? cursor, + }) { + return PaginationState( + 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 { int? get totalCount; int get fetchedCount; @@ -27,51 +63,84 @@ abstract class PaginationFiltered { Future applyFilter(F filter); } -mixin AsyncPaginationController on AsyncNotifier> +mixin AsyncPaginationController on AsyncNotifier> implements PaginationController { @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> 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> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); } @override Future refresh() async { - isLoading = true; - isReloading = true; - totalCount = null; - hasMore = true; - cursor = null; - state = AsyncLoading>(); + state = AsyncData( + state.value!.copyWith( + isLoading: true, + isReloading: true, + totalCount: null, + hasMore: true, + cursor: null, + ), + ); - final newState = await AsyncValue.guard>(() 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 on AsyncNotifier> if (fetchedAll) return; if (isLoading) return; - isLoading = true; - state = AsyncLoading>(); + state = AsyncData(state.value!.copyWith(isLoading: true)); - final newState = await AsyncValue.guard>(() 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 on AsyncPaginationController @override Future applyFilter(F filter) async { if (currentFilter == filter) return; - // Reset the data - isReloading = true; - isLoading = true; - totalCount = null; - hasMore = true; - cursor = null; - state = AsyncLoading>(); + + state = AsyncData( + state.value!.copyWith( + isReloading: true, + isLoading: true, + totalCount: null, + hasMore: true, + cursor: null, + ), + ); currentFilter = filter; - final newState = await AsyncValue.guard>(() 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, + ), + ); } } diff --git a/lib/pods/post/post_categories.dart b/lib/pods/post/post_categories.dart index fa2f8110..a2b335e4 100644 --- a/lib/pods/post/post_categories.dart +++ b/lib/pods/post/post_categories.dart @@ -1,4 +1,6 @@ // Post Categories Notifier +import 'dart:async'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/post_category.dart'; import 'package:island/models/post_tag.dart'; @@ -8,11 +10,25 @@ import 'package:island/pods/paging.dart'; final postCategoriesProvider = AsyncNotifierProvider.autoDispose< PostCategoriesNotifier, - List + PaginationState >(PostCategoriesNotifier.new); -class PostCategoriesNotifier extends AsyncNotifier> +class PostCategoriesNotifier + extends AsyncNotifier> with AsyncPaginationController { + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override Future> fetch() async { final client = ref.read(apiClientProvider); @@ -30,12 +46,26 @@ class PostCategoriesNotifier extends AsyncNotifier> // Post Tags Notifier final postTagsProvider = - AsyncNotifierProvider.autoDispose>( - PostTagsNotifier.new, - ); + AsyncNotifierProvider.autoDispose< + PostTagsNotifier, + PaginationState + >(PostTagsNotifier.new); -class PostTagsNotifier extends AsyncNotifier> +class PostTagsNotifier extends AsyncNotifier> with AsyncPaginationController { + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override Future> fetch() async { final client = ref.read(apiClientProvider); diff --git a/lib/pods/post/post_list.dart b/lib/pods/post/post_list.dart index 0e58767c..e0013a13 100644 --- a/lib/pods/post/post_list.dart +++ b/lib/pods/post/post_list.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/post.dart'; @@ -39,7 +41,7 @@ final postListProvider = AsyncNotifierProvider.autoDispose.family( PostListNotifier.new, ); -class PostListNotifier extends AsyncNotifier> +class PostListNotifier extends AsyncNotifier> with AsyncPaginationController, AsyncPaginationFilter { @@ -53,9 +55,17 @@ class PostListNotifier extends AsyncNotifier> late PostListQuery currentFilter; @override - Future> build() async { + FutureOr> build() async { currentFilter = config.initialFilter; - return fetch(); + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); } @override diff --git a/lib/pods/timeline.dart b/lib/pods/timeline.dart index ae2ffc9c..d5ff358f 100644 --- a/lib/pods/timeline.dart +++ b/lib/pods/timeline.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/activity.dart'; import 'package:island/pods/network.dart'; @@ -7,12 +9,26 @@ final activityListProvider = AsyncNotifierProvider.autoDispose( ActivityListNotifier.new, ); -class ActivityListNotifier extends AsyncNotifier> +class ActivityListNotifier + extends AsyncNotifier> with AsyncPaginationController, AsyncPaginationFilter { static const int pageSize = 20; + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override String? currentFilter; @@ -54,9 +70,9 @@ class ActivityListNotifier extends AsyncNotifier> final currentState = state.value; if (currentState == null) return; - final updatedItems = [...currentState]; + final updatedItems = [...currentState.items]; updatedItems[index] = activity; - state = AsyncData(updatedItems); + state = AsyncData(currentState.copyWith(items: updatedItems)); } } diff --git a/lib/screens/account/credits.dart b/lib/screens/account/credits.dart index 88291329..20bcf901 100644 --- a/lib/screens/account/credits.dart +++ b/lib/screens/account/credits.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; @@ -28,10 +30,23 @@ final socialCreditHistoryNotifierProvider = AsyncNotifierProvider.autoDispose( ); class SocialCreditHistoryNotifier - extends AsyncNotifier> + extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override Future> fetch() async { final client = ref.read(apiClientProvider); diff --git a/lib/screens/account/leveling.dart b/lib/screens/account/leveling.dart index 0325cddc..087aa845 100644 --- a/lib/screens/account/leveling.dart +++ b/lib/screens/account/leveling.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -14,14 +16,30 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:island/widgets/paging/pagination_list.dart'; import 'package:styled_widget/styled_widget.dart'; -final levelingHistoryNotifierProvider = AsyncNotifierProvider.autoDispose( - LevelingHistoryNotifier.new, -); +final levelingHistoryNotifierProvider = + AsyncNotifierProvider.autoDispose< + LevelingHistoryNotifier, + PaginationState + >(LevelingHistoryNotifier.new); -class LevelingHistoryNotifier extends AsyncNotifier> +class LevelingHistoryNotifier + extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override Future> fetch() async { final client = ref.read(apiClientProvider); diff --git a/lib/screens/account/relationship.dart b/lib/screens/account/relationship.dart index 9e75a294..72ea3910 100644 --- a/lib/screens/account/relationship.dart +++ b/lib/screens/account/relationship.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -29,12 +31,28 @@ Future> sentFriendRequest(Ref ref) async { .toList(); } -final relationshipListNotifierProvider = AsyncNotifierProvider.autoDispose( - RelationshipListNotifier.new, -); +final relationshipListNotifierProvider = + AsyncNotifierProvider.autoDispose< + RelationshipListNotifier, + PaginationState + >(RelationshipListNotifier.new); -class RelationshipListNotifier extends AsyncNotifier> +class RelationshipListNotifier + extends AsyncNotifier> with AsyncPaginationController { + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override Future> fetch() async { final client = ref.read(apiClientProvider); diff --git a/lib/screens/chat/room_detail.dart b/lib/screens/chat/room_detail.dart index 836db20c..6416e014 100644 --- a/lib/screens/chat/room_detail.dart +++ b/lib/screens/chat/room_detail.dart @@ -588,7 +588,8 @@ final chatMemberListProvider = AsyncNotifierProvider.autoDispose.family( ChatMemberListNotifier.new, ); -class ChatMemberListNotifier extends AsyncNotifier> +class ChatMemberListNotifier + extends AsyncNotifier> with AsyncPaginationController { static const pageSize = 20; diff --git a/lib/screens/creators/hub.dart b/lib/screens/creators/hub.dart index eb01692f..52a010dd 100644 --- a/lib/screens/creators/hub.dart +++ b/lib/screens/creators/hub.dart @@ -96,13 +96,27 @@ Future publisherActorStatus( final publisherMemberListNotifierProvider = AsyncNotifierProvider.family .autoDispose(PublisherMemberListNotifier.new); -class PublisherMemberListNotifier extends AsyncNotifier> +class PublisherMemberListNotifier + extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; final String arg; PublisherMemberListNotifier(this.arg); + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override Future> fetch() async { final apiClient = ref.read(apiClientProvider); diff --git a/lib/screens/creators/poll/poll_list.dart b/lib/screens/creators/poll/poll_list.dart index 18f066a6..35e04256 100644 --- a/lib/screens/creators/poll/poll_list.dart +++ b/lib/screens/creators/poll/poll_list.dart @@ -21,7 +21,7 @@ final pollListNotifierProvider = AsyncNotifierProvider.family.autoDispose( PollListNotifier.new, ); -class PollListNotifier extends AsyncNotifier> +class PollListNotifier extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; diff --git a/lib/screens/creators/sites/site_list.dart b/lib/screens/creators/sites/site_list.dart index b980c8f7..92e86535 100644 --- a/lib/screens/creators/sites/site_list.dart +++ b/lib/screens/creators/sites/site_list.dart @@ -18,7 +18,7 @@ final siteListNotifierProvider = AsyncNotifierProvider.family.autoDispose( SiteListNotifier.new, ); -class SiteListNotifier extends AsyncNotifier> +class SiteListNotifier extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; diff --git a/lib/screens/creators/stickers/stickers.dart b/lib/screens/creators/stickers/stickers.dart index 6dcef2a1..5704aa51 100644 --- a/lib/screens/creators/stickers/stickers.dart +++ b/lib/screens/creators/stickers/stickers.dart @@ -140,7 +140,7 @@ final stickerPacksProvider = AsyncNotifierProvider.family.autoDispose( StickerPacksNotifier.new, ); -class StickerPacksNotifier extends AsyncNotifier> +class StickerPacksNotifier extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; diff --git a/lib/screens/dashboard/dash.dart b/lib/screens/dashboard/dash.dart index cf8096a7..c7c6b39f 100644 --- a/lib/screens/dashboard/dash.dart +++ b/lib/screens/dashboard/dash.dart @@ -412,11 +412,11 @@ class NotificationsCard extends HookConsumerWidget { loading: () => const SkeletonNotificationTile(), error: (error, stack) => Center(child: Text('Error: $error')), data: (notificationList) { - if (notificationList.isEmpty) { + if (notificationList.items.isEmpty) { return Center(child: Text('noNotificationsYet').tr()); } // Get the most recent notification (first in the list) - final recentNotification = notificationList.first; + final recentNotification = notificationList.items.first; return Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/screens/discovery/articles.dart b/lib/screens/discovery/articles.dart index 20faa6aa..7219d0bd 100644 --- a/lib/screens/discovery/articles.dart +++ b/lib/screens/discovery/articles.dart @@ -22,7 +22,7 @@ final articlesListNotifierProvider = AsyncNotifierProvider.family.autoDispose( ArticlesListNotifier.new, ); -class ArticlesListNotifier extends AsyncNotifier> +class ArticlesListNotifier extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; diff --git a/lib/screens/discovery/feeds/feed_detail.dart b/lib/screens/discovery/feeds/feed_detail.dart index bebc7a6b..8b2ba38b 100644 --- a/lib/screens/discovery/feeds/feed_detail.dart +++ b/lib/screens/discovery/feeds/feed_detail.dart @@ -26,7 +26,7 @@ final marketplaceWebFeedContentNotifierProvider = AsyncNotifierProvider.family .autoDispose(MarketplaceWebFeedContentNotifier.new); class MarketplaceWebFeedContentNotifier - extends AsyncNotifier> + extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; diff --git a/lib/screens/discovery/feeds/feed_marketplace.dart b/lib/screens/discovery/feeds/feed_marketplace.dart index 8295e264..1d57cd80 100644 --- a/lib/screens/discovery/feeds/feed_marketplace.dart +++ b/lib/screens/discovery/feeds/feed_marketplace.dart @@ -16,7 +16,8 @@ final marketplaceWebFeedsNotifierProvider = AsyncNotifierProvider.autoDispose( MarketplaceWebFeedsNotifier.new, ); -class MarketplaceWebFeedsNotifier extends AsyncNotifier> +class MarketplaceWebFeedsNotifier + extends AsyncNotifier> with AsyncPaginationController, AsyncPaginationFilter { diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart index ddfb09df..da5eb878 100644 --- a/lib/screens/notification.dart +++ b/lib/screens/notification.dart @@ -164,10 +164,24 @@ final notificationListProvider = AsyncNotifierProvider.autoDispose( NotificationListNotifier.new, ); -class NotificationListNotifier extends AsyncNotifier> +class NotificationListNotifier + extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 5; + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override Future> fetch() async { final client = ref.read(apiClientProvider); diff --git a/lib/screens/posts/post_search.dart b/lib/screens/posts/post_search.dart index 2322a577..6056a69e 100644 --- a/lib/screens/posts/post_search.dart +++ b/lib/screens/posts/post_search.dart @@ -160,7 +160,7 @@ class PostSearchScreen extends HookConsumerWidget { ); }, ), - if (searchState.value?.isEmpty == true && + if (searchState.value?.items.isEmpty == true && searchController.text.isNotEmpty && !searchState.isLoading) SliverFillRemaining( @@ -290,7 +290,7 @@ class PostSearchScreen extends HookConsumerWidget { ); }, ), - if (searchState.value?.isEmpty == true && + if (searchState.value?.items.isEmpty == true && searchController.text.isNotEmpty && !searchState.isLoading) SliverFillRemaining( diff --git a/lib/screens/realm/realm_detail.dart b/lib/screens/realm/realm_detail.dart index dd7d29f5..dcf336b9 100644 --- a/lib/screens/realm/realm_detail.dart +++ b/lib/screens/realm/realm_detail.dart @@ -492,11 +492,10 @@ class _RealmActionMenu extends HookConsumerWidget { } final realmMemberListNotifierProvider = AsyncNotifierProvider.autoDispose - .family, String>( - RealmMemberListNotifier.new, - ); + .family(RealmMemberListNotifier.new); -class RealmMemberListNotifier extends AsyncNotifier> +class RealmMemberListNotifier + extends AsyncNotifier> with AsyncPaginationController { String arg; RealmMemberListNotifier(this.arg); diff --git a/lib/screens/stickers/sticker_marketplace.dart b/lib/screens/stickers/sticker_marketplace.dart index c6ba0967..09a4fa76 100644 --- a/lib/screens/stickers/sticker_marketplace.dart +++ b/lib/screens/stickers/sticker_marketplace.dart @@ -31,7 +31,8 @@ sealed class MarketplaceStickerQuery with _$MarketplaceStickerQuery { final marketplaceStickerPacksNotifierProvider = AsyncNotifierProvider.autoDispose(MarketplaceStickerPacksNotifier.new); -class MarketplaceStickerPacksNotifier extends AsyncNotifier> +class MarketplaceStickerPacksNotifier + extends AsyncNotifier> with AsyncPaginationController, AsyncPaginationFilter { diff --git a/lib/screens/wallet.dart b/lib/screens/wallet.dart index 25431c47..d5420416 100644 --- a/lib/screens/wallet.dart +++ b/lib/screens/wallet.dart @@ -963,7 +963,8 @@ final transactionListProvider = AsyncNotifierProvider.autoDispose( TransactionListNotifier.new, ); -class TransactionListNotifier extends AsyncNotifier> +class TransactionListNotifier + extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; @@ -992,7 +993,7 @@ final walletFundsProvider = AsyncNotifierProvider.autoDispose( WalletFundsNotifier.new, ); -class WalletFundsNotifier extends AsyncNotifier> +class WalletFundsNotifier extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; @@ -1020,7 +1021,7 @@ final walletFundRecipientsProvider = AsyncNotifierProvider.autoDispose( ); class WalletFundRecipientsNotifier - extends AsyncNotifier> + extends AsyncNotifier> with AsyncPaginationController { static const int _pageSize = 20; @@ -1484,7 +1485,7 @@ class WalletScreen extends HookConsumerWidget { return funds.when( data: (fundList) { - if (fundList.isEmpty) { + if (fundList.items.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -1514,9 +1515,9 @@ class WalletScreen extends HookConsumerWidget { return ListView.builder( padding: const EdgeInsets.all(16), - itemCount: fundList.length, + itemCount: fundList.items.length, itemBuilder: (context, index) { - final fund = fundList[index]; + final fund = fundList.items[index]; final claimedCount = fund.recipients .where((r) => r.isReceived) .length; diff --git a/lib/widgets/chat/chat_link_attachments.dart b/lib/widgets/chat/chat_link_attachments.dart index 89c83303..071bb908 100644 --- a/lib/widgets/chat/chat_link_attachments.dart +++ b/lib/widgets/chat/chat_link_attachments.dart @@ -17,7 +17,8 @@ final chatCloudFileListNotifierProvider = AsyncNotifierProvider.autoDispose( ChatCloudFileListNotifier.new, ); -class ChatCloudFileListNotifier extends AsyncNotifier> +class ChatCloudFileListNotifier + extends AsyncNotifier> with AsyncPaginationController { @override Future> fetch() async { @@ -31,10 +32,9 @@ class ChatCloudFileListNotifier extends AsyncNotifier> queryParameters: queryParameters, ); - final List items = - (response.data as List) - .map((e) => SnCloudFile.fromJson(e as Map)) - .toList(); + final List items = (response.data as List) + .map((e) => SnCloudFile.fromJson(e as Map)) + .toList(); totalCount = int.parse(response.headers.value('X-Total') ?? '0'); return items; @@ -83,28 +83,24 @@ class ChatLinkAttachment extends HookConsumerWidget { width: 48, child: switch (itemType) { 'image' => CloudImageWidget(file: item), - 'audio' => - const Icon( - Symbols.audio_file, - fill: 1, - ).center(), - 'video' => - const Icon( - Symbols.video_file, - fill: 1, - ).center(), - _ => - const Icon( - Symbols.body_system, - fill: 1, - ).center(), + 'audio' => const Icon( + Symbols.audio_file, + fill: 1, + ).center(), + 'video' => const Icon( + Symbols.video_file, + fill: 1, + ).center(), + _ => const Icon( + Symbols.body_system, + fill: 1, + ).center(), }, ), ), - title: - item.name.isEmpty - ? Text('untitled').tr().italic() - : Text(item.name), + title: item.name.isEmpty + ? Text('untitled').tr().italic() + : Text(item.name), onTap: () { Navigator.pop(context, item); }, @@ -128,9 +124,8 @@ class ChatLinkAttachment extends HookConsumerWidget { ), ), ), - onTapOutside: - (_) => - FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(16), InkWell( diff --git a/lib/widgets/paging/pagination_list.dart b/lib/widgets/paging/pagination_list.dart index 0ade53bc..c4395f87 100644 --- a/lib/widgets/paging/pagination_list.dart +++ b/lib/widgets/paging/pagination_list.dart @@ -15,7 +15,7 @@ import 'package:super_sliver_list/super_sliver_list.dart'; import 'package:visibility_detector/visibility_detector.dart'; class PaginationList extends HookConsumerWidget { - final ProviderListenable>> provider; + final ProviderListenable>> provider; final Refreshable> notifier; final Widget? Function(BuildContext, int, T) itemBuilder; final Widget? Function(BuildContext, int, T)? seperatorBuilder; @@ -48,7 +48,8 @@ class PaginationList extends HookConsumerWidget { // For sliver cases, avoid animation to prevent complex sliver issues if (isSliver) { - if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) { + if ((data.isLoading || data.value?.isLoading == true) && + data.value?.items.isEmpty == true) { final content = List.generate( 10, (_) => Skeletonizer( @@ -77,9 +78,9 @@ class PaginationList extends HookConsumerWidget { } final listView = SuperSliverList.separated( - itemCount: (data.value?.length ?? 0) + 1, + itemCount: (data.value?.items.length ?? 0) + 1, itemBuilder: (context, idx) { - if (idx == data.value?.length) { + if (idx == data.value?.items.length) { return PaginationListFooter( noti: noti, data: data, @@ -87,13 +88,13 @@ class PaginationList extends HookConsumerWidget { skeletonMaxWidth: footerSkeletonMaxWidth, ); } - final entry = data.value?[idx]; + final entry = data.value?.items[idx]; if (entry != null) return itemBuilder(context, idx, entry); return null; }, separatorBuilder: (context, index) { if (seperatorBuilder != null) { - final entry = data.value?[index]; + final entry = data.value?.items[index]; if (entry != null) { return seperatorBuilder!(context, index, entry) ?? const SizedBox(); @@ -114,7 +115,8 @@ class PaginationList extends HookConsumerWidget { // For non-sliver cases, use AnimatedSwitcher for smooth transitions Widget buildContent() { - if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) { + if ((data.isLoading || data.value?.isLoading == true) && + data.value?.items.isEmpty == true) { final content = List.generate( 10, (_) => Skeletonizer( @@ -147,9 +149,9 @@ class PaginationList extends HookConsumerWidget { final listView = SuperListView.separated( padding: padding, - itemCount: (data.value?.length ?? 0) + 1, + itemCount: (data.value?.items.length ?? 0) + 1, itemBuilder: (context, idx) { - if (idx == data.value?.length) { + if (idx == data.value?.items.length) { return PaginationListFooter( noti: noti, data: data, @@ -157,13 +159,13 @@ class PaginationList extends HookConsumerWidget { skeletonMaxWidth: footerSkeletonMaxWidth, ); } - final entry = data.value?[idx]; + final entry = data.value?.items[idx]; if (entry != null) return itemBuilder(context, idx, entry); return null; }, separatorBuilder: (context, index) { if (seperatorBuilder != null) { - final entry = data.value?[index]; + final entry = data.value?.items[index]; if (entry != null) { return seperatorBuilder!(context, index, entry) ?? const SizedBox(); @@ -193,7 +195,7 @@ class PaginationList extends HookConsumerWidget { } class PaginationWidget extends HookConsumerWidget { - final ProviderListenable>> provider; + final ProviderListenable>> provider; final Refreshable> notifier; final Widget Function(List, Widget) contentBuilder; final bool isRefreshable; @@ -220,7 +222,8 @@ class PaginationWidget extends HookConsumerWidget { // For sliver cases, avoid animation to prevent complex sliver issues if (isSliver) { - if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) { + if ((data.isLoading || data.value?.isLoading == true) && + data.value?.items.isEmpty == true) { final content = List.generate( 10, (_) => Skeletonizer( @@ -254,7 +257,7 @@ class PaginationWidget extends HookConsumerWidget { skeletonChild: footerSkeletonChild, skeletonMaxWidth: footerSkeletonMaxWidth, ); - final content = contentBuilder(data.value ?? [], footer); + final content = contentBuilder(data.value?.items ?? [], footer); return isRefreshable ? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: content) @@ -263,7 +266,8 @@ class PaginationWidget extends HookConsumerWidget { // For non-sliver cases, use AnimatedSwitcher for smooth transitions Widget buildContent() { - if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) { + if ((data.isLoading || data.value?.isLoading == true) && + data.value?.items.isEmpty == true) { final content = List.generate( 10, (_) => Skeletonizer( @@ -300,7 +304,7 @@ class PaginationWidget extends HookConsumerWidget { skeletonChild: footerSkeletonChild, skeletonMaxWidth: footerSkeletonMaxWidth, ); - final content = contentBuilder(data.value ?? [], footer); + final content = contentBuilder(data.value?.items ?? [], footer); return SizedBox( key: const ValueKey('data'), @@ -319,7 +323,7 @@ class PaginationWidget extends HookConsumerWidget { class PaginationListFooter extends HookConsumerWidget { final PaginationController noti; - final AsyncValue> data; + final AsyncValue> data; final Widget? skeletonChild; final double? skeletonMaxWidth; final bool isSliver; @@ -347,7 +351,7 @@ class PaginationListFooter extends HookConsumerWidget { child: skeletonChild ?? _DefaultSkeletonChild(maxWidth: skeletonMaxWidth), ); final child = hasBeenVisible.value - ? data.isLoading + ? (data.isLoading || data.value?.isLoading == true) ? placeholder : Row( spacing: 8, @@ -363,7 +367,9 @@ class PaginationListFooter extends HookConsumerWidget { key: Key("pagination-list-${noti.hashCode}"), onVisibilityChanged: (VisibilityInfo info) { hasBeenVisible.value = true; - if (!noti.fetchedAll && !data.isLoading && !data.hasError) { + if (!noti.fetchedAll && + !(data.isLoading || data.value?.isLoading == true) && + !data.hasError) { if (context.mounted) noti.fetchFurther(); } }, diff --git a/lib/widgets/poll/poll_feedback.dart b/lib/widgets/poll/poll_feedback.dart index a200849e..156d97af 100644 --- a/lib/widgets/poll/poll_feedback.dart +++ b/lib/widgets/poll/poll_feedback.dart @@ -15,12 +15,11 @@ import 'package:island/widgets/poll/poll_stats_widget.dart'; import 'package:island/widgets/response.dart'; import 'package:styled_widget/styled_widget.dart'; -final pollFeedbackNotifierProvider = AsyncNotifierProvider.autoDispose - .family, String>( - PollFeedbackNotifier.new, - ); +final pollFeedbackNotifierProvider = AsyncNotifierProvider.autoDispose.family( + PollFeedbackNotifier.new, +); -class PollFeedbackNotifier extends AsyncNotifier> +class PollFeedbackNotifier extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; @@ -70,7 +69,8 @@ class PollFeedbackSheet extends HookConsumerWidget { return Column( children: [ _PollAnswerTile(answer: answer, poll: data), - if (index < (ref.read(provider).value?.length ?? 0) - 1) + if (index < + (ref.read(provider).value?.items.length ?? 0) - 1) const Divider(height: 1).padding(vertical: 4), ], ); diff --git a/lib/widgets/post/compose_fund.dart b/lib/widgets/post/compose_fund.dart index 0f50e48d..45bdebcf 100644 --- a/lib/widgets/post/compose_fund.dart +++ b/lib/widgets/post/compose_fund.dart @@ -44,144 +44,125 @@ class ComposeFundSheet extends HookConsumerWidget { children: [ // Link/Select existing fund list fundsData.when( - data: - (funds) => - funds.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Symbols.money_bag, - size: 48, - color: - Theme.of( - context, - ).colorScheme.outline, - ), - const Gap(16), - Text( - 'noFundsCreated'.tr(), - style: - Theme.of( - context, - ).textTheme.titleMedium, - ), - ], - ), - ) - : ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: funds.length, - itemBuilder: (context, index) { - final fund = funds[index]; - - return Card( - margin: const EdgeInsets.only(bottom: 8), - child: InkWell( - onTap: - () => - Navigator.of(context).pop(fund), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Symbols.money_bag, - color: - Theme.of( - context, - ).colorScheme.primary, - fill: 1, - ), - const Gap(8), - Expanded( - child: Text( - '${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}', - style: TextStyle( - fontSize: 18, - fontWeight: - FontWeight.bold, - color: - Theme.of(context) - .colorScheme - .primary, - ), - ), - ), - Container( - padding: - const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: - _getFundStatusColor( - context, - fund.status, - ).withOpacity(0.1), - borderRadius: - BorderRadius.circular( - 12, - ), - ), - child: Text( - _getFundStatusText( - fund.status, - ), - style: TextStyle( - color: - _getFundStatusColor( - context, - fund.status, - ), - fontSize: 12, - fontWeight: - FontWeight.w600, - ), - ), - ), - ], - ), - if (fund.message != null && - fund.message!.isNotEmpty) ...[ - const Gap(8), - Text( - fund.message!, - style: - Theme.of( - context, - ).textTheme.bodyMedium, - ), - ], - const Gap(8), - Text( - '${'recipients'.tr()}: ${fund.recipients.where((r) => r.isReceived).length}/${fund.recipients.length}', - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith( - color: - Theme.of(context) - .colorScheme - .onSurfaceVariant, - ), - ), - ], - ), - ), - ), - ); - }, + data: (funds) => funds.items.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Symbols.money_bag, + size: 48, + color: Theme.of(context).colorScheme.outline, ), - loading: - () => const Center(child: CircularProgressIndicator()), - error: - (error, stack) => Center(child: Text('Error: $error')), + const Gap(16), + Text( + 'noFundsCreated'.tr(), + style: Theme.of( + context, + ).textTheme.titleMedium, + ), + ], + ), + ) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: funds.items.length, + itemBuilder: (context, index) { + final fund = funds.items[index]; + + return Card( + margin: const EdgeInsets.only(bottom: 8), + child: InkWell( + onTap: () => Navigator.of(context).pop(fund), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Symbols.money_bag, + color: Theme.of( + context, + ).colorScheme.primary, + fill: 1, + ), + const Gap(8), + Expanded( + child: Text( + '${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Theme.of( + context, + ).colorScheme.primary, + ), + ), + ), + Container( + padding: + const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: _getFundStatusColor( + context, + fund.status, + ).withOpacity(0.1), + borderRadius: + BorderRadius.circular(12), + ), + child: Text( + _getFundStatusText(fund.status), + style: TextStyle( + color: _getFundStatusColor( + context, + fund.status, + ), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + if (fund.message != null && + fund.message!.isNotEmpty) ...[ + const Gap(8), + Text( + fund.message!, + style: Theme.of( + context, + ).textTheme.bodyMedium, + ), + ], + const Gap(8), + Text( + '${'recipients'.tr()}: ${fund.recipients.where((r) => r.isReceived).length}/${fund.recipients.length}', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + ), + ); + }, + ), + loading: () => + const Center(child: CircularProgressIndicator()), + error: (error, stack) => + Center(child: Text('Error: $error')), ), // Create new fund and return it @@ -208,127 +189,125 @@ class ComposeFundSheet extends HookConsumerWidget { Align( alignment: Alignment.centerRight, child: FilledButton.icon( - icon: - isPushing.value - ? const SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : const Icon(Symbols.add_circle), + icon: isPushing.value + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon(Symbols.add_circle), label: Text('create'.tr()), - onPressed: - isPushing.value - ? null - : () async { - errorText.value = null; + onPressed: isPushing.value + ? null + : () async { + errorText.value = null; - isPushing.value = true; - // Show modal bottom sheet with fund creation form and await result - final result = await showModalBottomSheet< - Map - >( - context: context, - useRootNavigator: true, - isScrollControlled: true, - builder: - (context) => - const CreateFundSheet(), + isPushing.value = true; + // Show modal bottom sheet with fund creation form and await result + final result = + await showModalBottomSheet< + Map + >( + context: context, + useRootNavigator: true, + isScrollControlled: true, + builder: (context) => + const CreateFundSheet(), + ); + + if (result == null) { + isPushing.value = false; + return; + } + + try { + if (!context.mounted) return; + + final client = ref.read( + apiClientProvider, + ); + showLoadingModal(context); + + final resp = await client.post( + '/pass/wallets/funds', + data: result, + options: Options( + headers: {'X-Noop': true}, + ), ); - if (result == null) { - isPushing.value = false; + final fund = SnWalletFund.fromJson( + resp.data, + ); + + if (fund.status == 0) { + // Return the fund that was just created (but not yet paid) + if (context.mounted) { + hideLoadingModal(context); + Navigator.of(context).pop(fund); + } return; } - try { - if (!context.mounted) return; + final orderResp = await client.post( + '/pass/wallets/funds/${fund.id}/order', + ); + final order = SnWalletOrder.fromJson( + orderResp.data, + ); - final client = ref.read( - apiClientProvider, - ); + if (context.mounted) { + hideLoadingModal(context); + } + + // Show payment overlay to complete the payment + if (!context.mounted) return; + final paidOrder = + await PaymentOverlay.show( + context: context, + order: order, + enableBiometric: true, + ); + + if (paidOrder != null && + context.mounted) { showLoadingModal(context); - final resp = await client.post( - '/pass/wallets/funds', - data: result, - options: Options( - headers: {'X-Noop': true}, - ), + // Wait for server to handle order + await Future.delayed( + const Duration(seconds: 1), ); + ref.invalidate(walletFundsProvider); - final fund = SnWalletFund.fromJson( - resp.data, + // Return the created fund + final updatedResp = await client.get( + '/pass/wallets/funds/${fund.id}', ); - - if (fund.status == 0) { - // Return the fund that was just created (but not yet paid) - if (context.mounted) { - hideLoadingModal(context); - Navigator.of(context).pop(fund); - } - return; - } - - final orderResp = await client.post( - '/pass/wallets/funds/${fund.id}/order', - ); - final order = SnWalletOrder.fromJson( - orderResp.data, - ); - - if (context.mounted) { - hideLoadingModal(context); - } - - // Show payment overlay to complete the payment - if (!context.mounted) return; - final paidOrder = - await PaymentOverlay.show( - context: context, - order: order, - enableBiometric: true, + final updatedFund = + SnWalletFund.fromJson( + updatedResp.data, ); - if (paidOrder != null && - context.mounted) { - showLoadingModal(context); - - // Wait for server to handle order - await Future.delayed( - const Duration(seconds: 1), - ); - ref.invalidate(walletFundsProvider); - - // Return the created fund - final updatedResp = await client.get( - '/pass/wallets/funds/${fund.id}', - ); - final updatedFund = - SnWalletFund.fromJson( - updatedResp.data, - ); - - if (context.mounted) { - hideLoadingModal(context); - Navigator.of( - context, - ).pop(updatedFund); - } - } else { - isPushing.value = false; - } - } catch (err) { if (context.mounted) { hideLoadingModal(context); + Navigator.of( + context, + ).pop(updatedFund); } - errorText.value = err.toString(); + } else { isPushing.value = false; } - }, + } catch (err) { + if (context.mounted) { + hideLoadingModal(context); + } + errorText.value = err.toString(); + isPushing.value = false; + } + }, ), ), ], diff --git a/lib/widgets/post/compose_link_attachments.dart b/lib/widgets/post/compose_link_attachments.dart index 14fbf56b..2bfe61de 100644 --- a/lib/widgets/post/compose_link_attachments.dart +++ b/lib/widgets/post/compose_link_attachments.dart @@ -13,12 +13,11 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; -final cloudFileListNotifierProvider = - AsyncNotifierProvider.autoDispose>( - CloudFileListNotifier.new, - ); +final cloudFileListNotifierProvider = AsyncNotifierProvider.autoDispose( + CloudFileListNotifier.new, +); -class CloudFileListNotifier extends AsyncNotifier> +class CloudFileListNotifier extends AsyncNotifier> with AsyncPaginationController { @override Future> fetch() async { @@ -83,28 +82,24 @@ class ComposeLinkAttachment extends HookConsumerWidget { width: 48, child: switch (itemType) { 'image' => CloudImageWidget(file: item), - 'audio' => - const Icon( - Symbols.audio_file, - fill: 1, - ).center(), - 'video' => - const Icon( - Symbols.video_file, - fill: 1, - ).center(), - _ => - const Icon( - Symbols.body_system, - fill: 1, - ).center(), + 'audio' => const Icon( + Symbols.audio_file, + fill: 1, + ).center(), + 'video' => const Icon( + Symbols.video_file, + fill: 1, + ).center(), + _ => const Icon( + Symbols.body_system, + fill: 1, + ).center(), }, ), ), - title: - item.name.isEmpty - ? Text('untitled').tr().italic() - : Text(item.name), + title: item.name.isEmpty + ? Text('untitled').tr().italic() + : Text(item.name), onTap: () { Navigator.pop(context, item); }, @@ -128,9 +123,8 @@ class ComposeLinkAttachment extends HookConsumerWidget { ), ), ), - onTapOutside: - (_) => - FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(16), InkWell( diff --git a/lib/widgets/post/compose_settings_sheet.dart b/lib/widgets/post/compose_settings_sheet.dart index 782b99be..10c5e9b9 100644 --- a/lib/widgets/post/compose_settings_sheet.dart +++ b/lib/widgets/post/compose_settings_sheet.dart @@ -291,7 +291,9 @@ class ComposeSettingsSheet extends HookConsumerWidget { ), ), hint: Text('categories'.tr(), style: TextStyle(fontSize: 15)), - items: (postCategories.value ?? []).map((item) { + items: (postCategories.value?.items ?? []).map(( + item, + ) { return DropdownMenuItem( value: item, enabled: false, @@ -337,7 +339,7 @@ class ComposeSettingsSheet extends HookConsumerWidget { value: currentCategories.isEmpty ? null : currentCategories.last, onChanged: (_) {}, selectedItemBuilder: (context) { - return (postCategories.value ?? []).map((item) { + return (postCategories.value?.items ?? []).map((item) { return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( diff --git a/lib/widgets/post/post_award_history_sheet.dart b/lib/widgets/post/post_award_history_sheet.dart index efcde706..cb41c516 100644 --- a/lib/widgets/post/post_award_history_sheet.dart +++ b/lib/widgets/post/post_award_history_sheet.dart @@ -6,12 +6,11 @@ import 'package:island/pods/paging.dart'; import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/paging/pagination_list.dart'; -final postAwardListNotifierProvider = AsyncNotifierProvider.autoDispose - .family, String>( - PostAwardListNotifier.new, - ); +final postAwardListNotifierProvider = AsyncNotifierProvider.autoDispose.family( + PostAwardListNotifier.new, +); -class PostAwardListNotifier extends AsyncNotifier> +class PostAwardListNotifier extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; @@ -52,7 +51,7 @@ class PostAwardHistorySheet extends HookConsumerWidget { return Column( children: [ PostAwardItem(award: award), - if (index < (ref.read(provider).value?.length ?? 0) - 1) + if (index < (ref.read(provider).value?.items.length ?? 0) - 1) const Divider(height: 1), ], ); diff --git a/lib/widgets/post/post_reaction_sheet.dart b/lib/widgets/post/post_reaction_sheet.dart index 5a453c58..c0eac698 100644 --- a/lib/widgets/post/post_reaction_sheet.dart +++ b/lib/widgets/post/post_reaction_sheet.dart @@ -31,12 +31,12 @@ sealed class ReactionListQuery with _$ReactionListQuery { }) = _ReactionListQuery; } -final reactionListNotifierProvider = AsyncNotifierProvider.autoDispose - .family, ReactionListQuery>( - ReactionListNotifier.new, - ); +final reactionListNotifierProvider = AsyncNotifierProvider.autoDispose.family( + ReactionListNotifier.new, +); -class ReactionListNotifier extends AsyncNotifier> +class ReactionListNotifier + extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; diff --git a/lib/widgets/post/post_replies.dart b/lib/widgets/post/post_replies.dart index ef709f44..139dc4bb 100644 --- a/lib/widgets/post/post_replies.dart +++ b/lib/widgets/post/post_replies.dart @@ -11,7 +11,7 @@ final postRepliesProvider = AsyncNotifierProvider.autoDispose.family( PostRepliesNotifier.new, ); -class PostRepliesNotifier extends AsyncNotifier> +class PostRepliesNotifier extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; diff --git a/lib/widgets/post/post_shuffle.dart b/lib/widgets/post/post_shuffle.dart index 688b1708..997a54ae 100644 --- a/lib/widgets/post/post_shuffle.dart +++ b/lib/widgets/post/post_shuffle.dart @@ -42,9 +42,9 @@ class PostShuffleScreen extends HookConsumerWidget { kBottomControlHeight + MediaQuery.of(context).padding.bottom, ), child: Builder( - key: ValueKey(postListState.value?.length ?? 0), + key: ValueKey(postListState.value?.items.length ?? 0), builder: (context) { - final items = postListState.value ?? []; + final items = postListState.value?.items ?? []; if (items.isNotEmpty) { return CardSwiper( controller: cardSwiperController, diff --git a/lib/widgets/realm/realm_list.dart b/lib/widgets/realm/realm_list.dart index a374cf25..5b5f51c0 100644 --- a/lib/widgets/realm/realm_list.dart +++ b/lib/widgets/realm/realm_list.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/realm.dart'; @@ -8,15 +10,30 @@ import 'package:island/widgets/realm/realm_list_tile.dart'; import 'package:styled_widget/styled_widget.dart'; final realmListNotifierProvider = AsyncNotifierProvider.autoDispose - .family, String?>(RealmListNotifier.new); + .family, String?>( + RealmListNotifier.new, + ); -class RealmListNotifier extends AsyncNotifier> +class RealmListNotifier extends AsyncNotifier> with AsyncPaginationController { String? arg; RealmListNotifier(this.arg); static const int _pageSize = 20; + @override + FutureOr> build() async { + final items = await fetch(); + return PaginationState( + items: items, + isLoading: false, + isReloading: false, + totalCount: totalCount, + hasMore: hasMore, + cursor: cursor, + ); + } + @override Future> fetch() async { final client = ref.read(apiClientProvider); diff --git a/lib/widgets/thought/thought_sequence_list.dart b/lib/widgets/thought/thought_sequence_list.dart index 0a7268d5..0b76c16a 100644 --- a/lib/widgets/thought/thought_sequence_list.dart +++ b/lib/widgets/thought/thought_sequence_list.dart @@ -7,13 +7,12 @@ import 'package:island/services/time.dart'; import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/paging/pagination_list.dart'; -final thoughtSequenceListNotifierProvider = AsyncNotifierProvider.autoDispose< - ThoughtSequenceListNotifier, - List ->(ThoughtSequenceListNotifier.new); +final thoughtSequenceListNotifierProvider = AsyncNotifierProvider.autoDispose( + ThoughtSequenceListNotifier.new, +); class ThoughtSequenceListNotifier - extends AsyncNotifier> + extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20;