react-native/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js

447 lines
14 KiB
JavaScript

/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
* all intellectual property and other proprietary rights, in and to the React
* Native CustomComponents software (the "Software"). Subject to your
* compliance with these terms, you are hereby granted a non-exclusive,
* worldwide, royalty-free copyright license to (1) use and copy the Software;
* and (2) reproduce and distribute the Software as part of your own software
* ("Your Software"). Facebook reserves all rights not expressly granted to
* you in this license agreement.
*
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @providesModule NavigationLegacyNavigator
* @flow
*/
'use strict';
const Animated = require('Animated');
const NavigationAnimatedValueSubscription = require('NavigationAnimatedValueSubscription');
const NavigationAnimatedView = require('NavigationAnimatedView');
const NavigationCard = require('NavigationCard');
const NavigationCardStackStyleInterpolator = require('NavigationCardStackStyleInterpolator');
const NavigationContext = require('NavigationContext');
const NavigationLegacyNavigatorRouteStack = require('NavigationLegacyNavigatorRouteStack');
const NavigationCardStackPanResponder = require('NavigationCardStackPanResponder');
const NavigationPagerPanResponder = require('NavigationPagerPanResponder');
const NavigationPagerStyleInterpolator = require('NavigationPagerStyleInterpolator');
const NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
const NavigatorNavigationBar = require('NavigatorNavigationBar');
const NavigatorSceneConfigs = require('NavigatorSceneConfigs');
const React = require('React');
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
import type {
NavigationActionCaller,
NavigationAnimatedValue,
NavigationAnimationSetter,
NavigationParentState,
NavigationSceneRenderer,
NavigationSceneRendererProps,
} from 'NavigationTypeDefinition';
type Props = {
configureScene: any,
initialRoute: any,
initialRouteStack: any,
navigationBar: any,
navigationBarNavigator: any,
navigator: any,
onDidFocus: any,
onWillFocus: any,
renderScene: any,
renderScene: any,
style: any,
};
type State = {
presentedIndex: number,
routeStack: Array<any>,
};
const RouteStack = NavigationLegacyNavigatorRouteStack;
/**
* NavigationLegacyNavigator is meant to replace Navigator seemlessly with
* minimum API changes.
*
* While the APIs remain compatible with Navigator, it is built with good
* intention by using the new Navigation API such as
* `NavigationAnimatedView`...etc.
*/
class NavigationLegacyNavigator extends React.Component<any, Props, State> {
static BreadcrumbNavigationBar: any;
static NavigationBar: any;
static SceneConfigs: any;
_applyAnimation: NavigationAnimationSetter;
_navigationBarRef: any;
_onNavigationBarRef: (ref: any) => void;
_onPositionChange: (data: {value: number}) => void;
_positionListener: ?NavigationAnimatedValueSubscription;
_previousStack: NavigationLegacyNavigatorRouteStack;
_renderCard: NavigationSceneRenderer;
_renderHeader: NavigationSceneRenderer;
_renderScene: NavigationSceneRenderer;
_routeFocused: any;
_routeToFocus: any;
_onNavigate: NavigationActionCaller;
_stack: NavigationLegacyNavigatorRouteStack;
_useAnimation: boolean;
navigationContext: NavigationContext;
parentNavigator: any;
props: Props;
state: State;
constructor(props: Props, context: any) {
super(props, context);
const stack = this._getInitialRouteStack();
// Unfortunately, due to historical reasons, the `state` has been exposed
// as public members of the navigator, therefore we'd keep private state
// as private members.
this._previousStack = stack;
this._stack = stack;
this._useAnimation = false;
// Legacy members portred from `Navigator`.
this.parentNavigator = props.navigator;
this.navigationContext = new NavigationContext();
this.state = {
routeStack: stack.toArray(),
presentedIndex: stack.index,
};
}
jumpTo(route: any): void {
this._applyStack(this._stack.jumpTo(route));
}
jumpForward(): void {
this._applyStack(this._stack.jumpForward());
}
jumpBack(): void {
this._applyStack(this._stack.jumpBack());
}
push(route: any): void {
this._applyStack(this._stack.push(route));
}
pop(): void {
this._applyStack(this._stack.pop());
}
replaceAtIndex(route: any, index: number): void {
this._applyStack(this._stack.replaceAtIndex(index, route));
}
replace(route: any): void {
this.replaceAtIndex(route, this._stack.index);
}
replacePrevious(route: any): void {
this.replaceAtIndex(route, this._stack.index - 1);
}
popToTop(): void {
this._applyStack(this._stack.slice(0, 1));
}
popToRoute(route: any): void {
this._applyStack(this._stack.popToRoute(route));
}
replacePreviousAndPop(route: any): void {
this._applyStack(this._stack.replacePreviousAndPop(route));
}
resetTo(route: any): void {
this._applyStack(this._stack.resetTo(route));
}
immediatelyResetRouteStack(routes: Array<any>): void {
this._applyStack(this._stack.resetRoutes(routes), true);
}
getCurrentRoutes(): Array<any> {
return this._stack.toArray();
}
// Life cycle and private methods below.
shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
this,
nextProps,
nextState
);
}
componentWillMount(): void {
this._applyAnimation = this._applyAnimation.bind(this);
this._onNavigate = this._onNavigate.bind(this);
this._onNavigationBarRef = this._onNavigationBarRef.bind(this);
this._onPositionChange = this._onPositionChange.bind(this);
this._renderCard = this._renderCard.bind(this);
this._renderHeader = this._renderHeader.bind(this);
this._renderScene = this._renderScene.bind(this);
this._willFocus();
}
componentDidMount(): void {
this._didFocus();
}
componentWillUnmount(): void {
this._positionListener && this._positionListener.remove();
}
componentWillUpdate(nextProps: Props, nextState: State): void {
this._willFocus();
}
componentDidUpdate(prevProps: Props, prevState: State): void {
if (this._useAnimation) {
// will play animation.
return;
}
this._didFocus();
}
render(): ReactElement {
return (
<NavigationAnimatedView
applyAnimation={this._applyAnimation}
navigationState={this._stack.toNavigationState()}
onNavigate={this._onNavigate}
renderOverlay={this._renderHeader}
renderScene={this._renderCard}
style={this.props.style}
/>
);
}
_getInitialRouteStack(): RouteStack {
const {initialRouteStack, initialRoute} = this.props;
const routes = initialRouteStack || [initialRoute];
const index = initialRoute ?
routes.indexOf(initialRoute) :
routes.length - 1;
return new RouteStack(index, routes);
}
_renderHeader(props: NavigationSceneRendererProps): ?ReactElement {
// `_renderHeader` is always called before `_renderCard`. We should
// subscribe to the position here.
this._positionListener && this._positionListener.remove();
this._positionListener = new NavigationAnimatedValueSubscription(
props.position,
this._onPositionChange,
);
const {navigationBar, navigationBarNavigator} = this.props;
if (!navigationBar) {
return null;
}
return React.cloneElement(
navigationBar,
{
key: 'header_' + props.scene.key,
ref: this._onNavigationBarRef,
navigator: navigationBarNavigator || this,
navState: {...this.state},
}
);
}
_renderCard(props: NavigationSceneRendererProps): ReactElement {
const {scene} = props;
const {configureScene} = this.props;
// Default getters for style and pan responders.
let styleGetter = NavigationCardStackStyleInterpolator.forHorizontal;
let panResponderGetter = NavigationCardStackPanResponder.forHorizontal;
if (configureScene) {
const route = RouteStack.getRouteByNavigationState(scene.navigationState);
const config = configureScene(route, this.state.routeStack);
if (config) {
const gestures = config.gestures || {};
if (gestures.pop && gestures.pop.direction === 'left-to-right') {
// pass, will use default getters.
} else if (gestures.pop && gestures.pop.direction === 'top-to-bottom') {
styleGetter = NavigationCardStackStyleInterpolator.forVertical;
panResponderGetter = NavigationCardStackPanResponder.forVertical;
} else if (
gestures.jumpBack &&
gestures.jumpForward &&
gestures.jumpBack.direction === 'left-to-right' &&
gestures.jumpForward.direction === 'right-to-left'
) {
styleGetter = NavigationPagerStyleInterpolator.forHorizontal;
panResponderGetter = NavigationPagerPanResponder.forHorizontal;
} else if (__DEV__) {
console.warn('unsupported scene configuration', config);
}
}
}
const style = styleGetter(props);
const panHandlers = panResponderGetter(props);
return (
<NavigationCard
{...props}
key={'card_' + props.scene.key}
panHandlers={panHandlers}
renderScene={this._renderScene}
style={style}
/>
);
}
_renderScene(props: NavigationSceneRendererProps): ReactElement {
const {navigationState} = props.scene;
const route = RouteStack.getRouteByNavigationState(navigationState);
return this.props.renderScene(route, this);
}
_applyStack(
stack: NavigationLegacyNavigatorRouteStack,
noAnimation: ?boolean,
): void {
if (stack !== this._stack) {
this._previousStack = this._stack;
this._stack = stack;
this._useAnimation = noAnimation ||
this._previousStack.index !== stack.index;
this.setState({
presentedIndex: stack.index,
routeStack: stack.toArray(),
});
}
}
_onNavigationBarRef(navigationBarRef: any): void {
this._navigationBarRef = navigationBarRef;
const {navigationBar} = this.props;
if (navigationBar && typeof navigationBar.ref === 'function') {
navigationBar.ref(navigationBarRef);
}
}
_onPositionChange(data: {value: number}): void {
const fromIndex = this._previousStack.index;
const toIndex = this._stack.index;
if (
fromIndex !== toIndex &&
this._navigationBarRef &&
typeof this._navigationBarRef.updateProgress === 'function'
) {
const progress = (data.value - fromIndex) / (toIndex - fromIndex);
this._navigationBarRef.updateProgress(progress, fromIndex, toIndex);
}
const diff = this._stack.index - data.value;
// When animation stops, the `diff` can still be very a small non-zero value
// (e.g. 0.00000002). Call `willFocus` when `diff` is small enough.
if (diff < 0.05) {
this._didFocus();
}
}
_applyAnimation(
position: NavigationAnimatedValue,
nextState: NavigationParentState,
prevState: NavigationParentState,
): void {
const {index} = nextState;
if (!this._useAnimation) {
position.setValue(index);
return;
}
Animated.timing(
position,
{
duration: 500,
toValue: index,
}
).start();
}
_willFocus(): void {
const route = this._stack.get(this._stack.index);
if (this._routeToFocus === route) {
return;
}
this._routeToFocus = route;
this.navigationContext.emit('willfocus', {route: route});
this.props.onWillFocus && this.props.onWillFocus(route);
}
_didFocus(): void {
const route = this._stack.get(this._stack.index);
if (this._routeFocused === route) {
return;
}
this._routeFocused = route;
this.navigationContext.emit('didfocus', {route: route});
this.props.onDidFocus && this.props.onDidFocus(route);
}
_onNavigate(action: any): void {
switch (action) {
case NavigationCardStackPanResponder.Actions.BACK:
this.pop();
break;
case NavigationPagerPanResponder.Actions.JUMP_BACK:
this.jumpBack();
break;
case NavigationPagerPanResponder.Actions.JUMP_FORWARD:
this.jumpForward();
break;
default:
if (__DEV__) {
console.warn('unsupported gesture action', action);
}
}
}
}
// Legacy static members.
NavigationLegacyNavigator.BreadcrumbNavigationBar = NavigatorBreadcrumbNavigationBar;
NavigationLegacyNavigator.NavigationBar = NavigatorNavigationBar;
NavigationLegacyNavigator.SceneConfigs = NavigatorSceneConfigs;
module.exports = NavigationLegacyNavigator;