mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-09 22:51:57 +08:00
210 lines
5.7 KiB
TypeScript
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,
|
|
},
|
|
});
|