Skip to content

Commit

Permalink
Deep linking (#2148)
Browse files Browse the repository at this point in the history
* Deep linking draft (doesn't work properly yet)

* fix build

* fix errors
  • Loading branch information
aksonov authored Nov 8, 2017
1 parent 29f8fee commit 8485389
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 31 deletions.
6 changes: 5 additions & 1 deletion Example/Example.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Platform, StyleSheet, Text, View } from 'react-native';
import Launch from './components/Launch';
import Register from './components/Register';
import Login from './components/Login';
Expand Down Expand Up @@ -59,10 +59,14 @@ const getSceneStyle = () => ({
shadowRadius: 3,
});

// on Android, the URI prefix typically contains a host in addition to scheme
const prefix = Platform.OS === 'android' ? 'mychat://mychat/' : 'mychat://';

const Example = () => (
<Router
createReducer={reducerCreate}
getSceneStyle={getSceneStyle}
uriPrefix={prefix}
>
<Overlay key="overlay">
<Modal key="modal"
Expand Down
9 changes: 9 additions & 0 deletions Example/ios/Example/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>

@implementation AppDelegate

Expand All @@ -34,4 +35,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
return YES;
}

// Add this above the `@end`:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [RCTLinkingManager application:application openURL:url
sourceApplication:sourceApplication annotation:annotation];
}

@end
12 changes: 11 additions & 1 deletion Example/ios/Example/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSAppTransportSecurity</key>
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
<dict>
<key>NSExceptionDomains</key>
<dict>
Expand All @@ -52,5 +51,16 @@
</dict>
</dict>
</dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>org.rnrf</string>
<key>CFBundleURLSchemes</key>
<array>
<string>mychat</string>
</array>
</dict>
</array>
</dict>
</plist>
46 changes: 33 additions & 13 deletions dist/Router.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Object.defineProperty(exports,"__esModule",{value:true});var _extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};var _createClass=function(){function defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if("value"in descriptor)descriptor.writable=true;Object.defineProperty(target,descriptor.key,descriptor);}}return function(Constructor,protoProps,staticProps){if(protoProps)defineProperties(Constructor.prototype,protoProps);if(staticProps)defineProperties(Constructor,staticProps);return Constructor;};}();var _class,_class2,_temp2,_jsxFileName='src/Router.js';var _react=require('react');var _react2=_interopRequireDefault(_react);
Object.defineProperty(exports,"__esModule",{value:true});var _extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};var _createClass=function(){function defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if("value"in descriptor)descriptor.writable=true;Object.defineProperty(target,descriptor.key,descriptor);}}return function(Constructor,protoProps,staticProps){if(protoProps)defineProperties(Constructor.prototype,protoProps);if(staticProps)defineProperties(Constructor,staticProps);return Constructor;};}();var _class,_jsxFileName='src/Router.js';var _react=require('react');var _react2=_interopRequireDefault(_react);
var _native=require('mobx-react/native');
var _reactNative=require('react-native');
var _navigationStore=require('./navigationStore');var _navigationStore2=_interopRequireDefault(_navigationStore);
var _propTypes=require('prop-types');var _propTypes2=_interopRequireDefault(_propTypes);
var _reactNavigation=require('react-navigation');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}function _objectWithoutProperties(obj,keys){var target={};for(var i in obj){if(keys.indexOf(i)>=0)continue;if(!Object.prototype.hasOwnProperty.call(obj,i))continue;target[i]=obj[i];}return target;}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self,call){if(!self){throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call&&(typeof call==="object"||typeof call==="function")?call:self;}function _inherits(subClass,superClass){if(typeof superClass!=="function"&&superClass!==null){throw new TypeError("Super expression must either be null or a function, not "+typeof superClass);}subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,enumerable:false,writable:true,configurable:true}});if(superClass)Object.setPrototypeOf?Object.setPrototypeOf(subClass,superClass):subClass.__proto__=superClass;}var


