diff --git a/ExNavigationBar.js b/ExNavigationBar.js new file mode 100644 index 0000000..59ab634 --- /dev/null +++ b/ExNavigationBar.js @@ -0,0 +1,233 @@ +/** + * 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. + */ +'use strict'; + +var React = require('React'); +var NavigatorNavigationBarStylesAndroid = require('NavigatorNavigationBarStylesAndroid'); +var NavigatorNavigationBarStylesIOS = require('NavigatorNavigationBarStylesIOS'); +var Platform = require('Platform'); +var StaticContainer = require('StaticContainer.react'); +var StyleSheet = require('StyleSheet'); +var View = require('View'); + +var { Map } = require('immutable'); + +var COMPONENT_NAMES = ['Title', 'LeftButton', 'RightButton']; + +var NavigatorNavigationBarStyles = Platform.OS === 'android' ? + NavigatorNavigationBarStylesAndroid : NavigatorNavigationBarStylesIOS; + +var navStatePresentedIndex = function(navState) { + if (navState.presentedIndex !== undefined) { + return navState.presentedIndex; + } + // TODO: rename `observedTopOfStack` to `presentedIndex` in `NavigatorIOS` + return navState.observedTopOfStack; +}; + +var NavigatorNavigationBar = React.createClass({ + + propTypes: { + navigator: React.PropTypes.object, + routeMapper: React.PropTypes.shape({ + Title: React.PropTypes.func.isRequired, + LeftButton: React.PropTypes.func.isRequired, + RightButton: React.PropTypes.func.isRequired, + }).isRequired, + navState: React.PropTypes.shape({ + routeStack: React.PropTypes.arrayOf(React.PropTypes.object), + presentedIndex: React.PropTypes.number, + }), + navigationStyles: React.PropTypes.object, + style: View.propTypes.style, + }, + + statics: { + Styles: NavigatorNavigationBarStyles, + StylesAndroid: NavigatorNavigationBarStylesAndroid, + StylesIOS: NavigatorNavigationBarStylesIOS, + }, + + getDefaultProps() { + return { + navigationStyles: NavigatorNavigationBarStyles, + }; + }, + + componentWillMount: function() { + this._components = {}; + this._descriptors = {}; + + COMPONENT_NAMES.forEach(componentName => { + this._components[componentName] = new Map(); + this._descriptors[componentName] = new Map(); + }); + }, + + _getReusableProps: function( + /*string*/componentName, + /*number*/index + ) /*object*/ { + if (!this._reusableProps) { + this._reusableProps = {}; + } + var propStack = this._reusableProps[componentName]; + if (!propStack) { + propStack = this._reusableProps[componentName] = []; + } + var props = propStack[index]; + if (!props) { + props = propStack[index] = {style:{}}; + } + return props; + }, + + _updateIndexProgress: function( + /*number*/progress, + /*number*/index, + /*number*/fromIndex, + /*number*/toIndex + ) { + var amount = toIndex > fromIndex ? progress : (1 - progress); + var oldDistToCenter = index - fromIndex; + var newDistToCenter = index - toIndex; + var interpolate; + if (oldDistToCenter > 0 && newDistToCenter === 0 || + newDistToCenter > 0 && oldDistToCenter === 0) { + interpolate = this.props.navigationStyles.Interpolators.RightToCenter; + } else if (oldDistToCenter < 0 && newDistToCenter === 0 || + newDistToCenter < 0 && oldDistToCenter === 0) { + interpolate = this.props.navigationStyles.Interpolators.CenterToLeft; + } else if (oldDistToCenter === newDistToCenter) { + interpolate = this.props.navigationStyles.Interpolators.RightToCenter; + } else { + interpolate = this.props.navigationStyles.Interpolators.RightToLeft; + } + + COMPONENT_NAMES.forEach(function (componentName) { + var component = this._components[componentName].get(this.props.navState.routeStack[index]); + var props = this._getReusableProps(componentName, index); + if (component && interpolate[componentName](props.style, amount)) { + component.setNativeProps(props); + } + }, this); + }, + + updateProgress: function( + /*number*/progress, + /*number*/fromIndex, + /*number*/toIndex + ) { + var max = Math.max(fromIndex, toIndex); + var min = Math.min(fromIndex, toIndex); + for (var index = min; index <= max; index++) { + this._updateIndexProgress(progress, index, fromIndex, toIndex); + } + }, + + render: function() { + var navBarStyle = { + height: this.props.navigationStyles.General.TotalNavHeight, + }; + var navState = this.props.navState; + var components = COMPONENT_NAMES.map(function (componentName) { + return navState.routeStack.map( + this._getComponent.bind(this, componentName) + ); + }, this); + + if (this.props.navState.routeStack.slice(-1)[0].showNavigationBar === false) { + return null; + } else if (this.props.navState.routeStack.slice(-1)[0].showNavigationBar === true) { + return ( + + {components} + + ); + } else { + if (this.props.navigator.props.showNavigationBar === false) { + return null; + } else { + return ( + + {components} + + ); + } + } + }, + + _getComponent: function( + /*string*/componentName, + /*object*/route, + /*number*/index + ) /*?Object*/ { + if (this._descriptors[componentName].includes(route)) { + return this._descriptors[componentName].get(route); + } + + var rendered = null; + + var content = this.props.routeMapper[componentName]( + this.props.navState.routeStack[index], + this.props.navigator, + index, + this.props.navState + ); + if (!content) { + return null; + } + + var initialStage = index === navStatePresentedIndex(this.props.navState) ? + this.props.navigationStyles.Stages.Center : + this.props.navigationStyles.Stages.Left; + rendered = ( + { + this._components[componentName] = this._components[componentName].set(route, ref); + }} + style={initialStage[componentName]}> + {content} + + ); + + this._descriptors[componentName] = this._descriptors[componentName].set(route, rendered); + return rendered; + }, + +}); + + +var styles = StyleSheet.create({ + navBarContainer: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + backgroundColor: 'transparent', + }, +}); + +module.exports = NavigatorNavigationBar; diff --git a/ExNavigator.js b/ExNavigator.js index f4fb42a..d61f7c9 100644 --- a/ExNavigator.js +++ b/ExNavigator.js @@ -16,6 +16,7 @@ import ExNavigatorMixin from './ExNavigatorMixin'; import ExNavigatorStyles from './ExNavigatorStyles'; import ExRouteRenderer from './ExRouteRenderer'; import ExSceneConfigs from './ExSceneConfigs'; +import ExNavigationBar from './ExNavigationBar'; import type * as ExRoute from './ExRoute'; @@ -38,7 +39,7 @@ export default class ExNavigator extends React.Component { ...Navigator.defaultProps, showNavigationBar: true, renderNavigationBar: props => { - return + return }, }; @@ -93,10 +94,6 @@ export default class ExNavigator extends React.Component { } _renderNavigationBar(): ?Navigator.NavigationBar { - if (!this.props.showNavigationBar) { - return null; - } - return this.props.renderNavigationBar({ routeMapper: this._routeRenderer.navigationBarRouteMapper, style: [ExNavigatorStyles.bar, this.props.navigationBarStyle], diff --git a/package.json b/package.json index f1c1748..8b26bb6 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dependencies": { "@exponent/react-native-responsive-image": "0.0.4", "autobind-decorator": "^1.3.2", + "immutable": "^3.7.5", "invariant": "^2.1.0", "react-native-clone-referenced-element": "^1.0.0" }