import * as React from 'react'; import { Platform, StyleSheet, View, ViewProps } from 'react-native'; // @ts-ignore Getting private component import AppContainer from 'react-native/Libraries/ReactNative/AppContainer'; import warnOnce from 'warn-once'; import { ScreenStack, StackPresentationTypes, ScreenContext, } from 'react-native-screens'; import { ParamListBase, StackActions, StackNavigationState, useTheme, Route, NavigationState, PartialState, } from '@react-navigation/native'; import { useSafeAreaFrame, useSafeAreaInsets, } from 'react-native-safe-area-context'; import { NativeStackDescriptorMap, NativeStackNavigationHelpers, NativeStackNavigationOptions, } from '../types'; import HeaderConfig from './HeaderConfig'; import SafeAreaProviderCompat from '../utils/SafeAreaProviderCompat'; import getDefaultHeaderHeight from '../utils/getDefaultHeaderHeight'; import HeaderHeightContext from '../utils/HeaderHeightContext'; const isAndroid = Platform.OS === 'android'; let Container = View; if (__DEV__) { const DebugContainer = ( props: ViewProps & { stackPresentation: StackPresentationTypes } ) => { const { stackPresentation, ...rest } = props; if (Platform.OS === 'ios' && stackPresentation !== 'push') { return ( ); } return ; }; // @ts-ignore Wrong props Container = DebugContainer; } const MaybeNestedStack = ({ options, route, stackPresentation, children, }: { options: NativeStackNavigationOptions; route: Route; stackPresentation: StackPresentationTypes; children: React.ReactNode; }) => { const { colors } = useTheme(); const { headerShown = true, contentStyle } = options; const Screen = React.useContext(ScreenContext); const isHeaderInModal = isAndroid ? false : stackPresentation !== 'push' && headerShown === true; const headerShownPreviousRef = React.useRef(headerShown); React.useEffect(() => { warnOnce( !isAndroid && stackPresentation !== 'push' && headerShownPreviousRef.current !== headerShown, `Dynamically changing 'headerShown' in modals will result in remounting the screen and losing all local state. See options for the screen '${route.name}'.` ); headerShownPreviousRef.current = headerShown; }, [headerShown, stackPresentation, route.name]); const content = ( {children} ); const topInset = useSafeAreaInsets().top; const dimensions = useSafeAreaFrame(); const headerHeight = getDefaultHeaderHeight( dimensions, topInset, stackPresentation ); if (isHeaderInModal) { return ( {content} ); } return content; }; type NavigationRoute< ParamList extends ParamListBase, RouteName extends keyof ParamList > = Route, ParamList[RouteName]> & { state?: NavigationState | PartialState; }; const RouteView = ({ descriptors, route, index, navigation, stateKey, }: { descriptors: NativeStackDescriptorMap; route: NavigationRoute; index: number; navigation: NativeStackNavigationHelpers; stateKey: string; }) => { const { options, render: renderScene } = descriptors[route.key]; const { gestureEnabled, headerShown, hideKeyboardOnSwipe, homeIndicatorHidden, nativeBackButtonDismissalEnabled = false, navigationBarColor, navigationBarHidden, replaceAnimation = 'pop', screenOrientation, statusBarAnimation, statusBarColor, statusBarHidden, statusBarStyle, statusBarTranslucent, swipeDirection = 'horizontal', transitionDuration, freezeOnBlur, } = options; let { customAnimationOnSwipe, fullScreenSwipeEnabled, gestureResponseDistance, stackAnimation, stackPresentation = 'push', } = options; if (swipeDirection === 'vertical') { // for `vertical` direction to work, we need to set `fullScreenSwipeEnabled` to `true` // so the screen can be dismissed from any point on screen. // `customAnimationOnSwipe` needs to be set to `true` so the `stackAnimation` set by user can be used, // otherwise `simple_push` will be used. // Also, the default animation for this direction seems to be `slide_from_bottom`. if (fullScreenSwipeEnabled === undefined) { fullScreenSwipeEnabled = true; } if (customAnimationOnSwipe === undefined) { customAnimationOnSwipe = true; } if (stackAnimation === undefined) { stackAnimation = 'slide_from_bottom'; } } if (index === 0) { // first screen should always be treated as `push`, it resolves problems with no header animation // for navigator with first screen as `modal` and the next as `push` stackPresentation = 'push'; } const isHeaderInPush = isAndroid ? headerShown : stackPresentation === 'push' && headerShown !== false; const dimensions = useSafeAreaFrame(); const topInset = useSafeAreaInsets().top; const headerHeight = getDefaultHeaderHeight( dimensions, topInset, stackPresentation ); const parentHeaderHeight = React.useContext(HeaderHeightContext); const Screen = React.useContext(ScreenContext); 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, }); }} 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, }); }}> {renderScene()} ); }; type Props = { state: StackNavigationState; navigation: NativeStackNavigationHelpers; descriptors: NativeStackDescriptorMap; }; function NativeStackViewInner({ state, navigation, descriptors, }: Props): JSX.Element { const { key, routes } = state; return ( {routes.map((route, index) => ( ))} ); } export default function NativeStackView(props: Props) { return ( ); } const styles = StyleSheet.create({ container: { flex: 1, }, });