[AdsManager] Correct back button functionality

This commit is contained in:
Eric Vicenti 2015-04-07 16:03:36 -07:00
parent 31b6ff6d1a
commit ccbd656070
4 changed files with 298 additions and 24 deletions

View File

@ -27,11 +27,14 @@
'use strict'; 'use strict';
var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule; var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule;
var BackAndroid = require('BackAndroid');
var Dimensions = require('Dimensions'); var Dimensions = require('Dimensions');
var InteractionMixin = require('InteractionMixin'); var InteractionMixin = require('InteractionMixin');
var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
var NavigatorNavigationBar = require('NavigatorNavigationBar');
var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar'); var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
var NavigatorInterceptor = require('NavigatorInterceptor');
var NavigatorNavigationBar = require('NavigatorNavigationBar');
var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
var NavigatorStaticContextContainer = require('NavigatorStaticContextContainer');
var PanResponder = require('PanResponder'); var PanResponder = require('PanResponder');
var React = require('React'); var React = require('React');
var StaticContainer = require('StaticContainer.react'); var StaticContainer = require('StaticContainer.react');
@ -40,6 +43,7 @@ var Subscribable = require('Subscribable');
var TimerMixin = require('react-timer-mixin'); var TimerMixin = require('react-timer-mixin');
var View = require('View'); var View = require('View');
var getNavigatorContext = require('getNavigatorContext');
var clamp = require('clamp'); var clamp = require('clamp');
var invariant = require('invariant'); var invariant = require('invariant');
var keyMirror = require('keyMirror'); var keyMirror = require('keyMirror');
@ -182,12 +186,11 @@ var Navigator = React.createClass({
/** /**
* Required function which renders the scene for a given route. Will be * Required function which renders the scene for a given route. Will be
* invoked with the route, the navigator object, and a ref handler that * invoked with the route and the navigator object
* will allow a ref to your scene to be provided by props.onItemRef
* *
* ``` * ```
* (route, navigator, onRef) => * (route, navigator) =>
* <MySceneComponent title={route.title} ref={onRef} /> * <MySceneComponent title={route.title} />
* ``` * ```
*/ */
renderScene: PropTypes.func.isRequired, renderScene: PropTypes.func.isRequired,
@ -241,10 +244,16 @@ var Navigator = React.createClass({
sceneStyle: View.propTypes.style, sceneStyle: View.propTypes.style,
}, },
contextTypes: {
navigator: PropTypes.object,
},
statics: { statics: {
BreadcrumbNavigationBar: NavigatorBreadcrumbNavigationBar, BreadcrumbNavigationBar: NavigatorBreadcrumbNavigationBar,
NavigationBar: NavigatorNavigationBar, NavigationBar: NavigatorNavigationBar,
SceneConfigs: NavigatorSceneConfigs, SceneConfigs: NavigatorSceneConfigs,
Interceptor: NavigatorInterceptor,
getContext: getNavigatorContext,
}, },
mixins: [TimerMixin, InteractionMixin, Subscribable.Mixin], mixins: [TimerMixin, InteractionMixin, Subscribable.Mixin],
@ -293,7 +302,20 @@ var Navigator = React.createClass({
}, },
componentWillMount: function() { componentWillMount: function() {
this.navigatorActions = { this.parentNavigator = getNavigatorContext(this) || this.props.navigator;
this.navigatorContext = {
setHandlerForRoute: this.setHandlerForRoute,
request: this.request,
parentNavigator: this.parentNavigator,
getCurrentRoutes: this.getCurrentRoutes,
// We want to bubble focused routes to the top navigation stack. If we
// are a child navigator, this allows us to call props.navigator.on*Focus
// of the topmost Navigator
onWillFocus: this.props.onWillFocus,
onDidFocus: this.props.onDidFocus,
// Legacy, imperitive nav actions. Use request when possible.
jumpBack: this.jumpBack, jumpBack: this.jumpBack,
jumpForward: this.jumpForward, jumpForward: this.jumpForward,
jumpTo: this.jumpTo, jumpTo: this.jumpTo,
@ -307,14 +329,8 @@ var Navigator = React.createClass({
resetTo: this.resetTo, resetTo: this.resetTo,
popToRoute: this.popToRoute, popToRoute: this.popToRoute,
popToTop: this.popToTop, popToTop: this.popToTop,
parentNavigator: this.props.navigator,
getCurrentRoutes: this.getCurrentRoutes,
// We want to bubble focused routes to the top navigation stack. If we
// are a child navigator, this allows us to call props.navigator.on*Focus
// of the topmost Navigator
onWillFocus: this.props.onWillFocus,
onDidFocus: this.props.onDidFocus,
}; };
this._handlers = {};
this.panGesture = PanResponder.create({ this.panGesture = PanResponder.create({
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture, onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
@ -330,6 +346,46 @@ var Navigator = React.createClass({
this._emitWillFocus(this.state.presentedIndex); this._emitWillFocus(this.state.presentedIndex);
}, },
request: function(action, arg1, arg2) {
if (this.parentNavigator) {
return this.parentNavigator.request.apply(null, arguments);
}
return this._handleRequest.apply(null, arguments);
},
_handleRequest: function(action, arg1, arg2) {
var childHandler = this._handlers[this.state.presentedIndex];
if (childHandler && childHandler(action, arg1, arg2)) {
return true;
}
switch (action) {
case 'pop':
return this._handlePop();
case 'push':
return this._handlePush(arg1);
default:
invariant(false, 'Unsupported request type ' + action);
return false;
}
},
_handlePop: function() {
if (this.state.presentedIndex === 0) {
return false;
}
this.pop();
return true;
},
_handlePush: function(route) {
this.push(route);
return true;
},
setHandlerForRoute: function(route, handler) {
this._handlers[this.state.routeStack.indexOf(route)] = handler;
},
_configureSpring: function(animationConfig) { _configureSpring: function(animationConfig) {
var config = this.spring.getSpringConfig(); var config = this.spring.getSpringConfig();
config.friction = animationConfig.springFriction; config.friction = animationConfig.springFriction;
@ -345,6 +401,28 @@ var Navigator = React.createClass({
this.spring.addListener(this); this.spring.addListener(this);
this.onSpringUpdate(); this.onSpringUpdate();
this._emitDidFocus(this.state.presentedIndex); this._emitDidFocus(this.state.presentedIndex);
if (this.parentNavigator) {
this.parentNavigator.setHandler(this._handleRequest);
} else {
// There is no navigator in our props or context, so this is the
// top-level navigator. We will handle back button presses here
BackAndroid.addEventListener('hardwareBackPress', this._handleBackPress);
}
},
componentWillUnmount: function() {
if (this.parentNavigator) {
this.parentNavigator.setHandler(null);
} else {
BackAndroid.removeEventListener('hardwareBackPress', this._handleBackPress);
}
},
_handleBackPress: function() {
var didPop = this.request('pop');
if (!didPop) {
BackAndroid.exitApp();
}
}, },
/** /**
@ -791,8 +869,9 @@ var Navigator = React.createClass({
}, },
pop: function() { pop: function() {
if (this.props.navigator && this.state.routeStack.length === 1) { // TODO (t6707686): remove this parentNavigator call after transitioning call sites to `.request('pop')`
return this.props.navigator.pop(); if (this.parentNavigator && this.state.routeStack.length === 1) {
return this.parentNavigator.pop();
} }
this.popN(1); this.popN(1);
}, },
@ -889,7 +968,7 @@ var Navigator = React.createClass({
return this.state.routeStack; return this.state.routeStack;
}, },
_onItemRef: function(itemId, ref) { _handleItemRef: function(itemId, ref) {
this._itemRefs[itemId] = ref; this._itemRefs[itemId] = ref;
var itemIndex = this.state.idStack.indexOf(itemId); var itemIndex = this.state.idStack.indexOf(itemId);
if (itemIndex === -1) { if (itemIndex === -1) {
@ -922,23 +1001,33 @@ var Navigator = React.createClass({
this.state.updatingRangeLength !== 0 && this.state.updatingRangeLength !== 0 &&
i >= this.state.updatingRangeStart && i >= this.state.updatingRangeStart &&
i <= this.state.updatingRangeStart + this.state.updatingRangeLength; i <= this.state.updatingRangeStart + this.state.updatingRangeLength;
var sceneNavigatorContext = {
...this.navigatorContext,
route,
setHandler: (handler) => {
this.navigatorContext.setHandlerForRoute(route, handler);
},
};
var child = this.props.renderScene( var child = this.props.renderScene(
route, route,
this.navigatorActions, sceneNavigatorContext
this._onItemRef.bind(null, this.state.idStack[i])
); );
var initialSceneStyle = var initialSceneStyle =
i === this.state.presentedIndex ? styles.presentNavItem : styles.futureNavItem; i === this.state.presentedIndex ? styles.presentNavItem : styles.futureNavItem;
return ( return (
<StaticContainer key={'nav' + i} shouldUpdate={shouldUpdateChild}> <NavigatorStaticContextContainer
navigatorContext={sceneNavigatorContext}
key={'nav' + i}
shouldUpdate={shouldUpdateChild}>
<View <View
key={this.state.idStack[i]} key={this.state.idStack[i]}
ref={'scene_' + i} ref={'scene_' + i}
style={[initialSceneStyle, this.props.sceneStyle]}> style={[initialSceneStyle, this.props.sceneStyle]}>
{child} {React.cloneElement(child, {
ref: this._handleItemRef.bind(null, this.state.idStack[i]),
})}
</View> </View>
</StaticContainer> </NavigatorStaticContextContainer>
); );
}, },
@ -967,7 +1056,7 @@ var Navigator = React.createClass({
} }
return React.cloneElement(this.props.navigationBar, { return React.cloneElement(this.props.navigationBar, {
ref: (navBar) => { this._navBar = navBar; }, ref: (navBar) => { this._navBar = navBar; },
navigator: this.navigatorActions, navigator: this.navigatorContext,
navState: this.state, navState: this.state,
}); });
}, },

View File

@ -0,0 +1,95 @@
/**
* 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 NavigatorInterceptor
*/
'use strict';
var React = require('React');
var getNavigatorContext = require('getNavigatorContext');
var NavigatorInterceptor = React.createClass({
contextTypes: {
navigator: React.PropTypes.object,
},
componentWillMount: function() {
this.navigator = getNavigatorContext(this);
},
componentDidMount: function() {
this.navigator.setHandler(this._navigatorHandleRequest);
},
childContextTypes: {
navigator: React.PropTypes.object,
},
getChildContext: function() {
return {
navigator: {
...this.navigator,
setHandler: (handler) => {
this._childNavigationHandler = handler;
},
}
};
},
componentWillUnmount: function() {
this.navigator.setHandler(null);
},
_navigatorHandleRequest: function(action, arg1, arg2) {
if (this._interceptorHandle(action, arg1, arg2)) {
return true;
}
if (this._childNavigationHandler && this._childNavigationHandler(action, arg1, arg2)) {
return true;
}
},
_interceptorHandle: function(action, arg1, arg2) {
if (this.props.onRequest && this.props.onRequest(action, arg1, arg2)) {
return true;
}
switch (action) {
case 'pop':
return this.props.onPopRequest && this.props.onPopRequest(action, arg1, arg2);
case 'push':
return this.props.onPushRequest && this.props.onPushRequest(action, arg1, arg2);
default:
return false;
}
},
render: function() {
return this.props.children;
},
});
module.exports = NavigatorInterceptor;

View File

@ -0,0 +1,53 @@
/**
* 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 NavigatorStaticContextContainer
*/
'use strict';
var React = require('React');
var StaticContainer = require('StaticContainer.react');
var PropTypes = React.PropTypes;
var NavigatorStaticContextContainer = React.createClass({
childContextTypes: {
navigator: PropTypes.object,
},
getChildContext: function() {
return {
navigator: this.props.navigatorContext,
};
},
render: function() {
return (
<StaticContainer {...this.props} />
);
},
});
module.exports = NavigatorStaticContextContainer;

View File

@ -0,0 +1,37 @@
/**
* 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 getNavigatorContext
*/
'use strict';
var ReactInstanceMap = require('ReactInstanceMap');
function getNavigatorContext(el) {
// TODO (t6707746): replace with `el.context.navigator` when parent context is supported
return ReactInstanceMap.get(el)._context.navigator;
}
module.exports = getNavigatorContext;