Files
zapstore/lib/widgets/latest_releases_container.dart
T

144 lines
4.4 KiB
Dart

import 'dart:async';
import 'package:async_button_builder/async_button_builder.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/widgets/app_card.dart';
class LatestReleasesContainer extends HookConsumerWidget {
final ScrollController scrollController;
const LatestReleasesContainer({super.key, required this.scrollController});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(latestReleasesAppProvider);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Latest releases',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
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: [
if (state.hasError) Text('Error fetching: ${state.error}'),
if (state.isLoading)
for (final _ in List.generate(3, (_) => _)) SkeletonAppCard(),
if (state.hasValue)
// NOTE: Since we're showing apps but it's really a list of releases
// apps will appear repeated, to convert to set
for (final app in state.value!) AppCard(model: app),
if (state.hasValue)
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'),
),
],
)
],
);
}
}
class LatestReleasesAppNotifier extends StateNotifier<AsyncValue<List<App>>> {
DateTime? oldestCreatedAt;
int page = 1;
Ref ref;
LatestReleasesAppNotifier(this.ref) : super(AsyncLoading());
// @override
// Future<List<App>> build() async {
// print('in latestrelease build');
// await fetch();
// return _fetchLocal();
// }
Future<void> fetch({bool next = false}) async {
if (next) {
page++;
} else {
final apps = _fetchLocal();
if (apps.isNotEmpty) {
state = AsyncData(apps);
return;
}
}
// Use ignore return, only saved models is important
// TODO: ignoreReturn, and bip340 validate in isolate
await ref.apps.findAll(
params: {
'includes': true,
'limit': 10,
'until': next ? oldestCreatedAt : null
},
);
state = AsyncData(_fetchLocal());
}
List<App> _fetchLocal() {
final model = ref.apps.findAllLocal();
final apps = model
.where((a) => a.latestMetadata != null)
.sortedByLatest
.take(page * 10)
.toList();
// Set timestamp of oldest, to prepare for next query
if (apps.isNotEmpty) {
oldestCreatedAt = apps.last.createdAt;
}
return apps;
}
}
final latestReleasesAppProvider =
StateNotifierProvider<LatestReleasesAppNotifier, AsyncValue<List<App>>>(
LatestReleasesAppNotifier.new);