Skip to content

Commit

Permalink
Add Share module
Browse files Browse the repository at this point in the history
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
deminoth authored and Facebook Github Bot 6 committed Jul 25, 2016
1 parent c21d3a1 commit 3b35732
Show file tree
Hide file tree
Showing 16 changed files with 700 additions and 0 deletions.
124 changes: 124 additions & 0 deletions Examples/UIExplorer/js/ShareExample.js
Original file line number Diff line number Diff line change
@@ -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,
},
});
4 changes: 4 additions & 0 deletions Examples/UIExplorer/js/UIExplorerList.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ const APIExamples = [
key: 'PointerEventsExample',
module: require('./PointerEventsExample'),
},
{
key: 'ShareExample',
module: require('./ShareExample'),
},
{
key: 'TimePickerAndroidExample',
module: require('./TimePickerAndroidExample'),
Expand Down
4 changes: 4 additions & 0 deletions Examples/UIExplorer/js/UIExplorerList.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ const APIExamples: Array<UIExplorerExample> = [
key: 'RCTRootViewIOSExample',
module: require('./RCTRootViewIOSExample'),
},
{
key: 'ShareExample',
module: require('./ShareExample'),
},
{
key: 'SnapshotExample',
module: require('./SnapshotExample'),
Expand Down
116 changes: 116 additions & 0 deletions Libraries/Share/Share.js
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions Libraries/react-native/react-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'); },
Expand Down
1 change: 1 addition & 0 deletions Libraries/react-native/react-native.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
@@ -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());

}

}
Loading

0 comments on commit 3b35732

Please sign in to comment.