add mechanism for running navigation actions when it's ready

This commit is contained in:
Samuel Newman
2025-07-02 17:16:22 +03:00
parent 07b028ee66
commit 0597b8e1aa
3 changed files with 92 additions and 42 deletions
+7 -2
View File
@@ -123,6 +123,7 @@ import {QuoteNotificationSettingsScreen} from './screens/Settings/NotificationSe
import {ReplyNotificationSettingsScreen} from './screens/Settings/NotificationSettings/ReplyNotificationSettings'
import {RepostNotificationSettingsScreen} from './screens/Settings/NotificationSettings/RepostNotificationSettings'
import {RepostsOnRepostsNotificationSettingsScreen} from './screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings'
import {NavigationAvailable} from './state/shell/navigation-available'
const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
@@ -825,6 +826,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
const {currentAccount} = useSession()
const prevLoggedRouteName = React.useRef<string | undefined>(undefined)
const emailDialogControl = useEmailDialogControl()
const navigationAvailableRef = React.useRef<{onReady: () => void}>(null)
function onReady() {
prevLoggedRouteName.current = getCurrentRouteName()
@@ -837,7 +839,9 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
}
return (
<>
<NavigationAvailable
ref={navigationAvailableRef}
navigationRef={navigationRef}>
<NavigationContainer
ref={navigationRef}
linking={LINKING}
@@ -856,6 +860,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
attachRouteToLogEvents(getCurrentRouteName)
logModuleInitTime()
onReady()
navigationAvailableRef.current?.onReady()
logger.metric('router:navigate', {}, {statsig: false})
}}
// WARNING: Implicit navigation to nested navigators is depreciated in React Navigation 7.x
@@ -867,7 +872,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
navigationInChildEnabled>
{children}
</NavigationContainer>
</>
</NavigationAvailable>
)
}
+28 -40
View File
@@ -16,6 +16,7 @@ import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread'
import {truncateAndInvalidate} from '#/state/queries/util'
import {useSession} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useRunWhenNavigationAvailable} from '#/state/shell/navigation-available'
import {useCloseAllActiveElements} from '#/state/util'
import {resetToTab} from '#/Navigation'
@@ -74,6 +75,7 @@ export function useNotificationsHandler() {
const {currentConvoId} = useCurrentConvoId()
const {setShowLoggedOut} = useLoggedOutViewControls()
const closeAllActiveElements = useCloseAllActiveElements()
const runWhenNavigationAvailable = useRunWhenNavigationAvailable()
const {_} = useLingui()
// On Android, we cannot control which sound is used for a notification on Android
@@ -228,10 +230,17 @@ export function useNotificationsHandler() {
}
} else {
switch (payload.reason) {
case 'like':
case 'repost':
case 'reply':
case 'quote':
case 'mention':
case 'like-via-repost':
case 'repost-via-repost':
case 'subscribed-post':
const urip = new AtUri(payload.uri)
if (urip.collection === 'app.bsky.feed.post') {
setTimeout(() => {
runWhenNavigationAvailable(() => {
const urip = new AtUri(payload.uri)
if (urip.collection === 'app.bsky.feed.post') {
// @ts-expect-error types are weird here
navigation.navigate('HomeTab', {
screen: 'PostThread',
@@ -240,51 +249,29 @@ export function useNotificationsHandler() {
rkey: urip.rkey,
},
})
}, 500)
} else {
resetToTab('NotificationsTab')
}
} else {
resetToTab('NotificationsTab')
}
})
break
case 'like':
case 'repost':
case 'follow':
case 'mention':
case 'quote':
case 'reply':
case 'starterpack-joined':
case 'like-via-repost':
case 'repost-via-repost':
runWhenNavigationAvailable(() => {
const urip = new AtUri(payload.uri)
// @ts-expect-error types are weird here
navigation.navigate('HomeTab', {
screen: 'Profile',
params: {
name: urip.host,
},
})
})
break
case 'verified':
case 'unverified':
default:
resetToTab('NotificationsTab')
break
// TODO implement these after we have an idea of how to handle each individual case
// case 'follow':
// const uri = new AtUri(payload.uri)
// setTimeout(() => {
// // @ts-expect-error types are weird here
// navigation.navigate('HomeTab', {
// screen: 'Profile',
// params: {
// name: uri.host,
// },
// })
// }, 500)
// break
// case 'mention':
// case 'reply':
// const urip = new AtUri(payload.uri)
// setTimeout(() => {
// // @ts-expect-error types are weird here
// navigation.navigate('HomeTab', {
// screen: 'PostThread',
// params: {
// name: urip.host,
// rkey: urip.rkey,
// },
// })
// }, 500)
}
}
}
@@ -416,5 +403,6 @@ export function useNotificationsHandler() {
navigation,
onPressSwitchAccount,
setShowLoggedOut,
runWhenNavigationAvailable,
])
}
+57
View File
@@ -0,0 +1,57 @@
import {createContext, useContext, useImperativeHandle, useState} from 'react'
import {type NavigationContainerRefWithCurrent} from '@react-navigation/native'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {type AllNavigatorParams} from '#/lib/routes/types'
type QueueAction = () => void
const NavigationAvailableContext = createContext<
((cb: QueueAction) => void) | null
>(null)
export function NavigationAvailable({
children,
ref,
navigationRef,
}: {
children: React.ReactNode
ref: React.Ref<{onReady: () => void}>
navigationRef: NavigationContainerRefWithCurrent<AllNavigatorParams>
}) {
const [queue, setQueue] = useState<QueueAction[]>([])
useImperativeHandle(ref, () => ({
onReady: () => {
for (const item of queue) {
item()
}
setQueue([])
},
}))
const runWhenNavigationAvailable = useNonReactiveCallback(
(cb: QueueAction) => {
if (navigationRef.isReady()) {
cb()
} else {
setQueue(prev => [...prev, cb])
}
},
)
return (
<NavigationAvailableContext.Provider value={runWhenNavigationAvailable}>
{children}
</NavigationAvailableContext.Provider>
)
}
export function useRunWhenNavigationAvailable() {
const context = useContext(NavigationAvailableContext)
if (!context) {
throw new Error(
'useRunWhenNavigationAvailable must be used within a NavigationAvailable component',
)
}
return context
}