mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 20:48:24 +00:00
Stacks should query apps with full pubkey + app ID
This commit is contained in:
@@ -63,24 +63,30 @@ class _AppStackContentWithApps extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// Derive identifiers directly from the raw event tags — stable and complete
|
||||
// regardless of what's been cached locally. The 'a' tag format is
|
||||
// '32267:pubkey:d-tag'; we extract just the d-tag (identifier) part.
|
||||
final appIdentifiers = stack.event
|
||||
final appAddressableIds = stack.event
|
||||
.getTagSetValues('a')
|
||||
.where((id) => id.startsWith('32267:'))
|
||||
.map((id) => id.split(':').skip(2).join(':'))
|
||||
.toSet();
|
||||
|
||||
if (appIdentifiers.isEmpty) {
|
||||
if (appAddressableIds.isEmpty) {
|
||||
return _AppStackContent(stack: stack, apps: const []);
|
||||
}
|
||||
|
||||
// Query apps with release and metadata relationships (same pattern as search/user screens)
|
||||
final authors = <String>{};
|
||||
final identifiers = <String>{};
|
||||
for (final id in appAddressableIds) {
|
||||
final parts = id.split(':');
|
||||
if (parts.length >= 3) {
|
||||
authors.add(parts[1]);
|
||||
identifiers.add(parts.skip(2).join(':'));
|
||||
}
|
||||
}
|
||||
|
||||
final appsState = ref.watch(
|
||||
query<App>(
|
||||
authors: authors,
|
||||
tags: {
|
||||
'#d': appIdentifiers,
|
||||
'#d': identifiers,
|
||||
'#f': {'android-arm64-v8a'},
|
||||
},
|
||||
and: (app) => {
|
||||
@@ -96,9 +102,9 @@ class _AppStackContentWithApps extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
|
||||
// Preserve the original stack order where possible
|
||||
final appsMap = {for (final app in appsState.models) app.identifier: app};
|
||||
final orderedApps = appIdentifiers
|
||||
// Key by addressable ID and preserve the original stack order
|
||||
final appsMap = {for (final app in appsState.models) app.id: app};
|
||||
final orderedApps = appAddressableIds
|
||||
.map((id) => appsMap[id])
|
||||
.whereType<App>()
|
||||
.toList();
|
||||
|
||||
@@ -156,18 +156,21 @@ class AppStacksScreen extends HookConsumerWidget {
|
||||
};
|
||||
final isAuthorsLoading = authorsState is StorageLoading;
|
||||
|
||||
final allPreviewIdentifiers = <String>{};
|
||||
final allPreviewIds = <String>{};
|
||||
final stackPreviewIds = <String, List<String>>{};
|
||||
for (final stack in items) {
|
||||
final ids = getPreviewIdentifiers(stack);
|
||||
final ids = getPreviewAddressableIds(stack);
|
||||
stackPreviewIds[stack.id] = ids;
|
||||
allPreviewIdentifiers.addAll(ids);
|
||||
allPreviewIds.addAll(ids);
|
||||
}
|
||||
|
||||
final previewAppsState = allPreviewIdentifiers.isNotEmpty
|
||||
final (:authors, :identifiers) = decomposeAddressableIds(allPreviewIds);
|
||||
|
||||
final previewAppsState = allPreviewIds.isNotEmpty
|
||||
? ref.watch(
|
||||
query<App>(
|
||||
tags: {'#d': allPreviewIdentifiers},
|
||||
authors: authors,
|
||||
tags: {'#d': identifiers},
|
||||
source: const LocalAndRemoteSource(
|
||||
relays: 'AppCatalog',
|
||||
stream: false,
|
||||
@@ -179,7 +182,7 @@ class AppStacksScreen extends HookConsumerWidget {
|
||||
|
||||
final appsMap = {
|
||||
for (final app in previewAppsState?.models ?? <App>[])
|
||||
app.identifier: app,
|
||||
app.id: app,
|
||||
};
|
||||
|
||||
final isInitialLoading = state.firstPage is StorageLoading && items.isEmpty;
|
||||
@@ -345,19 +348,22 @@ class _MigrationBanner extends HookConsumerWidget {
|
||||
profile.pubkey: profile,
|
||||
};
|
||||
|
||||
final allPreviewIdentifiers = <String>{};
|
||||
final allPreviewIds = <String>{};
|
||||
final stackPreviewIds = <String, List<String>>{};
|
||||
for (final stack in stacks) {
|
||||
final ids = getPreviewIdentifiers(stack);
|
||||
final ids = getPreviewAddressableIds(stack);
|
||||
stackPreviewIds[stack.id] = ids;
|
||||
allPreviewIdentifiers.addAll(ids);
|
||||
allPreviewIds.addAll(ids);
|
||||
}
|
||||
|
||||
final (:authors, :identifiers) = decomposeAddressableIds(allPreviewIds);
|
||||
|
||||
// Query preview apps
|
||||
final previewAppsState = allPreviewIdentifiers.isNotEmpty
|
||||
final previewAppsState = allPreviewIds.isNotEmpty
|
||||
? ref.watch(
|
||||
query<App>(
|
||||
tags: {'#d': allPreviewIdentifiers},
|
||||
authors: authors,
|
||||
tags: {'#d': identifiers},
|
||||
source: const LocalAndRemoteSource(
|
||||
relays: 'AppCatalog',
|
||||
stream: false,
|
||||
@@ -368,7 +374,7 @@ class _MigrationBanner extends HookConsumerWidget {
|
||||
: null;
|
||||
final appsMap = {
|
||||
for (final app in previewAppsState?.models ?? <App>[])
|
||||
app.identifier: app,
|
||||
app.id: app,
|
||||
};
|
||||
|
||||
return Container(
|
||||
|
||||
@@ -22,17 +22,30 @@ Set<String> getRawAppTagValues(AppStack stack) {
|
||||
return stack.event.getTagSetValues('a');
|
||||
}
|
||||
|
||||
/// Extract just the d-tag (identifier) from a full addressable id
|
||||
String? extractStackIdentifier(String addressableId) {
|
||||
final parts = addressableId.split(':');
|
||||
return parts.length >= 3 ? parts.sublist(2).join(':') : null;
|
||||
/// Helper to compute preview app addressable IDs for a stack (3 apps max).
|
||||
/// Returns full addressable IDs (e.g. '32267:pubkey:identifier').
|
||||
List<String> getPreviewAddressableIds(AppStack stack) {
|
||||
final rawTags = getRawAppTagValues(stack)
|
||||
.where((id) => id.startsWith('32267:'))
|
||||
.toList()
|
||||
..shuffle(Random(stack.id.hashCode));
|
||||
return rawTags.take(3).toList();
|
||||
}
|
||||
|
||||
/// Helper to compute preview app identifiers for a stack (3 apps max)
|
||||
List<String> getPreviewIdentifiers(AppStack stack) {
|
||||
final rawTags = getRawAppTagValues(stack).toList()
|
||||
..shuffle(Random(stack.id.hashCode));
|
||||
return rawTags.take(3).map(extractStackIdentifier).whereType<String>().toList();
|
||||
/// Decompose addressable IDs (e.g. '32267:pubkey:identifier') into
|
||||
/// the sets of authors and identifiers needed for a query filter.
|
||||
({Set<String> authors, Set<String> identifiers}) decomposeAddressableIds(
|
||||
Iterable<String> addressableIds) {
|
||||
final authors = <String>{};
|
||||
final identifiers = <String>{};
|
||||
for (final id in addressableIds) {
|
||||
final parts = id.split(':');
|
||||
if (parts.length >= 3) {
|
||||
authors.add(parts[1]);
|
||||
identifiers.add(parts.skip(2).join(':'));
|
||||
}
|
||||
}
|
||||
return (authors: authors, identifiers: identifiers);
|
||||
}
|
||||
|
||||
/// Seed generated once per app session for stable shuffle order
|
||||
@@ -95,18 +108,21 @@ class AppStackContainer extends HookConsumerWidget {
|
||||
final displayedStacks = sortedStacks.take(visibleCount.value).toList();
|
||||
|
||||
// Batch load preview apps for displayed stacks (3 per stack)
|
||||
final allPreviewIdentifiers = <String>{};
|
||||
final allPreviewIds = <String>{};
|
||||
final stackPreviewIds = <String, List<String>>{};
|
||||
for (final stack in displayedStacks) {
|
||||
final ids = getPreviewIdentifiers(stack);
|
||||
final ids = getPreviewAddressableIds(stack);
|
||||
stackPreviewIds[stack.id] = ids;
|
||||
allPreviewIdentifiers.addAll(ids);
|
||||
allPreviewIds.addAll(ids);
|
||||
}
|
||||
|
||||
final previewAppsState = allPreviewIdentifiers.isNotEmpty
|
||||
final (:authors, :identifiers) = decomposeAddressableIds(allPreviewIds);
|
||||
|
||||
final previewAppsState = allPreviewIds.isNotEmpty
|
||||
? ref.watch(
|
||||
query<App>(
|
||||
tags: {'#d': allPreviewIdentifiers},
|
||||
authors: authors,
|
||||
tags: {'#d': identifiers},
|
||||
source: const LocalAndRemoteSource(
|
||||
relays: 'AppCatalog',
|
||||
stream: false,
|
||||
@@ -118,7 +134,7 @@ class AppStackContainer extends HookConsumerWidget {
|
||||
|
||||
final appsMap = {
|
||||
for (final app in previewAppsState?.models ?? <App>[])
|
||||
app.identifier: app,
|
||||
app.id: app,
|
||||
};
|
||||
|
||||
// Infinite horizontal scroll: load more when near end
|
||||
|
||||
Reference in New Issue
Block a user