Implements `renderHeader` for legacy navigator.

Reviewed By: ericvicenti

Differential Revision: D3029188

fb-gh-sync-id: 6f0b91244bc0d0e5eee0e610764a39adaaffe001
shipit-source-id: 6f0b91244bc0d0e5eee0e610764a39adaaffe001
This commit is contained in:
Hedger Wang 2016-03-10 17:16:19 -08:00 committed by Facebook Github Bot 3
parent 26b2aa91b6
commit 3833c1b751
3 changed files with 178 additions and 36 deletions

View File

@ -0,0 +1,48 @@
/**
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
*
* 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 NavigationAnimatedValueSubscription
* @flow
*/
'use strict';
import type {
NavigationAnimatedValue
} from 'NavigationTypeDefinition';
class NavigationAnimatedValueSubscription {
_value: NavigationAnimatedValue;
_token: string;
constructor(value: NavigationAnimatedValue, callback: Function) {
this._value = value;
this._token = value.addListener(callback);
}
remove(): void {
this._value.removeListener(this._token);
}
}
module.exports = NavigationAnimatedValueSubscription;

View File

@ -27,6 +27,7 @@
*/ */
'use strict'; 'use strict';
const NavigationAnimatedValueSubscription = require('NavigationAnimatedValueSubscription');
const NavigationAnimatedView = require('NavigationAnimatedView'); const NavigationAnimatedView = require('NavigationAnimatedView');
const NavigationCard = require('NavigationCard'); const NavigationCard = require('NavigationCard');
const NavigationContext = require('NavigationContext'); const NavigationContext = require('NavigationContext');
@ -45,16 +46,22 @@ import type {
NavigationSceneRendererProps, NavigationSceneRendererProps,
} from 'NavigationTypeDefinition'; } from 'NavigationTypeDefinition';
type State = any;
type Props = { type Props = {
configureScene: any, configureScene: any,
initialRoute: any, initialRoute: any,
initialRouteStack: any, initialRouteStack: any,
renderScene: any, renderScene: any,
navigationBar: any,
navigationBarNavigator: any,
renderScene: any,
style: any, style: any,
}; };
type State = {
presentedIndex: number,
routeStack: Array<any>,
};
function getConfigPopDirection(config: any): ?string { function getConfigPopDirection(config: any): ?string {
if (config && config.gestures && config.gestures.pop) { if (config && config.gestures && config.gestures.pop) {
const direction = config.gestures.pop.direction; const direction = config.gestures.pop.direction;
@ -75,15 +82,24 @@ const RouteStack = NavigationLegacyNavigatorRouteStack;
* `NavigationAnimatedView`...etc. * `NavigationAnimatedView`...etc.
*/ */
class NavigationLegacyNavigator extends React.Component<any, Props, State> { class NavigationLegacyNavigator extends React.Component<any, Props, State> {
static BreadcrumbNavigationBar; static BreadcrumbNavigationBar: any;
static NavigationBar: any; static NavigationBar: any;
static SceneConfigs: any; static SceneConfigs: any;
_key: string;
_navigationBarRef: any;
_onNavigationBarRef: (ref: any) => void;
_onPositionChange: (data: {value: number}) => void;
_positionListener: ?NavigationAnimatedValueSubscription;
_previousStack: NavigationLegacyNavigatorRouteStack;
_renderCard: NavigationSceneRenderer; _renderCard: NavigationSceneRenderer;
_renderHeader: NavigationSceneRenderer; _renderHeader: NavigationSceneRenderer;
_renderScene: NavigationSceneRenderer; _renderScene: NavigationSceneRenderer;
_stack: NavigationLegacyNavigatorRouteStack;
navigationContext: NavigationContext; navigationContext: NavigationContext;
props: Props;
state: State;
constructor(props: Props, context: any) { constructor(props: Props, context: any) {
super(props, context); super(props, context);
@ -91,14 +107,22 @@ class NavigationLegacyNavigator extends React.Component<any, Props, State> {
this.navigationContext = new NavigationContext(); this.navigationContext = new NavigationContext();
const stack = this._getInitialRouteStack(); 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._key = guid();
this._previousStack = stack;
this._stack = stack;
this.state = { this.state = {
key: guid(), routeStack: stack.toArray(),
stack, presentedIndex: stack.index,
}; };
} }
jumpTo(route: any): void { jumpTo(route: any): void {
const index = this.state.stack.indexOf(route); const index = this._stack.indexOf(route);
invariant( invariant(
index > -1, index > -1,
'Cannot jump to route that is not in the route stack' 'Cannot jump to route that is not in the route stack'
@ -107,26 +131,26 @@ class NavigationLegacyNavigator extends React.Component<any, Props, State> {
} }
jumpForward(): void { jumpForward(): void {
this._jumpToIndex(this.state.stack.index + 1); this._jumpToIndex(this._stack.index + 1);
} }
jumpBack(): void { jumpBack(): void {
this._jumpToIndex(this.state.stack.index - 1); this._jumpToIndex(this._stack.index - 1);
} }
push(route: any): void { push(route: any): void {
this.setState({stack: this.state.stack.push(route)}); this._applyStack(this._stack.push(route));
} }
pop(): void { pop(): void {
const {stack} = this.state; const stack = this._stack;
if (stack.size > 1) { if (stack.size > 1) {
this.setState({stack: stack.pop()}); this._applyStack(stack.pop());
} }
} }
replaceAtIndex(route: any, index: number): void { replaceAtIndex(route: any, index: number): void {
const {stack} = this.state; const stack = this._stack;
if (index < 0) { if (index < 0) {
index += stack.size; index += stack.size;
@ -137,65 +161,63 @@ class NavigationLegacyNavigator extends React.Component<any, Props, State> {
return; return;
} }
this.setState({stack: stack.replaceAtIndex(index, route)}); this._applyStack(stack.replaceAtIndex(index, route));
} }
replace(route: any): void { replace(route: any): void {
this.replaceAtIndex(route, this.state.stack.index); this.replaceAtIndex(route, this._stack.index);
} }
replacePrevious(route: any): void { replacePrevious(route: any): void {
this.replaceAtIndex(route, this.state.stack.index - 1); this.replaceAtIndex(route, this._stack.index - 1);
} }
popToTop(): void { popToTop(): void {
this.setState({stack: this.state.stack.slice(0, 1)}); this._applyStack(this._stack.slice(0, 1));
} }
popToRoute(route: any): void { popToRoute(route: any): void {
const {stack} = this.state; const stack = this._stack;
const nextIndex = stack.indexOf(route); const nextIndex = stack.indexOf(route);
invariant( invariant(
nextIndex > -1, nextIndex > -1,
'Calling popToRoute for a route that doesn\'t exist!' 'Calling popToRoute for a route that doesn\'t exist!'
); );
this.setState({stack: stack.slice(0, nextIndex + 1)}); this._applyStack(stack.slice(0, nextIndex + 1));
} }
replacePreviousAndPop(route: any): void { replacePreviousAndPop(route: any): void {
const {stack} = this.state; const stack = this._stack;
const nextIndex = stack.index - 1; const nextIndex = stack.index - 1;
if (nextIndex < 0) { if (nextIndex < 0) {
return; return;
} }
this.setState({stack: stack.replaceAtIndex(nextIndex, route).pop()}); this._applyStack(stack.replaceAtIndex(nextIndex, route).pop());
} }
resetTo(route: any): void { resetTo(route: any): void {
invariant(!!route, 'Must supply route'); invariant(!!route, 'Must supply route');
this.setState({stack: this.state.stack.slice(0).replaceAtIndex(0, route)}); this._applyStack(this._stack.slice(0).replaceAtIndex(0, route));
} }
immediatelyResetRouteStack(routes: Array<any>): void { immediatelyResetRouteStack(routes: Array<any>): void {
const index = routes.length - 1; const index = routes.length - 1;
const stack = new RouteStack(index, routes); const stack = new RouteStack(index, routes);
this.setState({ // Immediately blow away all current scenes with a new key.
key: guid(), this._key = guid();
stack, this._applyStack(stack);
});
} }
getCurrentRoutes(): Array<any> { getCurrentRoutes(): Array<any> {
return this.state.stack.toArray(); return this._stack.toArray();
} }
_jumpToIndex(index: number): void { _jumpToIndex(index: number): void {
const {stack} = this.state; const stack = this._stack;
if (index < 0 || index >= stack.size) { if (index < 0 || index >= stack.size) {
return; return;
} }
const nextStack = stack.jumpToIndex(index); this._applyStack(stack.jumpToIndex(index));
this.setState({stack: nextStack});
} }
// Lyfe cycle and private methods below. // Lyfe cycle and private methods below.
@ -209,16 +231,22 @@ class NavigationLegacyNavigator extends React.Component<any, Props, State> {
} }
componentWillMount(): void { componentWillMount(): void {
this._onNavigationBarRef = this._onNavigationBarRef.bind(this);
this._onPositionChange = this._onPositionChange.bind(this);
this._renderCard = this._renderCard.bind(this); this._renderCard = this._renderCard.bind(this);
this._renderHeader = this._renderHeader.bind(this); this._renderHeader = this._renderHeader.bind(this);
this._renderScene = this._renderScene.bind(this); this._renderScene = this._renderScene.bind(this);
} }
componentWillUnmount(): void {
this._positionListener && this._positionListener.remove();
}
render(): ReactElement { render(): ReactElement {
return ( return (
<NavigationAnimatedView <NavigationAnimatedView
key={'main_' + this.state.key} key={'main_' + this._key}
navigationState={this.state.stack.toNavigationState()} navigationState={this._stack.toNavigationState()}
renderOverlay={this._renderHeader} renderOverlay={this._renderHeader}
renderScene={this._renderCard} renderScene={this._renderCard}
style={this.props.style} style={this.props.style}
@ -236,10 +264,28 @@ class NavigationLegacyNavigator extends React.Component<any, Props, State> {
} }
_renderHeader(props: NavigationSceneRendererProps): ?ReactElement { _renderHeader(props: NavigationSceneRendererProps): ?ReactElement {
// TODO(hedger): Render the legacy header. this._positionListener && this._positionListener.remove();
this._positionListener = new NavigationAnimatedValueSubscription(
props.position,
this._onPositionChange,
);
const {navigationBar, navigationBarNavigator} = this.props;
if (!navigationBar) {
return null; return null;
} }
return React.cloneElement(
navigationBar,
{
ref: this._onNavigationBarRef,
navigator: navigationBarNavigator || this,
navState: {...this.state},
}
);
}
_renderCard(props: NavigationSceneRendererProps): ReactElement { _renderCard(props: NavigationSceneRendererProps): ReactElement {
let direction = 'horizontal'; let direction = 'horizontal';
@ -248,7 +294,7 @@ class NavigationLegacyNavigator extends React.Component<any, Props, State> {
if (configureScene) { if (configureScene) {
const route = RouteStack.getRouteByNavigationState(navigationState); const route = RouteStack.getRouteByNavigationState(navigationState);
const config = configureScene(route, this.state.stack.toArray()); const config = configureScene(route, this.state.routeStack);
switch (getConfigPopDirection(config)) { switch (getConfigPopDirection(config)) {
case 'left-to-right': case 'left-to-right':
@ -282,6 +328,39 @@ class NavigationLegacyNavigator extends React.Component<any, Props, State> {
const route = RouteStack.getRouteByNavigationState(navigationState); const route = RouteStack.getRouteByNavigationState(navigationState);
return this.props.renderScene(route, this); return this.props.renderScene(route, this);
} }
_applyStack(stack: NavigationLegacyNavigatorRouteStack): void {
if (stack !== this._stack) {
this._previousStack = this._stack;
this._stack = stack;
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);
}
}
} }
// Legacy static members. // Legacy static members.

View File

@ -16,6 +16,7 @@ const NavigationContainer = require('NavigationContainer');
const NavigationPropTypes = require('NavigationPropTypes'); const NavigationPropTypes = require('NavigationPropTypes');
const NavigationStateUtils = require('NavigationStateUtils'); const NavigationStateUtils = require('NavigationStateUtils');
const React = require('react-native'); const React = require('react-native');
const StyleSheet = require('StyleSheet');
const View = require('View'); const View = require('View');
import type { import type {
@ -211,16 +212,24 @@ class NavigationAnimatedView
} }
render(): ReactElement { render(): ReactElement {
const overlay = this._renderOverlay();
const scenes = this._renderScenes();
return ( return (
<View <View
onLayout={this._onLayout} onLayout={this._onLayout}
style={this.props.style}> style={this.props.style}>
{this.state.scenes.map(this._renderScene, this)} <View style={styles.scenes} key="scenes">
{this._renderOverlay()} {scenes}
</View>
{overlay}
</View> </View>
); );
} }
_renderScenes(): Array<?ReactElement> {
return this.state.scenes.map(this._renderScene, this);
}
_renderScene(scene: NavigationScene): ?ReactElement { _renderScene(scene: NavigationScene): ?ReactElement {
const { const {
navigationState, navigationState,
@ -284,6 +293,12 @@ class NavigationAnimatedView
} }
} }
const styles = StyleSheet.create({
scenes: {
flex: 1,
},
});
NavigationAnimatedView.propTypes = propTypes; NavigationAnimatedView.propTypes = propTypes;
NavigationAnimatedView.defaultProps = defaultProps; NavigationAnimatedView.defaultProps = defaultProps;