Remove background flag, fix redirect to Amber

This commit is contained in:
franzap
2025-12-15 22:17:16 -03:00
parent 663c1786e3
commit d9196cb7d0
17 changed files with 72 additions and 96 deletions
+7
View File
@@ -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';
-1
View File
@@ -275,7 +275,6 @@ Future<void> onSignInSuccess(Ref ref) async {
RequestFilter<AppCatalogRelayList>(authors: {pubkey}).toRequest(),
source: const RemoteSource(
relays: 'bootstrap',
background: false,
stream: false,
),
),
+3 -33
View File
@@ -36,27 +36,6 @@ final routerProvider = Provider<GoRouter>((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<GoRouter>((ref) {
GoRoute(
path: '/search',
builder: (context, state) => const SearchScreen(),
routes: [
_appDetailRoute(),
_userRoute(),
],
routes: [_appDetailRoute(), _userRoute()],
),
],
),
@@ -93,10 +69,7 @@ final routerProvider = Provider<GoRouter>((ref) {
GoRoute(
path: '/updates',
builder: (context, state) => const UpdatesScreen(),
routes: [
_appDetailRoute(),
_userRoute(),
],
routes: [_appDetailRoute(), _userRoute()],
),
],
),
@@ -106,10 +79,7 @@ final routerProvider = Provider<GoRouter>((ref) {
GoRoute(
path: '/profile',
builder: (context, state) => const ProfileScreen(),
routes: [
_appDetailRoute(),
_userRoute(),
],
routes: [_appDetailRoute(), _userRoute()],
),
],
),
+40 -16
View File
@@ -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<App>(
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>(
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<Profile>(
authors: {app.pubkey},
source: const LocalAndRemoteSource(relays: {'social', 'vertex'}, cachedFor: Duration(hours: 2)),
));
final authorState = ref.watch(
query<Profile>(
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)
+2 -6
View File
@@ -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();
}
-1
View File
@@ -46,7 +46,6 @@ class SearchScreen extends HookConsumerWidget {
source: const RemoteSource(
relays: 'AppCatalog',
stream: false,
background: false,
),
subscriptionPrefix: 'search-results',
),
+2 -11
View File
@@ -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',
),
@@ -92,7 +92,6 @@ Future<bool> _checkForUpdatesInBackground() async {
source: const RemoteSource(
relays: 'AppCatalog',
stream: false,
background: false,
),
);
@@ -107,7 +106,6 @@ Future<bool> _checkForUpdatesInBackground() async {
source: const LocalAndRemoteSource(
relays: 'AppCatalog',
stream: false,
background: false,
),
);
}
-1
View File
@@ -24,7 +24,6 @@ final bookmarksProvider = FutureProvider<Set<String>>((ref) async {
source: const LocalAndRemoteSource(
relays: 'social',
stream: false,
background: false, // Single fetch; no live streaming
),
subscriptionPrefix: 'user-bookmarks',
),
-3
View File
@@ -79,12 +79,10 @@ class CategorizedAppsNotifier extends StateNotifier<CategorizedApps> {
},
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<CategorizedApps> {
RequestFilter<Profile>(authors: authorPubkeys).toRequest(),
source: const RemoteSource(
relays: 'social',
background: false,
stream: false,
),
);
+1 -2
View File
@@ -100,7 +100,7 @@ extension AppsExt on Iterable<App> {
.nonNulls
.toList(),
),
source: const LocalAndRemoteSource(stream: false, background: true),
source: const LocalAndRemoteSource(stream: false),
);
if (withAuthors) {
@@ -113,7 +113,6 @@ extension AppsExt on Iterable<App> {
source: const LocalAndRemoteSource(
relays: 'vertex',
stream: false,
background: true,
),
);
}
-2
View File
@@ -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',
),
+8 -7
View File
@@ -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 {
-1
View File
@@ -30,7 +30,6 @@ class CommentsSection extends HookConsumerWidget {
},
source: LocalAndRemoteSource(
stream: true,
background: true,
relays: 'social',
),
subscriptionPrefix: 'app-comments',
+1 -2
View File
@@ -281,7 +281,6 @@ class LatestReleasesNotifier extends StateNotifier<LatestReleasesState> {
source: const LocalAndRemoteSource(
relays: 'AppCatalog',
stream: true,
background: true,
),
andSource: const LocalAndRemoteSource(
relays: 'AppCatalog',
@@ -337,7 +336,7 @@ class LatestReleasesNotifier extends StateNotifier<LatestReleasesState> {
'#f': {'android-arm64-v8a'},
},
).toRequest(),
source: const LocalAndRemoteSource(stream: false, background: false),
source: const LocalAndRemoteSource(stream: false),
);
if (olderPage.isNotEmpty) {
@@ -193,7 +193,6 @@ final relevantWhoFollowProvider = FutureProvider.autoDispose
source: const LocalAndRemoteSource(
relays: 'social',
stream: false,
background: false,
),
);
return profiles;
+8 -7
View File
@@ -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();