diff --git a/lib/screens/app_stacks_screen.dart b/lib/screens/app_stacks_screen.dart index 9be8fd6..933b171 100644 --- a/lib/screens/app_stacks_screen.dart +++ b/lib/screens/app_stacks_screen.dart @@ -17,6 +17,9 @@ import 'package:zapstore/widgets/app_stack_container.dart'; class StacksNotifier extends PagedSubscriptionNotifier { StacksNotifier(super.ref, {required this.platform}); + @override + int get pageSize => 20; + final String platform; ProviderSubscription>? _sub; @@ -31,14 +34,12 @@ class StacksNotifier extends PagedSubscriptionNotifier { _sub = ref.listen( query( tags: _tags, - source: const LocalAndRemoteSource(relays: 'AppCatalog', stream: false), + until: DateTime.now(), + limit: pageSize, + source: const LocalAndRemoteSource(relays: 'AppCatalog', stream: true), subscriptionPrefix: 'app-stacks', - schemaFilter: appStackEventFilter, ), - (_, next) { - updateFirstPage(next); - state = state.copyWith(hasMore: false); - }, + (_, next) => updateFirstPage(next), fireImmediately: true, ); } @@ -47,7 +48,17 @@ class StacksNotifier extends PagedSubscriptionNotifier { Future<({List items, int count})> fetchOlderPage( DateTime until, ) async { - return (items: [], count: 0); + final storage = ref.read(storageNotifierProvider.notifier); + final items = await storage.query( + RequestFilter( + tags: _tags, + until: until, + limit: pageSize, + ).toRequest(), + source: const LocalAndRemoteSource(relays: 'AppCatalog', stream: false), + subscriptionPrefix: 'app-stacks-older', + ); + return (items: items, count: items.length); } @override @@ -106,8 +117,7 @@ class AppStacksScreen extends HookConsumerWidget { final platform = ref.read(packageManagerProvider.notifier).platform; final state = ref.watch(stacksProvider); - final items = state.combined - ..sort((a, b) => b.event.createdAt.compareTo(a.event.createdAt)); + final items = state.combined; // Query signed-in user's stacks that may need migration final signedInPubkey = ref.watch(Signer.activePubkeyProvider); @@ -174,6 +184,37 @@ class AppStacksScreen extends HookConsumerWidget { final isInitialLoading = state.firstPage is StorageLoading && items.isEmpty; + useEffect(() { + void onScroll() { + if (!scrollController.hasClients) return; + final s = ref.read(stacksProvider); + if (s.isLoadingMore || !s.hasMore) return; + final pos = scrollController.position; + if (pos.pixels >= pos.maxScrollExtent - 300) { + ref.read(stacksProvider.notifier).loadMore(); + } + } + + scrollController.addListener(onScroll); + return () => scrollController.removeListener(onScroll); + }, [scrollController]); + + useEffect(() { + if (state.firstPage is StorageLoading || + state.isLoadingMore || + !state.hasMore) { + return null; + } + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!scrollController.hasClients) return; + final pos = scrollController.position; + if (pos.maxScrollExtent <= 0) { + ref.read(stacksProvider.notifier).loadMore(); + } + }); + return null; + }, [state.combined.length, state.isLoadingMore]); + return Scaffold( body: CustomScrollView( controller: scrollController, @@ -256,6 +297,13 @@ class AppStacksScreen extends HookConsumerWidget { }, childCount: items.length), ), ), + if (state.isLoadingMore) + const SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.all(16.0), + child: Center(child: CircularProgressIndicator()), + ), + ), const SliverToBoxAdapter(child: SizedBox(height: 16)), ], ), diff --git a/lib/utils/paged_subscription_notifier.dart b/lib/utils/paged_subscription_notifier.dart index 547162b..5b3991f 100644 --- a/lib/utils/paged_subscription_notifier.dart +++ b/lib/utils/paged_subscription_notifier.dart @@ -108,7 +108,7 @@ abstract class PagedSubscriptionNotifier> final oldest = combined .map(getCreatedAt) .reduce((a, b) => a.isBefore(b) ? a : b) - .subtract(const Duration(milliseconds: 1)); + .subtract(const Duration(seconds: 1)); state = state.copyWith(isLoadingMore: true); @@ -119,10 +119,11 @@ abstract class PagedSubscriptionNotifier> final unique = result.items .where((item) => !existingIds.contains(getId(item))) .toList(); + final hasMore = unique.isNotEmpty && result.count >= pageSize; state = state.copyWith( olderItems: [...state.olderItems, ...unique], isLoadingMore: false, - hasMore: result.count >= pageSize, + hasMore: hasMore, ); } else { state = state.copyWith(isLoadingMore: false, hasMore: false);