mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 20:48:24 +00:00
Fix latest release queries (remote and local), load users once
This commit is contained in:
+60
-35
@@ -43,7 +43,7 @@ class App extends BaseApp with DataModelMixin<App> {
|
||||
Map<String, dynamic> toJson() => super.toMap();
|
||||
|
||||
Release? get latestRelease {
|
||||
return releases.ordered.firstOrNull;
|
||||
return releases.toList().sortedByLatest.firstOrNull;
|
||||
}
|
||||
|
||||
FileMetadata? get latestMetadata {
|
||||
@@ -168,48 +168,68 @@ class App extends BaseApp with DataModelMixin<App> {
|
||||
|
||||
mixin AppAdapter on Adapter<App> {
|
||||
Future<List<App>> fetchAppModels(Map<String, dynamic> params) async {
|
||||
final apps = await super.findAll(
|
||||
params: {
|
||||
...params,
|
||||
'#f': ['android-arm64-v8a'],
|
||||
},
|
||||
);
|
||||
// Find all appid@version ($3) as we need to pick one tag to query on
|
||||
// (filters by kind ($1) and pubkey ($2) done locally)
|
||||
final latestReleaseIdentifiers =
|
||||
apps.map((app) => app.linkedReplaceableEvents.firstOrNull?.$3).nonNulls;
|
||||
final releases = await ref.releases.findAll(
|
||||
params: {'#d': latestReleaseIdentifiers},
|
||||
);
|
||||
final byRelease = params.remove('by-release');
|
||||
|
||||
// TODO: Deprecated, will be removed
|
||||
// Some developers without access to the latest zapstore-cli
|
||||
// have not published their apps with latest release identifiers
|
||||
// so load as usual
|
||||
final deprecatedApps =
|
||||
apps.where((app) => app.linkedReplaceableEvents.isEmpty);
|
||||
final deprecatedReleases = await ref.releases.findAll(
|
||||
params: {
|
||||
'#a':
|
||||
deprecatedApps.map((app) => app.getReplaceableEventLink().formatted)
|
||||
},
|
||||
);
|
||||
late final List<App> apps;
|
||||
late final List<Release> releases;
|
||||
if (byRelease != null) {
|
||||
// This param fetches releases with params and then apps
|
||||
releases = await ref.releases.findAll(params: params);
|
||||
final appIdentifiers =
|
||||
releases.map((r) => r.linkedReplaceableEvents.first.$3).nonNulls;
|
||||
apps = await super.findAll(
|
||||
params: {
|
||||
'#d': appIdentifiers,
|
||||
'#f': ['android-arm64-v8a'],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
apps = await super.findAll(
|
||||
params: {
|
||||
...params,
|
||||
'#f': ['android-arm64-v8a'],
|
||||
},
|
||||
);
|
||||
// Find all appid@version ($3) as we need to pick one tag to query on
|
||||
// (filters by kind ($1) and pubkey ($2) done locally)
|
||||
final latestReleaseIdentifiers = apps
|
||||
.map((app) => app.linkedReplaceableEvents.firstOrNull?.$3)
|
||||
.nonNulls;
|
||||
releases = await ref.releases.findAll(
|
||||
params: {'#d': latestReleaseIdentifiers},
|
||||
);
|
||||
|
||||
final groupedDeprecatedReleases =
|
||||
deprecatedReleases.groupListsBy((r) => r.app.value!);
|
||||
for (final e in groupedDeprecatedReleases.entries) {
|
||||
final mostRecentRelease =
|
||||
e.value.sorted((a, b) => b.createdAt!.compareTo(a.createdAt!)).first;
|
||||
releases.add(mostRecentRelease);
|
||||
// TODO: Deprecated, will be removed
|
||||
// Some developers without access to the latest zapstore-cli
|
||||
// have not published their apps with latest release identifiers
|
||||
// so load as usual
|
||||
final deprecatedApps =
|
||||
apps.where((app) => app.linkedReplaceableEvents.isEmpty);
|
||||
final deprecatedReleases = await ref.releases.findAll(
|
||||
params: {
|
||||
'#a': deprecatedApps
|
||||
.map((app) => app.getReplaceableEventLink().formatted)
|
||||
},
|
||||
);
|
||||
|
||||
final groupedDeprecatedReleases =
|
||||
deprecatedReleases.groupListsBy((r) => r.app.value!);
|
||||
for (final e in groupedDeprecatedReleases.entries) {
|
||||
final mostRecentRelease = e.value.sortedByLatest.first;
|
||||
releases.add(mostRecentRelease);
|
||||
}
|
||||
// End deprecated
|
||||
}
|
||||
// End deprecated
|
||||
|
||||
final metadataIds =
|
||||
releases.map((r) => r.linkedEvents).nonNulls.expand((_) => _);
|
||||
|
||||
// For now load users only once
|
||||
final userIds = {
|
||||
for (final app in apps) app.signer.id,
|
||||
for (final app in apps) app.developer.id
|
||||
for (final app in apps)
|
||||
if (!app.signer.isPresent) app.signer.id,
|
||||
for (final app in apps)
|
||||
if (!app.developer.isPresent) app.developer.id,
|
||||
}.nonNulls;
|
||||
|
||||
// Metadata and users probably go to separate relays
|
||||
@@ -335,6 +355,11 @@ Future<bool> _isHashMismatch(String path, String hash) async {
|
||||
});
|
||||
}
|
||||
|
||||
extension AppExt on Iterable<App> {
|
||||
List<App> get sortedByLatest =>
|
||||
sorted((a, b) => b.createdAt!.compareTo(a.createdAt!));
|
||||
}
|
||||
|
||||
// class
|
||||
|
||||
sealed class AppInstallProgress {}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:purplebase/purplebase.dart';
|
||||
|
||||
// NOTE: Very important to use const in relay args to preserve equality in Riverpod families
|
||||
const kAppRelays = ['ws://10.0.2.2:3000'];
|
||||
// const kAppRelays = ['wss://relay.zap.store'];
|
||||
// const kAppRelays = ['ws://10.0.2.2:3000'];
|
||||
const kAppRelays = ['wss://relay.zap.store'];
|
||||
const kSocialRelays = ['wss://relay.primal.net', 'wss://relay.nostr.band'];
|
||||
|
||||
mixin NostrAdapter<T extends DataModelMixin<T>> on Adapter<T> {
|
||||
|
||||
@@ -46,7 +46,7 @@ mixin ReleaseAdapter on Adapter<Release> {
|
||||
}
|
||||
}
|
||||
|
||||
extension HasManyReleaseX on HasMany<Release> {
|
||||
List<Release> get ordered =>
|
||||
toList().sorted((a, b) => b.createdAt!.compareTo(a.createdAt!));
|
||||
extension ReleaseExt on Iterable<Release> {
|
||||
List<Release> get sortedByLatest =>
|
||||
sorted((a, b) => b.createdAt!.compareTo(a.createdAt!));
|
||||
}
|
||||
|
||||
@@ -175,6 +175,13 @@ final dataLibrariesInitializer = FutureProvider<void>((ref) async {
|
||||
// In this initial phase, load there more or less fixed curation sets here
|
||||
await ref.appCurationSets.findAll();
|
||||
|
||||
await ref.apps.findAll(
|
||||
params: {
|
||||
'by-release': true,
|
||||
'limit': 10,
|
||||
},
|
||||
);
|
||||
|
||||
// Handle deep links
|
||||
final appLinksSub = appLinks.uriLinkStream.listen((uri) async {
|
||||
if (uri.scheme == "zapstore") {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:zapstore/main.data.dart';
|
||||
import 'package:zapstore/models/app.dart';
|
||||
import 'package:zapstore/models/release.dart';
|
||||
import 'package:zapstore/widgets/app_card.dart';
|
||||
|
||||
class LatestReleasesContainer extends HookConsumerWidget {
|
||||
@@ -53,6 +53,8 @@ class LatestReleasesContainer extends HookConsumerWidget {
|
||||
if (state.isLoading)
|
||||
for (final _ in List.generate(3, (_) => _)) AppCard(app: null),
|
||||
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(app: app),
|
||||
if (state.hasValue)
|
||||
AsyncButtonBuilder(
|
||||
@@ -96,31 +98,46 @@ class LatestReleasesAppNotifier extends AutoDisposeAsyncNotifier<List<App>> {
|
||||
@override
|
||||
Future<List<App>> build() async {
|
||||
// TODO: Should be ref.watching a pool state change (from purplebase)
|
||||
final timer = Timer.periodic(Duration(minutes: 1), (_) => fetch());
|
||||
final timer = Timer.periodic(Duration(minutes: 10), (_) => fetch());
|
||||
ref.onDispose(timer.cancel);
|
||||
// Trigger fetch ONLY on the most recent 10 (next=false)
|
||||
await fetch();
|
||||
|
||||
// Do not fetch here as first 10 are preloaded
|
||||
// await fetch();
|
||||
return localFetch();
|
||||
}
|
||||
|
||||
List<App> localFetch() {
|
||||
final apps = ref.apps
|
||||
// Find all releases that are the latest of each, and sort chronologically
|
||||
final releases = ref.releases
|
||||
.findAllLocal()
|
||||
.sorted((a, b) => b.createdAt!.compareTo(a.createdAt!))
|
||||
.where((r) => r.app.value?.latestMetadata != null)
|
||||
.sortedByLatest;
|
||||
// Set timestamp of oldest, to prepare for next query
|
||||
if (releases.isNotEmpty) {
|
||||
oldestTimestamp = releases.last.createdAtMs;
|
||||
}
|
||||
// Return only (first 10 * page) releases that have an associated app
|
||||
// (it should be the case, but keep as we migrate
|
||||
// from older event format)
|
||||
return releases
|
||||
.map((r) => r.app.value)
|
||||
.nonNulls
|
||||
.toSet()
|
||||
.take(page * 10)
|
||||
.toList();
|
||||
if (apps.isNotEmpty) {
|
||||
oldestTimestamp = apps.last.createdAtMs;
|
||||
}
|
||||
return apps;
|
||||
}
|
||||
|
||||
Future<void> fetch({bool next = false}) async {
|
||||
if (next) {
|
||||
page++;
|
||||
}
|
||||
await ref.apps
|
||||
.findAll(params: {'limit': 10, 'until': next ? oldestTimestamp : null});
|
||||
await ref.apps.findAll(
|
||||
params: {
|
||||
'by-release': true,
|
||||
'limit': 10,
|
||||
'until': next ? oldestTimestamp : null
|
||||
},
|
||||
);
|
||||
update((_) => localFetch());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user