mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 20:48:24 +00:00
Merge branch 'zapstore:master' into feat/web-links
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
# 0.1.8
|
||||
|
||||
- Bugfix: Prevent unnecessary relay initialization
|
||||
- Bugfix: [purplebase] Block until enough relay EOSEs
|
||||
- Bugfix: Fix regression, offline-first models
|
||||
|
||||
# 0.1.7
|
||||
|
||||
- Performance: Faster, background downloads (#172)
|
||||
|
||||
@@ -13,11 +13,12 @@ import 'package:zapstore/widgets/latest_releases_container.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.zapstore.dev'};
|
||||
const kSocialRelays = {'wss://relay.damus.io', 'wss://relay.nostr.band'};
|
||||
const kSocialRelays = {'wss://relay.damus.io', 'wss://relay.primal.net'};
|
||||
|
||||
mixin NostrAdapter<T extends DataModelMixin<T>> on Adapter<T> {
|
||||
RelayMessageNotifier get relay =>
|
||||
ref.read(relayProviderFamily(kAppRelays).notifier);
|
||||
|
||||
RelayMessageNotifier get socialRelays =>
|
||||
ref.read(relayProviderFamily(kSocialRelays).notifier);
|
||||
|
||||
@@ -25,24 +26,29 @@ mixin NostrAdapter<T extends DataModelMixin<T>> on Adapter<T> {
|
||||
Future<void> onInitialized() async {
|
||||
await super.onInitialized();
|
||||
|
||||
// Upon adapter initialization, configure relays
|
||||
// with the event verification caching function
|
||||
relay.configure(
|
||||
isEventVerified: (Map<String, dynamic> map) {
|
||||
// If replaceable, we check for that ID
|
||||
final identifier = (map['tags'] as Iterable)
|
||||
.firstWhereOrNull((t) => t[0] == 'd')?[1]
|
||||
?.toString();
|
||||
final id = identifier != null
|
||||
? (map['kind'] as int, map['pubkey'].toString(), identifier)
|
||||
.formatted
|
||||
: map['id'];
|
||||
return ref.apps.nostrAdapter.existsId(id);
|
||||
},
|
||||
);
|
||||
socialRelays.configure(
|
||||
isEventVerified: (map) => ref.apps.nostrAdapter.existsId(map['pubkey']),
|
||||
);
|
||||
// DO NOT run on isolates, the mere fact of calling the
|
||||
// relay getter will trigger its initialization
|
||||
// as providers are obviously not cached across isolates
|
||||
if (!inIsolate) {
|
||||
// Upon adapter initialization, configure relays
|
||||
// with the event verification caching function
|
||||
relay.configure(
|
||||
isEventVerified: (Map<String, dynamic> map) {
|
||||
// If replaceable, we check for that ID
|
||||
final identifier = (map['tags'] as Iterable)
|
||||
.firstWhereOrNull((t) => t[0] == 'd')?[1]
|
||||
?.toString();
|
||||
final id = identifier != null
|
||||
? (map['kind'] as int, map['pubkey'].toString(), identifier)
|
||||
.formatted
|
||||
: map['id'];
|
||||
return ref.apps.nostrAdapter.existsId(id);
|
||||
},
|
||||
);
|
||||
socialRelays.configure(
|
||||
isEventVerified: (map) => ref.apps.nostrAdapter.existsId(map['pubkey']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
int get kind {
|
||||
@@ -182,7 +188,7 @@ class RelayListenerNotifier extends Notifier<void> {
|
||||
|
||||
Future<void> fetch() async {
|
||||
await Future.microtask(() async {
|
||||
await ref.read(latestReleasesAppProvider.notifier).fetch();
|
||||
await ref.read(latestReleasesAppProvider.notifier).fetchRemote();
|
||||
await ref.apps.appAdapter.checkForUpdates();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,12 +31,12 @@ final appInitializer = FutureProvider<void>((ref) async {
|
||||
if (ref.appCurationSets.countLocal == 0) {
|
||||
// If we are here, local storage is empty
|
||||
// Preload curation sets
|
||||
await ref.appCurationSets.findAll();
|
||||
await ref.appCurationSets.findAll(syncLocal: true);
|
||||
// Preload updates in the background (no await)
|
||||
ref.apps.appAdapter.checkForUpdates();
|
||||
} else {
|
||||
// Preload curation sets in the background (no await)
|
||||
ref.appCurationSets.findAll();
|
||||
ref.appCurationSets.findAll(syncLocal: true, background: true);
|
||||
}
|
||||
|
||||
// Preload zapstore's nostr curation set
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
|
||||
abstract class PreloadingStateNotifier<T> extends StateNotifier<AsyncValue<T>> {
|
||||
final Ref ref;
|
||||
PreloadingStateNotifier(this.ref) : super(AsyncLoading()) {
|
||||
state = fetchLocal();
|
||||
fetchRemote();
|
||||
}
|
||||
|
||||
AsyncValue<T> fetchLocal();
|
||||
Future<void> fetchRemote();
|
||||
}
|
||||
@@ -18,8 +18,11 @@ class AppCurationContainer extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final scrollController = useScrollController();
|
||||
final selectedAppCurationSet = ref.watch(_selectedIdProvider);
|
||||
final appCurationSets =
|
||||
ref.appCurationSets.findAllLocal().sortedBy((s) => s.createdAt!);
|
||||
final appCurationSets = ref.appCurationSets
|
||||
.findAllLocal()
|
||||
.sortedBy((s) => s.createdAt!)
|
||||
.reversed
|
||||
.toList();
|
||||
|
||||
// Custom curation set to place nostr set first (as its preloaded)
|
||||
final nostrCurationSet = appCurationSets.firstWhereOrNull(
|
||||
@@ -108,9 +111,12 @@ class AppCurationSetNotifier
|
||||
|
||||
Future<void> fetch() async {
|
||||
final appCurationSet = await future;
|
||||
state = AsyncLoading();
|
||||
|
||||
if (appCurationSet.apps.isEmpty) {
|
||||
state = AsyncLoading();
|
||||
}
|
||||
state = await AsyncValue.guard(() async {
|
||||
// Only load apps (no releases for now)
|
||||
// Only load apps (no releases for now, includes=false)
|
||||
// We can use ignoreReturn here as we do not care about releases
|
||||
await ref.apps.findAll(
|
||||
params: {
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:zapstore/widgets/rounded_image.dart';
|
||||
class AuthorContainer extends StatelessWidget {
|
||||
final User user;
|
||||
final String beforeText;
|
||||
final String afterText;
|
||||
final String? afterText;
|
||||
final bool oneLine;
|
||||
final double size;
|
||||
|
||||
@@ -15,7 +15,7 @@ class AuthorContainer extends StatelessWidget {
|
||||
super.key,
|
||||
required this.user,
|
||||
this.beforeText = 'Signed by',
|
||||
this.afterText = '',
|
||||
this.afterText,
|
||||
this.oneLine = true,
|
||||
this.size = 14,
|
||||
});
|
||||
@@ -49,7 +49,11 @@ class AuthorContainer extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(afterText),
|
||||
if (afterText != null)
|
||||
Text(
|
||||
afterText!,
|
||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
||||
@@ -6,6 +6,7 @@ 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/utils/notifier.dart';
|
||||
import 'package:zapstore/widgets/app_card.dart';
|
||||
|
||||
class LatestReleasesContainer extends HookConsumerWidget {
|
||||
@@ -58,7 +59,7 @@ class LatestReleasesContainer extends HookConsumerWidget {
|
||||
onPressed: () async {
|
||||
return ref
|
||||
.read(latestReleasesAppProvider.notifier)
|
||||
.fetch(next: true);
|
||||
.fetchRemote(next: true);
|
||||
},
|
||||
builder: (context, child, callback, state) {
|
||||
return Padding(
|
||||
@@ -83,16 +84,14 @@ class LatestReleasesContainer extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class LatestReleasesAppNotifier extends StateNotifier<AsyncValue<List<App>>> {
|
||||
class LatestReleasesAppNotifier extends PreloadingStateNotifier<List<App>> {
|
||||
DateTime? _oldestCreatedAt;
|
||||
int _page = 1;
|
||||
final Ref ref;
|
||||
|
||||
LatestReleasesAppNotifier(this.ref) : super(AsyncLoading()) {
|
||||
fetch();
|
||||
}
|
||||
LatestReleasesAppNotifier(super.ref);
|
||||
|
||||
Future<void> fetch({bool next = false}) async {
|
||||
@override
|
||||
Future<void> fetchRemote({bool next = false}) async {
|
||||
if (next) {
|
||||
_page++;
|
||||
}
|
||||
@@ -107,12 +106,17 @@ class LatestReleasesAppNotifier extends StateNotifier<AsyncValue<List<App>>> {
|
||||
},
|
||||
);
|
||||
|
||||
state = AsyncData(_fetchLocal());
|
||||
state = fetchLocal();
|
||||
}
|
||||
|
||||
List<App> _fetchLocal() {
|
||||
@override
|
||||
AsyncValue<List<App>> fetchLocal() {
|
||||
final model = ref.apps.findAllLocal();
|
||||
|
||||
if (model.isEmpty) {
|
||||
return AsyncLoading();
|
||||
}
|
||||
|
||||
final apps = model
|
||||
.where((a) => a.latestMetadata != null)
|
||||
.sortedByLatest
|
||||
@@ -123,7 +127,7 @@ class LatestReleasesAppNotifier extends StateNotifier<AsyncValue<List<App>>> {
|
||||
if (apps.isNotEmpty) {
|
||||
_oldestCreatedAt = apps.last.createdAt;
|
||||
}
|
||||
return apps;
|
||||
return AsyncData(apps);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class SignerAndDeveloperRow extends ConsumerWidget {
|
||||
settingsState.model!.trustedUsers
|
||||
.contains(app.signer.value!)
|
||||
? '(trusted)'
|
||||
: '',
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
+2
-2
@@ -938,8 +938,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: cbe8342031adf908c8243125e176f52739d400d8
|
||||
resolved-ref: cbe8342031adf908c8243125e176f52739d400d8
|
||||
ref: "7f0eefd1fd1bcbc947123dc377183dbae58ea97a"
|
||||
resolved-ref: "7f0eefd1fd1bcbc947123dc377183dbae58ea97a"
|
||||
url: "https://github.com/purplebase/purplebase"
|
||||
source: git
|
||||
version: "0.2.0"
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ name: zapstore
|
||||
description: The permissionless app store
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.1.7+17
|
||||
version: 0.1.8+18
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.0 <4.0.0'
|
||||
@@ -57,7 +57,7 @@ dependency_overrides:
|
||||
# path: ../purplebase
|
||||
git:
|
||||
url: https://github.com/purplebase/purplebase
|
||||
ref: cbe8342031adf908c8243125e176f52739d400d8
|
||||
ref: 7f0eefd1fd1bcbc947123dc377183dbae58ea97a
|
||||
flutter_data:
|
||||
git:
|
||||
url: https://github.com/zapstore/flutter_data
|
||||
|
||||
Reference in New Issue
Block a user