diff --git a/Examples/UIExplorer/IntentAndroidExample.android.js b/Examples/UIExplorer/IntentAndroidExample.android.js new file mode 100644 index 000000000..e655bd965 --- /dev/null +++ b/Examples/UIExplorer/IntentAndroidExample.android.js @@ -0,0 +1,90 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +'use strict'; + +var React = require('react-native'); +var { + IntentAndroid, + StyleSheet, + Text, + TouchableNativeFeedback, + View, +} = React; +var UIExplorerBlock = require('./UIExplorerBlock'); + +var OpenURLButton = React.createClass({ + + propTypes: { + url: React.PropTypes.string, + }, + + handleClick: function() { + IntentAndroid.canOpenURL(this.props.url, (supported) => { + if (supported) { + IntentAndroid.openURL(this.props.url); + } else { + console.log('Don\'t know how to open URI: ' + this.props.url); + } + }); + }, + + render: function() { + return ( + + + Open {this.props.url} + + + ); + } +}); + +var IntentAndroidExample = React.createClass({ + + statics: { + title: 'IntentAndroid', + description: 'Shows how to use Android Intents to open URLs.', + }, + + render: function() { + return ( + + + + + + + ); + }, +}); + +var styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'white', + padding: 10, + paddingTop: 30, + }, + button: { + padding: 10, + backgroundColor: '#3B5998', + marginBottom: 10, + }, + text: { + color: 'white', + }, +}); + +module.exports = IntentAndroidExample; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index d04c80c6e..7024779a4 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -38,6 +38,7 @@ var COMPONENTS = [ var APIS = [ require('./AccessibilityAndroidExample.android'), require('./BorderExample'), + require('./IntentAndroidExample.android'), require('./LayoutEventsExample'), require('./LayoutExample'), require('./PanResponderExample'), diff --git a/Libraries/Components/Intent/IntentAndroid.android.js b/Libraries/Components/Intent/IntentAndroid.android.js new file mode 100644 index 000000000..75c620ee9 --- /dev/null +++ b/Libraries/Components/Intent/IntentAndroid.android.js @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule IntentAndroid + */ +'use strict'; + +var IntentAndroidModule = require('NativeModules').IntentAndroid; +var invariant = require('invariant'); + +/** + * `IntentAndroid` gives you a general interface to handle external links. + * + * #### Opening external links + * + * To start the corresponding activity for a link (web URL, email, contact etc.), call + * + * ``` + * IntentAndroid.openURL(url) + * ``` + * + * If you want to check if any installed app can handle a given URL beforehand you can call + * ``` + * IntentAndroid.canOpenURL(url, (supported) => { + * if (!supported) { + * console.log('Can\'t handle url: ' + url); + * } else { + * IntentAndroid.openURL(url); + * } + * }); + * ``` + */ +class IntentAndroid { + + /** + * Starts a corresponding external activity for the given URL. + * + * For example, if the URL is "https://www.facebook.com", the system browser will be opened, + * or the "choose application" dialog will be shown. + * + * You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact, + * or any other URL that can be opened with {@code Intent.ACTION_VIEW}. + * + * NOTE: This method will fail if the system doesn't know how to open the specified URL. + * If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first. + * + * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! + */ + static openURL(url: string) { + this._validateURL(url); + IntentAndroidModule.openURL(url); + } + + /** + * Determine whether or not an installed app can handle a given URL. + * + * You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact, + * or any other URL that can be opened with {@code Intent.ACTION_VIEW}. + * + * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! + * + * @param URL the URL to open + */ + static canOpenURL(url: string, callback: Function) { + this._validateURL(url); + invariant( + typeof callback === 'function', + 'A valid callback function is required' + ); + IntentAndroidModule.canOpenURL(url, callback); + } + + static _validateURL(url: string) { + invariant( + typeof url === 'string', + 'Invalid URL: should be a string. Was: ' + url + ); + invariant( + url, + 'Invalid URL: cannot be empty' + ); + } +} + +module.exports = IntentAndroid; diff --git a/Libraries/Components/Intent/IntentAndroid.ios.js b/Libraries/Components/Intent/IntentAndroid.ios.js new file mode 100644 index 000000000..7ae19934d --- /dev/null +++ b/Libraries/Components/Intent/IntentAndroid.ios.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule IntentAndroid + */ +'use strict'; + +module.exports = { + openURI: function(url) { + console.error('IntentAndroid is not supported on iOS'); + }, +}; diff --git a/Libraries/LinkingIOS/LinkingIOS.js b/Libraries/LinkingIOS/LinkingIOS.js index 917b04762..0ee8367df 100644 --- a/Libraries/LinkingIOS/LinkingIOS.js +++ b/Libraries/LinkingIOS/LinkingIOS.js @@ -23,7 +23,7 @@ var _initialURL = RCTLinkingManager && var DEVICE_NOTIF_EVENT = 'openURL'; /** - * `LinkingIOS` gives you a general interface to interact with both, incoming + * `LinkingIOS` gives you a general interface to interact with both incoming * and outgoing app links. * * ### Basic Usage @@ -65,13 +65,13 @@ var DEVICE_NOTIF_EVENT = 'openURL'; * * #### Triggering App links * - * To trigger an app link (browser, email or custom schemas) you call + * To trigger an app link (browser, email or custom schemas), call * * ``` * LinkingIOS.openURL(url) * ``` * - * If you want to check if any installed app can handle a given url beforehand you can call + * If you want to check if any installed app can handle a given URL beforehand you can call * ``` * LinkingIOS.canOpenURL(url, (supported) => { * if (!supported) { @@ -127,7 +127,7 @@ class LinkingIOS { } /** - * Determine whether or not an installed app can handle a given `url` + * Determine whether or not an installed app can handle a given URL. * The callback function will be called with `bool supported` as the only argument * * NOTE: As of iOS 9, your app needs to provide a `LSApplicationQueriesSchemes` key diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 0cc6afe3a..206b5f785 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -66,6 +66,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { Dimensions: require('Dimensions'), Easing: require('Easing'), ImagePickerIOS: require('ImagePickerIOS'), + IntentAndroid: require('IntentAndroid'), InteractionManager: require('InteractionManager'), LayoutAnimation: require('LayoutAnimation'), LinkingIOS: require('LinkingIOS'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java new file mode 100644 index 000000000..e8e632224 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.modules.intent; + +import android.content.Intent; +import android.net.Uri; + +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; + +/** + * Intent module. Launch other activities or open URLs. + */ +public class IntentModule extends ReactContextBaseJavaModule { + + public IntentModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return "IntentAndroid"; + } + + /** + * Starts a corresponding external activity for the given URL. + * + * For example, if the URL is "https://www.facebook.com", the system browser will be opened, + * or the "choose application" dialog will be shown. + * + * @param URL the URL to open + */ + @ReactMethod + public void openURL(String url) { + if (url == null || url.isEmpty()) { + throw new JSApplicationIllegalArgumentException("Invalid URL: " + url); + } + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + // We need Intent.FLAG_ACTIVITY_NEW_TASK since getReactApplicationContext() returns + // the ApplicationContext instead of the Activity context. + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getReactApplicationContext().startActivity(intent); + } catch (Exception e) { + throw new JSApplicationIllegalArgumentException( + "Could not open URL '" + url + "': " + e.getMessage()); + } + } + + /** + * Determine whether or not an installed app can handle a given URL. + * + * @param URL the URL to open + * @param promise a promise that is always resolved with a boolean argument + */ + @ReactMethod + public void canOpenURL(String url, Callback callback) { + if (url == null || url.isEmpty()) { + throw new JSApplicationIllegalArgumentException("Invalid URL: " + url); + } + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + // We need Intent.FLAG_ACTIVITY_NEW_TASK since getReactApplicationContext() returns + // the ApplicationContext instead of the Activity context. + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + boolean canOpen = + intent.resolveActivity(this.getReactApplicationContext().getPackageManager()) != null; + callback.invoke(canOpen); + } catch (Exception e) { + throw new JSApplicationIllegalArgumentException( + "Could not check if URL '" + url + "' can be opened: " + e.getMessage()); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index fb424ffcd..4d6dba8e5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -18,6 +18,7 @@ import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.modules.fresco.FrescoModule; +import com.facebook.react.modules.intent.IntentModule; import com.facebook.react.modules.network.NetworkingModule; import com.facebook.react.modules.storage.AsyncStorageModule; import com.facebook.react.modules.toast.ToastModule; @@ -47,6 +48,7 @@ public class MainReactPackage implements ReactPackage { return Arrays.asList( new AsyncStorageModule(reactContext), new FrescoModule(reactContext), + new IntentModule(reactContext), new NetworkingModule(reactContext), new WebSocketModule(reactContext), new ToastModule(reactContext));