diff --git a/src/Navigation.tsx b/src/Navigation.tsx index a9b3c00585..b3e75c93af 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -1,4 +1,4 @@ -import {type JSX, useCallback, useRef} from 'react' +import {type JSX, useCallback, useRef, useState} from 'react' import * as Linking from 'expo-linking' import * as Notifications from 'expo-notifications' import {i18n, type MessageDescriptor} from '@lingui/core' @@ -14,6 +14,7 @@ import { DefaultTheme, type LinkingOptions, NavigationContainer, + type NavigationState, StackActions, } from '@react-navigation/native' @@ -140,6 +141,7 @@ import {useAnalytics} from '#/analytics' import {setNavigationMetadata} from '#/analytics/metadata' import {IS_NATIVE, IS_WEB} from '#/env' import {router} from '#/routes' +import {device} from '#/storage' import {Referrer} from '../modules/expo-bluesky-swiss-army' const navigationRef = createNavigationContainerRef() @@ -886,6 +888,39 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { const closeAllActiveElements = useCloseAllActiveElements() const linkingUrl = Linking.useLinkingURL() + const [initialState] = useState(() => { + if (!IS_NATIVE) return + + const previousNavState = device.get(['navigationState']) + if (previousNavState) { + // we want to clear it asap - even if we don't use it + // if they're opening it via an intent, we obviously prioritize the intent + // but also the *subsequent* open after that, it would be weird if it restored to the + // nav state *before* that, hence the aggressive clearing -sfn + device.remove(['navigationState']) + + // we only want to use it if the current state is more-or-less where they left off: + // - logged in to the same account + // - no intent + // - not handling a notification + if (linkingUrl) return + if (notificationResponse) return + if (previousNavState.did !== currentAccount?.did) { + return + } + + return previousNavState.state + } + }) + + const persistState = (state?: NavigationState) => { + if (!IS_NATIVE) return + if (!currentAccount) return + if (!state) return + + device.set(['navigationState'], {did: currentAccount.did, state}) + } + /** * Handle navigation to a conversation, or prepares for account switch. * @@ -1012,10 +1047,11 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { return ( { + onStateChange={state => { const currentScreen = getCurrentRouteName() // do this before metric setNavigationMetadata({ @@ -1024,6 +1060,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { }) ax.metric('router:navigate', {from: previousScreen.current}) previousScreen.current = currentScreen + persistState(state) }} onReady={onNavigationReady} // WARNING: Implicit navigation to nested navigators is depreciated in React Navigation 7.x diff --git a/src/storage/schema.ts b/src/storage/schema.ts index 2dd99ace2d..1efbfbbd20 100644 --- a/src/storage/schema.ts +++ b/src/storage/schema.ts @@ -1,3 +1,5 @@ +import {type InitialState} from '@react-navigation/native' + import {type ID as PolicyUpdate202508} from '#/components/PolicyUpdateOverlay/updates/202508/config' import {type Geolocation} from '#/geolocation/types' @@ -66,6 +68,11 @@ export type Device = { */ policyUpdateDebugOverride?: boolean [PolicyUpdate202508]?: boolean + + navigationState?: { + did: string + state: InitialState + } } export type Account = {