Load more feature, closes #123

This commit is contained in:
franzap
2024-10-13 16:19:45 -03:00
parent 17d75ec56d
commit ba82286bc7
4 changed files with 90 additions and 21 deletions
+5 -1
View File
@@ -42,8 +42,12 @@ class App extends BaseApp with DataModelMixin<App> {
Map<String, dynamic> toJson() => super.toMap();
Release? get latestRelease {
return releases.ordered.firstOrNull;
}
FileMetadata? get latestMetadata {
return releases.ordered.firstOrNull?.artifacts
return latestRelease?.artifacts
.where((a) =>
a.mimeType == kAndroidMimeType &&
a.platforms.contains('android-arm64-v8a'))
+3
View File
@@ -147,3 +147,6 @@ mixin UserAdapter on NostrAdapter<User> {
return users!;
}
}
const kZapstorePubkey =
'78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d';
+3 -1
View File
@@ -13,8 +13,9 @@ import 'package:zapstore/widgets/rounded_image.dart';
class AppCard extends HookConsumerWidget {
final App? app;
final bool showDate;
const AppCard({super.key, required this.app});
const AppCard({super.key, required this.app, this.showDate = false});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -115,6 +116,7 @@ class AppCard extends HookConsumerWidget {
oneLine: true,
size: 12,
),
if (showDate) Text(app!.createdAt!.toIso8601String()),
],
),
),
+79 -19
View File
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:async_button_builder/async_button_builder.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
@@ -9,26 +10,78 @@ import 'package:zapstore/models/app.dart';
import 'package:zapstore/widgets/app_card.dart';
class LatestReleasesContainer extends HookConsumerWidget {
const LatestReleasesContainer({super.key});
final ScrollController scrollController;
const LatestReleasesContainer({super.key, required this.scrollController});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(latestReleasesAppProvider);
if (state.hasError) {
return Text(state.error.toString());
}
final apps = state.value ?? [];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Latest releases',
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Latest releases',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
Gap(20),
if (state.isLoading || state.isRefreshing || state.isReloading)
SizedBox(
height: 14,
width: 14,
child: CircularProgressIndicator(strokeWidth: 4),
),
ElevatedButton(
onPressed: () {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
},
style:
ElevatedButton.styleFrom(backgroundColor: Colors.transparent),
child: Text('See more'),
)
],
),
Gap(12),
Column(
children: (apps.isEmpty ? [null, null, null] : apps)
.map((app) => AppCard(app: app))
.toList(),
children: [
if (state.hasError) Text('Error fetching: ${state.error}'),
if (state.isLoading)
for (final _ in List.generate(4, (_) => _)) AppCard(app: null),
if (state.hasValue)
for (final app in state.value!) AppCard(app: app),
AsyncButtonBuilder(
loadingWidget: SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(),
),
onPressed: () async {
return ref
.read(latestReleasesAppProvider.notifier)
.fetch(next: true);
},
builder: (context, child, callback, state) {
return Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SizedBox(
child: ElevatedButton(
onPressed: callback,
style: ElevatedButton.styleFrom(
disabledBackgroundColor: Colors.transparent,
backgroundColor: Colors.grey[900]),
child: child,
),
),
);
},
child: Text('Load more'),
),
],
)
],
);
@@ -36,28 +89,35 @@ class LatestReleasesContainer extends HookConsumerWidget {
}
class LatestReleasesAppNotifier extends AutoDisposeAsyncNotifier<List<App>> {
int? oldestTimestamp;
int page = 1;
@override
Future<List<App>> build() async {
final timer = Timer.periodic(Duration(minutes: 5), (_) => fetch());
// TODO: Should be ref.watching a pool state change (from purplebase)
final timer = Timer.periodic(Duration(minutes: 1), (_) => fetch());
ref.onDispose(timer.cancel);
// Trigger fetch ONLY on the most recent 10 (next=false)
fetch();
// .catchError((e) {
// print('caught here');
// throw e;
// });
return localFetch();
}
List<App> localFetch() {
return ref.apps
final apps = ref.apps
.findAllLocal()
.sorted((a, b) => b.createdAt!.compareTo(a.createdAt!))
.take(10)
.take(page * 10)
.toList();
oldestTimestamp = apps.last.createdAtMs;
return apps;
}
Future<void> fetch() async {
await ref.apps.findAll(params: {'limit': 10});
Future<void> fetch({bool next = false}) async {
if (next) {
page++;
}
await ref.apps
.findAll(params: {'limit': 10, 'until': next ? oldestTimestamp : null});
update((_) => localFetch());
}
}