diff --git a/index.js b/index.js index 24111f0..b0cdc6a 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,13 @@ import NavBarBackButton from './lib/NavBarBackButton' import NavBar from './lib/NavBar' import NavigatorWrapper from './lib/NavigatorWrapper' import TopNavigatorWrapper from './lib/TopNavigatorWrapper' -import { defaultRouteMapper, leftButtonRouteMapperGenerator, rightButtonRouteMapperGenerator, titleRouteMapperGenerator } from './lib/RouteMapper' +import { + defaultRouteMapper, + leftButtonRouteMapperGenerator, + rightButtonRouteMapperGenerator, + titleRouteMapperGenerator, + CenteredText +} from './lib/RouteMapper' export { NavBarBackButton, @@ -13,4 +19,5 @@ export { leftButtonRouteMapperGenerator, rightButtonRouteMapperGenerator, titleRouteMapperGenerator, + CenteredText, } diff --git a/lib/NavBar.android.js b/lib/NavBar.android.js new file mode 100644 index 0000000..362f979 --- /dev/null +++ b/lib/NavBar.android.js @@ -0,0 +1,34 @@ +import React, { + Navigator, + StyleSheet, +} from 'react-native' + +const stylesAndroid = StyleSheet.create({ + navBar: { + backgroundColor: 'white', + } +}) + +class NavBar extends React.Component { + updateProgress (progress, fromIndex, toIndex) { + this._nav.updateProgress(progress, fromIndex, toIndex); + } + + render () { + return ( + { this._nav = nav }} + /> + ) + } +} + +NavBar.propTypes = { + ...Navigator.NavigationBar.propTypes, +} + +export default NavBar diff --git a/lib/NavBarBackButton.android.js b/lib/NavBarBackButton.android.js new file mode 100644 index 0000000..a3ec67a --- /dev/null +++ b/lib/NavBarBackButton.android.js @@ -0,0 +1,67 @@ +import React, { TouchableOpacity, Text, PropTypes, StyleSheet } from 'react-native' +import StyleSheetPropType from 'react-native/Libraries/StyleSheet/StyleSheetPropType' +import TextStylePropTypes from 'react-native/Libraries/Text/TextStylePropTypes' +import Ionicon from 'react-native-vector-icons/Ionicons' + +class NavBarBackButton extends React.Component { + constructor (props) { + super(props) + this.state = { + tintColor: props.tintColor || 'black' + } + } + + _renderBackTitle () { + if (this.props.showBackTitle) { + return ( + + {this.props.children} + + ) + } + } + + render () { + const touchableProps = { + onPress: this.props.onPress, + onPressIn: this.props.onPressIn, + onPressOut: this.props.onPressOut, + onLongPress: this.props.onLongPress + } + return ( + + + {this._renderBackTitle.bind(this)} + + ) + } +} + +NavBarBackButton.propTypes = { + ...TouchableOpacity.propTypes, + tintColor: PropTypes.string, + children: PropTypes.string.isRequired, + style: StyleSheetPropType(TextStylePropTypes), + showBackTitle: PropTypes.bool, +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + height: 50, + paddingRight: 15, + }, + icon: { + marginLeft: 10, + }, + navText: { + paddingLeft: 5, + paddingTop: 7, + }, +}) + +export default NavBarBackButton diff --git a/lib/NavigatorWrapper.js b/lib/NavigatorWrapper.js index 88aceb0..3c3b6cd 100644 --- a/lib/NavigatorWrapper.js +++ b/lib/NavigatorWrapper.js @@ -1,32 +1,75 @@ -import React, { View, PropTypes, Navigator } from 'react-native' +import React, { + View, + PropTypes, + Navigator, + BackAndroid, + Platform +} from 'react-native' import NavBar from './NavBar' import { defaultRouteMapper } from './RouteMapper' -import StyleSheetPropType from 'react-native/Libraries/StyleSheet/StyleSheetPropType' -import ViewStylePropTypes from 'react-native/Libraries/Components/View/ViewStylePropTypes' class NavigatorWrapper extends React.Component { + static isAndroid = Platform.OS !== 'ios'; + + _handleAndroidBackButton () { + if (this.navigator && !this.firstComponentInStack) { + this.navigator.pop() + return true + } + return false + } + + constructor (props) { + super(props) + this.navigator = undefined + this.firstComponentInStack = true + } + + componentDidMount() { + // Automatically handle back button under Android platform + if (NavigatorWrapper.isAndroid && this.props.initialRoute.handleBackAndroid) { + this.bindedBackFunction = this._handleAndroidBackButton.bind(this) + BackAndroid.addEventListener('hardwareBackPress', this.bindedBackFunction) + } + } + + componentWillUnmount () { + if (NavigatorWrapper.isAndroid) { + BackAndroid.removeEventListener('hardwareBackPress', this.bindedBackFunction) + } + } + renderScene (route, navigator) { + let marginTop = 64 + this.firstComponentInStack = route.handleBackAndroid + if (NavigatorWrapper.isAndroid) { + // Save navigator to handle back button under Android + if (!this.navigator) { + this.navigator = navigator + } + marginTop = 56 + } const RenderComponent = route.component return ( - + ) } render () { + const navAnimation = (NavigatorWrapper.isAndroid) ? Navigator.SceneConfigs.FadeAndroid : Navigator.SceneConfigs.PushFromRight return ( navAnimation} + initialRoute={this.props.initialRoute} + initialRouteStack={this.props.initialRouteStack} navigationBar={ { - if (index === 0) { - return null - } - const previousRoute = navState.routeStack[index - 1] - return ( - navigator.pop()} - style={styles} - tintColor={tintColor}> - {previousRoute.title} - - ) - } - } -} - -export function rightButtonRouteMapperGenerator (RightComponent, topNavigator) { - return { - RightButton: (route, navigator, index, navState) => { - if (RightComponent && !route.rightElement && index === 0) { - return - } - if (route.rightElement) { - return route.rightElement - } - } - } -} - -export function titleRouteMapperGenerator (TitleComponent, styles, topNavigator) { - return { - Title: (route, navigator, index, navState) => { - const title = route.title || '' - const Component = TitleComponent || Text - return ( - - {title} - - ) - } - } -} - -const defaultStyles = React.StyleSheet.create({ - back: { - flex: 1, - color: 'black', - }, - navFont: { - fontSize: 17, - }, - navText: { - flex: 1, - paddingTop: 8, - textAlign: 'center', - width: 200, - }, -}) - -export function defaultRouteMapper () { - return { - ...leftButtonRouteMapperGenerator(NavBarBackButton, [defaultStyles.navFont, defaultStyles.back], 'black'), - ...rightButtonRouteMapperGenerator(), - ...titleRouteMapperGenerator(Text, [defaultStyles.navFont, defaultStyles.navText]) - } -} diff --git a/lib/RouteMapper.js b/lib/RouteMapper.js new file mode 100644 index 0000000..fa78a2c --- /dev/null +++ b/lib/RouteMapper.js @@ -0,0 +1,90 @@ +import React, { View, Text } from 'react-native' +import NavBarBackButton from './NavBarBackButton' + +export function leftButtonRouteMapperGenerator (BackComponent, styles, tintColor, topNavigator) { + return { + LeftButton: (route, navigator, index, navState) => { + if (route.leftElement) { + return React.cloneElement(route.leftElement, { + navigator: navigator, + topNavigator: topNavigator, + }) + } else if (index > 0) { + const previousRoute = navState.routeStack[index - 1] + return ( + + navigator.pop()} + style={[{flex: 1}, styles]} + tintColor={tintColor}> + {previousRoute.title} + + + ) + } + return null + } + } +} + +export function rightButtonRouteMapperGenerator (RightComponent, topNavigator) { + return { + RightButton: (route, navigator, index, navState) => { + if (route.rightElement) { + return React.cloneElement(route.rightElement, { + navigator: navigator, + topNavigator: topNavigator, + }) + } else if (RightComponent) { + return + } + return null + } + } +} + +export function titleRouteMapperGenerator (TitleComponent, styles, topNavigator) { + return { + Title: (route, navigator, index, navState) => { + const Component = TitleComponent || Text + return ( + + {route.title} + + ) + } + } +} + +export class CenteredText extends React.Component { + render () { + return ( + + + {this.props.children} + + + ) + } +} + +const defaultStyles = React.StyleSheet.create({ + back: { + flex: 1, + color: 'black', + }, + navFont: { + fontSize: 17, + }, + navText: { + flex: 1, + }, +}) + +export function defaultRouteMapper () { + return { + ...leftButtonRouteMapperGenerator(NavBarBackButton, [defaultStyles.navFont, defaultStyles.back], 'black'), + ...rightButtonRouteMapperGenerator(), + ...titleRouteMapperGenerator(CenteredText, [defaultStyles.navFont, defaultStyles.navText]) + } +} diff --git a/lib/TopNavigatorWrapper.js b/lib/TopNavigatorWrapper.js index 77d397c..780280a 100644 --- a/lib/TopNavigatorWrapper.js +++ b/lib/TopNavigatorWrapper.js @@ -1,17 +1,19 @@ -import React, { Navigator, PropTypes } from 'react-native' +import React, { View, Navigator, PropTypes, Platform } from 'react-native' import NavigatorWrapper from './NavigatorWrapper' -import StyleSheetPropType from 'react-native/Libraries/StyleSheet/StyleSheetPropType' -import ViewStylePropTypes from 'react-native/Libraries/Components/View/ViewStylePropTypes' class TopNavigatorWrapper extends React.Component { + static isAndroid = Platform.OS !== 'ios'; + _renderScene (route, navigator) { // Render the inner component or the modal. This component will be the one // with push-like transitions. if (route.id === 'mainComponent') { return ( @@ -38,23 +43,40 @@ class TopNavigatorWrapper extends React.Component { } render () { + const modalAnimation = (TopNavigatorWrapper.isAndroid) ? Navigator.SceneConfigs.FloatFromBottomAndroid : Navigator.SceneConfigs.FloatFromBottom return ( (this._renderScene(route, navigator))} initialRoute={{id: 'mainComponent'}} - configureScene={route => Navigator.SceneConfigs.FloatFromBottom} + configureScene={(route, routeStack) => modalAnimation} /> ) } } TopNavigatorWrapper.propTypes = { - initialComponent: PropTypes.func.isRequired, - title: PropTypes.string, - navBarStyle: StyleSheetPropType(ViewStylePropTypes), + /** + * Provide the initial route or the initial route stack. + */ + initialRoute: PropTypes.shape({ + component: PropTypes.func.isRequired, + title: PropTypes.string.isRequired, + passProps: PropTypes.object, + }), + initialRouteStack: PropTypes.arrayOf(PropTypes.object), + + /** + * Optional style for the default navigation bar. + */ + navBarStyle: View.propTypes.style, + + /** + * Optional style for the default modal navigation bar. + */ + modalNavBarStyle: View.propTypes.style, + routeMapper: PropTypes.func, - modalNavBarStyle: StyleSheetPropType(ViewStylePropTypes), modalRouteMapper: PropTypes.func, } diff --git a/package.json b/package.json index 7aa383d..4789f67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-navigator-wrapper", - "version": "0.0.8", + "version": "0.1.0", "description": "A React Native Navigator component wrapper that implements nested navigators for both push and modal transitions.", "main": "index.js", "scripts": { @@ -12,6 +12,7 @@ }, "keywords": [ "ios", + "android", "react", "react-native", "react-component",