From 0f25a87c7af319b109f6605255cf00a763f5ca0a Mon Sep 17 00:00:00 2001 From: tboba Date: Fri, 6 Oct 2023 15:51:24 +0200 Subject: [PATCH 1/9] Fix typo --- src/reanimated/useReanimatedHeaderHeight.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reanimated/useReanimatedHeaderHeight.tsx b/src/reanimated/useReanimatedHeaderHeight.tsx index a012613c5b..c2b6a8015f 100644 --- a/src/reanimated/useReanimatedHeaderHeight.tsx +++ b/src/reanimated/useReanimatedHeaderHeight.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import ReanimatedHeaderHeightContext from './ReanimatedHeaderHeightContext'; -export default function useReanimatedTransitionProgress() { +export default function useReanimatedHeaderHeight() { const height = React.useContext(ReanimatedHeaderHeightContext); if (height === undefined) { From 4bb0159df4940f909894a238dfeeb8669640ee15 Mon Sep 17 00:00:00 2001 From: tboba Date: Sat, 7 Oct 2023 15:05:43 +0200 Subject: [PATCH 2/9] Add prop to Screen --- src/native-stack/views/NativeStackView.tsx | 1 + .../ReanimatedNativeStackScreen.tsx | 40 +++++++++++++++---- src/types.tsx | 2 + 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index eaad757dd4..9191a9e4be 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -264,6 +264,7 @@ const RouteView = ({ key={route.key} enabled isNativeStack + screenDescriptor={descriptors[route.key]} style={StyleSheet.absoluteFill} sheetAllowedDetents={sheetAllowedDetents} sheetLargestUndimmedDetent={sheetLargestUndimmedDetent} diff --git a/src/reanimated/ReanimatedNativeStackScreen.tsx b/src/reanimated/ReanimatedNativeStackScreen.tsx index 8232a6c59b..6b031d7cfe 100644 --- a/src/reanimated/ReanimatedNativeStackScreen.tsx +++ b/src/reanimated/ReanimatedNativeStackScreen.tsx @@ -11,6 +11,7 @@ import { import Animated, { useEvent, useSharedValue } from 'react-native-reanimated'; import ReanimatedTransitionProgressContext from './ReanimatedTransitionProgressContext'; import { + Rect, useSafeAreaFrame, useSafeAreaInsets, } from 'react-native-safe-area-context'; @@ -31,23 +32,26 @@ const ReanimatedNativeStackScreen = React.forwardRef< ScreenProps >((props, ref) => { const { children, ...rest } = props; - const { stackPresentation = 'push' } = rest; + const { stackPresentation = 'push', screenDescriptor } = rest; const dimensions = useSafeAreaFrame(); const topInset = useSafeAreaInsets().top; - let statusBarHeight = topInset; - const hasDynamicIsland = Platform.OS === 'ios' && topInset === 59; - if (hasDynamicIsland) { - // On models with Dynamic Island the status bar height is smaller than the safe area top inset. - statusBarHeight = 54; - } + const isStatusBarTranslucent = rest.statusBarTranslucent ?? false; + const statusBarHeight = getStatusBarHeight( + topInset, + dimensions, + isStatusBarTranslucent + ); + + const isLargeHeader = screenDescriptor?.options.headerLargeTitle ?? false; // Default header height, normally used in `useHeaderHeight` hook. // Here, it is used for returning a default value for shared value. const defaultHeaderHeight = getDefaultHeaderHeight( dimensions, statusBarHeight, - stackPresentation + stackPresentation, + isLargeHeader ); const cachedHeaderHeight = React.useRef(defaultHeaderHeight); @@ -111,6 +115,26 @@ const ReanimatedNativeStackScreen = React.forwardRef< ); }); +function getStatusBarHeight( + topInset: number, + dimensions: Rect, + isStatusBarTranslucent: boolean +) { + if (Platform.OS === 'ios') { + // It looks like some iOS devices don't have strictly set status bar height to 44. + // Thus, if the top inset is higher than 50, then the device should have a dynamic island. + // On models with Dynamic Island the status bar height is smaller than the safe area top inset by 5 pixels. + // See https://developer.apple.com/forums/thread/662466 for more details about status bar height. + const hasDynamicIsland = topInset > 50; + return hasDynamicIsland ? topInset - 5 : topInset; + } else if (Platform.OS === 'android') { + // On Android we should also rely on frame's y-axis position, as topInset is 0 on visible status bar. + return isStatusBarTranslucent ? topInset : dimensions.y; + } + + return topInset; +} + ReanimatedNativeStackScreen.displayName = 'ReanimatedNativeStackScreen'; export default ReanimatedNativeStackScreen; diff --git a/src/types.tsx b/src/types.tsx index 8026eaa303..f9b5b80c88 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -7,6 +7,7 @@ import { TextInputFocusEventData, ColorValue, } from 'react-native'; +import { NativeStackDescriptor } from './native-stack/types'; export type SearchBarCommands = { focus: () => void; @@ -97,6 +98,7 @@ export interface ScreenProps extends ViewProps { active?: 0 | 1 | Animated.AnimatedInterpolation; activityState?: 0 | 1 | 2 | Animated.AnimatedInterpolation; children?: React.ReactNode; + screenDescriptor?: NativeStackDescriptor; /** * Boolean indicating that swipe dismissal should trigger animation provided by `stackAnimation`. Defaults to `false`. * From f917fcc85f9d737bbd3aea8408860e7887f28589 Mon Sep 17 00:00:00 2001 From: tboba Date: Sat, 7 Oct 2023 15:09:06 +0200 Subject: [PATCH 3/9] Move implementation to new file --- src/native-stack/utils/getStatusBarHeight.tsx | 22 +++++++++++++++++++ src/native-stack/views/NativeStackView.tsx | 22 +------------------ .../ReanimatedNativeStackScreen.tsx | 22 +------------------ 3 files changed, 24 insertions(+), 42 deletions(-) create mode 100644 src/native-stack/utils/getStatusBarHeight.tsx diff --git a/src/native-stack/utils/getStatusBarHeight.tsx b/src/native-stack/utils/getStatusBarHeight.tsx new file mode 100644 index 0000000000..e54ec5c2d9 --- /dev/null +++ b/src/native-stack/utils/getStatusBarHeight.tsx @@ -0,0 +1,22 @@ +import { Rect } from 'react-native-safe-area-context'; +import { Platform } from 'react-native'; + +export default function getStatusBarHeight( + topInset: number, + dimensions: Rect, + isStatusBarTranslucent: boolean +) { + if (Platform.OS === 'ios') { + // It looks like some iOS devices don't have strictly set status bar height to 44. + // Thus, if the top inset is higher than 50, then the device should have a dynamic island. + // On models with Dynamic Island the status bar height is smaller than the safe area top inset by 5 pixels. + // See https://developer.apple.com/forums/thread/662466 for more details about status bar height. + const hasDynamicIsland = topInset > 50; + return hasDynamicIsland ? topInset - 5 : topInset; + } else if (Platform.OS === 'android') { + // On Android we should also rely on frame's y-axis position, as topInset is 0 on visible status bar. + return isStatusBarTranslucent ? topInset : dimensions.y; + } + + return topInset; +} diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index 9191a9e4be..84af9242a5 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -19,7 +19,6 @@ import { PartialState, } from '@react-navigation/native'; import { - Rect, useSafeAreaFrame, useSafeAreaInsets, } from 'react-native-safe-area-context'; @@ -31,6 +30,7 @@ import { import HeaderConfig from './HeaderConfig'; import SafeAreaProviderCompat from '../utils/SafeAreaProviderCompat'; import getDefaultHeaderHeight from '../utils/getDefaultHeaderHeight'; +import getStatusBarHeight from '../utils/getStatusBarHeight'; import HeaderHeightContext from '../utils/HeaderHeightContext'; import AnimatedHeaderHeightContext from '../utils/AnimatedHeaderHeightContext'; @@ -423,26 +423,6 @@ export default function NativeStackView(props: Props) { ); } -function getStatusBarHeight( - topInset: number, - dimensions: Rect, - isStatusBarTranslucent: boolean -) { - if (Platform.OS === 'ios') { - // It looks like some iOS devices don't have strictly set status bar height to 44. - // Thus, if the top inset is higher than 50, then the device should have a dynamic island. - // On models with Dynamic Island the status bar height is smaller than the safe area top inset by 5 pixels. - // See https://developer.apple.com/forums/thread/662466 for more details about status bar height. - const hasDynamicIsland = topInset > 50; - return hasDynamicIsland ? topInset - 5 : topInset; - } else if (Platform.OS === 'android') { - // On Android we should also rely on frame's y-axis position, as topInset is 0 on visible status bar. - return isStatusBarTranslucent ? topInset : dimensions.y; - } - - return topInset; -} - const styles = StyleSheet.create({ container: { flex: 1, diff --git a/src/reanimated/ReanimatedNativeStackScreen.tsx b/src/reanimated/ReanimatedNativeStackScreen.tsx index 6b031d7cfe..c280bdf10b 100644 --- a/src/reanimated/ReanimatedNativeStackScreen.tsx +++ b/src/reanimated/ReanimatedNativeStackScreen.tsx @@ -11,11 +11,11 @@ import { import Animated, { useEvent, useSharedValue } from 'react-native-reanimated'; import ReanimatedTransitionProgressContext from './ReanimatedTransitionProgressContext'; import { - Rect, useSafeAreaFrame, useSafeAreaInsets, } from 'react-native-safe-area-context'; import getDefaultHeaderHeight from '../native-stack/utils/getDefaultHeaderHeight'; +import getStatusBarHeight from '../native-stack/utils/getStatusBarHeight'; import ReanimatedHeaderHeightContext from './ReanimatedHeaderHeightContext'; const AnimatedScreen = Animated.createAnimatedComponent( @@ -115,26 +115,6 @@ const ReanimatedNativeStackScreen = React.forwardRef< ); }); -function getStatusBarHeight( - topInset: number, - dimensions: Rect, - isStatusBarTranslucent: boolean -) { - if (Platform.OS === 'ios') { - // It looks like some iOS devices don't have strictly set status bar height to 44. - // Thus, if the top inset is higher than 50, then the device should have a dynamic island. - // On models with Dynamic Island the status bar height is smaller than the safe area top inset by 5 pixels. - // See https://developer.apple.com/forums/thread/662466 for more details about status bar height. - const hasDynamicIsland = topInset > 50; - return hasDynamicIsland ? topInset - 5 : topInset; - } else if (Platform.OS === 'android') { - // On Android we should also rely on frame's y-axis position, as topInset is 0 on visible status bar. - return isStatusBarTranslucent ? topInset : dimensions.y; - } - - return topInset; -} - ReanimatedNativeStackScreen.displayName = 'ReanimatedNativeStackScreen'; export default ReanimatedNativeStackScreen; From 28a4d92f3bd536b93de1a62beb42e091aa00d49a Mon Sep 17 00:00:00 2001 From: tboba Date: Mon, 9 Oct 2023 16:00:50 +0200 Subject: [PATCH 4/9] Change prop to hook --- src/native-stack/types.tsx | 10 + src/native-stack/utils/ScreenInfoContext.tsx | 17 ++ src/native-stack/utils/useScreenInfo.tsx | 15 + src/native-stack/views/NativeStackView.tsx | 261 +++++++++--------- .../ReanimatedNativeStackScreen.tsx | 9 +- src/types.tsx | 2 - 6 files changed, 179 insertions(+), 135 deletions(-) create mode 100644 src/native-stack/utils/ScreenInfoContext.tsx create mode 100644 src/native-stack/utils/useScreenInfo.tsx diff --git a/src/native-stack/types.tsx b/src/native-stack/types.tsx index 8acd8bfb7e..cc5b570600 100644 --- a/src/native-stack/types.tsx +++ b/src/native-stack/types.tsx @@ -8,6 +8,9 @@ import { StackRouterOptions, StackActionHelpers, RouteProp, + Route, + NavigationState, + PartialState, } from '@react-navigation/native'; import * as React from 'react'; import { @@ -457,6 +460,13 @@ export type NativeStackNavigatorProps = StackRouterOptions & NativeStackNavigationConfig; +export type NativeStackNavigationRoute< + ParamList extends ParamListBase, + RouteName extends keyof ParamList +> = Route, ParamList[RouteName]> & { + state?: NavigationState | PartialState; +}; + export type NativeStackDescriptor = Descriptor< ParamListBase, string, diff --git a/src/native-stack/utils/ScreenInfoContext.tsx b/src/native-stack/utils/ScreenInfoContext.tsx new file mode 100644 index 0000000000..f59862c033 --- /dev/null +++ b/src/native-stack/utils/ScreenInfoContext.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { NativeStackDescriptor, NativeStackNavigationRoute } from '../types'; +import { ParamListBase } from '@react-navigation/native'; + +export type ScreenInfoType = { + descriptor: NativeStackDescriptor | undefined; + route: NativeStackNavigationRoute | undefined; + screenIndex: number; +}; + +const ScreenInfoContext = React.createContext({ + descriptor: undefined, + route: undefined, + screenIndex: -1, +}); + +export default ScreenInfoContext; diff --git a/src/native-stack/utils/useScreenInfo.tsx b/src/native-stack/utils/useScreenInfo.tsx new file mode 100644 index 0000000000..3b34e08f96 --- /dev/null +++ b/src/native-stack/utils/useScreenInfo.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +import ScreenInfoContext from './ScreenInfoContext'; + +export default function useScreenInfo() { + const screenInfo = React.useContext(ScreenInfoContext); + + if (screenInfo === undefined) { + throw new Error( + "Couldn't find information about the screen. Are you inside a screen in a navigator?" + ); + } + + return screenInfo; +} diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index 84af9242a5..50fcb47659 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -15,8 +15,6 @@ import { StackNavigationState, useTheme, Route, - NavigationState, - PartialState, } from '@react-navigation/native'; import { useSafeAreaFrame, @@ -24,6 +22,7 @@ import { } from 'react-native-safe-area-context'; import { NativeStackDescriptorMap, + NativeStackNavigationRoute, NativeStackNavigationHelpers, NativeStackNavigationOptions, } from '../types'; @@ -33,6 +32,7 @@ import getDefaultHeaderHeight from '../utils/getDefaultHeaderHeight'; import getStatusBarHeight from '../utils/getStatusBarHeight'; import HeaderHeightContext from '../utils/HeaderHeightContext'; import AnimatedHeaderHeightContext from '../utils/AnimatedHeaderHeightContext'; +import ScreenInfoContext, { ScreenInfoType } from '../utils/ScreenInfoContext'; const isAndroid = Platform.OS === 'android'; @@ -142,13 +142,6 @@ const MaybeNestedStack = ({ return content; }; -type NavigationRoute< - ParamList extends ParamListBase, - RouteName extends keyof ParamList -> = Route, ParamList[RouteName]> & { - state?: NavigationState | PartialState; -}; - const RouteView = ({ descriptors, route, @@ -157,7 +150,7 @@ const RouteView = ({ stateKey, }: { descriptors: NativeStackDescriptorMap; - route: NavigationRoute; + route: NativeStackNavigationRoute; index: number; navigation: NativeStackNavigationHelpers; stateKey: string; @@ -258,131 +251,137 @@ const RouteView = ({ const Screen = React.useContext(ScreenContext); const { dark } = useTheme(); + const screenInfo: ScreenInfoType = { + descriptor: descriptors[route.key], + route, + screenIndex: index, + }; return ( - { - navigation.dispatch({ - ...StackActions.pop(), - source: route.key, - target: stateKey, - }); - }} - onWillAppear={() => { - navigation.emit({ - type: 'transitionStart', - data: { closing: false }, - target: route.key, - }); - }} - onWillDisappear={() => { - navigation.emit({ - type: 'transitionStart', - data: { closing: true }, - target: route.key, - }); - }} - onAppear={() => { - navigation.emit({ - type: 'appear', - target: route.key, - }); - navigation.emit({ - type: 'transitionEnd', - data: { closing: false }, - target: route.key, - }); - }} - onDisappear={() => { - navigation.emit({ - type: 'transitionEnd', - data: { closing: true }, - target: route.key, - }); - }} - onHeaderHeightChange={e => { - const headerHeight = e.nativeEvent.headerHeight; - - if (cachedAnimatedHeaderHeight.current !== headerHeight) { - // Currently, we're setting value by Animated#setValue, because we want to cache animated value. - // Also, in React Native 0.72 there was a bug on Fabric causing a large delay between the screen transition, - // which should not occur. - // TODO: Check if it's possible to replace animated#setValue to Animated#event. - animatedHeaderHeight.setValue(headerHeight); - cachedAnimatedHeaderHeight.current = headerHeight; - } - }} - onDismissed={e => { - navigation.emit({ - type: 'dismiss', - target: route.key, - }); - - const dismissCount = - e.nativeEvent.dismissCount > 0 ? e.nativeEvent.dismissCount : 1; - - navigation.dispatch({ - ...StackActions.pop(dismissCount), - source: route.key, - target: stateKey, - }); - }} - onGestureCancel={() => { - navigation.emit({ - type: 'gestureCancel', - target: route.key, - }); - }}> - - - - {renderScene()} - - {/* HeaderConfig must not be first child of a Screen. + + { + navigation.dispatch({ + ...StackActions.pop(), + source: route.key, + target: stateKey, + }); + }} + onWillAppear={() => { + navigation.emit({ + type: 'transitionStart', + data: { closing: false }, + target: route.key, + }); + }} + onWillDisappear={() => { + navigation.emit({ + type: 'transitionStart', + data: { closing: true }, + target: route.key, + }); + }} + onAppear={() => { + navigation.emit({ + type: 'appear', + target: route.key, + }); + navigation.emit({ + type: 'transitionEnd', + data: { closing: false }, + target: route.key, + }); + }} + onDisappear={() => { + navigation.emit({ + type: 'transitionEnd', + data: { closing: true }, + target: route.key, + }); + }} + onHeaderHeightChange={e => { + const headerHeight = e.nativeEvent.headerHeight; + + if (cachedAnimatedHeaderHeight.current !== headerHeight) { + // Currently, we're setting value by Animated#setValue, because we want to cache animated value. + // Also, in React Native 0.72 there was a bug on Fabric causing a large delay between the screen transition, + // which should not occur. + // TODO: Check if it's possible to replace animated#setValue to Animated#event. + animatedHeaderHeight.setValue(headerHeight); + cachedAnimatedHeaderHeight.current = headerHeight; + } + }} + onDismissed={e => { + navigation.emit({ + type: 'dismiss', + target: route.key, + }); + + const dismissCount = + e.nativeEvent.dismissCount > 0 ? e.nativeEvent.dismissCount : 1; + + navigation.dispatch({ + ...StackActions.pop(dismissCount), + source: route.key, + target: stateKey, + }); + }} + onGestureCancel={() => { + navigation.emit({ + type: 'gestureCancel', + target: route.key, + }); + }}> + + + + {renderScene()} + + {/* HeaderConfig must not be first child of a Screen. See https://github.com/software-mansion/react-native-screens/pull/1825 for detailed explanation */} - - - - + + + + + ); }; diff --git a/src/reanimated/ReanimatedNativeStackScreen.tsx b/src/reanimated/ReanimatedNativeStackScreen.tsx index c280bdf10b..8a9c432b97 100644 --- a/src/reanimated/ReanimatedNativeStackScreen.tsx +++ b/src/reanimated/ReanimatedNativeStackScreen.tsx @@ -17,6 +17,7 @@ import { import getDefaultHeaderHeight from '../native-stack/utils/getDefaultHeaderHeight'; import getStatusBarHeight from '../native-stack/utils/getStatusBarHeight'; import ReanimatedHeaderHeightContext from './ReanimatedHeaderHeightContext'; +import useScreenInfo from '../native-stack/utils/useScreenInfo'; const AnimatedScreen = Animated.createAnimatedComponent( InnerScreen as unknown as React.ComponentClass @@ -31,8 +32,10 @@ const ReanimatedNativeStackScreen = React.forwardRef< typeof AnimatedScreen, ScreenProps >((props, ref) => { + const { descriptor } = useScreenInfo(); + const { children, ...rest } = props; - const { stackPresentation = 'push', screenDescriptor } = rest; + const { stackPresentation = 'push' } = rest; const dimensions = useSafeAreaFrame(); const topInset = useSafeAreaInsets().top; @@ -43,7 +46,7 @@ const ReanimatedNativeStackScreen = React.forwardRef< isStatusBarTranslucent ); - const isLargeHeader = screenDescriptor?.options.headerLargeTitle ?? false; + const isLargeHeader = descriptor?.options.headerLargeTitle ?? false; // Default header height, normally used in `useHeaderHeight` hook. // Here, it is used for returning a default value for shared value. @@ -54,6 +57,8 @@ const ReanimatedNativeStackScreen = React.forwardRef< isLargeHeader ); + console.log(defaultHeaderHeight); + const cachedHeaderHeight = React.useRef(defaultHeaderHeight); const headerHeight = useSharedValue(defaultHeaderHeight); diff --git a/src/types.tsx b/src/types.tsx index f9b5b80c88..8026eaa303 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -7,7 +7,6 @@ import { TextInputFocusEventData, ColorValue, } from 'react-native'; -import { NativeStackDescriptor } from './native-stack/types'; export type SearchBarCommands = { focus: () => void; @@ -98,7 +97,6 @@ export interface ScreenProps extends ViewProps { active?: 0 | 1 | Animated.AnimatedInterpolation; activityState?: 0 | 1 | 2 | Animated.AnimatedInterpolation; children?: React.ReactNode; - screenDescriptor?: NativeStackDescriptor; /** * Boolean indicating that swipe dismissal should trigger animation provided by `stackAnimation`. Defaults to `false`. * From 1d7d8ebf6f6836250d2e0a38deec4704966fc7ac Mon Sep 17 00:00:00 2001 From: tboba Date: Mon, 9 Oct 2023 16:03:01 +0200 Subject: [PATCH 5/9] Remove console.log --- src/reanimated/ReanimatedNativeStackScreen.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/reanimated/ReanimatedNativeStackScreen.tsx b/src/reanimated/ReanimatedNativeStackScreen.tsx index 8a9c432b97..777cef2822 100644 --- a/src/reanimated/ReanimatedNativeStackScreen.tsx +++ b/src/reanimated/ReanimatedNativeStackScreen.tsx @@ -57,8 +57,6 @@ const ReanimatedNativeStackScreen = React.forwardRef< isLargeHeader ); - console.log(defaultHeaderHeight); - const cachedHeaderHeight = React.useRef(defaultHeaderHeight); const headerHeight = useSharedValue(defaultHeaderHeight); From 2b1e945de4876cf5215c14961872ec1b06560474 Mon Sep 17 00:00:00 2001 From: tboba Date: Mon, 9 Oct 2023 16:31:10 +0200 Subject: [PATCH 6/9] Remove screenIndex and route from ScreenInfoContext, change descriptors to options --- src/native-stack/utils/ScreenInfoContext.tsx | 13 ++++--------- src/native-stack/views/NativeStackView.tsx | 9 ++------- src/reanimated/ReanimatedNativeStackScreen.tsx | 4 ++-- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/native-stack/utils/ScreenInfoContext.tsx b/src/native-stack/utils/ScreenInfoContext.tsx index f59862c033..cfe11946f7 100644 --- a/src/native-stack/utils/ScreenInfoContext.tsx +++ b/src/native-stack/utils/ScreenInfoContext.tsx @@ -1,17 +1,12 @@ import * as React from 'react'; -import { NativeStackDescriptor, NativeStackNavigationRoute } from '../types'; -import { ParamListBase } from '@react-navigation/native'; +import { NativeStackNavigationOptions } from '../types'; -export type ScreenInfoType = { - descriptor: NativeStackDescriptor | undefined; - route: NativeStackNavigationRoute | undefined; - screenIndex: number; +type ScreenInfoType = { + options: NativeStackNavigationOptions; }; const ScreenInfoContext = React.createContext({ - descriptor: undefined, - route: undefined, - screenIndex: -1, + options: {}, }); export default ScreenInfoContext; diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index 50fcb47659..4d76279c08 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -32,7 +32,7 @@ import getDefaultHeaderHeight from '../utils/getDefaultHeaderHeight'; import getStatusBarHeight from '../utils/getStatusBarHeight'; import HeaderHeightContext from '../utils/HeaderHeightContext'; import AnimatedHeaderHeightContext from '../utils/AnimatedHeaderHeightContext'; -import ScreenInfoContext, { ScreenInfoType } from '../utils/ScreenInfoContext'; +import ScreenInfoContext from '../utils/ScreenInfoContext'; const isAndroid = Platform.OS === 'android'; @@ -251,14 +251,9 @@ const RouteView = ({ const Screen = React.useContext(ScreenContext); const { dark } = useTheme(); - const screenInfo: ScreenInfoType = { - descriptor: descriptors[route.key], - route, - screenIndex: index, - }; return ( - + ((props, ref) => { - const { descriptor } = useScreenInfo(); + const { options } = useScreenInfo(); const { children, ...rest } = props; const { stackPresentation = 'push' } = rest; @@ -46,7 +46,7 @@ const ReanimatedNativeStackScreen = React.forwardRef< isStatusBarTranslucent ); - const isLargeHeader = descriptor?.options.headerLargeTitle ?? false; + const isLargeHeader = options.headerLargeTitle ?? false; // Default header height, normally used in `useHeaderHeight` hook. // Here, it is used for returning a default value for shared value. From ea4a6497c21e30cddda7a71c1de72e6ae8c238d5 Mon Sep 17 00:00:00 2001 From: tboba Date: Mon, 9 Oct 2023 16:52:23 +0200 Subject: [PATCH 7/9] Revert moving NavigationRoute to types.tsx --- src/native-stack/types.tsx | 10 ---------- src/native-stack/views/NativeStackView.tsx | 12 ++++++++++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/native-stack/types.tsx b/src/native-stack/types.tsx index cc5b570600..8acd8bfb7e 100644 --- a/src/native-stack/types.tsx +++ b/src/native-stack/types.tsx @@ -8,9 +8,6 @@ import { StackRouterOptions, StackActionHelpers, RouteProp, - Route, - NavigationState, - PartialState, } from '@react-navigation/native'; import * as React from 'react'; import { @@ -460,13 +457,6 @@ export type NativeStackNavigatorProps = StackRouterOptions & NativeStackNavigationConfig; -export type NativeStackNavigationRoute< - ParamList extends ParamListBase, - RouteName extends keyof ParamList -> = Route, ParamList[RouteName]> & { - state?: NavigationState | PartialState; -}; - export type NativeStackDescriptor = Descriptor< ParamListBase, string, diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index 4d76279c08..4d5f2d75ff 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -15,6 +15,8 @@ import { StackNavigationState, useTheme, Route, + NavigationState, + PartialState, } from '@react-navigation/native'; import { useSafeAreaFrame, @@ -22,7 +24,6 @@ import { } from 'react-native-safe-area-context'; import { NativeStackDescriptorMap, - NativeStackNavigationRoute, NativeStackNavigationHelpers, NativeStackNavigationOptions, } from '../types'; @@ -142,6 +143,13 @@ const MaybeNestedStack = ({ return content; }; +type NavigationRoute< + ParamList extends ParamListBase, + RouteName extends keyof ParamList +> = Route, ParamList[RouteName]> & { + state?: NavigationState | PartialState; +}; + const RouteView = ({ descriptors, route, @@ -150,7 +158,7 @@ const RouteView = ({ stateKey, }: { descriptors: NativeStackDescriptorMap; - route: NativeStackNavigationRoute; + route: NavigationRoute; index: number; navigation: NativeStackNavigationHelpers; stateKey: string; From b2d876ac20a3972688451fdf3f9b62c12086f6c2 Mon Sep 17 00:00:00 2001 From: tboba Date: Wed, 11 Oct 2023 12:02:48 +0200 Subject: [PATCH 8/9] Change ScreenInfoContext to single prop --- src/native-stack/utils/ScreenInfoContext.tsx | 12 - src/native-stack/utils/useScreenInfo.tsx | 15 -- src/native-stack/views/NativeStackView.tsx | 250 +++++++++--------- .../ReanimatedNativeStackScreen.tsx | 7 +- src/types.tsx | 4 + 5 files changed, 131 insertions(+), 157 deletions(-) delete mode 100644 src/native-stack/utils/ScreenInfoContext.tsx delete mode 100644 src/native-stack/utils/useScreenInfo.tsx diff --git a/src/native-stack/utils/ScreenInfoContext.tsx b/src/native-stack/utils/ScreenInfoContext.tsx deleted file mode 100644 index cfe11946f7..0000000000 --- a/src/native-stack/utils/ScreenInfoContext.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import * as React from 'react'; -import { NativeStackNavigationOptions } from '../types'; - -type ScreenInfoType = { - options: NativeStackNavigationOptions; -}; - -const ScreenInfoContext = React.createContext({ - options: {}, -}); - -export default ScreenInfoContext; diff --git a/src/native-stack/utils/useScreenInfo.tsx b/src/native-stack/utils/useScreenInfo.tsx deleted file mode 100644 index 3b34e08f96..0000000000 --- a/src/native-stack/utils/useScreenInfo.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; - -import ScreenInfoContext from './ScreenInfoContext'; - -export default function useScreenInfo() { - const screenInfo = React.useContext(ScreenInfoContext); - - if (screenInfo === undefined) { - throw new Error( - "Couldn't find information about the screen. Are you inside a screen in a navigator?" - ); - } - - return screenInfo; -} diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index 4d5f2d75ff..f5719ab88e 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -33,7 +33,6 @@ import getDefaultHeaderHeight from '../utils/getDefaultHeaderHeight'; import getStatusBarHeight from '../utils/getStatusBarHeight'; import HeaderHeightContext from '../utils/HeaderHeightContext'; import AnimatedHeaderHeightContext from '../utils/AnimatedHeaderHeightContext'; -import ScreenInfoContext from '../utils/ScreenInfoContext'; const isAndroid = Platform.OS === 'android'; @@ -131,7 +130,11 @@ const MaybeNestedStack = ({ if (isHeaderInModal) { return ( - + {content} @@ -261,130 +264,129 @@ const RouteView = ({ const { dark } = useTheme(); return ( - - { - navigation.dispatch({ - ...StackActions.pop(), - source: route.key, - target: stateKey, - }); - }} - onWillAppear={() => { - navigation.emit({ - type: 'transitionStart', - data: { closing: false }, - target: route.key, - }); - }} - onWillDisappear={() => { - navigation.emit({ - type: 'transitionStart', - data: { closing: true }, - target: route.key, - }); - }} - onAppear={() => { - navigation.emit({ - type: 'appear', - target: route.key, - }); - navigation.emit({ - type: 'transitionEnd', - data: { closing: false }, - target: route.key, - }); - }} - onDisappear={() => { - navigation.emit({ - type: 'transitionEnd', - data: { closing: true }, - target: route.key, - }); - }} - onHeaderHeightChange={e => { - const headerHeight = e.nativeEvent.headerHeight; - - if (cachedAnimatedHeaderHeight.current !== headerHeight) { - // Currently, we're setting value by Animated#setValue, because we want to cache animated value. - // Also, in React Native 0.72 there was a bug on Fabric causing a large delay between the screen transition, - // which should not occur. - // TODO: Check if it's possible to replace animated#setValue to Animated#event. - animatedHeaderHeight.setValue(headerHeight); - cachedAnimatedHeaderHeight.current = headerHeight; - } - }} - onDismissed={e => { - navigation.emit({ - type: 'dismiss', - target: route.key, - }); - - const dismissCount = - e.nativeEvent.dismissCount > 0 ? e.nativeEvent.dismissCount : 1; - - navigation.dispatch({ - ...StackActions.pop(dismissCount), - source: route.key, - target: stateKey, - }); - }} - onGestureCancel={() => { - navigation.emit({ - type: 'gestureCancel', - target: route.key, - }); - }}> - - - - {renderScene()} - - {/* HeaderConfig must not be first child of a Screen. + { + navigation.dispatch({ + ...StackActions.pop(), + source: route.key, + target: stateKey, + }); + }} + onWillAppear={() => { + navigation.emit({ + type: 'transitionStart', + data: { closing: false }, + target: route.key, + }); + }} + onWillDisappear={() => { + navigation.emit({ + type: 'transitionStart', + data: { closing: true }, + target: route.key, + }); + }} + onAppear={() => { + navigation.emit({ + type: 'appear', + target: route.key, + }); + navigation.emit({ + type: 'transitionEnd', + data: { closing: false }, + target: route.key, + }); + }} + onDisappear={() => { + navigation.emit({ + type: 'transitionEnd', + data: { closing: true }, + target: route.key, + }); + }} + onHeaderHeightChange={e => { + const headerHeight = e.nativeEvent.headerHeight; + + if (cachedAnimatedHeaderHeight.current !== headerHeight) { + // Currently, we're setting value by Animated#setValue, because we want to cache animated value. + // Also, in React Native 0.72 there was a bug on Fabric causing a large delay between the screen transition, + // which should not occur. + // TODO: Check if it's possible to replace animated#setValue to Animated#event. + animatedHeaderHeight.setValue(headerHeight); + cachedAnimatedHeaderHeight.current = headerHeight; + } + }} + onDismissed={e => { + navigation.emit({ + type: 'dismiss', + target: route.key, + }); + + const dismissCount = + e.nativeEvent.dismissCount > 0 ? e.nativeEvent.dismissCount : 1; + + navigation.dispatch({ + ...StackActions.pop(dismissCount), + source: route.key, + target: stateKey, + }); + }} + onGestureCancel={() => { + navigation.emit({ + type: 'gestureCancel', + target: route.key, + }); + }}> + + + + {renderScene()} + + {/* HeaderConfig must not be first child of a Screen. See https://github.com/software-mansion/react-native-screens/pull/1825 for detailed explanation */} - - - - - + + + + ); }; diff --git a/src/reanimated/ReanimatedNativeStackScreen.tsx b/src/reanimated/ReanimatedNativeStackScreen.tsx index 149776ea3f..7bde5741d7 100644 --- a/src/reanimated/ReanimatedNativeStackScreen.tsx +++ b/src/reanimated/ReanimatedNativeStackScreen.tsx @@ -17,7 +17,6 @@ import { import getDefaultHeaderHeight from '../native-stack/utils/getDefaultHeaderHeight'; import getStatusBarHeight from '../native-stack/utils/getStatusBarHeight'; import ReanimatedHeaderHeightContext from './ReanimatedHeaderHeightContext'; -import useScreenInfo from '../native-stack/utils/useScreenInfo'; const AnimatedScreen = Animated.createAnimatedComponent( InnerScreen as unknown as React.ComponentClass @@ -32,10 +31,8 @@ const ReanimatedNativeStackScreen = React.forwardRef< typeof AnimatedScreen, ScreenProps >((props, ref) => { - const { options } = useScreenInfo(); - const { children, ...rest } = props; - const { stackPresentation = 'push' } = rest; + const { stackPresentation = 'push', isLargeHeader } = rest; const dimensions = useSafeAreaFrame(); const topInset = useSafeAreaInsets().top; @@ -46,8 +43,6 @@ const ReanimatedNativeStackScreen = React.forwardRef< isStatusBarTranslucent ); - const isLargeHeader = options.headerLargeTitle ?? false; - // Default header height, normally used in `useHeaderHeight` hook. // Here, it is used for returning a default value for shared value. const defaultHeaderHeight = getDefaultHeaderHeight( diff --git a/src/types.tsx b/src/types.tsx index 8026eaa303..01a7b704b0 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -111,6 +111,10 @@ export interface ScreenProps extends ViewProps { * Internal boolean used to not attach events used only by native-stack. It prevents non native-stack navigators from sending transition progress from their Screen components. */ isNativeStack?: boolean; + /** + * Internal boolean used to detect if current header has large title on iOS. + */ + isLargeHeader?: boolean; /** * Whether inactive screens should be suspended from re-rendering. Defaults to `false`. * When `enableFreeze()` is run at the top of the application defaults to `true`. From cf7dcb1b684a51d8f0799a3cc49342a676b6e658 Mon Sep 17 00:00:00 2001 From: tboba Date: Wed, 11 Oct 2023 16:52:15 +0200 Subject: [PATCH 9/9] Changed isLargeHeader to hasLargeHeader --- src/native-stack/views/NativeStackView.tsx | 12 ++++++------ src/reanimated/ReanimatedNativeStackScreen.tsx | 4 ++-- src/types.tsx | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index f5719ab88e..43768aae92 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -118,13 +118,13 @@ const MaybeNestedStack = ({ isStatusBarTranslucent ); - const isLargeHeader = options.headerLargeTitle ?? false; + const hasLargeHeader = options.headerLargeTitle ?? false; const headerHeight = getDefaultHeaderHeight( dimensions, statusBarHeight, stackPresentation, - isLargeHeader + hasLargeHeader ); if (isHeaderInModal) { @@ -133,7 +133,7 @@ const MaybeNestedStack = ({ @@ -232,13 +232,13 @@ const RouteView = ({ isStatusBarTranslucent ); - const isLargeHeader = options.headerLargeTitle ?? false; + const hasLargeHeader = options.headerLargeTitle ?? false; const defaultHeaderHeight = getDefaultHeaderHeight( dimensions, statusBarHeight, stackPresentation, - isLargeHeader + hasLargeHeader ); const parentHeaderHeight = React.useContext(HeaderHeightContext); @@ -268,7 +268,7 @@ const RouteView = ({ key={route.key} enabled isNativeStack - isLargeHeader={isLargeHeader} + hasLargeHeader={hasLargeHeader} style={StyleSheet.absoluteFill} sheetAllowedDetents={sheetAllowedDetents} sheetLargestUndimmedDetent={sheetLargestUndimmedDetent} diff --git a/src/reanimated/ReanimatedNativeStackScreen.tsx b/src/reanimated/ReanimatedNativeStackScreen.tsx index 7bde5741d7..c5773a4229 100644 --- a/src/reanimated/ReanimatedNativeStackScreen.tsx +++ b/src/reanimated/ReanimatedNativeStackScreen.tsx @@ -32,7 +32,7 @@ const ReanimatedNativeStackScreen = React.forwardRef< ScreenProps >((props, ref) => { const { children, ...rest } = props; - const { stackPresentation = 'push', isLargeHeader } = rest; + const { stackPresentation = 'push', hasLargeHeader } = rest; const dimensions = useSafeAreaFrame(); const topInset = useSafeAreaInsets().top; @@ -49,7 +49,7 @@ const ReanimatedNativeStackScreen = React.forwardRef< dimensions, statusBarHeight, stackPresentation, - isLargeHeader + hasLargeHeader ); const cachedHeaderHeight = React.useRef(defaultHeaderHeight); diff --git a/src/types.tsx b/src/types.tsx index 01a7b704b0..379d8b35d1 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -114,7 +114,7 @@ export interface ScreenProps extends ViewProps { /** * Internal boolean used to detect if current header has large title on iOS. */ - isLargeHeader?: boolean; + hasLargeHeader?: boolean; /** * Whether inactive screens should be suspended from re-rendering. Defaults to `false`. * When `enableFreeze()` is run at the top of the application defaults to `true`.