Fix stacks infinite loading

This commit is contained in:
franzap
2026-04-06 19:46:49 -03:00
parent cd47a27e15
commit 862240a5b5
2 changed files with 60 additions and 11 deletions
+57 -9
View File
@@ -17,6 +17,9 @@ import 'package:zapstore/widgets/app_stack_container.dart';
class StacksNotifier extends PagedSubscriptionNotifier<AppStack> {
StacksNotifier(super.ref, {required this.platform});
@override
int get pageSize => 20;
final String platform;
ProviderSubscription<StorageState<AppStack>>? _sub;
@@ -31,14 +34,12 @@ class StacksNotifier extends PagedSubscriptionNotifier<AppStack> {
_sub = ref.listen(
query<AppStack>(
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<AppStack> {
Future<({List<AppStack> items, int count})> fetchOlderPage(
DateTime until,
) async {
return (items: <AppStack>[], count: 0);
final storage = ref.read(storageNotifierProvider.notifier);
final items = await storage.query(
RequestFilter<AppStack>(
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)),
],
),
+3 -2
View File
@@ -108,7 +108,7 @@ abstract class PagedSubscriptionNotifier<T extends Model<dynamic>>
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<T extends Model<dynamic>>
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);