Skip to content

Commit 3b35732

Browse files
deminothFacebook Github Bot 6
authored andcommitted
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
1 parent c21d3a1 commit 3b35732

File tree

16 files changed

+700
-0
lines changed

16 files changed

+700
-0
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* The examples provided by Facebook are for non-commercial testing and
3+
* evaluation purposes only.
4+
*
5+
* Facebook reserves all rights not expressly granted.
6+
*
7+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
8+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9+
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
10+
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
11+
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
12+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13+
*
14+
* @flow
15+
*/
16+
'use strict';
17+
18+
var React = require('react');
19+
var ReactNative = require('react-native');
20+
var {
21+
StyleSheet,
22+
View,
23+
Text,
24+
TouchableHighlight,
25+
Share,
26+
} = ReactNative;
27+
28+
exports.framework = 'React';
29+
exports.title = 'Share';
30+
exports.description = 'Share data with other Apps.';
31+
exports.examples = [{
32+
title: 'Share Text Content',
33+
render() {
34+
return <ShareMessageExample />;
35+
}
36+
}];
37+
38+
class ShareMessageExample extends React.Component {
39+
_shareMessage: Function;
40+
_shareText: Function;
41+
_showResult: Function;
42+
state: any;
43+
44+
constructor(props) {
45+
super(props);
46+
47+
this._shareMessage = this._shareMessage.bind(this);
48+
this._shareText = this._shareText.bind(this);
49+
this._showResult = this._showResult.bind(this);
50+
51+
this.state = {
52+
result: ''
53+
};
54+
}
55+
56+
render() {
57+
return (
58+
<View>
59+
<TouchableHighlight style={styles.wrapper}
60+
onPress={this._shareMessage}>
61+
<View style={styles.button}>
62+
<Text>Click to share message</Text>
63+
</View>
64+
</TouchableHighlight>
65+
<TouchableHighlight style={styles.wrapper}
66+
onPress={this._shareText}>
67+
<View style={styles.button}>
68+
<Text>Click to share message, URL and title</Text>
69+
</View>
70+
</TouchableHighlight>
71+
<Text>{this.state.result}</Text>
72+
</View>
73+
);
74+
}
75+
76+
_shareMessage() {
77+
Share.share({
78+
message: 'React Native | A framework for building native apps using React'
79+
})
80+
.then(this._showResult)
81+
.catch((error) => this.setState({result: 'error: ' + error.message}));
82+
}
83+
84+
_shareText() {
85+
Share.share({
86+
message: 'A framework for building native apps using React',
87+
url: 'http://facebook.github.io/react-native/',
88+
title: 'React Native'
89+
}, {
90+
dialogTitle: 'Share React Native website',
91+
excludedActivityTypes: [
92+
'com.apple.UIKit.activity.PostToTwitter'
93+
],
94+
tintColor: 'green'
95+
})
96+
.then(this._showResult)
97+
.catch((error) => this.setState({result: 'error: ' + error.message}));
98+
}
99+
100+
_showResult(result) {
101+
if (result.action === Share.sharedAction) {
102+
if (result.activityType) {
103+
this.setState({result: 'shared with an activityType: ' + result.activityType});
104+
} else {
105+
this.setState({result: 'shared'});
106+
}
107+
} else if (result.action === Share.dismissedAction) {
108+
this.setState({result: 'dismissed'});
109+
}
110+
}
111+
112+
}
113+
114+
115+
var styles = StyleSheet.create({
116+
wrapper: {
117+
borderRadius: 5,
118+
marginBottom: 5,
119+
},
120+
button: {
121+
backgroundColor: '#eeeeee',
122+
padding: 10,
123+
},
124+
});

