Update react-navigation (#5967)

This commit is contained in:
dan
2025-06-09 20:29:53 +01:00
committed by GitHub
parent 42c4da1ec7
commit f938963462
11 changed files with 293 additions and 256 deletions
+5 -5
View File
@@ -94,10 +94,10 @@
"@react-native-async-storage/async-storage": "2.1.2", "@react-native-async-storage/async-storage": "2.1.2",
"@react-native-menu/menu": "^1.2.3", "@react-native-menu/menu": "^1.2.3",
"@react-native-picker/picker": "2.11.0", "@react-native-picker/picker": "2.11.0",
"@react-navigation/bottom-tabs": "^6.5.20", "@react-navigation/bottom-tabs": "^7.3.13",
"@react-navigation/drawer": "^6.6.15", "@react-navigation/drawer": "^7.3.12",
"@react-navigation/native": "^6.1.17", "@react-navigation/native": "^7.1.9",
"@react-navigation/native-stack": "^6.9.26", "@react-navigation/native-stack": "^7.3.13",
"@sentry/react-native": "~6.10.0", "@sentry/react-native": "~6.10.0",
"@tanstack/query-async-storage-persister": "^5.25.0", "@tanstack/query-async-storage-persister": "^5.25.0",
"@tanstack/react-query": "^5.8.1", "@tanstack/react-query": "^5.8.1",
@@ -198,7 +198,7 @@
"react-native-reanimated": "~3.17.5", "react-native-reanimated": "~3.17.5",
"react-native-root-siblings": "^4.1.1", "react-native-root-siblings": "^4.1.1",
"react-native-safe-area-context": "5.4.0", "react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.10.0", "react-native-screens": "^4.11.1",
"react-native-svg": "15.11.2", "react-native-svg": "15.11.2",
"react-native-uitextview": "^1.4.0", "react-native-uitextview": "^1.4.0",
"react-native-url-polyfill": "^1.3.0", "react-native-url-polyfill": "^1.3.0",
+42 -62
View File
@@ -103,7 +103,7 @@ import {
import {Wizard} from '#/screens/StarterPack/Wizard' import {Wizard} from '#/screens/StarterPack/Wizard'
import TopicScreen from '#/screens/Topic' import TopicScreen from '#/screens/Topic'
import {VideoFeed} from '#/screens/VideoFeed' import {VideoFeed} from '#/screens/VideoFeed'
import {useTheme} from '#/alf' import {type Theme, useTheme} from '#/alf'
import { import {
EmailDialogScreenID, EmailDialogScreenID,
useEmailDialogControl, useEmailDialogControl,
@@ -127,7 +127,7 @@ const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
/** /**
* These "common screens" are reused across stacks. * These "common screens" are reused across stacks.
*/ */
function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
const title = (page: MessageDescriptor) => const title = (page: MessageDescriptor) =>
bskyTitle(i18n._(page), unreadCountLabel) bskyTitle(i18n._(page), unreadCountLabel)
@@ -499,6 +499,10 @@ function TabsNavigator() {
tabBar={tabBar}> tabBar={tabBar}>
<Tab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} /> <Tab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} />
<Tab.Screen name="SearchTab" getComponent={() => SearchTabNavigator} /> <Tab.Screen name="SearchTab" getComponent={() => SearchTabNavigator} />
<Tab.Screen
name="MessagesTab"
getComponent={() => MessagesTabNavigator}
/>
<Tab.Screen <Tab.Screen
name="NotificationsTab" name="NotificationsTab"
getComponent={() => NotificationsTabNavigator} getComponent={() => NotificationsTabNavigator}
@@ -507,29 +511,26 @@ function TabsNavigator() {
name="MyProfileTab" name="MyProfileTab"
getComponent={() => MyProfileTabNavigator} getComponent={() => MyProfileTabNavigator}
/> />
<Tab.Screen
name="MessagesTab"
getComponent={() => MessagesTabNavigator}
/>
</Tab.Navigator> </Tab.Navigator>
) )
} }
function screenOptions(t: Theme) {
return {
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
} as const
}
function HomeTabNavigator() { function HomeTabNavigator() {
const t = useTheme() const t = useTheme()
return ( return (
<HomeTab.Navigator <HomeTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Home">
screenOptions={{
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} /> <HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
<HomeTab.Screen name="Start" getComponent={() => HomeScreen} /> <HomeTab.Screen name="Start" getComponent={() => HomeScreen} />
{commonScreens(HomeTab)} {commonScreens(HomeTab as typeof Flat)}
</HomeTab.Navigator> </HomeTab.Navigator>
) )
} }
@@ -538,15 +539,10 @@ function SearchTabNavigator() {
const t = useTheme() const t = useTheme()
return ( return (
<SearchTab.Navigator <SearchTab.Navigator
screenOptions={{ screenOptions={screenOptions(t)}
animationDuration: 285, initialRouteName="Search">
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
<SearchTab.Screen name="Search" getComponent={() => SearchScreen} /> <SearchTab.Screen name="Search" getComponent={() => SearchScreen} />
{commonScreens(SearchTab as typeof HomeTab)} {commonScreens(SearchTab as typeof Flat)}
</SearchTab.Navigator> </SearchTab.Navigator>
) )
} }
@@ -555,19 +551,14 @@ function NotificationsTabNavigator() {
const t = useTheme() const t = useTheme()
return ( return (
<NotificationsTab.Navigator <NotificationsTab.Navigator
screenOptions={{ screenOptions={screenOptions(t)}
animationDuration: 285, initialRouteName="Notifications">
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
<NotificationsTab.Screen <NotificationsTab.Screen
name="Notifications" name="Notifications"
getComponent={() => NotificationsScreen} getComponent={() => NotificationsScreen}
options={{requireAuth: true}} options={{requireAuth: true}}
/> />
{commonScreens(NotificationsTab as typeof HomeTab)} {commonScreens(NotificationsTab as typeof Flat)}
</NotificationsTab.Navigator> </NotificationsTab.Navigator>
) )
} }
@@ -576,23 +567,16 @@ function MyProfileTabNavigator() {
const t = useTheme() const t = useTheme()
return ( return (
<MyProfileTab.Navigator <MyProfileTab.Navigator
screenOptions={{ screenOptions={screenOptions(t)}
animationDuration: 285, initialRouteName="MyProfile">
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
<MyProfileTab.Screen <MyProfileTab.Screen
// @ts-ignore // TODO: fix this broken type in ProfileScreen // MyProfile is not in AllNavigationParams - asserting as Profile at least
name="MyProfile" // gives us typechecking for initialParams -sfn
name={'MyProfile' as 'Profile'}
getComponent={() => ProfileScreen} getComponent={() => ProfileScreen}
initialParams={{ initialParams={{name: 'me', hideBackButton: true}}
name: 'me',
hideBackButton: true,
}}
/> />
{commonScreens(MyProfileTab as typeof HomeTab)} {commonScreens(MyProfileTab as unknown as typeof Flat)}
</MyProfileTab.Navigator> </MyProfileTab.Navigator>
) )
} }
@@ -601,13 +585,8 @@ function MessagesTabNavigator() {
const t = useTheme() const t = useTheme()
return ( return (
<MessagesTab.Navigator <MessagesTab.Navigator
screenOptions={{ screenOptions={screenOptions(t)}
animationDuration: 285, initialRouteName="Messages">
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
<MessagesTab.Screen <MessagesTab.Screen
name="Messages" name="Messages"
getComponent={() => MessagesScreen} getComponent={() => MessagesScreen}
@@ -616,7 +595,7 @@ function MessagesTabNavigator() {
animationTypeForReplace: route.params?.animation ?? 'push', animationTypeForReplace: route.params?.animation ?? 'push',
})} })}
/> />
{commonScreens(MessagesTab as typeof HomeTab)} {commonScreens(MessagesTab as typeof Flat)}
</MessagesTab.Navigator> </MessagesTab.Navigator>
) )
} }
@@ -634,13 +613,7 @@ const FlatNavigator = () => {
return ( return (
<Flat.Navigator <Flat.Navigator
screenListeners={screenListeners} screenListeners={screenListeners}
screenOptions={{ screenOptions={screenOptions(t)}>
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
<Flat.Screen <Flat.Screen
name="Home" name="Home"
getComponent={() => HomeScreen} getComponent={() => HomeScreen}
@@ -666,7 +639,7 @@ const FlatNavigator = () => {
getComponent={() => HomeScreen} getComponent={() => HomeScreen}
options={{title: title(msg`Home`)}} options={{title: title(msg`Home`)}}
/> />
{commonScreens(Flat as typeof HomeTab, numUnread)} {commonScreens(Flat, numUnread)}
</Flat.Navigator> </Flat.Navigator>
) )
} }
@@ -773,7 +746,14 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
logModuleInitTime() logModuleInitTime()
onReady() onReady()
logger.metric('router:navigate', {}, {statsig: false}) logger.metric('router:navigate', {}, {statsig: false})
}}> }}
// WARNING: Implicit navigation to nested navigators is depreciated in React Navigation 7.x
// However, there's a fair amount of places we do that, especially in when popping to the top of stacks.
// See BottomBar.tsx for an example of how to handle nested navigators in the tabs correctly.
// I'm scared of missing a spot (esp. with push notifications etc) so let's enable this legacy behaviour for now.
// We will need to confirm we handle nested navigators correctly by the time we migrate to React Navigation 8.x
// -sfn
navigationInChildEnabled>
{children} {children}
</NavigationContainer> </NavigationContainer>
</> </>
+26 -10
View File
@@ -1,7 +1,11 @@
import React from 'react' import React, {useMemo} from 'react'
import {type GestureResponderEvent} from 'react-native' import {type GestureResponderEvent} from 'react-native'
import {sanitizeUrl} from '@braintree/sanitize-url' import {sanitizeUrl} from '@braintree/sanitize-url'
import {StackActions, useLinkProps} from '@react-navigation/native' import {
type LinkProps as RNLinkProps,
StackActions,
useLinkBuilder,
} from '@react-navigation/native'
import {BSKY_DOWNLOAD_URL} from '#/lib/constants' import {BSKY_DOWNLOAD_URL} from '#/lib/constants'
import {useNavigationDeduped} from '#/lib/hooks/useNavigationDeduped' import {useNavigationDeduped} from '#/lib/hooks/useNavigationDeduped'
@@ -28,12 +32,11 @@ import {router} from '#/routes'
*/ */
export {useButtonContext as useLinkContext} from '#/components/Button' export {useButtonContext as useLinkContext} from '#/components/Button'
type BaseLinkProps = Pick< type BaseLinkProps = {
Parameters<typeof useLinkProps<AllNavigatorParams>>[0],
'to'
> & {
testID?: string testID?: string
to: RNLinkProps<AllNavigatorParams> | string
/** /**
* The React Navigation `StackAction` to perform when the link is pressed. * The React Navigation `StackAction` to perform when the link is pressed.
*/ */
@@ -92,10 +95,23 @@ export function useLink({
shouldProxy?: boolean shouldProxy?: boolean
}) { }) {
const navigation = useNavigationDeduped() const navigation = useNavigationDeduped()
const {href} = useLinkProps<AllNavigatorParams>({ const {buildHref} = useLinkBuilder()
to: const href = useMemo(() => {
typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to, return typeof to === 'string'
}) ? convertBskyAppUrlIfNeeded(sanitizeUrl(to))
: to.screen
? buildHref(to.screen, to.params)
: to.href
? convertBskyAppUrlIfNeeded(sanitizeUrl(to.href))
: undefined
}, [to, buildHref])
if (!href) {
throw new Error(
'Link `to` prop must be a string or an object with `screen` and `params` properties',
)
}
const isExternal = isExternalUrl(href) const isExternal = isExternalUrl(href)
const {openModal, closeModal} = useModalControls() const {openModal, closeModal} = useModalControls()
const openLink = useOpenLink() const openLink = useOpenLink()
+9 -38
View File
@@ -1,10 +1,8 @@
import React from 'react' import {useMemo} from 'react'
import {useNavigation} from '@react-navigation/core' import {useNavigation} from '@react-navigation/core'
import {NavigationState} from '@react-navigation/native'
import type {NavigationAction} from '@react-navigation/routers'
import {useDedupe} from '#/lib/hooks/useDedupe' import {useDedupe} from '#/lib/hooks/useDedupe'
import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
export type DebouncedNavigationProp = Pick< export type DebouncedNavigationProp = Pick<
NavigationProp, NavigationProp,
@@ -22,46 +20,19 @@ export function useNavigationDeduped() {
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const dedupe = useDedupe() const dedupe = useDedupe()
return React.useMemo( return useMemo<DebouncedNavigationProp>(
(): DebouncedNavigationProp => ({ () => ({
// Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts push: (...args: Parameters<typeof navigation.push>) => {
push: <RouteName extends keyof AllNavigatorParams>(
...args: undefined extends AllNavigatorParams[RouteName]
?
| [screen: RouteName]
| [screen: RouteName, params: AllNavigatorParams[RouteName]]
: [screen: RouteName, params: AllNavigatorParams[RouteName]]
) => {
dedupe(() => navigation.push(...args)) dedupe(() => navigation.push(...args))
}, },
// Types from @react-navigation/core/src/types.tsx navigate: (...args: Parameters<typeof navigation.navigate>) => {
navigate: <RouteName extends keyof AllNavigatorParams>(
...args: RouteName extends unknown
? undefined extends AllNavigatorParams[RouteName]
?
| [screen: RouteName]
| [screen: RouteName, params: AllNavigatorParams[RouteName]]
: [screen: RouteName, params: AllNavigatorParams[RouteName]]
: never
) => {
dedupe(() => navigation.navigate(...args)) dedupe(() => navigation.navigate(...args))
}, },
// Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts replace: (...args: Parameters<typeof navigation.replace>) => {
replace: <RouteName extends keyof AllNavigatorParams>(
...args: undefined extends AllNavigatorParams[RouteName]
?
| [screen: RouteName]
| [screen: RouteName, params: AllNavigatorParams[RouteName]]
: [screen: RouteName, params: AllNavigatorParams[RouteName]]
) => {
dedupe(() => navigation.replace(...args)) dedupe(() => navigation.replace(...args))
}, },
dispatch: ( dispatch: (...args: Parameters<typeof navigation.dispatch>) => {
action: dedupe(() => navigation.dispatch(...args))
| NavigationAction
| ((state: NavigationState) => NavigationAction),
) => {
dedupe(() => navigation.dispatch(action))
}, },
popToTop: () => { popToTop: () => {
dedupe(() => navigation.popToTop()) dedupe(() => navigation.popToTop())
+1 -1
View File
@@ -92,7 +92,7 @@ export type NotificationsTabNavigatorParams = CommonNavigatorParams & {
} }
export type MyProfileTabNavigatorParams = CommonNavigatorParams & { export type MyProfileTabNavigatorParams = CommonNavigatorParams & {
MyProfile: undefined MyProfile: {name: 'me'; hideBackButton: true}
} }
export type MessagesTabNavigatorParams = CommonNavigatorParams & { export type MessagesTabNavigatorParams = CommonNavigatorParams & {
+42 -37
View File
@@ -1,20 +1,20 @@
import React, {ComponentProps, memo, useMemo} from 'react' import {memo, useCallback, useMemo} from 'react'
import { import {
GestureResponderEvent, type GestureResponderEvent,
Platform, Platform,
Pressable, Pressable,
StyleProp, type StyleProp,
TextProps, type TextProps,
TextStyle, type TextStyle,
TouchableOpacity, type TouchableOpacity,
View, View,
ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import {sanitizeUrl} from '@braintree/sanitize-url' import {sanitizeUrl} from '@braintree/sanitize-url'
import {StackActions, useLinkProps} from '@react-navigation/native' import {StackActions} from '@react-navigation/native'
import { import {
DebouncedNavigationProp, type DebouncedNavigationProp,
useNavigationDeduped, useNavigationDeduped,
} from '#/lib/hooks/useNavigationDeduped' } from '#/lib/hooks/useNavigationDeduped'
import {useOpenLink} from '#/lib/hooks/useOpenLink' import {useOpenLink} from '#/lib/hooks/useOpenLink'
@@ -24,7 +24,7 @@ import {
isExternalUrl, isExternalUrl,
linkRequiresWarning, linkRequiresWarning,
} from '#/lib/strings/url-helpers' } from '#/lib/strings/url-helpers'
import {TypographyVariant} from '#/lib/ThemeContext' import {type TypographyVariant} from '#/lib/ThemeContext'
import {isAndroid, isWeb} from '#/platform/detection' import {isAndroid, isWeb} from '#/platform/detection'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
@@ -38,7 +38,7 @@ type Event =
| React.MouseEvent<HTMLAnchorElement, MouseEvent> | React.MouseEvent<HTMLAnchorElement, MouseEvent>
| GestureResponderEvent | GestureResponderEvent
interface Props extends ComponentProps<typeof TouchableOpacity> { interface Props extends React.ComponentProps<typeof TouchableOpacity> {
testID?: string testID?: string
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
href?: string href?: string
@@ -47,7 +47,7 @@ interface Props extends ComponentProps<typeof TouchableOpacity> {
hoverStyle?: StyleProp<ViewStyle> hoverStyle?: StyleProp<ViewStyle>
noFeedback?: boolean noFeedback?: boolean
asAnchor?: boolean asAnchor?: boolean
dataSet?: Object | undefined dataSet?: any
anchorNoUnderline?: boolean anchorNoUnderline?: boolean
navigationAction?: 'push' | 'replace' | 'navigate' navigationAction?: 'push' | 'replace' | 'navigate'
onPointerEnter?: () => void onPointerEnter?: () => void
@@ -69,6 +69,7 @@ export const Link = memo(function Link({
onBeforePress, onBeforePress,
accessibilityActions, accessibilityActions,
onAccessibilityAction, onAccessibilityAction,
dataSet: dataSetProp,
...props ...props
}: Props) { }: Props) {
const t = useTheme() const t = useTheme()
@@ -77,7 +78,7 @@ export const Link = memo(function Link({
const anchorHref = asAnchor ? sanitizeUrl(href) : undefined const anchorHref = asAnchor ? sanitizeUrl(href) : undefined
const openLink = useOpenLink() const openLink = useOpenLink()
const onPress = React.useCallback( const onPress = useCallback(
(e?: Event) => { (e?: Event) => {
onBeforePress?.() onBeforePress?.()
if (typeof href === 'string') { if (typeof href === 'string') {
@@ -99,6 +100,14 @@ export const Link = memo(function Link({
{name: 'activate', label: title}, {name: 'activate', label: title},
] ]
const dataSet = useMemo(() => {
const ds = {...dataSetProp}
if (anchorNoUnderline) {
ds.noUnderline = 1
}
return ds
}, [dataSetProp, anchorNoUnderline])
if (noFeedback) { if (noFeedback) {
return ( return (
<WebAuxClickWrapper> <WebAuxClickWrapper>
@@ -129,17 +138,6 @@ export const Link = memo(function Link({
) )
} }
if (anchorNoUnderline) {
// @ts-ignore web only -prf
props.dataSet = props.dataSet || {}
// @ts-ignore web only -prf
props.dataSet.noUnderline = 1
}
if (title && !props.accessibilityLabel) {
props.accessibilityLabel = title
}
const Com = props.hoverStyle ? PressableWithHover : Pressable const Com = props.hoverStyle ? PressableWithHover : Pressable
return ( return (
<Com <Com
@@ -148,8 +146,11 @@ export const Link = memo(function Link({
onPress={onPress} onPress={onPress}
accessible={accessible} accessible={accessible}
accessibilityRole="link" accessibilityRole="link"
accessibilityLabel={props.accessibilityLabel ?? title}
accessibilityHint={props.accessibilityHint}
// @ts-ignore web only -prf // @ts-ignore web only -prf
href={anchorHref} href={anchorHref}
dataSet={dataSet}
{...props}> {...props}>
{children ? children : <Text>{title || 'link'}</Text>} {children ? children : <Text>{title || 'link'}</Text>}
</Com> </Com>
@@ -164,14 +165,14 @@ export const TextLink = memo(function TextLink({
text, text,
numberOfLines, numberOfLines,
lineHeight, lineHeight,
dataSet, dataSet: dataSetProp,
title, title,
onPress, onPress: onPressProp,
onBeforePress, onBeforePress,
disableMismatchWarning, disableMismatchWarning,
navigationAction, navigationAction,
anchorNoUnderline, anchorNoUnderline,
...orgProps ...props
}: { }: {
testID?: string testID?: string
type?: TypographyVariant type?: TypographyVariant
@@ -187,7 +188,6 @@ export const TextLink = memo(function TextLink({
anchorNoUnderline?: boolean anchorNoUnderline?: boolean
onBeforePress?: () => void onBeforePress?: () => void
} & TextProps) { } & TextProps) {
const {...props} = useLinkProps({to: sanitizeUrl(href)})
const navigation = useNavigationDeduped() const navigation = useNavigationDeduped()
const {openModal, closeModal} = useModalControls() const {openModal, closeModal} = useModalControls()
const openLink = useOpenLink() const openLink = useOpenLink()
@@ -196,12 +196,15 @@ export const TextLink = memo(function TextLink({
console.error('Unable to detect mismatching label') console.error('Unable to detect mismatching label')
} }
const dataSet = useMemo(() => {
const ds = {...dataSetProp}
if (anchorNoUnderline) { if (anchorNoUnderline) {
dataSet = dataSet ?? {} ds.noUnderline = 1
dataSet.noUnderline = 1
} }
return ds
}, [dataSetProp, anchorNoUnderline])
props.onPress = React.useCallback( const onPress = useCallback(
(e?: Event) => { (e?: Event) => {
const requiresWarning = const requiresWarning =
!disableMismatchWarning && !disableMismatchWarning &&
@@ -224,10 +227,10 @@ export const TextLink = memo(function TextLink({
return return
} }
onBeforePress?.() onBeforePress?.()
if (onPress) { if (onPressProp) {
e?.preventDefault?.() e?.preventDefault?.()
// @ts-ignore function signature differs by platform -prf // @ts-expect-error function signature differs by platform -prf
return onPress() return onPressProp()
} }
return onPressInner( return onPressInner(
closeModal, closeModal,
@@ -240,7 +243,7 @@ export const TextLink = memo(function TextLink({
}, },
[ [
onBeforePress, onBeforePress,
onPress, onPressProp,
closeModal, closeModal,
openModal, openModal,
navigation, navigation,
@@ -273,8 +276,10 @@ export const TextLink = memo(function TextLink({
title={title} title={title}
// @ts-ignore web only -prf // @ts-ignore web only -prf
hrefAttrs={hrefAttrs} // hack to get open in new tab to work on safari. without this, safari will open in a new window hrefAttrs={hrefAttrs} // hack to get open in new tab to work on safari. without this, safari will open in a new window
{...props} onPress={onPress}
{...orgProps}> accessibilityRole="link"
href={convertBskyAppUrlIfNeeded(sanitizeUrl(href))}
{...props}>
{text} {text}
</Text> </Text>
) )
+18 -4
View File
@@ -160,7 +160,7 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
// = // =
const onPressTab = React.useCallback( const onPressTab = React.useCallback(
(tab: string) => { (tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => {
const state = navigation.getState() const state = navigation.getState()
setDrawerOpen(false) setDrawerOpen(false)
if (isWeb) { if (isWeb) {
@@ -168,7 +168,7 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
if (tab === 'MyProfile') { if (tab === 'MyProfile') {
navigation.navigate('Profile', {name: currentAccount!.handle}) navigation.navigate('Profile', {name: currentAccount!.handle})
} else { } else {
// @ts-ignore must be Home, Search, Notifications, or MyProfile // @ts-expect-error struggles with string unions, apparently
navigation.navigate(tab) navigation.navigate(tab)
} }
} else { } else {
@@ -176,9 +176,23 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
if (tabState === TabState.InsideAtRoot) { if (tabState === TabState.InsideAtRoot) {
emitSoftReset() emitSoftReset()
} else if (tabState === TabState.Inside) { } else if (tabState === TabState.Inside) {
navigation.dispatch(StackActions.popToTop()) // find the correct navigator in which to pop-to-top
const target = state.routes.find(route => route.name === `${tab}Tab`)
?.state?.key
if (target) {
// if we found it, trigger pop-to-top
navigation.dispatch({
...StackActions.popToTop(),
target,
})
} else {
// fallback: reset navigation
navigation.reset({
index: 0,
routes: [{name: `${tab}Tab`}],
})
}
} else { } else {
// @ts-ignore must be Home, Search, Notifications, or MyProfile
navigation.navigate(`${tab}Tab`) navigation.navigate(`${tab}Tab`)
} }
} }
+30 -22
View File
@@ -1,4 +1,4 @@
import React, {type ComponentProps} from 'react' import {useCallback} from 'react'
import {type GestureResponderEvent, View} from 'react-native' import {type GestureResponderEvent, View} from 'react-native'
import Animated from 'react-native-reanimated' import Animated from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
@@ -52,13 +52,7 @@ import {
import {useDemoMode} from '#/storage/hooks/demo-mode' import {useDemoMode} from '#/storage/hooks/demo-mode'
import {styles} from './BottomBarStyles' import {styles} from './BottomBarStyles'
type TabOptions = type TabOptions = 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile'
| 'Home'
| 'Search'
| 'Notifications'
| 'MyProfile'
| 'Feeds'
| 'Messages'
export function BottomBar({navigation}: BottomTabBarProps) { export function BottomBar({navigation}: BottomTabBarProps) {
const {hasSession, currentAccount} = useSession() const {hasSession, currentAccount} = useSession()
@@ -81,48 +75,62 @@ export function BottomBar({navigation}: BottomTabBarProps) {
const gate = useGate() const gate = useGate()
const iconWidth = 28 const iconWidth = 28
const showSignIn = React.useCallback(() => { const showSignIn = useCallback(() => {
closeAllActiveElements() closeAllActiveElements()
requestSwitchToAccount({requestedAccount: 'none'}) requestSwitchToAccount({requestedAccount: 'none'})
}, [requestSwitchToAccount, closeAllActiveElements]) }, [requestSwitchToAccount, closeAllActiveElements])
const showCreateAccount = React.useCallback(() => { const showCreateAccount = useCallback(() => {
closeAllActiveElements() closeAllActiveElements()
requestSwitchToAccount({requestedAccount: 'new'}) requestSwitchToAccount({requestedAccount: 'new'})
// setShowLoggedOut(true) // setShowLoggedOut(true)
}, [requestSwitchToAccount, closeAllActiveElements]) }, [requestSwitchToAccount, closeAllActiveElements])
const onPressTab = React.useCallback( const onPressTab = useCallback(
(tab: TabOptions) => { (tab: TabOptions) => {
const state = navigation.getState() const state = navigation.getState()
const tabState = getTabState(state, tab) const tabState = getTabState(state, tab)
if (tabState === TabState.InsideAtRoot) { if (tabState === TabState.InsideAtRoot) {
emitSoftReset() emitSoftReset()
} else if (tabState === TabState.Inside) { } else if (tabState === TabState.Inside) {
dedupe(() => navigation.dispatch(StackActions.popToTop())) // find the correct navigator in which to pop-to-top
const target = state.routes.find(route => route.name === `${tab}Tab`)
?.state?.key
dedupe(() => {
if (target) {
// if we found it, trigger pop-to-top
navigation.dispatch({
...StackActions.popToTop(),
target,
})
} else {
// fallback: reset navigation
navigation.reset({
index: 0,
routes: [{name: `${tab}Tab`}],
})
}
})
} else { } else {
dedupe(() => navigation.navigate(`${tab}Tab`)) dedupe(() => navigation.navigate(`${tab}Tab`))
} }
}, },
[navigation, dedupe], [navigation, dedupe],
) )
const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab]) const onPressHome = useCallback(() => onPressTab('Home'), [onPressTab])
const onPressSearch = React.useCallback( const onPressSearch = useCallback(() => onPressTab('Search'), [onPressTab])
() => onPressTab('Search'), const onPressNotifications = useCallback(
[onPressTab],
)
const onPressNotifications = React.useCallback(
() => onPressTab('Notifications'), () => onPressTab('Notifications'),
[onPressTab], [onPressTab],
) )
const onPressProfile = React.useCallback(() => { const onPressProfile = useCallback(() => {
onPressTab('MyProfile') onPressTab('MyProfile')
}, [onPressTab]) }, [onPressTab])
const onPressMessages = React.useCallback(() => { const onPressMessages = useCallback(() => {
onPressTab('Messages') onPressTab('Messages')
}, [onPressTab]) }, [onPressTab])
const onLongPressProfile = React.useCallback(() => { const onLongPressProfile = useCallback(() => {
playHaptic() playHaptic()
accountSwitchControl.open() accountSwitchControl.open()
}, [accountSwitchControl, playHaptic]) }, [accountSwitchControl, playHaptic])
@@ -361,7 +369,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
interface BtnProps interface BtnProps
extends Pick< extends Pick<
ComponentProps<typeof PressableScale>, React.ComponentProps<typeof PressableScale>,
| 'accessible' | 'accessible'
| 'accessibilityRole' | 'accessibilityRole'
| 'accessibilityHint' | 'accessibilityHint'
@@ -1,25 +1,29 @@
import * as React from 'react' import * as React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
// Based on @react-navigation/native-stack/src/createNativeStackNavigator.ts // Based on @react-navigation/native-stack/src/navigators/createNativeStackNavigator.ts
// MIT License // MIT License
// Copyright (c) 2017 React Navigation Contributors // Copyright (c) 2017 React Navigation Contributors
import { import {
createNavigatorFactory, createNavigatorFactory,
type EventArg, type EventArg,
type NavigatorTypeBagBase,
type ParamListBase, type ParamListBase,
type StackActionHelpers, type StackActionHelpers,
StackActions, StackActions,
type StackNavigationState, type StackNavigationState,
StackRouter, StackRouter,
type StackRouterOptions, type StackRouterOptions,
type StaticConfig,
type TypedNavigator,
useNavigationBuilder, useNavigationBuilder,
} from '@react-navigation/native' } from '@react-navigation/native'
import {NativeStackView} from '@react-navigation/native-stack'
import { import {
type NativeStackNavigationEventMap, type NativeStackNavigationEventMap,
type NativeStackNavigationOptions, type NativeStackNavigationOptions,
type NativeStackNavigationProp,
type NativeStackNavigatorProps,
} from '@react-navigation/native-stack' } from '@react-navigation/native-stack'
import {NativeStackView} from '@react-navigation/native-stack'
import {type NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types'
import {PWI_ENABLED} from '#/lib/build-flags' import {PWI_ENABLED} from '#/lib/build-flags'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
@@ -48,12 +52,14 @@ function NativeStackNavigator({
id, id,
initialRouteName, initialRouteName,
children, children,
layout,
screenListeners, screenListeners,
screenOptions, screenOptions,
screenLayout,
...rest ...rest
}: NativeStackNavigatorProps) { }: NativeStackNavigatorProps) {
// --- this is copy and pasted from the original native stack navigator --- // --- this is copy and pasted from the original native stack navigator ---
const {state, descriptors, navigation, NavigationContent} = const {state, describe, descriptors, navigation, NavigationContent} =
useNavigationBuilder< useNavigationBuilder<
StackNavigationState<ParamListBase>, StackNavigationState<ParamListBase>,
StackRouterOptions, StackRouterOptions,
@@ -64,9 +70,12 @@ function NativeStackNavigator({
id, id,
initialRouteName, initialRouteName,
children, children,
layout,
screenListeners, screenListeners,
screenOptions, screenOptions,
screenLayout,
}) })
React.useEffect( React.useEffect(
() => () =>
// @ts-expect-error: there may not be a tab navigator in parent // @ts-expect-error: there may not be a tab navigator in parent
@@ -148,7 +157,8 @@ function NativeStackNavigator({
{...rest} {...rest}
state={state} state={state}
navigation={navigation} navigation={navigation}
descriptors={newDescriptors} descriptors={descriptors}
describe={describe}
/> />
</View> </View>
{isWeb && ( {isWeb && (
@@ -161,9 +171,25 @@ function NativeStackNavigator({
) )
} }
export const createNativeStackNavigatorWithAuth = createNavigatorFactory< export function createNativeStackNavigatorWithAuth<
StackNavigationState<ParamListBase>, const ParamList extends ParamListBase,
NativeStackNavigationOptionsWithAuth, const NavigatorID extends string | undefined = undefined,
NativeStackNavigationEventMap, const TypeBag extends NavigatorTypeBagBase = {
typeof NativeStackNavigator ParamList: ParamList
>(NativeStackNavigator) NavigatorID: NavigatorID
State: StackNavigationState<ParamList>
ScreenOptions: NativeStackNavigationOptionsWithAuth
EventMap: NativeStackNavigationEventMap
NavigationList: {
[RouteName in keyof ParamList]: NativeStackNavigationProp<
ParamList,
RouteName,
NavigatorID
>
}
Navigator: typeof NativeStackNavigator
},
const Config extends StaticConfig<TypeBag> = StaticConfig<TypeBag>,
>(config?: Config): TypedNavigator<TypeBag, Config> {
return createNavigatorFactory(NativeStackNavigator)(config)
}
+8 -8
View File
@@ -1,10 +1,10 @@
import React from 'react' import {useCallback, useMemo, useState} from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api' import {type AppBskyActorDefs} from '@atproto/api'
import {msg, plural, Trans} from '@lingui/macro' import {msg, plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import { import {
useLinkProps, useLinkTo,
useNavigation, useNavigation,
useNavigationState, useNavigationState,
} from '@react-navigation/native' } from '@react-navigation/native'
@@ -326,7 +326,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
const {_} = useLingui() const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {leftNavMinimal} = useLayoutBreakpoints() const {leftNavMinimal} = useLayoutBreakpoints()
const [pathName] = React.useMemo(() => router.matchPath(href), [href]) const [pathName] = useMemo(() => router.matchPath(href), [href])
const currentRouteInfo = useNavigationState(state => { const currentRouteInfo = useNavigationState(state => {
if (!state) { if (!state) {
return {name: 'Home'} return {name: 'Home'}
@@ -339,8 +339,8 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
(currentRouteInfo.params as CommonNavigatorParams['Profile']).name === (currentRouteInfo.params as CommonNavigatorParams['Profile']).name ===
currentAccount?.handle currentAccount?.handle
: isTab(currentRouteInfo.name, pathName) : isTab(currentRouteInfo.name, pathName)
const {onPress} = useLinkProps({to: href}) const linkTo = useLinkTo()
const onPressWrapped = React.useCallback( const onPressWrapped = useCallback(
(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => { (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (e.ctrlKey || e.metaKey || e.altKey) { if (e.ctrlKey || e.metaKey || e.altKey) {
return return
@@ -349,10 +349,10 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
if (isCurrent) { if (isCurrent) {
emitSoftReset() emitSoftReset()
} else { } else {
onPress() linkTo(href)
} }
}, },
[onPress, isCurrent], [linkTo, href, isCurrent],
) )
return ( return (
@@ -468,7 +468,7 @@ function ComposeBtn() {
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
const {_} = useLingui() const {_} = useLingui()
const {leftNavMinimal} = useLayoutBreakpoints() const {leftNavMinimal} = useLayoutBreakpoints()
const [isFetchingHandle, setIsFetchingHandle] = React.useState(false) const [isFetchingHandle, setIsFetchingHandle] = useState(false)
const fetchHandle = useFetchHandle() const fetchHandle = useFetchHandle()
const getProfileHandle = async () => { const getProfileHandle = async () => {
+73 -56
View File
@@ -6289,65 +6289,69 @@
invariant "^2.2.4" invariant "^2.2.4"
nullthrows "^1.1.1" nullthrows "^1.1.1"
"@react-navigation/bottom-tabs@^6.5.20": "@react-navigation/bottom-tabs@^7.3.13":
version "6.5.20" version "7.3.14"
resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.20.tgz#5335e75b02c527ef0569bd97d4f9185d65616e49" resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-7.3.14.tgz#9ee02baea86ab24abe267726665bc69c6df0bf4c"
integrity sha512-ow6Z06iS4VqBO8d7FP+HsGjJLWt2xTWIvuWjpoCvsM/uQXzCRDIjBv9HaKcXbF0yTW7IMir0oDAbU5PFzEDdgA== integrity sha512-s2qinJggS2HYZdCOey9A+fN+bNpWeEKwiL/FjAVOTcv+uofxPWN6CtEZUZGPEjfRjis/srURBmCmpNZSI6sQ9Q==
dependencies: dependencies:
"@react-navigation/elements" "^1.3.30" "@react-navigation/elements" "^2.4.3"
color "^4.2.3" color "^4.2.3"
warn-once "^0.1.0"
"@react-navigation/core@^6.4.16": "@react-navigation/core@^7.10.0":
version "6.4.16" version "7.10.0"
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-6.4.16.tgz#f9369a134805174536b9aa0f0f483b930511caf9" resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.10.0.tgz#8205ea6b84ce34b2fc2c196701b4cd9b434211b9"
integrity sha512-UDTJBsHxnzgFETR3ZxhctP+RWr4SkyeZpbhpkQoIGOuwSCkt1SE0qjU48/u6r6w6XlX8OqVudn1Ab0QFXTHxuQ== integrity sha512-qZBA5gGm+9liT4+EHk+kl9apwvqh7HqhLF1XeX6SQRmC/n2QI0u1B8OevKc+EPUDEM9Od15IuwT/GRbSs7/Umw==
dependencies: dependencies:
"@react-navigation/routers" "^6.1.9" "@react-navigation/routers" "^7.4.0"
escape-string-regexp "^4.0.0" escape-string-regexp "^4.0.0"
nanoid "^3.1.23" nanoid "^3.3.11"
query-string "^7.1.3" query-string "^7.1.3"
react-is "^16.13.0" react-is "^19.1.0"
use-latest-callback "^0.1.9" use-latest-callback "^0.2.3"
use-sync-external-store "^1.5.0"
"@react-navigation/drawer@^6.6.15": "@react-navigation/drawer@^7.3.12":
version "6.6.15" version "7.4.1"
resolved "https://registry.yarnpkg.com/@react-navigation/drawer/-/drawer-6.6.15.tgz#fcedba68f735103dbc035911f5959ce926081d62" resolved "https://registry.yarnpkg.com/@react-navigation/drawer/-/drawer-7.4.1.tgz#50517d8c57f09cdbfc20a485c47016066b918e76"
integrity sha512-GLkFQNxjtmxB/qXSHmu1DfoB89jCzW64tmX68iPndth+9U+0IP27GcCCaMZxQfwj+nI8Kn2zlTlXAZDIIHE+DQ== integrity sha512-kj5wL31smDLw/6l+0KPR5cjaOZg6oHJCl3RPQonFPuYolUPZBVnuS++uvlifWcD/mqdGmhl3rgLTircRH4vQ7Q==
dependencies: dependencies:
"@react-navigation/elements" "^1.3.30" "@react-navigation/elements" "^2.4.3"
color "^4.2.3" color "^4.2.3"
warn-once "^0.1.0" react-native-drawer-layout "^4.1.10"
use-latest-callback "^0.2.3"
"@react-navigation/elements@^1.3.30": "@react-navigation/elements@^2.4.3":
version "1.3.30" version "2.4.3"
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.30.tgz#a81371f599af1070b12014f05d6c09b1a611fd9a" resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.4.3.tgz#cc1dde4c98739d35a0c9c23872316063962cfaee"
integrity sha512-plhc8UvCZs0UkV+sI+3bisIyn78wz9O/BiWZXpounu72k/R/Sj5PuZYFJ1fi6psvriUveMCGh4LeZckAZu2qiQ== integrity sha512-psoNmnZ0DQIt9nxxPITVLtYW04PGCAfnmd/Pcd3yhiBs93aj+HYKH+SDZDpUnXMf3BN7Wvo4+jPI+/Xjqb+m9w==
"@react-navigation/native-stack@^6.9.26":
version "6.9.26"
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.9.26.tgz#90facf7783c9927f094bc9f01c613af75b6c241e"
integrity sha512-++dueQ+FDj2XkZ902DVrK79ub1vp19nSdAZWxKRgd6+Bc0Niiesua6rMCqymYOVaYh+dagwkA9r00bpt/U5WLw==
dependencies: dependencies:
"@react-navigation/elements" "^1.3.30" color "^4.2.3"
warn-once "^0.1.0"
"@react-navigation/native@^6.1.17": "@react-navigation/native-stack@^7.3.13":
version "6.1.17" version "7.3.14"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-6.1.17.tgz#439f15a99809d26ea4682d2a3766081cf2ca31cf" resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.3.14.tgz#d1c90f2e50cd13bbced923991cf2faee8083f725"
integrity sha512-mer3OvfwWOHoUSMJyLa4vnBH3zpFmCwuzrBPlw7feXklurr/ZDiLjLxUScOot6jLRMz/67GyilEYMmP99LL0RQ== integrity sha512-45Sf7ReqSCIySXS5nrKtLGmNlFXm5x+u32YQMwKDONCqVGOBCfo4ryKqeQq1EMJ7Py6IDyOwHMhA+jhNOxnfPw==
dependencies: dependencies:
"@react-navigation/core" "^6.4.16" "@react-navigation/elements" "^2.4.3"
warn-once "^0.1.1"
"@react-navigation/native@^7.1.9":
version "7.1.10"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.1.10.tgz#768f674f7c09b6a57215762052aa62a7dc107402"
integrity sha512-Ug4IML0DkAxZTMF/E7lyyLXSclkGAYElY2cxZWITwfBjtlVeda0NjsdnTWY5EGjnd7bwvhTIUC+CO6qSlrDn5A==
dependencies:
"@react-navigation/core" "^7.10.0"
escape-string-regexp "^4.0.0" escape-string-regexp "^4.0.0"
fast-deep-equal "^3.1.3" fast-deep-equal "^3.1.3"
nanoid "^3.1.23" nanoid "^3.3.11"
use-latest-callback "^0.2.3"
"@react-navigation/routers@^6.1.9": "@react-navigation/routers@^7.4.0":
version "6.1.9" version "7.4.0"
resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-6.1.9.tgz#73f5481a15a38e36592a0afa13c3c064b9f90bed" resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-7.4.0.tgz#5bace799713ac163310c18711b98dfbe418c6b36"
integrity sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA== integrity sha512-th5THnuWKJlmr7GGHiicy979di11ycDWub9iIXbEDvQwmwmsRzppmVbfs2nD8bC/MgyMgqWu/gxfys+HqN+kcw==
dependencies: dependencies:
nanoid "^3.1.23" nanoid "^3.3.11"
"@remirror/core-constants@3.0.0": "@remirror/core-constants@3.0.0":
version "3.0.0" version "3.0.0"
@@ -14962,11 +14966,16 @@ mz@^2.7.0:
object-assign "^4.0.1" object-assign "^4.0.1"
thenify-all "^1.0.0" thenify-all "^1.0.0"
nanoid@^3.1.23, nanoid@^3.3.1, nanoid@^3.3.6: nanoid@^3.3.1, nanoid@^3.3.6:
version "3.3.6" version "3.3.6"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
nanoid@^3.3.11:
version "3.3.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
nanoid@^3.3.7: nanoid@^3.3.7:
version "3.3.7" version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
@@ -16727,12 +16736,12 @@ react-image-crop@^11.0.7:
resolved "https://registry.yarnpkg.com/react-image-crop/-/react-image-crop-11.0.7.tgz#25f3d37ccbb65a05d19d23b4740a5912835c741e" resolved "https://registry.yarnpkg.com/react-image-crop/-/react-image-crop-11.0.7.tgz#25f3d37ccbb65a05d19d23b4740a5912835c741e"
integrity sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ== integrity sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ==
react-is@19, react-is@^19.0.0: react-is@19, react-is@^19.0.0, react-is@^19.1.0:
version "19.1.0" version "19.1.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.1.0.tgz#805bce321546b7e14c084989c77022351bbdd11b" resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.1.0.tgz#805bce321546b7e14c084989c77022351bbdd11b"
integrity sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg== integrity sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==
react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0: react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -16764,6 +16773,13 @@ react-native-dotenv@^3.4.11:
dependencies: dependencies:
dotenv "^16.4.5" dotenv "^16.4.5"
react-native-drawer-layout@^4.1.10:
version "4.1.10"
resolved "https://registry.yarnpkg.com/react-native-drawer-layout/-/react-native-drawer-layout-4.1.10.tgz#9007cb747767ca8e1c9c3337671ad35ed95ad4d9"
integrity sha512-wejQo0F+EffCkOkRh+DP6ENWMB+aWEHkXV8Pd564PmtoySZLUsV/ksYrh/mrufh7T7EuvGT8+fNHz7mMRYftWg==
dependencies:
use-latest-callback "^0.2.3"
react-native-drawer-layout@^4.1.6: react-native-drawer-layout@^4.1.6:
version "4.1.7" version "4.1.7"
resolved "https://registry.yarnpkg.com/react-native-drawer-layout/-/react-native-drawer-layout-4.1.7.tgz#1c741c9bf9c739d6672201692e4ba4839ca0c8ff" resolved "https://registry.yarnpkg.com/react-native-drawer-layout/-/react-native-drawer-layout-4.1.7.tgz#1c741c9bf9c739d6672201692e4ba4839ca0c8ff"
@@ -16806,7 +16822,7 @@ react-native-ios-context-menu@^1.15.3:
dependencies: dependencies:
"@dominicstop/ts-event-emitter" "^1.1.0" "@dominicstop/ts-event-emitter" "^1.1.0"
react-native-is-edge-to-edge@1.1.7: react-native-is-edge-to-edge@1.1.7, react-native-is-edge-to-edge@^1.1.7:
version "1.1.7" version "1.1.7"
resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz#28947688f9fafd584e73a4f935ea9603bd9b1939" resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz#28947688f9fafd584e73a4f935ea9603bd9b1939"
integrity sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w== integrity sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==
@@ -16875,12 +16891,13 @@ react-native-safe-area-context@5.4.0:
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz#04b51940408c114f75628a12a93569d30c525454" resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz#04b51940408c114f75628a12a93569d30c525454"
integrity sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA== integrity sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA==
react-native-screens@~4.10.0: react-native-screens@^4.11.1:
version "4.10.0" version "4.11.1"
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.10.0.tgz#40634aead590c6b7034ded6a9f92465d1d611906" resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.11.1.tgz#7d0f3d313d8ddc1e55437c5e038f15f8805dc991"
integrity sha512-Tw21NGuXm3PbiUGtZd0AnXirUixaAbPXDjNR0baBH7/WJDaDTTELLcQ7QRXuqAWbmr/EVCrKj1348ei1KFIr8A== integrity sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw==
dependencies: dependencies:
react-freeze "^1.0.0" react-freeze "^1.0.0"
react-native-is-edge-to-edge "^1.1.7"
warn-once "^0.1.0" warn-once "^0.1.0"
react-native-svg@15.11.2: react-native-svg@15.11.2:
@@ -19388,11 +19405,6 @@ use-isomorphic-layout-effect@^1.1.1:
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
use-latest-callback@^0.1.9:
version "0.1.9"
resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.9.tgz#10191dc54257e65a8e52322127643a8940271e2a"
integrity sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw==
use-latest-callback@^0.2.3: use-latest-callback@^0.2.3:
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.3.tgz#2d644d3063040b9bc2d4c55bb525a13ae3de9e16" resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.3.tgz#2d644d3063040b9bc2d4c55bb525a13ae3de9e16"
@@ -19426,6 +19438,11 @@ use-sync-external-store@^1.2.2:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9"
integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==
use-sync-external-store@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0"
integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@@ -19527,7 +19544,7 @@ walker@^1.0.7, walker@^1.0.8:
dependencies: dependencies:
makeerror "1.0.12" makeerror "1.0.12"
warn-once@0.1.1, warn-once@^0.1.0: warn-once@0.1.1, warn-once@^0.1.0, warn-once@^0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43" resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43"
integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q== integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==