Files
react-navigation/packages/native-stack/src/views/NativeStackView.tsx
2021-06-05 07:33:26 +02:00

210 lines
5.7 KiB
TypeScript

import { SafeAreaProviderCompat } from '@react-navigation/elements';
import {
ParamListBase,
Route,
StackActions,
StackNavigationState,
useTheme,
} from '@react-navigation/native';
import * as React from 'react';
import { Platform, StyleSheet } from 'react-native';
import {
Screen,
ScreenStack,
StackPresentationTypes,
} from 'react-native-screens';
import warnOnce from 'warn-once';
import type {
NativeStackDescriptorMap,
NativeStackNavigationHelpers,
NativeStackNavigationOptions,
} from '../types';
import DebugContainer from './DebugContainer';
import HeaderConfig from './HeaderConfig';
const isAndroid = Platform.OS === 'android';
const MaybeNestedStack = ({
options,
route,
presentation,
children,
}: {
options: NativeStackNavigationOptions;
route: Route<string>;
presentation: Exclude<StackPresentationTypes, 'push'> | 'card';
children: React.ReactNode;
}) => {
const { colors } = useTheme();
const { headerShown = true, contentStyle } = options;
const isHeaderInModal = isAndroid
? false
: presentation !== 'card' && headerShown === true;
const headerShownPreviousRef = React.useRef(headerShown);
React.useEffect(() => {
warnOnce(
!isAndroid &&
presentation !== 'card' &&
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, presentation, route.name]);
const content = (
<DebugContainer
style={[
styles.container,
presentation !== 'transparentModal' &&
presentation !== 'containedTransparentModal' && {
backgroundColor: colors.background,
},
contentStyle,
]}
stackPresentation={presentation === 'card' ? 'push' : presentation}
>
{children}
</DebugContainer>
);
if (isHeaderInModal) {
return (
<ScreenStack style={styles.container}>
<Screen enabled style={StyleSheet.absoluteFill}>
<HeaderConfig {...options} route={route} />
{content}
</Screen>
</ScreenStack>
);
}
return content;
};
type Props = {
state: StackNavigationState<ParamListBase>;
navigation: NativeStackNavigationHelpers;
descriptors: NativeStackDescriptorMap;
};
function NativeStackViewInner({ state, navigation, descriptors }: Props) {
return (
<ScreenStack style={styles.container}>
{state.routes.map((route, index) => {
const { options, render: renderScene } = descriptors[route.key];
const {
gestureEnabled,
headerShown,
animationTypeForReplace = 'pop',
animation,
orientation,
statusBarAnimation,
statusBarHidden,
statusBarStyle,
} = options;
let { presentation = 'card' } = options;
if (index === 0) {
// first screen should always be treated as `card`, it resolves problems with no header animation
// for navigator with first screen as `modal` and the next as `card`
presentation = 'card';
}
const isHeaderInPush = isAndroid
? headerShown
: presentation === 'card' && headerShown !== false;
return (
<Screen
key={route.key}
enabled
style={StyleSheet.absoluteFill}
gestureEnabled={
isAndroid
? // This prop enables handling of system back gestures on Android
// Since we handle them in JS side, we disable this
false
: gestureEnabled
}
replaceAnimation={animationTypeForReplace}
stackPresentation={presentation === 'card' ? 'push' : presentation}
stackAnimation={animation}
screenOrientation={orientation}
statusBarAnimation={statusBarAnimation}
statusBarHidden={statusBarHidden}
statusBarStyle={statusBarStyle}
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: 'transitionEnd',
data: { closing: false },
target: route.key,
});
}}
onDisappear={() => {
navigation.emit({
type: 'transitionEnd',
data: { closing: true },
target: route.key,
});
}}
onDismissed={() => {
navigation.dispatch({
...StackActions.pop(),
source: route.key,
target: state.key,
});
}}
>
<HeaderConfig
{...options}
route={route}
headerShown={isHeaderInPush}
/>
<MaybeNestedStack
options={options}
route={route}
presentation={presentation}
>
{renderScene()}
</MaybeNestedStack>
</Screen>
);
})}
</ScreenStack>
);
}
export default function NativeStackView(props: Props) {
return (
<SafeAreaProviderCompat>
<NativeStackViewInner {...props} />
</SafeAreaProviderCompat>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});