Skip to content

Commit

Permalink
Navigation Back support and examples for Android
Browse files Browse the repository at this point in the history
Summary:
public
- Intro new back action
- Add support in the two main reducers
- Use it in examples to support Android back button
- Disable NavigationCard gestures on Android

Reviewed By: hedgerwang

Differential Revision: D2914154

fb-gh-sync-id: d4dce6538e19613a2ffca21e2e3b2ecaded3d5dc
shipit-source-id: d4dce6538e19613a2ffca21e2e3b2ecaded3d5dc
  • Loading branch information
Eric Vicenti authored and facebook-github-bot-5 committed Feb 9, 2016
1 parent 7b57b5c commit 7b2b0c3
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,18 @@ class NavigationAnimatedExample extends React.Component {
return (
<NavigationRootContainer
reducer={NavigationBasicReducer}
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
persistenceKey="NavigationAnimatedExampleState"
renderNavigation={this._renderNavigated}
/>
);
}
handleBackAction() {
return (
this.navRootContainer &&
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
);
}
_renderNavigated(navigationState, onNavigate) {
if (!navigationState) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const NavigationBasicExample = React.createClass({
<NavigationRootContainer
reducer={NavigationBasicReducer}
persistenceKey="NavigationBasicExampleState"
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
renderNavigation={(navState, onNavigate) => {
if (!navState) { return null; }
return (
Expand Down Expand Up @@ -69,6 +70,14 @@ const NavigationBasicExample = React.createClass({
/>
);
},

handleBackAction: function() {
return (
this.navRootContainer &&
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
);
},

});

const styles = StyleSheet.create({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,18 @@ function stateTypeTitleMap(pageState) {
}
}

let ExampleTabScreen = React.createClass({
render: function() {
class ExampleTabScreen extends React.Component {
render() {
return (
<NavigationAnimatedView
style={styles.tabContent}
navigationState={this.props.navigationState}
renderOverlay={this._renderHeader}
renderScene={this._renderScene}
renderOverlay={this._renderHeader.bind(this)}
renderScene={this._renderScene.bind(this)}
/>
);
},
_renderHeader: function(position, layout) {
}
_renderHeader(position, layout) {
return (
<NavigationHeader
navigationState={this.props.navigationState}
Expand All @@ -155,8 +155,8 @@ let ExampleTabScreen = React.createClass({
getTitle={state => stateTypeTitleMap(state)}
/>
);
},
_renderScene: function(child, index, position, layout) {
}
_renderScene(child, index, position, layout) {
return (
<NavigationCard
key={child.key}
Expand Down Expand Up @@ -187,21 +187,28 @@ let ExampleTabScreen = React.createClass({
</ScrollView>
</NavigationCard>
);
},
});
}
}
ExampleTabScreen = NavigationContainer.create(ExampleTabScreen);

const NavigationCompositionExample = React.createClass({
render: function() {
class NavigationCompositionExample extends React.Component {
render() {
return (
<NavigationRootContainer
reducer={ExampleAppReducer}
persistenceKey="NavigationCompositionExampleState"
renderNavigation={this.renderApp}
ref={navRootContainer => { 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;
}
Expand All @@ -217,11 +224,11 @@ const NavigationCompositionExample = React.createClass({
/>
</View>
);
},
});
}
}

let ExampleMainView = React.createClass({
render: function() {
class ExampleMainView extends React.Component {
render() {
return (
<NavigationView
navigationState={this.props.navigationState}
Expand All @@ -230,20 +237,20 @@ let ExampleMainView = React.createClass({
<ExampleTabScreen
key={tabState.key}
navigationState={tabState}
onNavigate={this._handleNavigation.bind(null, tabState.key)}
onNavigate={this._handleNavigation.bind(this, tabState.key)}
/>
)}
/>
);
},
_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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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 <Component onExampleExit={this._exitInnerExample} />;
return (
<Component
onExampleExit={this._exitInnerExample}
ref={exampleRef => { this.exampleRef = exampleRef; }}
/>
);
}
return null;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,13 @@ const ExampleTabsReducer = NavigationReducer.TabsReducer({
],
});

const NavigationTabsExample = React.createClass({
render: function() {
class NavigationTabsExample extends React.Component {
render() {
return (
<NavigationRootContainer
reducer={ExampleTabsReducer}
persistenceKey="NAV_EXAMPLE_STATE_TABS"
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
renderNavigation={(navigationState) => {
if (!navigationState) { return null; }
return (
Expand All @@ -88,8 +89,14 @@ const NavigationTabsExample = React.createClass({
}}
/>
);
},
});
}
handleBackAction() {
return (
this.navRootContainer &&
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
);
}
}

const styles = StyleSheet.create({
topView: {
Expand Down
11 changes: 10 additions & 1 deletion Examples/UIExplorer/UIExplorerApp.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,21 @@ var UIExplorerApp = React.createClass({
style={styles.toolbar}
title={this.state.example.title}
/>
<Component />
<Component
ref={(example) => { this._exampleRef = example; }}
/>
</View>
);
},

_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;
Expand Down
1 change: 1 addition & 0 deletions Examples/UIExplorer/UIExplorerList.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ var APIS = [
require('./IntentAndroidExample.android'),
require('./LayoutEventsExample'),
require('./LayoutExample'),
require('./NavigationExperimental/NavigationExperimentalExample'),
require('./NetInfoExample'),
require('./PanResponderExample'),
require('./PointerEventsExample'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

This comment has been minimized.

Copy link
@mkonicek

mkonicek Feb 9, 2016

Contributor

Nice!


import type {
NavigationParentState
} from 'NavigationState';
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 (
<Animated.View
{...this._responder.panHandlers}
{...touchResponderHandlers}
style={[
styles.card,
{
Expand Down
26 changes: 21 additions & 5 deletions Libraries/NavigationExperimental/NavigationRootContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,29 @@
*/
'use strict';

var AsyncStorage = require('AsyncStorage');
var React = require('React');
const AsyncStorage = require('AsyncStorage');
const React = require('React');
const BackAndroid = require('BackAndroid');
const Platform = require('Platform');

import type {
NavigationState,
NavigationReducer
} from 'NavigationState';

type NavigationRenderer = (
export type NavigationRenderer = (
navigationState: NavigationState,
onNavigate: Function
) => ReactElement;

export type BackAction = {
type: 'BackAction';
};

function getBackAction(): BackAction {
return { type: 'BackAction' };
}

type Props = {
renderNavigation: NavigationRenderer;
reducer: NavigationReducer;
Expand Down Expand Up @@ -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
);
Expand All @@ -83,4 +97,6 @@ NavigationRootContainer.childContextTypes = {
onNavigate: React.PropTypes.func,
};

NavigationRootContainer.getBackAction = getBackAction;

module.exports = NavigationRootContainer;
9 changes: 8 additions & 1 deletion Libraries/NavigationExperimental/NavigationState.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ export type NavigationParentState = {
children: Array<NavigationState>;
};

export type NavigationAction = {
type: string;
};

export type NavigationReducer = (
state: ?NavigationState,
action: ?any
action: ?NavigationAction
) => ?NavigationState;

export type NavigationReducerWithDefault = (
Expand Down Expand Up @@ -142,6 +146,9 @@ export function set(state: ?NavigationState, key: string, nextChildren: Array<Na

export function jumpToIndex(state: NavigationState, index: number): NavigationState {
const parentState = getParent(state);
if (parentState && parentState.index === index) {
return parentState;
}
return {
...parentState,
index,
Expand Down
Loading

0 comments on commit 7b2b0c3

Please sign in to comment.