diff --git a/Libraries/Linking/Linking.js b/Libraries/Linking/Linking.js index 5800a462a53..66dde553758 100644 --- a/Libraries/Linking/Linking.js +++ b/Libraries/Linking/Linking.js @@ -81,6 +81,20 @@ class Linking extends NativeEventEmitter { return LinkingManager.getInitialURL(); } + /* + * Launch an Android intent with extras (optional) + * + * @platform android + * + * See https://facebook.github.io/react-native/docs/linking.html#sendintent + */ + sendIntent( + action: String, + extras?: [{key: string, value: string | number | boolean}], + ) { + return LinkingManager.sendIntent(action, extras); + } + _validateURL(url: string) { invariant( typeof url === 'string', diff --git a/RNTester/js/LinkingExample.js b/RNTester/js/LinkingExample.js index 318341fbe6d..def07bdb523 100644 --- a/RNTester/js/LinkingExample.js +++ b/RNTester/js/LinkingExample.js @@ -12,9 +12,11 @@ const React = require('react'); const { Linking, + Platform, StyleSheet, Text, TouchableOpacity, + ToastAndroid, View, } = require('react-native'); @@ -46,20 +48,56 @@ class OpenURLButton extends React.Component { } } +class SendIntentButton extends React.Component { + handleIntent = async () => { + try { + await Linking.sendIntent(this.props.action, this.props.extras); + } catch (e) { + ToastAndroid.show(e.message, ToastAndroid.LONG); + } + }; + + render() { + return ( + + + {this.props.action} + + + ); + } +} + class IntentAndroidExample extends React.Component { static title = 'Linking'; static description = 'Shows how to use Linking to open URLs.'; render() { return ( - - - - - - - - + + + + + + + + + + {Platform.OS === 'android' && ( + + + + Next one will crash if Facebook app is not installed. + + + + )} + ); } } @@ -70,9 +108,15 @@ const styles = StyleSheet.create({ backgroundColor: '#3B5998', marginBottom: 10, }, + buttonIntent: { + backgroundColor: '#009688', + }, text: { color: 'white', }, + textSeparator: { + paddingBottom: 8, + }, }); module.exports = IntentAndroidExample; 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 index 0549cd49466..ed0b54a4070 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java @@ -10,6 +10,7 @@ package com.facebook.react.modules.intent; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; @@ -17,8 +18,13 @@ import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableType; import com.facebook.react.module.annotations.ReactModule; +import javax.annotation.Nullable; + /** * Intent module. Launch other activities or open URLs. */ @@ -133,4 +139,67 @@ public class IntentModule extends ReactContextBaseJavaModule { "Could not check if URL '" + url + "' can be opened: " + e.getMessage())); } } + + /** + * Allows to send intents on Android + * + * For example, you can open the Notification Category screen for a specific application + * passing action = 'android.settings.CHANNEL_NOTIFICATION_SETTINGS' + * and extras = [ + * { 'android.provider.extra.APP_PACKAGE': 'your.package.name.here' }, + * { 'android.provider.extra.CHANNEL_ID': 'your.channel.id.here } + * ] + * + * @param action The general action to be performed + * @param extras An array of extras [{ String, String | Number | Boolean }] + */ + @ReactMethod + public void sendIntent(String action, @Nullable ReadableArray extras, Promise promise) { + if (action == null || action.isEmpty()) { + promise.reject(new JSApplicationIllegalArgumentException("Invalid Action: " + action + ".")); + return; + } + + Intent intent = new Intent(action); + + PackageManager packageManager = getReactApplicationContext().getPackageManager(); + if (intent.resolveActivity(packageManager) == null) { + promise.reject(new JSApplicationIllegalArgumentException("Could not launch Intent with action " + action + ".")); + return; + } + + if (extras != null) { + for (int i = 0; i < extras.size(); i++) { + ReadableMap map = extras.getMap(i); + String name = map.keySetIterator().nextKey(); + ReadableType type = map.getType(name); + + switch (type) { + case String: { + intent.putExtra(name, map.getString(name)); + break; + } + case Number: { + // We cannot know from JS if is an Integer or Double + // See: https://github.com/facebook/react-native/issues/4141 + // We might need to find a workaround if this is really an issue + Double number = map.getDouble(name); + intent.putExtra(name, number); + break; + } + case Boolean: { + intent.putExtra(name, map.getBoolean(name)); + break; + } + default: { + promise.reject(new JSApplicationIllegalArgumentException( + "Extra type for " + name + " not supported.")); + return; + } + } + } + } + + getReactApplicationContext().startActivity(intent); + } } diff --git a/jest/setup.js b/jest/setup.js index f58eebe9965..1cb89755cc9 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -171,6 +171,7 @@ const mockNativeModules = { addEventListener: jest.fn(), getInitialURL: jest.fn(() => Promise.resolve()), removeEventListener: jest.fn(), + sendIntent: jest.fn(), }, LocationObserver: { getCurrentPosition: jest.fn(),