diff --git a/lib/constants/app_constants.dart b/lib/constants/app_constants.dart index 7717e1e..033115a 100644 --- a/lib/constants/app_constants.dart +++ b/lib/constants/app_constants.dart @@ -11,3 +11,10 @@ const kFranzapPubkey = /// Identifier for storing user bookmarks const kAppBookmarksIdentifier = 'zapstore-bookmarks'; + +/// Amber signer package ID +const kAmberPackageId = 'com.greenart7c3.nostrsigner'; + +/// Amber signer naddr +const kAmberNaddr = + 'naddr1qqdkxmmd9enhyet9deshyaphvvejumn0wd68yumfvahx2uszyp6hjpmdntls5n8aa7n7ypzlyjrv0ewch33ml3452wtjx0smhl93jqcyqqq8uzcgpp6ky'; diff --git a/lib/main.dart b/lib/main.dart index e378839..130c24e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -275,7 +275,6 @@ Future onSignInSuccess(Ref ref) async { RequestFilter(authors: {pubkey}).toRequest(), source: const RemoteSource( relays: 'bootstrap', - background: false, stream: false, ), ), diff --git a/lib/router.dart b/lib/router.dart index ee0e746..44d904e 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -36,27 +36,6 @@ final routerProvider = Provider((ref) { return GoRouter( initialLocation: '/search', routes: [ - // Top-level deep link route: /apps/:naddr - // Redirect to search branch to show with nav bar - GoRoute( - path: '/apps/:naddr', - redirect: (context, state) { - final naddr = state.pathParameters['naddr']; - if (naddr == null || naddr.isEmpty) { - return '/search'; - } - // Decode naddr to get app identifier - try { - final decoded = Utils.decodeShareableIdentifier(naddr); - if (decoded is AddressData && decoded.identifier.isNotEmpty) { - return '/search/app/${decoded.identifier}'; - } - } catch (_) { - // Invalid naddr - } - return '/search'; - }, - ), // Top-level route for market:// intents // Redirect to search branch to show with nav bar GoRoute( @@ -80,10 +59,7 @@ final routerProvider = Provider((ref) { GoRoute( path: '/search', builder: (context, state) => const SearchScreen(), - routes: [ - _appDetailRoute(), - _userRoute(), - ], + routes: [_appDetailRoute(), _userRoute()], ), ], ), @@ -93,10 +69,7 @@ final routerProvider = Provider((ref) { GoRoute( path: '/updates', builder: (context, state) => const UpdatesScreen(), - routes: [ - _appDetailRoute(), - _userRoute(), - ], + routes: [_appDetailRoute(), _userRoute()], ), ], ), @@ -106,10 +79,7 @@ final routerProvider = Provider((ref) { GoRoute( path: '/profile', builder: (context, state) => const ProfileScreen(), - routes: [ - _appDetailRoute(), - _userRoute(), - ], + routes: [_appDetailRoute(), _userRoute()], ), ], ), diff --git a/lib/screens/app_detail_screen.dart b/lib/screens/app_detail_screen.dart index c9a4c46..b9cc86c 100644 --- a/lib/screens/app_detail_screen.dart +++ b/lib/screens/app_detail_screen.dart @@ -48,14 +48,32 @@ class _AppLoaderView extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final platform = ref.read(packageManagerProvider.notifier).platform; + // Check if appId is an naddr and decode it + String? identifier; + String? author; + + if (appId.startsWith('naddr1')) { + try { + final decoded = Utils.decodeShareableIdentifier(appId); + if (decoded is AddressData) { + identifier = decoded.identifier; + author = decoded.author; + } + } catch (e) { + // Invalid naddr, fall through to use appId as-is + } + } + + // If we decoded an naddr, query by author/identifier, otherwise by #d tag final appsState = ref.watch( query( + authors: author != null ? {author} : null, tags: { - '#d': {appId}, + '#d': {identifier ?? appId}, '#f': {platform}, }, limit: 1, - source: const LocalAndRemoteSource(stream: false, background: true), + source: const LocalAndRemoteSource(relays: 'AppCatalog', stream: false), subscriptionPrefix: 'app-detail-loader', ), ); @@ -79,7 +97,10 @@ class _AppLoaderView extends ConsumerWidget { // Loading state return const Scaffold( body: SafeArea( - child: Padding(padding: EdgeInsets.all(16), child: AppDetailSkeleton()), + child: SingleChildScrollView( + padding: EdgeInsets.all(16), + child: AppDetailSkeleton(), + ), ), ); } @@ -125,20 +146,22 @@ class _AppDetailView extends HookConsumerWidget { final appState = ref.watch( model( app, - and: (a) => { - a.latestRelease, - if (a.latestRelease.value != null) - a.latestRelease.value!.latestMetadata, - }, + and: (a) => {a.latestRelease, a.latestRelease.value?.latestMetadata}, source: LocalAndRemoteSource(relays: 'AppCatalog', stream: false), + subscriptionPrefix: 'app-detail', ), ); // Query author profile from social relays - final authorState = ref.watch(query( - authors: {app.pubkey}, - source: const LocalAndRemoteSource(relays: {'social', 'vertex'}, cachedFor: Duration(hours: 2)), - )); + final authorState = ref.watch( + query( + authors: {app.pubkey}, + source: const LocalAndRemoteSource( + relays: {'social', 'vertex'}, + cachedFor: Duration(hours: 2), + ), + ), + ); final author = switch (authorState) { StorageData(:final models) => models.firstOrNull, _ => null, @@ -227,9 +250,7 @@ class _AppDetailView extends HookConsumerWidget { final first = segments.isNotEmpty ? segments.first : 'search'; - context.push( - '/$first/user/${author.pubkey}', - ); + context.push('/$first/user/${author.pubkey}'); }, ) : const AuthorSkeleton(), @@ -327,7 +348,10 @@ class _AppDetailView extends HookConsumerWidget { ), ), - CommentsSection(app: currentApp, fileMetadata: latestMetadata), + CommentsSection( + app: currentApp, + fileMetadata: latestMetadata, + ), // Debug section - only visible for specific pubkey if (showDebugSections) diff --git a/lib/screens/profile_screen.dart b/lib/screens/profile_screen.dart index eb786ec..62a6afb 100644 --- a/lib/screens/profile_screen.dart +++ b/lib/screens/profile_screen.dart @@ -177,9 +177,8 @@ class _AuthenticationSection extends ConsumerWidget { Widget _buildSignInOptions(BuildContext context, WidgetRef ref) { final packageManager = ref.watch(packageManagerProvider); - const amberPackageId = 'com.greenart7c3.nostrsigner'; final isAmberInstalled = packageManager.any( - (p) => p.appId == amberPackageId, + (p) => p.appId == kAmberPackageId, ); return Column( @@ -241,10 +240,7 @@ class _SignInButtonWithAmberCheck extends ConsumerWidget { return AsyncButtonBuilder( onPressed: () async { if (!isAmberInstalled) { - // Navigate to Amber app page (within profile tab to keep bottom nav) - context.push( - '/profile/apps/naddr1qqdkxmmd9enhyet9deshyaphvvejumn0wd68yumfvahx2uszyp6hjpmdntls5n8aa7n7ypzlyjrv0ewch33ml3452wtjx0smhl93jqcyqqq8uzcgpp6ky', - ); + context.push('/profile/app/$kAmberNaddr'); } else { await ref.read(amberSignerProvider).signIn(); } diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index 70b7fa3..77883a8 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -46,7 +46,6 @@ class SearchScreen extends HookConsumerWidget { source: const RemoteSource( relays: 'AppCatalog', stream: false, - background: false, ), subscriptionPrefix: 'search-results', ), diff --git a/lib/screens/user_screen.dart b/lib/screens/user_screen.dart index bd0758e..9bca168 100644 --- a/lib/screens/user_screen.dart +++ b/lib/screens/user_screen.dart @@ -44,11 +44,7 @@ class UserScreen extends HookConsumerWidget { app.latestRelease, app.latestRelease.value?.latestMetadata, }, - source: const LocalAndRemoteSource( - relays: 'AppCatalog', - stream: false, - background: true, - ), + source: const LocalAndRemoteSource(relays: 'AppCatalog', stream: false), subscriptionPrefix: 'user-apps', ), ); @@ -64,15 +60,10 @@ class UserScreen extends HookConsumerWidget { authors: {pubkey}, limit: 20, and: (pack) => {pack.apps}, - source: const LocalAndRemoteSource( - stream: false, - background: true, - relays: 'social', - ), + source: const LocalAndRemoteSource(stream: false, relays: 'social'), andSource: const LocalAndRemoteSource( relays: 'AppCatalog', stream: false, - background: true, ), subscriptionPrefix: 'user-packs', ), diff --git a/lib/services/background_update_service.dart b/lib/services/background_update_service.dart index b3713e2..1205197 100644 --- a/lib/services/background_update_service.dart +++ b/lib/services/background_update_service.dart @@ -92,7 +92,6 @@ Future _checkForUpdatesInBackground() async { source: const RemoteSource( relays: 'AppCatalog', stream: false, - background: false, ), ); @@ -107,7 +106,6 @@ Future _checkForUpdatesInBackground() async { source: const LocalAndRemoteSource( relays: 'AppCatalog', stream: false, - background: false, ), ); } diff --git a/lib/services/bookmarks_service.dart b/lib/services/bookmarks_service.dart index a7eead1..4e31941 100644 --- a/lib/services/bookmarks_service.dart +++ b/lib/services/bookmarks_service.dart @@ -24,7 +24,6 @@ final bookmarksProvider = FutureProvider>((ref) async { source: const LocalAndRemoteSource( relays: 'social', stream: false, - background: false, // Single fetch; no live streaming ), subscriptionPrefix: 'user-bookmarks', ), diff --git a/lib/services/updates_service.dart b/lib/services/updates_service.dart index ce2a66f..61fb69e 100644 --- a/lib/services/updates_service.dart +++ b/lib/services/updates_service.dart @@ -79,12 +79,10 @@ class CategorizedAppsNotifier extends StateNotifier { }, source: const LocalAndRemoteSource( relays: 'AppCatalog', - background: true, stream: true, ), andSource: const LocalAndRemoteSource( relays: 'AppCatalog', - background: true, stream: false, ), subscriptionPrefix: 'updates', @@ -113,7 +111,6 @@ class CategorizedAppsNotifier extends StateNotifier { RequestFilter(authors: authorPubkeys).toRequest(), source: const RemoteSource( relays: 'social', - background: false, stream: false, ), ); diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index b4c36ba..bacad18 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -100,7 +100,7 @@ extension AppsExt on Iterable { .nonNulls .toList(), ), - source: const LocalAndRemoteSource(stream: false, background: true), + source: const LocalAndRemoteSource(stream: false), ); if (withAuthors) { @@ -113,7 +113,6 @@ extension AppsExt on Iterable { source: const LocalAndRemoteSource( relays: 'vertex', stream: false, - background: true, ), ); } diff --git a/lib/widgets/app_pack_container.dart b/lib/widgets/app_pack_container.dart index 9bba35b..f3ae651 100644 --- a/lib/widgets/app_pack_container.dart +++ b/lib/widgets/app_pack_container.dart @@ -29,13 +29,11 @@ class AppPackContainer extends HookConsumerWidget { and: (pack) => {pack.apps}, source: const LocalAndRemoteSource( stream: true, - background: true, relays: 'social', ), andSource: const LocalAndRemoteSource( relays: 'AppCatalog', stream: false, - background: true, ), subscriptionPrefix: 'app-pack', ), diff --git a/lib/widgets/auth_widgets.dart b/lib/widgets/auth_widgets.dart index 9c641cc..ac06cd4 100644 --- a/lib/widgets/auth_widgets.dart +++ b/lib/widgets/auth_widgets.dart @@ -2,15 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:zapstore/constants/app_constants.dart'; import 'package:zapstore/main.dart'; import 'package:zapstore/services/notification_service.dart'; import 'package:zapstore/services/package_manager/package_manager.dart'; import 'package:zapstore/utils/debug_utils.dart'; -const _amberPackageId = 'com.greenart7c3.nostrsigner'; -const _amberNaddr = - 'naddr1qqdkxmmd9enhyet9deshyaphvvejumn0wd68yumfvahx2uszyp6hjpmdntls5n8aa7n7ypzlyjrv0ewch33ml3452wtjx0smhl93jqcyqqq8uzcgpp6ky'; - /// Reusable sign-in prompt widget for dialogs and bottom sheets. /// Displays a styled tappable message prompting the user to sign in. class SignInPrompt extends HookConsumerWidget { @@ -24,7 +21,7 @@ class SignInPrompt extends HookConsumerWidget { final theme = Theme.of(context); final packageManager = ref.watch(packageManagerProvider); final isAmberInstalled = packageManager.any( - (p) => p.appId == _amberPackageId, + (p) => p.appId == kAmberPackageId, ); final isLoading = useState(false); @@ -32,8 +29,12 @@ class SignInPrompt extends HookConsumerWidget { if (isLoading.value) return; if (!isAmberInstalled) { - context.showInfo('Install Amber to sign in with your Nostr identity'); - context.push('/profile/apps/$_amberNaddr'); + context.showInfo( + 'Install Amber to sign in with your Nostr identity', + actions: [ + ('Open Amber', () async => context.push('/search/app/$kAmberNaddr')), + ], + ); } else { isLoading.value = true; try { diff --git a/lib/widgets/comments_section.dart b/lib/widgets/comments_section.dart index 7eb7d51..a9533a0 100644 --- a/lib/widgets/comments_section.dart +++ b/lib/widgets/comments_section.dart @@ -30,7 +30,6 @@ class CommentsSection extends HookConsumerWidget { }, source: LocalAndRemoteSource( stream: true, - background: true, relays: 'social', ), subscriptionPrefix: 'app-comments', diff --git a/lib/widgets/latest_releases_container.dart b/lib/widgets/latest_releases_container.dart index 269e65c..33cdb00 100644 --- a/lib/widgets/latest_releases_container.dart +++ b/lib/widgets/latest_releases_container.dart @@ -281,7 +281,6 @@ class LatestReleasesNotifier extends StateNotifier { source: const LocalAndRemoteSource( relays: 'AppCatalog', stream: true, - background: true, ), andSource: const LocalAndRemoteSource( relays: 'AppCatalog', @@ -337,7 +336,7 @@ class LatestReleasesNotifier extends StateNotifier { '#f': {'android-arm64-v8a'}, }, ).toRequest(), - source: const LocalAndRemoteSource(stream: false, background: false), + source: const LocalAndRemoteSource(stream: false), ); if (olderPage.isNotEmpty) { diff --git a/lib/widgets/relevant_who_follow_container.dart b/lib/widgets/relevant_who_follow_container.dart index 6405d59..90658c1 100644 --- a/lib/widgets/relevant_who_follow_container.dart +++ b/lib/widgets/relevant_who_follow_container.dart @@ -193,7 +193,6 @@ final relevantWhoFollowProvider = FutureProvider.autoDispose source: const LocalAndRemoteSource( relays: 'social', stream: false, - background: false, ), ); return profiles; diff --git a/lib/widgets/sign_in_button.dart b/lib/widgets/sign_in_button.dart index 65a8b96..896ba65 100644 --- a/lib/widgets/sign_in_button.dart +++ b/lib/widgets/sign_in_button.dart @@ -2,15 +2,12 @@ import 'package:async_button_builder/async_button_builder.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:zapstore/constants/app_constants.dart'; import 'package:zapstore/main.dart'; import 'package:zapstore/services/notification_service.dart'; import 'package:zapstore/services/package_manager/package_manager.dart'; import 'package:zapstore/utils/debug_utils.dart'; -const _amberPackageId = 'com.greenart7c3.nostrsigner'; -const _amberNaddr = - 'naddr1qqdkxmmd9enhyet9deshyaphvvejumn0wd68yumfvahx2uszyp6hjpmdntls5n8aa7n7ypzlyjrv0ewch33ml3452wtjx0smhl93jqcyqqq8uzcgpp6ky'; - class SignInButton extends ConsumerWidget { const SignInButton({ super.key, @@ -27,14 +24,18 @@ class SignInButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final packageManager = ref.watch(packageManagerProvider); final isAmberInstalled = packageManager.any( - (p) => p.appId == _amberPackageId, + (p) => p.appId == kAmberPackageId, ); return AsyncButtonBuilder( onPressed: () async { if (!isAmberInstalled) { - context.showInfo('Install Amber to sign in with your Nostr identity'); - context.push('/profile/apps/$_amberNaddr'); + context.showInfo( + 'Install Amber to sign in with your Nostr identity', + actions: [ + ('Open Amber', () async => context.push('/search/app/$kAmberNaddr')), + ], + ); } else { try { await ref.read(amberSignerProvider).signIn();