Permalink
Browse files

Add Share module

Summary:
revision of #5476

It has only one method `shareTextContent` and next will be`shareBinaryContent`.

In Android, Promise can't receive a result, because `startActivityForResult` is not working with `Intent.ACTION_SEND`. Maybe we can use `createChooser(Intent target, CharSequence title, IntentSender sender)` which requires API level 22.
Closes #5904

Differential Revision: D3612889

fbshipit-source-id: 0e7aaf34b076a99089cc76bd649e6da067d9a760
  • Loading branch information...
1 parent c21d3a1 commit 3b357328004c1e8283a8dbac931aad959afdb89e @deminoth deminoth committed with Facebook Github Bot 6 Jul 25, 2016
@@ -0,0 +1,124 @@
+/**
+ * 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.
+ *
+ * @flow
+ */
+'use strict';
+
+var React = require('react');
+var ReactNative = require('react-native');
+var {
+ StyleSheet,
+ View,
+ Text,
+ TouchableHighlight,
+ Share,
+} = ReactNative;
+
+exports.framework = 'React';
+exports.title = 'Share';
+exports.description = 'Share data with other Apps.';
+exports.examples = [{
+ title: 'Share Text Content',
+ render() {
+ return <ShareMessageExample />;
+ }
+}];
+
+class ShareMessageExample extends React.Component {
+ _shareMessage: Function;
+ _shareText: Function;
+ _showResult: Function;
+ state: any;
+
+ constructor(props) {
+ super(props);
+
+ this._shareMessage = this._shareMessage.bind(this);
+ this._shareText = this._shareText.bind(this);
+ this._showResult = this._showResult.bind(this);
+
+ this.state = {
+ result: ''
+ };
+ }
+
+ render() {
+ return (
+ <View>
+ <TouchableHighlight style={styles.wrapper}
+ onPress={this._shareMessage}>
+ <View style={styles.button}>
+ <Text>Click to share message</Text>
+ </View>
+ </TouchableHighlight>
+ <TouchableHighlight style={styles.wrapper}
+ onPress={this._shareText}>
+ <View style={styles.button}>
+ <Text>Click to share message, URL and title</Text>
+ </View>
+ </TouchableHighlight>
+ <Text>{this.state.result}</Text>
+ </View>
+ );
+ }
+
+ _shareMessage() {
+ Share.share({
+ message: 'React Native | A framework for building native apps using React'
+ })
+ .then(this._showResult)
+ .catch((error) => this.setState({result: 'error: ' + error.message}));
+ }
+
+ _shareText() {
+ Share.share({
+ message: 'A framework for building native apps using React',
+ url: 'http://facebook.github.io/react-native/',
+ title: 'React Native'
+ }, {
+ dialogTitle: 'Share React Native website',
+ excludedActivityTypes: [
+ 'com.apple.UIKit.activity.PostToTwitter'
+ ],
+ tintColor: 'green'
+ })
+ .then(this._showResult)
+ .catch((error) => this.setState({result: 'error: ' + error.message}));
+ }
+
+ _showResult(result) {
+ if (result.action === Share.sharedAction) {
+ if (result.activityType) {
+ this.setState({result: 'shared with an activityType: ' + result.activityType});
+ } else {
+ this.setState({result: 'shared'});
+ }
+ } else if (result.action === Share.dismissedAction) {
+ this.setState({result: 'dismissed'});
+ }
+ }
+
+}
+
+
+var styles = StyleSheet.create({
+ wrapper: {
+ borderRadius: 5,
+ marginBottom: 5,
+ },
+ button: {
+ backgroundColor: '#eeeeee',
+ padding: 10,
+ },
+});
@@ -182,6 +182,10 @@ const APIExamples = [
module: require('./PointerEventsExample'),
},
{
+ key: 'ShareExample',
+ module: require('./ShareExample'),
+ },
+ {
key: 'TimePickerAndroidExample',
module: require('./TimePickerAndroidExample'),
},
@@ -248,6 +248,10 @@ const APIExamples: Array<UIExplorerExample> = [
module: require('./RCTRootViewIOSExample'),
},
{
+ key: 'ShareExample',
+ module: require('./ShareExample'),
+ },
+ {
key: 'SnapshotExample',
module: require('./SnapshotExample'),
},
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2016-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 Share
+ * @flow
+ */
+'use strict';
+
+const Platform = require('Platform');
+const {
+ ActionSheetManager,
+ ShareModule
+} = require('NativeModules');
+const invariant = require('fbjs/lib/invariant');
+const processColor = require('processColor');
+
+type Content = { title?: string, message: string } | { title?: string, url: string };
+type Options = { dialogTitle?: string, excludeActivityTypes?: Array<string>, tintColor?: string };
+
+class Share {
+
+ /**
+ * Open a dialog to share text content.
+ *
+ * In iOS, Returns a Promise which will be invoked an object containing `action`, `activityType`.
+ * If the user dismissed the dialog, the Promise will still be resolved with action being `Share.dismissedAction`
+ * and all the other keys being undefined.
+ *
+ * In Android, Returns a Promise which always be resolved with action being `Share.sharedAction`.
+ *
+ * ### Content
+ *
+ * - `message` - a message to share
+ * - `title` - title of the message
+ *
+ * #### iOS
+ *
+ * - `url` - an URL to share
+ *
+ * At least one of URL and message is required.
+ *
+ * ### Options
+ *
+ * #### iOS
+ *
+ * - `excludedActivityTypes`
+ * - `tintColor`
+ *
+ * #### Android
+ *
+ * - `dialogTitle`
+ *
+ */
+ static share(content: Content, options: Options = {}): Promise<Object> {
+ invariant(
+ typeof content === 'object' && content !== null,
+ 'Content must a valid object'
+ );
+ invariant(
+ typeof content.url === 'string' || typeof content.message === 'string',
+ 'At least one of URL and message is required'
+ );
+ invariant(
+ typeof options === 'object' && options !== null,
+ 'Options must be a valid object'
+ );
+
+ if (Platform.OS === 'android') {
+ invariant(
+ !content.title || typeof content.title === 'string',
+ 'Invalid title: title should be a string.'
+ );
+ return ShareModule.share(content, options.dialogTitle);
+ } else if (Platform.OS === 'ios') {
+ return new Promise((resolve, reject) => {
+ ActionSheetManager.showShareActionSheetWithOptions(
+ {...content, ...options, tintColor: processColor(options.tintColor)},
+ (error) => reject(error),
+ (success, activityType) => {
+ if (success) {
+ resolve({
+ 'action': 'sharedAction',
+ 'activityType': activityType
+ });
+ } else {
+ resolve({
+ 'action': 'dismissedAction'
+ });
+ }
+ }
+ );
+ });
+ } else {
+ return Promise.reject(new Error('Unsupported platform'));
+ }
+ }
+
+ /**
+ * The content was successfully shared.
+ */
+ static get sharedAction() { return 'sharedAction'; }
+
+ /**
+ * The dialog has been dismissed.
+ * @platform ios
+ */
+ static get dismissedAction() { return 'dismissedAction'; }
+
+}
+
+module.exports = Share;
@@ -101,6 +101,7 @@ const ReactNative = {
get PixelRatio() { return require('PixelRatio'); },
get PushNotificationIOS() { return require('PushNotificationIOS'); },
get Settings() { return require('Settings'); },
+ get Share() { return require('Share'); },
get StatusBarIOS() { return require('StatusBarIOS'); },
get StyleSheet() { return require('StyleSheet'); },
get Systrace() { return require('Systrace'); },
@@ -113,6 +113,7 @@ var ReactNative = {
PixelRatio: require('PixelRatio'),
PushNotificationIOS: require('PushNotificationIOS'),
Settings: require('Settings'),
+ Share: require('Share'),
StatusBarIOS: require('StatusBarIOS'),
StyleSheet: require('StyleSheet'),
Systrace: require('Systrace'),
@@ -14,6 +14,7 @@ deps = [
react_native_target('java/com/facebook/react/common:common'),
react_native_target('java/com/facebook/react/modules/core:core'),
react_native_target('java/com/facebook/react/modules/datepicker:datepicker'),
+ react_native_target('java/com/facebook/react/modules/share:share'),
react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'),
react_native_target('java/com/facebook/react/modules/timepicker:timepicker'),
react_native_target('java/com/facebook/react/touch:touch'),
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2014-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.tests;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Instrumentation.ActivityMonitor;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentFilter.MalformedMimeTypeException;
+import android.support.v4.app.DialogFragment;
+
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.testing.ReactInstanceSpecForTest;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeMap;
+import com.facebook.react.modules.share.ShareModule;
+import com.facebook.react.testing.ReactAppInstrumentationTestCase;
+
+/**
+ * Test case for {@link ShareModule}.
+ */
+public class ShareTestCase extends ReactAppInstrumentationTestCase {
+
+ private static interface ShareTestModule extends JavaScriptModule {
+ public void showShareDialog(WritableMap content, WritableMap options);
+ }
+
+ private static class ShareRecordingModule extends BaseJavaModule {
+
+ private int mOpened = 0;
+ private int mErrors = 0;
+
+ @Override
+ public String getName() {
+ return "ShareRecordingModule";
+ }
+
+ @ReactMethod
+ public void recordOpened() {
+ mOpened++;
+ }
+
+ @ReactMethod
+ public void recordError() {
+ mErrors++;
+ }
+
+ public int getOpened() {
+ return mOpened;
+ }
+
+ public int getErrors() {
+ return mErrors;
+ }
+
+ }
+
+ final ShareRecordingModule mRecordingModule = new ShareRecordingModule();
+
+ @Override
+ protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
+ return super.createReactInstanceSpecForTest()
+ .addNativeModule(mRecordingModule)
+ .addJSModule(ShareTestModule.class);
+ }
+
+ @Override
+ protected String getReactApplicationKeyUnderTest() {
+ return "ShareTestApp";
+ }
+
+ private ShareTestModule getTestModule() {
+ return getReactContext().getCatalystInstance().getJSModule(ShareTestModule.class);
+ }
+
+ public void testShowBasicShareDialog() {
+ final WritableMap content = new WritableNativeMap();
+ content.putString("message", "Hello, ReactNative!");
+ final WritableMap options = new WritableNativeMap();
+
+ IntentFilter intentFilter = new IntentFilter(Intent.ACTION_CHOOSER);
+ intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+ ActivityMonitor monitor = getInstrumentation().addMonitor(intentFilter, null, true);
+
+ getTestModule().showShareDialog(content, options);
+
+ waitForBridgeAndUIIdle();
+ getInstrumentation().waitForIdleSync();
+
+ assertEquals(1, monitor.getHits());
+ assertEquals(1, mRecordingModule.getOpened());
+ assertEquals(0, mRecordingModule.getErrors());
+
+ }
+
+}
Oops, something went wrong.

0 comments on commit 3b35732

Please sign in to comment.