-
Notifications
You must be signed in to change notification settings - Fork 24.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Share module #5904
Add Share module #5904
Changes from 47 commits
a76c6c0
424a665
5da16c7
8c3bece
1f5f0c0
fb5f6ea
f21d709
8917788
767f914
de58c94
68804ca
fb88c94
9917041
72c6d50
2678aee
32847bb
0371750
dfe1f63
772523e
43209dc
74e6966
dc7fdaa
7f39ae3
08c29bd
375ee94
851c4ea
4d6add8
e398fd9
71a67fb
4351eec
92a793b
68ca817
d3e13ed
ae290f0
918aaf6
f4d7d3a
29b5e3b
db99ac4
d0f6fb8
bc14456
a404f49
9c896e5
de1ab8f
5127f30
0834fc7
994cda0
2b52ff5
dc1c57c
94023f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/** | ||
* 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no-trailing-spaces: Trailing spaces not allowed. |
||
* of patent rights can be found in the PATENTS file in the same directory. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no-trailing-spaces: Trailing spaces not allowed. |
||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no-trailing-spaces: Trailing spaces not allowed. |
||
* @providesModule Share | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no-trailing-spaces: Trailing spaces not allowed. |
||
* @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` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fyi, this pattern(resolve with constant action) comes from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @deminoth Interesting. We've so many patterns. Let's keep this for now then. |
||
* 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 | ||
* - `url` - an URL to share. In Android, this will overwrite message | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better just to ignore this prop on Android instead of overwriting message. It becomes more explicit then, and the user can decide what to do. For example, I would usually prefer to combine the message and url into a single text on Android. |
||
* - `title` - title of the message | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potentially unsafe get/set usage Getters and setters with side effects are potentially unsafe and disabled by default. You may opt-in to using them anyway by putting |
||
* | ||
* 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' | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no-trailing-spaces: Trailing spaces not allowed. |
||
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' | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this invariant should go under |
||
|
||
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') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we skip this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @grabbou this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok - then probably something to remember for other files :) thanks. |
||
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 { | ||
console.warn('Share.share is not supported on this platform'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to both |
||
return Promise.reject(new Error('Unsupported platform')); | ||
} | ||
} | ||
|
||
/** | ||
* The content was successfully shared. | ||
*/ | ||
static get sharedAction() { return 'sharedAction'; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's just have plain strings instead of constants. |
||
|
||
/** | ||
* The dialog has been dismissed. | ||
* @platform ios | ||
*/ | ||
static get dismissedAction() { return 'dismissedAction'; } | ||
|
||
} | ||
|
||
module.exports = Share; |
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()); | ||
|
||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no-trailing-spaces: Trailing spaces not allowed.