diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e55b0a2..e3956e2 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -25,7 +25,6 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" - android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" diff --git a/lib/main.dart b/lib/main.dart index 96aff49..2db330c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -284,7 +284,7 @@ final appInitializationProvider = FutureProvider((ref) async { final backgroundService = ref.read(backgroundUpdateServiceProvider); unawaited(backgroundService.initialize()); - unawaited(ref.read(deepLinkServiceProvider).initialize()); + await ref.read(deepLinkServiceProvider).initialize(); await _attemptAutoSignIn(ref); }); diff --git a/lib/router.dart b/lib/router.dart index 8c01ffa..7e486ed 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -13,7 +13,6 @@ import 'package:zapstore/screens/search_screen.dart'; import 'package:zapstore/screens/updates_screen.dart'; import 'package:zapstore/screens/profile_screen.dart'; import 'package:zapstore/services/package_manager/package_manager.dart'; -import 'package:zapstore/services/deep_link_resolver.dart'; import 'package:zapstore/services/updates_service.dart'; /// Root paths for each navigation branch (used for back navigation handling) @@ -117,10 +116,9 @@ final routerProvider = Provider((ref) { final router = GoRouter( navigatorKey: rootNavigatorKey, initialLocation: '/search', - // Intercept deep link URIs that GoRouter cannot match as paths. - // This fires before any route is rendered, eliminating the GoException - // flash when the app is launched via market:// or https://zapstore.dev. - redirect: (context, state) => resolveDeepLinkPath(state.uri), + onException: (context, state, router) { + router.go('/search'); + }, routes: [ // Single stateful shell route that handles everything StatefulShellRoute.indexedStack( diff --git a/lib/services/deep_link_resolver.dart b/lib/services/deep_link_resolver.dart index 2fe4a1e..d98a21d 100644 --- a/lib/services/deep_link_resolver.dart +++ b/lib/services/deep_link_resolver.dart @@ -4,19 +4,20 @@ import 'package:flutter/foundation.dart'; /// the URI is not a recognized deep link. /// /// Handles: -/// - `https://zapstore.dev/apps/{id}` +/// - `https://zapstore.dev/apps/{id}` (full URI from app_links) +/// - `/apps/{id}` (path-only, as seen by GoRouter's onException) /// - `market://details?id=com.example.app` /// - `market://search?q=search+query` String? resolveDeepLinkPath(Uri uri) { - // https://zapstore.dev/apps/ - if (uri.scheme == 'https' && - uri.host == 'zapstore.dev' && - uri.pathSegments.length == 2 && - uri.pathSegments[0] == 'apps') { - final id = uri.pathSegments[1]; - if (id.isNotEmpty) { - debugPrint('App link: app ID = $id'); - return '/search/app/$id'; + // https://zapstore.dev/apps/ OR bare /apps/ from GoRouter + if (uri.pathSegments.length == 2 && uri.pathSegments[0] == 'apps') { + final isFullUri = uri.scheme == 'https' && uri.host == 'zapstore.dev'; + final isBarePath = uri.host.isEmpty; + if (isFullUri || isBarePath) { + final id = uri.pathSegments[1]; + if (id.isNotEmpty) { + return '/search/app/$id'; + } } } @@ -25,7 +26,6 @@ String? resolveDeepLinkPath(Uri uri) { if (uri.host == 'details' || uri.path == '/details') { final id = uri.queryParameters['id']; if (id != null && id.isNotEmpty) { - debugPrint('Market intent: app ID = $id'); return '/search/app/$id'; } } diff --git a/lib/services/deep_link_service.dart b/lib/services/deep_link_service.dart index 418bf65..37f900f 100644 --- a/lib/services/deep_link_service.dart +++ b/lib/services/deep_link_service.dart @@ -22,13 +22,15 @@ class DeepLinkService { /// Initialize the service - call once after app is ready. /// - /// The initial launch URI is not re-processed here because GoRouter's - /// top-level redirect already maps it to the correct path before any - /// screen renders. Only links received while the app is running are handled. + /// Handles both the cold-launch URI (the link that opened the app) and + /// links received while the app is already running. Future initialize() async { if (_initialized) return; _initialized = true; + final initialUri = await _appLinks.getInitialLink(); + _handleUri(initialUri); + _subscription = _appLinks.uriLinkStream.listen(_handleUri); }