App=(0,_native.observer)(_class=(_temp2=_class2=function(_React$Component){_inherits(App,_React$Component);function App(){var _ref;var _temp,_this,_ret;_classCallCheck(this,App);for(var _len=arguments.length,args=Array(_len),_key=0;_key<_len;_key++){args[_key]=arguments[_key];}return _ret=(_temp=(_this=_possibleConstructorReturn(this,(_ref=App.__proto__||Object.getPrototypeOf(App)).call.apply(_ref,[this].concat(args))),_this),_this.
App=(0,_native.observer)(_class=function(_React$Component){_inherits(App,_React$Component);function App(){var _ref;var _temp,_this,_ret;_classCallCheck(this,App);for(var _len=arguments.length,args=Array(_len),_key=0;_key<_len;_key++){args[_key]=arguments[_key];}return _ret=(_temp=(_this=_possibleConstructorReturn(this,(_ref=App.__proto__||Object.getPrototypeOf(App)).call.apply(_ref,[this].concat(args))),_this),_this.



Expand All @@ -16,24 +16,43 @@ App=(0,_native.observer)(_class=(_temp2=_class2=function(_React$Component){_inhe



onBackPress=function(){
_navigationStore2.default.pop();
return _navigationStore2.default.currentScene!==_navigationStore2.default.prevScene;
},_this.






onBackPress=function(){
_navigationStore2.default.pop();
return _navigationStore2.default.currentScene!==_navigationStore2.default.prevScene;
},_temp),_possibleConstructorReturn(_this,_ret);}_createClass(App,[{key:'componentDidMount',value:function componentDidMount(){_reactNative.BackHandler.addEventListener('hardwareBackPress',this.props.backAndroidHandler||this.onBackPress);}},{key:'componentWillUnmount',value:function componentWillUnmount(){_reactNative.BackHandler.removeEventListener('hardwareBackPress',this.props.backAndroidHandler||this.onBackPress);}},{key:'render',value:function render()







_handleOpenURL=function(url){
var parsedUrl=_this._urlToPathAndParams(url);
if(parsedUrl){var
path=parsedUrl.path,params=parsedUrl.params;
var action=_navigationStore2.default.router.getActionForPathAndParams(path,params);
console.log('HANDLE URL:',url,action,path,params);
if(action){
_navigationStore2.default.dispatch(action);
}
}
},_temp),_possibleConstructorReturn(_this,_ret);}_createClass(App,[{key:'componentDidMount',value:function componentDidMount(){var _this2=this;_reactNative.BackHandler.addEventListener('hardwareBackPress',this.props.backAndroidHandler||this.onBackPress);_reactNative.Linking.addEventListener('url',function(_ref2){var url=_ref2.url;_this2._handleOpenURL(url);});}},{key:'componentWillUnmount',value:function componentWillUnmount(){_reactNative.BackHandler.removeEventListener('hardwareBackPress',this.props.backAndroidHandler||this.onBackPress);}},{key:'_urlToPathAndParams',value:function _urlToPathAndParams(url){var params={};var delimiter=this.props.uriPrefix||'://';var path=url.split(delimiter)[1];if(!path){path=url;}return{path:path,params:params};}},{key:'render',value:function render()
{
var AppNavigator=this.props.navigator;
return(
_react2.default.createElement(AppNavigator,{navigation:(0,_reactNavigation.addNavigationHelpers)({dispatch:_navigationStore2.default.dispatch,state:_navigationStore2.default.state}),__source:{fileName:_jsxFileName,lineNumber:31}}));
_react2.default.createElement(AppNavigator,{navigation:(0,_reactNavigation.addNavigationHelpers)({dispatch:_navigationStore2.default.dispatch,state:_navigationStore2.default.state}),__source:{fileName:_jsxFileName,lineNumber:50}}));

}}]);return App;}(_react2.default.Component),_class2.propTypes={navigator:_propTypes2.default.func,backAndroidHandler:_propTypes2.default.func},_temp2))||_class;
}}]);return App;}(_react2.default.Component))||_class;


var Router=function Router(_ref2){var createReducer=_ref2.createReducer,sceneStyle=_ref2.sceneStyle,scenes=_ref2.scenes,navigator=_ref2.navigator,getSceneStyle=_ref2.getSceneStyle,children=_ref2.children,state=_ref2.state,dispatch=_ref2.dispatch,_ref2$wrapBy=_ref2.wrapBy,wrapBy=_ref2$wrapBy===undefined?function(props){return props;}:_ref2$wrapBy,props=_objectWithoutProperties(_ref2,['createReducer','sceneStyle','scenes','navigator','getSceneStyle','children','state','dispatch','wrapBy']);
var Router=function Router(_ref3){var createReducer=_ref3.createReducer,uriPrefix=_ref3.uriPrefix,sceneStyle=_ref3.sceneStyle,scenes=_ref3.scenes,navigator=_ref3.navigator,getSceneStyle=_ref3.getSceneStyle,children=_ref3.children,state=_ref3.state,dispatch=_ref3.dispatch,_ref3$wrapBy=_ref3.wrapBy,wrapBy=_ref3$wrapBy===undefined?function(props){return props;}:_ref3$wrapBy,props=_objectWithoutProperties(_ref3,['createReducer','uriPrefix','sceneStyle','scenes','navigator','getSceneStyle','children','state','dispatch','wrapBy']);
var data=_extends({},props);
if(getSceneStyle){
data.cardStyle=getSceneStyle(props);
Expand All @@ -47,9 +66,9 @@ if(dispatch&&state){

_navigationStore2.default.setState(state);
_navigationStore2.default.dispatch=dispatch;
return _react2.default.createElement(AppNavigator,{navigation:(0,_reactNavigation.addNavigationHelpers)({dispatch:dispatch,state:state}),__source:{fileName:_jsxFileName,lineNumber:50}});
return _react2.default.createElement(AppNavigator,{navigation:(0,_reactNavigation.addNavigationHelpers)({dispatch:dispatch,state:state}),uriPrefix:uriPrefix,__source:{fileName:_jsxFileName,lineNumber:69}});
}
return _react2.default.createElement(App,_extends({},props,{navigator:AppNavigator,__source:{fileName:_jsxFileName,lineNumber:52}}));
return _react2.default.createElement(App,_extends({},props,{navigator:AppNavigator,uriPrefix:uriPrefix,__source:{fileName:_jsxFileName,lineNumber:71}}));
};
Router.propTypes={
createReducer:_propTypes2.default.func,
Expand All @@ -59,8 +78,9 @@ scenes:_propTypes2.default.func,
navigator:_propTypes2.default.func,
wrapBy:_propTypes2.default.func,
getSceneStyle:_propTypes2.default.func,
sceneStyle:_reactNative.ViewPropTypes.style,
children:_propTypes2.default.element};exports.default=
sceneStyle:ViewPropTypes.style,
children:_propTypes2.default.element,
uriPrefix:_propTypes2.default.string};exports.default=


Router;
8 changes: 5 additions & 3 deletions dist/navigationStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ _child){
var key=_child.key||'key'+counter++;
var init=key===children[0].key;
(0,_Util.assert)(reservedKeys.indexOf(key)===-1,'Scene name cannot be reserved word: '+_child.key);var _child$props=
_child.props,component=_child$props.component,_child$props$type=_child$props.type,type=_child$props$type===undefined?tabs||drawer?'jump':'push':_child$props$type,onEnter=_child$props.onEnter,onExit=_child$props.onExit,on=_child$props.on,failure=_child$props.failure,success=_child$props.success,wrap=_child$props.wrap,props=_objectWithoutProperties(_child$props,['component','type','onEnter','onExit','on','failure','success','wrap']);
_child.props,component=_child$props.component,_child$props$type=_child$props.type,type=_child$props$type===undefined?tabs||drawer?'jump':'push':_child$props$type,onEnter=_child$props.onEnter,path=_child$props.path,onExit=_child$props.onExit,on=_child$props.on,failure=_child$props.failure,success=_child$props.success,wrap=_child$props.wrap,props=_objectWithoutProperties(_child$props,['component','type','onEnter','path','onExit','on','failure','success','wrap']);
if(!_this2.states[key]){
_this2.states[key]={};
}
Expand All @@ -461,6 +461,7 @@ failure:function(args){console.log('Transition to state='+failure);_this2[failur
}

var screen={
path:path,
screen:createWrapper(component,wrapBy,_this2)||_this2.processScene(_child,commonProps,clones)||lightbox&&_reactNative.View,
navigationOptions:createNavigationOptions(_extends({},commonProps,getProperties(component),_child.props,{init:init,component:component}))};

Expand All @@ -472,6 +473,7 @@ wrapNavBar=false;
}
if(component&&wrapNavBar){
res[key]={
path:path,
screen:_this2.processScene({key:key,props:{children:{key:'_'+key,props:_extends({},_child.props,{wrap:false})}}},commonProps,clones,wrapBy),
navigationOptions:createNavigationOptions(_extends({},commonProps,_child.props))};

Expand Down Expand Up @@ -508,8 +510,8 @@ if(lightbox){
return(0,_LightboxNavigator2.default)(res,_extends({mode:mode,initialRouteParams:initialRouteParams,initialRouteName:initialRouteName},commonProps,{navigationOptions:createNavigationOptions(commonProps)}));
}else if(tabs){
if(!tabBarComponent){
tabBarComponent=tabBarPosition==='top'?function(props){return _react2.default.createElement(_reactNavigation.TabBarTop,_extends({},props,commonProps,{__source:{fileName:_jsxFileName,lineNumber:511}}));}:
function(props){return _react2.default.createElement(_reactNavigation.TabBarBottom,_extends({},props,commonProps,{__source:{fileName:_jsxFileName,lineNumber:512}}));};
tabBarComponent=tabBarPosition==='top'?function(props){return _react2.default.createElement(_reactNavigation.TabBarTop,_extends({},props,commonProps,{__source:{fileName:_jsxFileName,lineNumber:513}}));}:
function(props){return _react2.default.createElement(_reactNavigation.TabBarBottom,_extends({},props,commonProps,{__source:{fileName:_jsxFileName,lineNumber:514}}));};
}
if(!tabBarPosition){
tabBarPosition=_reactNative.Platform.OS==='android'?'top':'bottom';
Expand Down
48 changes: 37 additions & 11 deletions src/Router.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,49 @@
import React from 'react';
import { observer } from 'mobx-react/native';
import { ViewPropTypes, BackHandler } from 'react-native';
import { BackHandler, Linking, ViewPropTypes } from 'react-native';
import navigationStore from './navigationStore';
import PropTypes from 'prop-types';
import { addNavigationHelpers } from 'react-navigation';

@observer
class App extends React.Component {
static propTypes = {
navigator: PropTypes.func,
backAndroidHandler: PropTypes.func,
};

componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.props.backAndroidHandler || this.onBackPress);
Linking.addEventListener('url', ({ url }: { url: string }) => {
this._handleOpenURL(url);
});
}

componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.props.backAndroidHandler || this.onBackPress);
}

onBackPress = () => {
navigationStore.pop();
return navigationStore.currentScene !== navigationStore.prevScene;
};
_urlToPathAndParams(url: string) {
const params = {};
const delimiter = this.props.uriPrefix || '://';
let path = url.split(delimiter)[1];
if (!path) {
path = url;
}
return {
path,
params,
};
}

_handleOpenURL = (url: string) => {
const parsedUrl = this._urlToPathAndParams(url);
if (parsedUrl) {
const { path, params } = parsedUrl;
const action = navigationStore.router.getActionForPathAndParams(path, params);
console.log('HANDLE URL:', url, action, path, params);
if (action) {
navigationStore.dispatch(action);
}
}
};
render() {
const AppNavigator = this.props.navigator;
return (
Expand All @@ -33,7 +52,13 @@ class App extends React.Component {
}
}

const Router = ({ createReducer, sceneStyle, scenes, navigator, getSceneStyle, children, state, dispatch, wrapBy = props => props, ...props }) => {
App.propTypes = {
navigator: PropTypes.func,
backAndroidHandler: PropTypes.func,
uriPrefix: PropTypes.string,
};

const Router = ({ createReducer, uriPrefix, sceneStyle, scenes, navigator, getSceneStyle, children, state, dispatch, wrapBy = props => props, ...props }) => {
const data = { ...props };
if (getSceneStyle) {
data.cardStyle = getSceneStyle(props);
Expand All @@ -47,9 +72,9 @@ const Router = ({ createReducer, sceneStyle, scenes, navigator, getSceneStyle, c
// set external state and dispatch
navigationStore.setState(state);
navigationStore.dispatch = dispatch;
return <AppNavigator navigation={addNavigationHelpers({ dispatch, state })} />;
return <AppNavigator navigation={addNavigationHelpers({ dispatch, state })} uriPrefix={uriPrefix} />;
}
return <App {...props} navigator={AppNavigator} />;
return <App {...props} navigator={AppNavigator} uriPrefix={uriPrefix} />;
};
Router.propTypes = {
createReducer: PropTypes.func,
Expand All @@ -61,6 +86,7 @@ Router.propTypes = {
getSceneStyle: PropTypes.func,
sceneStyle: ViewPropTypes.style,
children: PropTypes.element,
uriPrefix: PropTypes.string,
};

export default Router;
6 changes: 4 additions & 2 deletions src/navigationStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ class NavigationStore {
const key = child.key || `key${counter++}`;
const init = key === children[0].key;
assert(reservedKeys.indexOf(key) === -1, `Scene name cannot be reserved word: ${child.key}`);
const { component, type = tabs || drawer ? 'jump' : 'push', onEnter, onExit, on, failure, success, wrap, ...props } = child.props;
const { component, type = tabs || drawer ? 'jump' : 'push', onEnter, path, onExit, on, failure, success, wrap, ...props } = child.props;
if (!this.states[key]) {
this.states[key] = {};
}
Expand All @@ -459,8 +459,9 @@ class NavigationStore {
this.states[key].failure = failure instanceof Function ?
failure : args => { console.log(`Transition to state=${failure}`); this[failure](args); };
}
// console.log(`KEY ${key} DRAWER ${drawer} TABS ${tabs} WRAP ${wrap}`, JSON.stringify(commonProps));
// console.log(`KEY ${key} PATH ${path} DRAWER ${drawer} TABS ${tabs} WRAP ${wrap}`, JSON.stringify(commonProps));
const screen = {
path,
screen: createWrapper(component, wrapBy, this) || this.processScene(child, commonProps, clones) || (lightbox && View),
navigationOptions: createNavigationOptions({ ...commonProps, ...getProperties(component), ...child.props, init, component }),
};
Expand All @@ -472,6 +473,7 @@ class NavigationStore {
}
if (component && wrapNavBar) {
res[key] = {
path,
screen: this.processScene({ key, props: { children: { key: `_${key}`, props: { ...child.props, wrap: false } } } }, commonProps, clones, wrapBy),
navigationOptions: createNavigationOptions({ ...commonProps, ...child.props }),
};
Expand Down

0 comments on commit 8485389

Please sign in to comment.