diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js index 2139397908a517..e9f85ed1cb680c 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js @@ -44,11 +44,18 @@ class NavigationAnimatedExample extends React.Component { return ( { this.navRootContainer = navRootContainer; }} persistenceKey="NavigationAnimatedExampleState" renderNavigation={this._renderNavigated} /> ); } + handleBackAction() { + return ( + this.navRootContainer && + this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) + ); + } _renderNavigated(navigationState, onNavigate) { if (!navigationState) { return null; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js index 21f03eb63889b1..8f8669fb4c238c 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js @@ -40,6 +40,7 @@ const NavigationBasicExample = React.createClass({ { this.navRootContainer = navRootContainer; }} renderNavigation={(navState, onNavigate) => { if (!navState) { return null; } return ( @@ -69,6 +70,14 @@ const NavigationBasicExample = React.createClass({ /> ); }, + + handleBackAction: function() { + return ( + this.navRootContainer && + this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) + ); + }, + }); const styles = StyleSheet.create({ diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js index 1af4c63bbeb4cb..ee8472242c4a8f 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js @@ -135,18 +135,18 @@ function stateTypeTitleMap(pageState) { } } -let ExampleTabScreen = React.createClass({ - render: function() { +class ExampleTabScreen extends React.Component { + render() { return ( ); - }, - _renderHeader: function(position, layout) { + } + _renderHeader(position, layout) { return ( stateTypeTitleMap(state)} /> ); - }, - _renderScene: function(child, index, position, layout) { + } + _renderScene(child, index, position, layout) { return ( ); - }, -}); + } +} ExampleTabScreen = NavigationContainer.create(ExampleTabScreen); -const NavigationCompositionExample = React.createClass({ - render: function() { +class NavigationCompositionExample extends React.Component { + render() { return ( { this.navRootContainer = navRootContainer; }} + renderNavigation={this.renderApp.bind(this)} /> ); - }, - renderApp: function(navigationState, onNavigate) { + } + handleBackAction() { + return ( + this.navRootContainer && + this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) + ); + } + renderApp(navigationState, onNavigate) { if (!navigationState) { return null; } @@ -217,11 +224,11 @@ const NavigationCompositionExample = React.createClass({ /> ); - }, -}); + } +} -let ExampleMainView = React.createClass({ - render: function() { +class ExampleMainView extends React.Component { + render() { return ( )} /> ); - }, - _handleNavigation: function(tabKey, action) { + } + _handleNavigation(tabKey, action) { if (ExampleExitAction.match(action)) { this.props.onExampleExit(); return; } this.props.onNavigate(action); - }, -}); + } +} ExampleMainView = NavigationContainer.create(ExampleMainView); const styles = StyleSheet.create({ diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js index 0d11a8e649fcee..4aade5303c173f 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js @@ -25,7 +25,7 @@ var NavigationExampleRow = require('./NavigationExampleRow'); /* * Heads up! This file is not the real navigation example- only a utility to switch between them. * - * To learn how to use the Navigation API, take a look at the following exmample files: + * To learn how to use the Navigation API, take a look at the following example files: */ var EXAMPLES = { 'Tabs': require('./NavigationTabsExample'), @@ -106,13 +106,34 @@ var NavigationExperimentalExample = React.createClass({ this.setExample('menu'); }, + handleBackAction: function() { + const wasHandledByExample = ( + this.exampleRef && + this.exampleRef.handleBackAction && + this.exampleRef.handleBackAction() + ); + if (wasHandledByExample) { + return true; + } + if (this.state.example && this.state.example !== 'menu') { + this._exitInnerExample(); + return true; + } + return false; + }, + render: function() { if (this.state.example === 'menu') { return this._renderMenu(); } if (EXAMPLES[this.state.example]) { var Component = EXAMPLES[this.state.example]; - return ; + return ( + { this.exampleRef = exampleRef; }} + /> + ); } return null; }, diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js index beb3a6e35af396..ae4de59a6da8fa 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js @@ -64,12 +64,13 @@ const ExampleTabsReducer = NavigationReducer.TabsReducer({ ], }); -const NavigationTabsExample = React.createClass({ - render: function() { +class NavigationTabsExample extends React.Component { + render() { return ( { this.navRootContainer = navRootContainer; }} renderNavigation={(navigationState) => { if (!navigationState) { return null; } return ( @@ -88,8 +89,14 @@ const NavigationTabsExample = React.createClass({ }} /> ); - }, -}); + } + handleBackAction() { + return ( + this.navRootContainer && + this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) + ); + } +} const styles = StyleSheet.create({ topView: { diff --git a/Examples/UIExplorer/UIExplorerApp.android.js b/Examples/UIExplorer/UIExplorerApp.android.js index 25a4745bf115c5..cdf92c6848de45 100644 --- a/Examples/UIExplorer/UIExplorerApp.android.js +++ b/Examples/UIExplorer/UIExplorerApp.android.js @@ -109,12 +109,21 @@ var UIExplorerApp = React.createClass({ style={styles.toolbar} title={this.state.example.title} /> - + { this._exampleRef = example; }} + /> ); }, _handleBackButtonPress: function() { + if ( + this._exampleRef && + this._exampleRef.handleBackAction && + this._exampleRef.handleBackAction() + ) { + return true; + } if (this.state.example.title !== this._getUIExplorerHome().title) { this.onSelectExample(this._getUIExplorerHome()); return true; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index ed84e62aad11cf..8959a0919f9a63 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -54,6 +54,7 @@ var APIS = [ require('./IntentAndroidExample.android'), require('./LayoutEventsExample'), require('./LayoutExample'), + require('./NavigationExperimental/NavigationExperimentalExample'), require('./NetInfoExample'), require('./PanResponderExample'), require('./PointerEventsExample'), diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js index d25c45aefdec56..096e99d1c3ea37 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js @@ -31,10 +31,13 @@ const Animated = require('Animated'); const NavigationReducer = require('NavigationReducer'); const NavigationContainer = require('NavigationContainer'); const PanResponder = require('PanResponder'); +const Platform = require('Platform'); const React = require('React'); const StyleSheet = require('StyleSheet'); const View = require('View'); +const ENABLE_GESTURES = Platform.OS !== 'android'; + import type { NavigationParentState } from 'NavigationState'; @@ -62,7 +65,12 @@ class NavigationCard extends React.Component { _widthListener: string; _heightListener: string; props: Props; - componentWillMount(props) { + componentWillMount() { + if (ENABLE_GESTURES) { + this._enableGestures(); + } + } + _enableGestures() { this._responder = PanResponder.create({ onMoveShouldSetPanResponder: (e, {dx, dy, moveX, moveY, x0, y0}) => { if (this.props.navigationState.index === 0) { @@ -119,9 +127,10 @@ class NavigationCard extends React.Component { render() { const cardPosition = Animated.add(this.props.position, new Animated.Value(-this.props.index)); const gestureValue = Animated.multiply(cardPosition, this.props.layout.width); + const touchResponderHandlers = this._responder ? this._responder.panHandlers : null; return ( ReactElement; +export type BackAction = { + type: 'BackAction'; +}; + +function getBackAction(): BackAction { + return { type: 'BackAction' }; +} + type Props = { renderNavigation: NavigationRenderer; reducer: NavigationReducer; @@ -61,17 +71,21 @@ class NavigationRootContainer extends React.Component { onNavigate: this.handleNavigation, }; } - handleNavigation(action: Object) { + handleNavigation(action: Object): boolean { const navState = this.props.reducer(this.state.navState, action); + if (navState === this.state.navState) { + return false; + } this.setState({ navState, }); if (this.props.persistenceKey) { AsyncStorage.setItem(this.props.persistenceKey, JSON.stringify(navState)); } + return true; } render(): ReactElement { - var navigation = this.props.renderNavigation( + const navigation = this.props.renderNavigation( this.state.navState, this.handleNavigation ); @@ -83,4 +97,6 @@ NavigationRootContainer.childContextTypes = { onNavigate: React.PropTypes.func, }; +NavigationRootContainer.getBackAction = getBackAction; + module.exports = NavigationRootContainer; diff --git a/Libraries/NavigationExperimental/NavigationState.js b/Libraries/NavigationExperimental/NavigationState.js index 8ef085fd9b2ad7..d78d3559177ca4 100644 --- a/Libraries/NavigationExperimental/NavigationState.js +++ b/Libraries/NavigationExperimental/NavigationState.js @@ -23,9 +23,13 @@ export type NavigationParentState = { children: Array; }; +export type NavigationAction = { + type: string; +}; + export type NavigationReducer = ( state: ?NavigationState, - action: ?any + action: ?NavigationAction ) => ?NavigationState; export type NavigationReducerWithDefault = ( @@ -142,6 +146,9 @@ export function set(state: ?NavigationState, key: string, nextChildren: Array): ?NavigationReducer { +function NavigationFindReducer(reducers: Array): NavigationReducer { return function(lastState: ?NavigationState, action: ?any): ?NavigationState { for (let i = 0; i < reducers.length; i++) { let reducer = reducers[i]; diff --git a/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js index 7aec5f4ee4cd08..5dcf34966daf8c 100644 --- a/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js +++ b/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js @@ -18,7 +18,11 @@ import type { NavigationReducer, } from 'NavigationState'; -export type NavigationStackReducerAction = { +import type { + BackAction, +} from 'NavigationRootContainer'; + +export type NavigationStackReducerAction = BackAction | { type: string, }; @@ -101,6 +105,7 @@ function NavigationStackReducer({initialStates, initialIndex, key, matchAction, action.state ); case ActionTypes.POP: + case 'BackAction': if (lastParentState.index === 0 || lastParentState.children.length === 1) { return lastParentState; } diff --git a/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js index 6d31164f0cac33..9d6d76a4ff2224 100644 --- a/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js +++ b/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js @@ -17,7 +17,8 @@ const NavigationStateUtils = require('NavigationState'); import type { NavigationReducer, NavigationReducerWithDefault, - NavigationState + NavigationState, + NavigationParentState } from 'NavigationState'; const ActionTypes = { @@ -106,17 +107,15 @@ function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConf } } const subReducers = tabReducers.map((tabReducer, tabIndex) => { - return function reduceTab(lastTabState: ?NavigationState, tabAction: ?any): ?NavigationState { - if (!lastTabState) { - return tabReducer(lastTabState, tabAction); + return function reduceTab(lastNavState: ?NavigationState, tabAction: ?any): ?NavigationState { + if (!tabReducer || !lastNavState) { + return lastNavState; } - if (!lastParentNavState) { - return lastTabState; - } - const lastSubTabState = lastParentNavState.children[tabIndex]; + const lastParentNavState = NavigationStateUtils.getParent(lastNavState); + const lastSubTabState = lastParentNavState && lastParentNavState.children[tabIndex]; const nextSubTabState = tabReducer(lastSubTabState, tabAction); if (nextSubTabState && lastSubTabState !== nextSubTabState) { - const tabs = lastParentNavState.children; + const tabs = lastParentNavState && lastParentNavState.children || []; tabs[tabIndex] = nextSubTabState; return { ...lastParentNavState, @@ -129,11 +128,17 @@ function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConf }); let selectedTabReducer = subReducers.splice(lastParentNavState.index, 1)[0]; subReducers.unshift(selectedTabReducer); + subReducers.push((lastParentNavState: ?NavigationState, action: ?any) => { + if (lastParentNavState && action && action.type === 'BackAction') { + return NavigationStateUtils.jumpToIndex( + lastParentNavState, + 0 + ); + } + return lastParentNavState; + }); const findReducer = NavigationFindReducer(subReducers); - if (findReducer) { - return findReducer(lastParentNavState, action); - } - return lastParentNavState; + return findReducer(lastParentNavState, action); }; }