mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-25 05:18:15 +08:00
This change adds new event that gets dispatched from native stack when screen transitioning is finished. The new event is used by react-navigation to update the library internal state that the triggered action has been finished. Previously we were relying solely on onAppear and onDisappear events, however those does not get triggered when new iOS 13 modals are used or when transparent modals are displayed. This is because with transparent modals the view below is still visible. We therefore needed another way of notifying the library that screen transition have finished despite the fact that disappear event couldn't be triggered. As a part of this change I also refactored invalid ref cycle-break code on iOS which was ought to remove reference cycle between view and view controller. This code have been moved to viewWillDisappear callback. Also on Android part small refactoring has been done and we removed the necessity to keep mActiveScreens array which was occasionally getting out of sync with the list of active fragments.
258 lines
7.4 KiB
JavaScript
258 lines
7.4 KiB
JavaScript
import React from 'react';
|
|
import { StyleSheet, Text } from 'react-native';
|
|
import {
|
|
StackRouter,
|
|
SceneView,
|
|
StackActions,
|
|
createNavigator,
|
|
} from '@react-navigation/core';
|
|
import { createKeyboardAwareNavigator } from '@react-navigation/native';
|
|
import { HeaderBackButton } from 'react-navigation-stack';
|
|
import {
|
|
ScreenStack,
|
|
Screen,
|
|
ScreenStackHeaderConfig,
|
|
ScreenStackHeaderBackButtonImage,
|
|
ScreenStackHeaderLeftView,
|
|
ScreenStackHeaderRightView,
|
|
ScreenStackHeaderCenterView,
|
|
} from 'react-native-screens';
|
|
|
|
function renderComponentOrThunk(componentOrThunk, props) {
|
|
if (typeof componentOrThunk === 'function') {
|
|
return componentOrThunk(props);
|
|
}
|
|
return componentOrThunk;
|
|
}
|
|
|
|
class StackView extends React.Component {
|
|
_removeScene = route => {
|
|
console.warn('REMOVE SCENE');
|
|
this.props.navigation.dispatch(StackActions.pop({ key: route.key }));
|
|
};
|
|
|
|
_onAppear = (route, descriptor) => {
|
|
descriptor.options &&
|
|
descriptor.options.onAppear &&
|
|
descriptor.options.onAppear();
|
|
this.props.navigation.dispatch(
|
|
StackActions.completeTransition({
|
|
toChildKey: route.key,
|
|
key: this.props.navigation.state.key,
|
|
})
|
|
);
|
|
};
|
|
|
|
_onFinishTransitioning = () => {
|
|
console.warn('FINISH TRANSITIONING');
|
|
const { routes } = this.props.navigation.state;
|
|
let lastRoute = routes && routes.length && routes[routes.length - 1];
|
|
if (lastRoute) {
|
|
this.props.navigation.dispatch(
|
|
StackActions.completeTransition({
|
|
toChildKey: lastRoute.key,
|
|
key: this.props.navigation.state.key,
|
|
})
|
|
);
|
|
}
|
|
};
|
|
|
|
_renderHeaderConfig = (index, route, descriptor) => {
|
|
const { navigationConfig } = this.props;
|
|
const { options } = descriptor;
|
|
const { headerMode } = navigationConfig;
|
|
|
|
const {
|
|
title,
|
|
headerStyle,
|
|
headerTitleStyle,
|
|
headerBackTitleStyle,
|
|
headerBackTitle,
|
|
headerBackTitleVisible,
|
|
headerTintColor,
|
|
largeTitle,
|
|
headerLargeTitleStyle,
|
|
translucent,
|
|
hideShadow,
|
|
} = options;
|
|
|
|
const scene = {
|
|
index,
|
|
key: route.key,
|
|
route,
|
|
descriptor,
|
|
};
|
|
|
|
const headerOptions = {
|
|
translucent: translucent === undefined ? false : translucent,
|
|
title,
|
|
titleFontFamily: headerTitleStyle && headerTitleStyle.fontFamily,
|
|
titleColor: headerTintColor,
|
|
titleFontSize: headerTitleStyle && headerTitleStyle.fontSize,
|
|
backTitle: headerBackTitleVisible === false ? '' : headerBackTitle,
|
|
backTitleFontFamily:
|
|
headerBackTitleStyle && headerBackTitleStyle.fontFamily,
|
|
backTitleFontSize: headerBackTitleStyle && headerBackTitleStyle.fontSize,
|
|
color: headerTintColor,
|
|
largeTitle,
|
|
largeTitleFontFamily:
|
|
headerLargeTitleStyle && headerLargeTitleStyle.fontFamily,
|
|
largeTitleFontSize:
|
|
headerLargeTitleStyle && headerLargeTitleStyle.fontSize,
|
|
hideShadow,
|
|
};
|
|
|
|
const hasHeader = headerMode !== 'none' && options.header !== null;
|
|
if (!hasHeader) {
|
|
return <ScreenStackHeaderConfig {...headerOptions} hidden />;
|
|
}
|
|
|
|
if (headerStyle !== undefined) {
|
|
headerOptions.backgroundColor = headerStyle.backgroundColor;
|
|
}
|
|
|
|
const children = [];
|
|
|
|
if (options.backButtonImage) {
|
|
children.push(
|
|
<ScreenStackHeaderBackButtonImage
|
|
key="backImage"
|
|
source={options.backButtonImage}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (options.headerLeft !== undefined) {
|
|
children.push(
|
|
<ScreenStackHeaderLeftView key="left">
|
|
{renderComponentOrThunk(options.headerLeft, { scene })}
|
|
</ScreenStackHeaderLeftView>
|
|
);
|
|
} else if (options.headerBackImage !== undefined) {
|
|
const goBack = () => {
|
|
// Go back on next tick because button ripple effect needs to happen on Android
|
|
requestAnimationFrame(() => {
|
|
descriptor.navigation.goBack(descriptor.key);
|
|
});
|
|
};
|
|
|
|
children.push(
|
|
<ScreenStackHeaderLeftView key="left">
|
|
<HeaderBackButton
|
|
onPress={goBack}
|
|
pressColorAndroid={options.headerPressColorAndroid}
|
|
tintColor={options.headerTintColor}
|
|
backImage={options.headerBackImage}
|
|
title={options.backButtonTitle}
|
|
truncatedTitle={options.truncatedBackButtonTitle}
|
|
backTitleVisible={this.props.backTitleVisible}
|
|
titleStyle={options.headerBackTitleStyle}
|
|
layoutPreset={this.props.layoutPreset}
|
|
scene={scene}
|
|
/>
|
|
</ScreenStackHeaderLeftView>
|
|
);
|
|
}
|
|
|
|
if (options.headerTitle) {
|
|
if (title === undefined && typeof options.headerTitle === 'string') {
|
|
headerOptions.title = options.headerTitle;
|
|
} else {
|
|
children.push(
|
|
<ScreenStackHeaderCenterView key="center">
|
|
{renderComponentOrThunk(options.headerTitle, { scene })}
|
|
</ScreenStackHeaderCenterView>
|
|
);
|
|
}
|
|
}
|
|
|
|
if (options.headerRight) {
|
|
children.push(
|
|
<ScreenStackHeaderRightView key="right">
|
|
{renderComponentOrThunk(options.headerRight, { scene })}
|
|
</ScreenStackHeaderRightView>
|
|
);
|
|
}
|
|
|
|
if (children.length > 0) {
|
|
headerOptions.children = children;
|
|
}
|
|
|
|
return <ScreenStackHeaderConfig {...headerOptions} />;
|
|
};
|
|
|
|
_renderScene = (index, route, descriptor) => {
|
|
const { navigation, getComponent, options } = descriptor;
|
|
const { mode, transparentCard } = this.props.navigationConfig;
|
|
const SceneComponent = getComponent();
|
|
|
|
let stackPresentation = 'push';
|
|
if (mode === 'modal' || mode === 'containedModal') {
|
|
stackPresentation = mode;
|
|
if (transparentCard || options.cardTransparent) {
|
|
stackPresentation =
|
|
mode === 'containedModal'
|
|
? 'containedTransparentModal'
|
|
: 'transparentModal';
|
|
}
|
|
}
|
|
|
|
let stackAnimation = options.stackAnimation;
|
|
if (options.animationEnabled === false) {
|
|
stackAnimation = 'none';
|
|
}
|
|
|
|
const { screenProps } = this.props;
|
|
return (
|
|
<Screen
|
|
key={`screen_${route.key}`}
|
|
style={options.cardStyle}
|
|
stackAnimation={stackAnimation}
|
|
stackPresentation={stackPresentation}
|
|
gestureEnabled={
|
|
options.gestureEnabled === undefined ? true : options.gestureEnabled
|
|
}
|
|
onAppear={() => this._onAppear(route, descriptor)}
|
|
onDismissed={() => this._removeScene(route)}>
|
|
{this._renderHeaderConfig(index, route, descriptor)}
|
|
<SceneView
|
|
screenProps={screenProps}
|
|
navigation={navigation}
|
|
component={SceneComponent}
|
|
/>
|
|
</Screen>
|
|
);
|
|
};
|
|
|
|
render() {
|
|
const { navigation, descriptors } = this.props;
|
|
|
|
return (
|
|
<ScreenStack
|
|
style={styles.scenes}
|
|
onFinishTransitioning={this._onFinishTransitioning}>
|
|
{navigation.state.routes.map((route, i) =>
|
|
this._renderScene(i, route, descriptors[route.key])
|
|
)}
|
|
</ScreenStack>
|
|
);
|
|
}
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
scenes: { flex: 1 },
|
|
});
|
|
|
|
function createStackNavigator(routeConfigMap, stackConfig = {}) {
|
|
const router = StackRouter(routeConfigMap, stackConfig);
|
|
// Create a navigator with StackView as the view
|
|
let Navigator = createNavigator(StackView, router, stackConfig);
|
|
// if (!stackConfig.disableKeyboardHandling) {
|
|
// Navigator = createKeyboardAwareNavigator(Navigator, stackConfig);
|
|
// }
|
|
|
|
return Navigator;
|
|
}
|
|
|
|
export default createStackNavigator;
|