diff --git a/Examples/UIExplorer/ClipboardExample.js b/Examples/UIExplorer/ClipboardExample.js new file mode 100644 index 000000000..bcc53dfbb --- /dev/null +++ b/Examples/UIExplorer/ClipboardExample.js @@ -0,0 +1,60 @@ +/** + * 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-native'); +var { + Clipboard, + View, + Text, +} = React; + +var ClipboardExample = React.createClass({ + getInitialState: function() { + return { + content: 'Content will appear here' + }; + }, + + _setContentToClipboard:function(){ + Clipboard.setString('Hello World'); + Clipboard.getString(content => { + this.setState({content}); + }); + }, + + render() { + return ( + + + Tap to put "Hello World" in the clipboard + + + {this.state.content} + + + ); + } +}); + +exports.title = 'Clipboard'; +exports.description = 'Show Clipboard contents.'; +exports.examples = [ + { + title: 'Clipboard.setString() and getString()', + render(): ReactElement { return ; } + } +]; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index a2ff43096..36746c558 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -39,6 +39,7 @@ var COMPONENTS = [ var APIS = [ require('./AccessibilityAndroidExample.android'), require('./BorderExample'), + require('./ClipboardExample'), require('./GeolocationExample'), require('./IntentAndroidExample.android'), require('./LayoutEventsExample'), diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 940bd79ba..61a213097 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -66,6 +66,7 @@ var APIS = [ require('./AsyncStorageExample'), require('./BorderExample'), require('./CameraRollExample.ios'), + require('./ClipboardExample'), require('./GeolocationExample'), require('./LayoutExample'), require('./NetInfoExample'), diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js index 113a09262..164508ddd 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js @@ -54,8 +54,8 @@ var NativeModules = { AlertManager: { alertWithArgs: jest.genMockFunction(), }, - Pasteboard: { - setPasteboardString: jest.genMockFunction(), + Clipboard: { + setString: jest.genMockFunction(), }, }; diff --git a/Libraries/Components/Clipboard/Clipboard.js b/Libraries/Components/Clipboard/Clipboard.js new file mode 100644 index 000000000..fd8461ba4 --- /dev/null +++ b/Libraries/Components/Clipboard/Clipboard.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2015-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 Clipboard + */ +'use strict'; + +module.exports = require('NativeModules').Clipboard; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 9eb675a84..be3dfd26a 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -58,6 +58,7 @@ var ReactNative = { get AsyncStorage() { return require('AsyncStorage'); }, get BackAndroid() { return require('BackAndroid'); }, get CameraRoll() { return require('CameraRoll'); }, + get Clipboard() { return require('Clipboard'); }, get Dimensions() { return require('Dimensions'); }, get Easing() { return require('Easing'); }, get ImagePickerIOS() { return require('ImagePickerIOS'); }, diff --git a/Libraries/react-native/react-native.js.flow b/Libraries/react-native/react-native.js.flow index 0f314456a..d6c4aac73 100644 --- a/Libraries/react-native/react-native.js.flow +++ b/Libraries/react-native/react-native.js.flow @@ -70,6 +70,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { AsyncStorage: require('AsyncStorage'), BackAndroid: require('BackAndroid'), CameraRoll: require('CameraRoll'), + Clipboard: require('Clipboard'), Dimensions: require('Dimensions'), Easing: require('Easing'), ImagePickerIOS: require('ImagePickerIOS'), diff --git a/React/Modules/RCTPasteboard.h b/React/Modules/RCTClipboard.h similarity index 86% rename from React/Modules/RCTPasteboard.h rename to React/Modules/RCTClipboard.h index d29a3fb1a..bc65c6239 100644 --- a/React/Modules/RCTPasteboard.h +++ b/React/Modules/RCTClipboard.h @@ -9,6 +9,6 @@ #import "RCTBridgeModule.h" -@interface RCTPasteboard : NSObject +@interface RCTClipboard : NSObject @end diff --git a/React/Modules/RCTPasteboard.m b/React/Modules/RCTClipboard.m similarity index 52% rename from React/Modules/RCTPasteboard.m rename to React/Modules/RCTClipboard.m index 41ad7c044..0a62f3f88 100644 --- a/React/Modules/RCTPasteboard.m +++ b/React/Modules/RCTClipboard.m @@ -7,11 +7,13 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTPasteboard.h" +#import "RCTClipboard.h" + +#import "RCTUtils.h" #import -@implementation RCTPasteboard +@implementation RCTClipboard RCT_EXPORT_MODULE() @@ -20,9 +22,16 @@ RCT_EXPORT_MODULE() return dispatch_get_main_queue(); } -RCT_EXPORT_METHOD(setPasteboardString:(NSString *)string) +RCT_EXPORT_METHOD(getString:(RCTResponseSenderBlock)callback) { - [[UIPasteboard generalPasteboard] setString:string]; + UIPasteboard *clipboard = [UIPasteboard generalPasteboard]; + callback(@[RCTNullIfNil(clipboard.string)]); +} + +RCT_EXPORT_METHOD(setString:(NSString *)content) +{ + UIPasteboard *clipboard = [UIPasteboard generalPasteboard]; + clipboard.string = content; } @end diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 53a11ed1d..d5ea0c76d 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -38,12 +38,12 @@ 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080131A69489C00A75B9A /* RCTNavItemManager.m */; }; 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */; }; 13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; }; - 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BB3D011BECD54500932C10 /* RCTImageSource.m */; }; - 13B202011BFB945300C07393 /* RCTPasteboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202001BFB945300C07393 /* RCTPasteboard.m */; }; 13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */; }; + 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BB3D011BECD54500932C10 /* RCTImageSource.m */; }; 13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; }; 13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156041AB1A2840079392D /* RCTWebViewManager.m */; }; 13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */ = {isa = PBXBuildFile; fileRef = 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */; }; + 13D033631C1837FE0021DC29 /* RCTClipboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D033621C1837FE0021DC29 /* RCTClipboard.m */; }; 13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067491A70F434002CDEE1 /* RCTUIManager.m */; }; 13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */; }; 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; }; @@ -174,12 +174,10 @@ 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActivityIndicatorViewManager.m; sourceTree = ""; }; 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWrapperViewController.h; sourceTree = ""; }; 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = ""; }; - 13BB3D001BECD54500932C10 /* RCTImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageSource.h; sourceTree = ""; }; - 13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = ""; }; - 13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = ""; }; - 13B202001BFB945300C07393 /* RCTPasteboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPasteboard.m; sourceTree = ""; }; 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapAnnotation.h; sourceTree = ""; }; 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapAnnotation.m; sourceTree = ""; }; + 13BB3D001BECD54500932C10 /* RCTImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageSource.h; sourceTree = ""; }; + 13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = ""; }; 13C156011AB1A2840079392D /* RCTWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebView.h; sourceTree = ""; }; 13C156021AB1A2840079392D /* RCTWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebView.m; sourceTree = ""; }; 13C156031AB1A2840079392D /* RCTWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewManager.h; sourceTree = ""; }; @@ -189,6 +187,8 @@ 13C325281AA63B6A0048765F /* RCTComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTComponent.h; sourceTree = ""; }; 13CC8A801B17642100940AE7 /* RCTBorderDrawing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderDrawing.h; sourceTree = ""; }; 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBorderDrawing.m; sourceTree = ""; }; + 13D033611C1837FE0021DC29 /* RCTClipboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTClipboard.h; sourceTree = ""; }; + 13D033621C1837FE0021DC29 /* RCTClipboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTClipboard.m; sourceTree = ""; }; 13E067481A70F434002CDEE1 /* RCTUIManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUIManager.h; sourceTree = ""; }; 13E067491A70F434002CDEE1 /* RCTUIManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManager.m; sourceTree = ""; }; 13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowView.h; sourceTree = ""; }; @@ -318,14 +318,14 @@ 1372B7091AB030C200659ED6 /* RCTAppState.m */, 58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */, 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */, + 13D033611C1837FE0021DC29 /* RCTClipboard.h */, + 13D033621C1837FE0021DC29 /* RCTClipboard.m */, 13A0C2851B74F71200B29F6F /* RCTDevLoadingView.h */, 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */, 13A0C2871B74F71200B29F6F /* RCTDevMenu.h */, 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */, 13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */, 13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */, - 13B201FF1BFB945300C07393 /* RCTPasteboard.h */, - 13B202001BFB945300C07393 /* RCTPasteboard.m */, 13F17A831B8493E5007D4C75 /* RCTRedBox.h */, 13F17A841B8493E5007D4C75 /* RCTRedBox.m */, 000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */, @@ -628,7 +628,6 @@ 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */, 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */, 133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */, - 13B202011BFB945300C07393 /* RCTPasteboard.m in Sources */, 14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */, 13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */, 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */, @@ -669,6 +668,7 @@ 14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */, 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */, 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */, + 13D033631C1837FE0021DC29 /* RCTClipboard.m in Sources */, 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */, 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */, 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */, diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java new file mode 100644 index 000000000..b3689f980 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2015-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.modules.clipboard; + +import android.annotation.SuppressLint; +import android.content.ClipboardManager; +import android.content.ClipData; +import android.os.Build; + +import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.common.ReactConstants; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A module that allows JS to get/set clipboard contents. + */ +public class ClipboardModule extends ReactContextBaseJavaModule { + + public ClipboardModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return "Clipboard"; + } + + private ClipboardManager getClipboardService() { + ReactApplicationContext reactContext = getReactApplicationContext(); + return (ClipboardManager) reactContext.getSystemService(reactContext.CLIPBOARD_SERVICE); + } + + @ReactMethod + public void getString(Callback cb) { + try { + ClipboardManager clipboard = getClipboardService(); + ClipData clipData = clipboard.getPrimaryClip(); + if (clipData == null) { + cb.invoke(""); + return; + } + if (clipData.getItemCount() >= 1) { + ClipData.Item firstItem = clipboard.getPrimaryClip().getItemAt(0); + String text = "" + firstItem.getText(); + cb.invoke(text); + } else { + cb.invoke(""); + } + } catch(Exception e) { + FLog.w(ReactConstants.TAG, "Cannot get clipboard contents: " + e.getMessage()); + } + } + + @SuppressLint("DeprecatedMethod") + @ReactMethod + public void setString(String text) { + ReactApplicationContext reactContext = getReactApplicationContext(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + ClipData clipdata = ClipData.newPlainText(null, text); + ClipboardManager clipboard = getClipboardService(); + clipboard.setPrimaryClip(clipdata); + } else { + ClipboardManager clipboard = getClipboardService(); + clipboard.setText(text); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index bf91bade6..973cfbd84 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -41,6 +41,7 @@ import com.facebook.react.views.toolbar.ReactToolbarManager; import com.facebook.react.views.view.ReactViewManager; import com.facebook.react.views.viewpager.ReactViewPagerManager; import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager; +import com.facebook.react.modules.clipboard.ClipboardModule; /** * Package defining basic modules and view managers. @@ -51,6 +52,7 @@ public class MainReactPackage implements ReactPackage { public List createNativeModules(ReactApplicationContext reactContext) { return Arrays.asList( new AsyncStorageModule(reactContext), + new ClipboardModule(reactContext), new FrescoModule(reactContext), new IntentModule(reactContext), new LocationModule(reactContext),