From d87838f617ea824d6829fe571c98cdfc61fa9019 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Mar 2026 13:11:36 +0000 Subject: [PATCH] Fix deep link URL re-processing on account switch and back gesture reliability Two fixes for initialUrl/deep link handling: 1. Prevent initial URL from being re-processed on account switch. The key={currentAccount?.did} in App.native.tsx remounts NavigationContainer, which re-calls getInitialURL() and navigates to the deep link again. Fixed by tracking consumption in a module-level flag and providing a custom getInitialURL that returns null after first call. 2. Fix back swipe gesture not working reliably from deep links. Two changes: - Changed isStateAtTabRoot to return false when state is undefined (during init), preventing the Drawer gesture from activating during the async window before the initial URL resolves. - Added custom subscribe handler that navigates (pushes) on native instead of letting React Navigation reset the entire state, ensuring proper native back gesture setup. https://claude.ai/code/session_01NCogJsPSFXu8YJG8dWwEbC --- src/Navigation.tsx | 50 +++++++++++++++++++++++++++++++++++++++ src/lib/routes/helpers.ts | 10 ++++---- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 03ff43d739..293acb77b1 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -18,6 +18,7 @@ import { } from '@react-navigation/native' import {timeout} from '#/lib/async/timeout' +import {parseLinkingUrl} from '#/lib/parseLinkingUrl' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' @@ -833,11 +834,60 @@ const FlatNavigator = ({ * to the navigation context. */ +// Prevents re-processing the initial deep link URL on remount +// (e.g. when account switch changes the key in App.native.tsx) +let initialUrlConsumed = false + const LINKING = { // TODO figure out what we are going to use // note: `bluesky://` is what is used in app.config.js prefixes: ['bsky://', 'bluesky://', 'https://bsky.app'], + async getInitialURL(): Promise { + if (initialUrlConsumed) { + return null + } + initialUrlConsumed = true + return Linking.getInitialURL() + }, + + subscribe(listener: (url: string) => void) { + const sub = Linking.addEventListener('url', ({url}: {url: string}) => { + if (IS_NATIVE && navigationRef.isReady()) { + const urlp = parseLinkingUrl(url) + const path = urlp.pathname + + // Intent URLs are handled by useIntentHandler + if (path.includes('intent/')) { + return + } + + const [name, params] = router.matchPath(path) + + // For tab roots, switch to the tab + if (name === 'Search') { + resetToTab('SearchTab') + } else if (name === 'Notifications') { + resetToTab('NotificationsTab') + } else if (name === 'Home') { + resetToTab('HomeTab') + } else if (name === 'Messages') { + resetToTab('MessagesTab') + } else { + // Navigate (push) instead of resetting state, so that the + // native back gesture is properly set up + // @ts-ignore nested navigators aren't typed -sfn + navigate('HomeTab', {screen: name, params}) + } + return + } + + // Web or navigation not ready: use default React Navigation behavior + listener(url) + }) + return () => sub.remove() + }, + getPathFromState(state: State) { // find the current node in the navigation tree let node = state.routes[state.index || 0] diff --git a/src/lib/routes/helpers.ts b/src/lib/routes/helpers.ts index 33e7563a3c..536782ef07 100644 --- a/src/lib/routes/helpers.ts +++ b/src/lib/routes/helpers.ts @@ -25,11 +25,11 @@ export function getCurrentRoute(state?: State) { export function isStateAtTabRoot(state: State | undefined) { if (!state) { - // NOTE - // if state is not defined it's because init is occurring - // and therefore we can safely assume we're at root - // -prf - return true + // During initialization (before navigation state is set), default to + // not-at-root. This prevents the drawer gesture from activating during + // the async window before the initial URL is resolved, which would + // compete with the native stack's back gesture on deep link opens. + return false } const currentRoute = getCurrentRoute(state) return (