Examples/UIExplorer/js/UIExplorerList.android.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ const APIExamples = [
181181
key: 'PointerEventsExample',
182182
module: require('./PointerEventsExample'),
183183
},
184+
{
185+
key: 'ShareExample',
186+
module: require('./ShareExample'),
187+
},
184188
{
185189
key: 'TimePickerAndroidExample',
186190
module: require('./TimePickerAndroidExample'),

Examples/UIExplorer/js/UIExplorerList.ios.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ const APIExamples: Array<UIExplorerExample> = [
247247
key: 'RCTRootViewIOSExample',
248248
module: require('./RCTRootViewIOSExample'),
249249
},
250+
{
251+
key: 'ShareExample',
252+
module: require('./ShareExample'),
253+
},
250254
{
251255
key: 'SnapshotExample',
252256
module: require('./SnapshotExample'),

Libraries/Share/Share.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* Copyright (c) 2016-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule Share
10+
* @flow
11+
*/
12+
'use strict';
13+
14+
const Platform = require('Platform');
15+
const {
16+
ActionSheetManager,
17+
ShareModule
18+
} = require('NativeModules');
19+
const invariant = require('fbjs/lib/invariant');
20+
const processColor = require('processColor');
21+
22+
type Content = { title?: string, message: string } | { title?: string, url: string };
23+
type Options = { dialogTitle?: string, excludeActivityTypes?: Array<string>, tintColor?: string };
24+
25+
class Share {
26+
27+
/**
28+
* Open a dialog to share text content.
29+
*
30+
* In iOS, Returns a Promise which will be invoked an object containing `action`, `activityType`.
31+
* If the user dismissed the dialog, the Promise will still be resolved with action being `Share.dismissedAction`
32+
* and all the other keys being undefined.
33+
*
34+
* In Android, Returns a Promise which always be resolved with action being `Share.sharedAction`.
35+
*
36+
* ### Content
37+
*
38+
* - `message` - a message to share
39+
* - `title` - title of the message
40+
*
41+
* #### iOS
42+
*
43+
* - `url` - an URL to share
44+
*
45+
* At least one of URL and message is required.
46+
*
47+
* ### Options
48+
*
49+
* #### iOS
50+
*
51+
* - `excludedActivityTypes`
52+
* - `tintColor`
53+
*
54+
* #### Android
55+
*
56+
* - `dialogTitle`
57+
*
58+
*/
59+
static share(content: Content, options: Options = {}): Promise<Object> {
60+
invariant(
61+
typeof content === 'object' && content !== null,
62+
'Content must a valid object'
63+
);
64+
invariant(
65+
typeof content.url === 'string' || typeof content.message === 'string',
66+
'At least one of URL and message is required'
67+
);
68+
invariant(
69+
typeof options === 'object' && options !== null,
70+
'Options must be a valid object'
71+
);
72+
73+
if (Platform.OS === 'android') {
74+
invariant(
75+
!content.title || typeof content.title === 'string',
76+
'Invalid title: title should be a string.'
77+
);
78+
return ShareModule.share(content, options.dialogTitle);
79+
} else if (Platform.OS === 'ios') {
80+
return new Promise((resolve, reject) => {
81+
ActionSheetManager.showShareActionSheetWithOptions(
82+
{...content, ...options, tintColor: processColor(options.tintColor)},
83+
(error) => reject(error),
84+
(success, activityType) => {
85+
if (success) {
86+
resolve({
87+
'action': 'sharedAction',
88+
'activityType': activityType
89+
});
90+
} else {
91+
resolve({
92+
'action': 'dismissedAction'
93+
});
94+
}
95+
}
96+
);
97+
});
98+
} else {
99+
return Promise.reject(new Error('Unsupported platform'));
100+
}
101+
}
102+
103+
/**
104+
* The content was successfully shared.
105+
*/
106+
static get sharedAction() { return 'sharedAction'; }
107+
108+
/**
109+
* The dialog has been dismissed.
110+
* @platform ios
111+
*/
112+
static get dismissedAction() { return 'dismissedAction'; }
113+
114+
}
115+
116+
module.exports = Share;

Libraries/react-native/react-native.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ const ReactNative = {
101101
get PixelRatio() { return require('PixelRatio'); },
102102
get PushNotificationIOS() { return require('PushNotificationIOS'); },
103103
get Settings() { return require('Settings'); },
104+
get Share() { return require('Share'); },
104105
get StatusBarIOS() { return require('StatusBarIOS'); },
105106
get StyleSheet() { return require('StyleSheet'); },
106107
get Systrace() { return require('Systrace'); },

Libraries/react-native/react-native.js.flow

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ var ReactNative = {
113113
PixelRatio: require('PixelRatio'),
114114
PushNotificationIOS: require('PushNotificationIOS'),
115115
Settings: require('Settings'),
116+
Share: require('Share'),
116117
StatusBarIOS: require('StatusBarIOS'),
117118
StyleSheet: require('StyleSheet'),
118119
Systrace: require('Systrace'),

ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ deps = [
1414
react_native_target('java/com/facebook/react/common:common'),
1515
react_native_target('java/com/facebook/react/modules/core:core'),
1616
react_native_target('java/com/facebook/react/modules/datepicker:datepicker'),
17+
react_native_target('java/com/facebook/react/modules/share:share'),
1718
react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'),
1819
react_native_target('java/com/facebook/react/modules/timepicker:timepicker'),
1920
react_native_target('java/com/facebook/react/touch:touch'),
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Copyright (c) 2014-present, Facebook, Inc.
3+
* All rights reserved.
4+
* This source code is licensed under the BSD-style license found in the
5+
* LICENSE file in the root directory of this source tree. An additional grant
6+
* of patent rights can be found in the PATENTS file in the same directory.
7+
*/
8+
9+
package com.facebook.react.tests;
10+
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
14+
import android.app.Activity;
15+
import android.app.AlertDialog;
16+
import android.app.Instrumentation.ActivityMonitor;
17+
import android.content.DialogInterface;
18+
import android.content.Intent;
19+
import android.content.IntentFilter;
20+
import android.content.IntentFilter.MalformedMimeTypeException;
21+
import android.support.v4.app.DialogFragment;
22+
23+
import com.facebook.react.bridge.BaseJavaModule;
24+
import com.facebook.react.testing.ReactInstanceSpecForTest;
25+
import com.facebook.react.bridge.ReactMethod;
26+
import com.facebook.react.bridge.JavaScriptModule;
27+
import com.facebook.react.bridge.WritableMap;
28+
import com.facebook.react.bridge.WritableNativeMap;
29+
import com.facebook.react.modules.share.ShareModule;
30+
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
31+
32+
/**
33+
* Test case for {@link ShareModule}.
34+
*/
35+
public class ShareTestCase extends ReactAppInstrumentationTestCase {
36+
37+
private static interface ShareTestModule extends JavaScriptModule {
38+
public void showShareDialog(WritableMap content, WritableMap options);
39+
}
40+
41+
private static class ShareRecordingModule extends BaseJavaModule {
42+
43+
private int mOpened = 0;
44+
private int mErrors = 0;
45+
46+
@Override
47+
public String getName() {
48+
return "ShareRecordingModule";
49+
}
50+
51+
@ReactMethod
52+
public void recordOpened() {
53+
mOpened++;
54+
}
55+
56+
@ReactMethod
57+
public void recordError() {
58+
mErrors++;
59+
}
60+
61+
public int getOpened() {
62+
return mOpened;
63+
}
64+
65+
public int getErrors() {
66+
return mErrors;
67+
}
68+
69+
}
70+
71+
final ShareRecordingModule mRecordingModule = new ShareRecordingModule();
72+
73+
@Override
74+
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
75+
return super.createReactInstanceSpecForTest()
76+
.addNativeModule(mRecordingModule)
77+
.addJSModule(ShareTestModule.class);
78+
}
79+
80+
@Override
81+
protected String getReactApplicationKeyUnderTest() {
82+
return "ShareTestApp";
83+
}
84+
85+
private ShareTestModule getTestModule() {
86+
return getReactContext().getCatalystInstance().getJSModule(ShareTestModule.class);
87+
}
88+
89+
public void testShowBasicShareDialog() {
90+
final WritableMap content = new WritableNativeMap();
91+
content.putString("message", "Hello, ReactNative!");
92+
final WritableMap options = new WritableNativeMap();
93+
94+
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_CHOOSER);
95+
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
96+
ActivityMonitor monitor = getInstrumentation().addMonitor(intentFilter, null, true);
97+
98+
getTestModule().showShareDialog(content, options);
99+
100+
waitForBridgeAndUIIdle();
101+
getInstrumentation().waitForIdleSync();
102+
103+
assertEquals(1, monitor.getHits());
104+
assertEquals(1, mRecordingModule.getOpened());
105+
assertEquals(0, mRecordingModule.getErrors());
106+
107+
}
108+
109+
}

0 commit comments

Comments
 (0)