Skip to content

Commit af06d9e

Browse files
committed
Android: Pass clipboard data to onPaste event
1 parent 27743c6 commit af06d9e

File tree

5 files changed

+90
-8
lines changed

5 files changed

+90
-8
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
* from the EditText to JS
1414
*/
1515
interface PasteWatcher {
16-
public void onPaste();
16+
public void onPaste(String type, String data);
1717
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java

+33-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@
99

1010
import static com.facebook.react.uimanager.UIManagerHelper.getReactContext;
1111

12+
import android.content.ClipboardManager;
13+
import android.content.ClipData;
14+
import android.content.ClipDescription;
15+
import android.content.ContentResolver;
1216
import android.content.Context;
1317
import android.graphics.Canvas;
1418
import android.graphics.Color;
1519
import android.graphics.Paint;
1620
import android.graphics.Rect;
1721
import android.graphics.Typeface;
1822
import android.graphics.drawable.Drawable;
23+
import android.net.Uri;
1924
import android.os.Build;
2025
import android.os.Bundle;
2126
import android.text.Editable;
@@ -27,6 +32,7 @@
2732
import android.text.TextWatcher;
2833
import android.text.method.KeyListener;
2934
import android.text.method.QwertyKeyListener;
35+
import android.util.Base64;
3036
import android.util.TypedValue;
3137
import android.view.ActionMode;
3238
import android.view.Gravity;
@@ -68,6 +74,7 @@
6874
import com.facebook.react.views.text.internal.span.ReactUnderlineSpan;
6975
import com.facebook.react.views.text.internal.span.TextInlineImageSpan;
7076
import com.facebook.react.views.view.ReactViewBackgroundManager;
77+
import java.io.IOException;
7178
import java.util.ArrayList;
7279
import java.util.Objects;
7380

@@ -326,7 +333,32 @@ public boolean onTextContextMenuItem(int id) {
326333
if (id == android.R.id.paste || id == android.R.id.pasteAsPlainText) {
327334
id = android.R.id.pasteAsPlainText;
328335
if (mPasteWatcher != null) {
329-
mPasteWatcher.onPaste();
336+
ClipboardManager clipboardManager =
337+
(ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
338+
ClipData clipData = clipboardManager.getPrimaryClip();
339+
String type = null;
340+
String data = null;
341+
if (clipData.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
342+
type = ClipDescription.MIMETYPE_TEXT_PLAIN;
343+
data = clipData.getItemAt(0).getText().toString();
344+
} else {
345+
Uri itemUri = clipData.getItemAt(0).getUri();
346+
if (itemUri != null) {
347+
ContentResolver cr = getReactContext(this).getContentResolver();
348+
type = cr.getType(itemUri);
349+
if (type != null) {
350+
try {
351+
String encodedData = Base64.encodeToString(cr.openInputStream(itemUri).readAllBytes(), Base64.DEFAULT);
352+
data = "data:" + type + ";base64," + encodedData;
353+
} catch (IOException e) {
354+
e.printStackTrace();
355+
}
356+
}
357+
}
358+
}
359+
if (type != null && data != null) {
360+
mPasteWatcher.onPaste(type, data);
361+
}
330362
}
331363
}
332364
return super.onTextContextMenuItem(id);

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1332,9 +1332,9 @@ public ReactPasteWatcher(ReactEditText editText) {
13321332
}
13331333

13341334
@Override
1335-
public void onPaste() {
1335+
public void onPaste(String type, String data) {
13361336
mEventDispatcher.dispatchEvent(
1337-
new ReactTextInputPasteEvent(mSurfaceId, mReactEditText.getId()));
1337+
new ReactTextInputPasteEvent(mSurfaceId, mReactEditText.getId(), type, data));
13381338
}
13391339
}
13401340

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.java

+18-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import androidx.annotation.Nullable;
1111
import com.facebook.react.bridge.Arguments;
1212
import com.facebook.react.bridge.WritableMap;
13+
import com.facebook.react.bridge.WritableArray;
1314
import com.facebook.react.uimanager.common.ViewUtil;
1415
import com.facebook.react.uimanager.events.Event;
1516

@@ -20,13 +21,18 @@ class ReactTextInputPasteEvent extends Event<ReactTextInputPasteEvent> {
2021

2122
private static final String EVENT_NAME = "topPaste";
2223

24+
private String mType;
25+
private String mData;
26+
2327
@Deprecated
24-
public ReactTextInputPasteEvent(int viewId) {
25-
this(ViewUtil.NO_SURFACE_ID, viewId);
28+
public ReactTextInputPasteEvent(int viewId, String type, String data) {
29+
this(ViewUtil.NO_SURFACE_ID, viewId, type, data);
2630
}
2731

28-
public ReactTextInputPasteEvent(int surfaceId, int viewId) {
32+
public ReactTextInputPasteEvent(int surfaceId, int viewId, String type, String data) {
2933
super(surfaceId, viewId);
34+
mType = type;
35+
mData = data;
3036
}
3137

3238
@Override
@@ -43,6 +49,15 @@ public boolean canCoalesce() {
4349
@Override
4450
protected WritableMap getEventData() {
4551
WritableMap eventData = Arguments.createMap();
52+
53+
WritableArray items = Arguments.createArray();
54+
WritableMap primaryClip = Arguments.createMap();
55+
primaryClip.putString("type", mType);
56+
primaryClip.putString("data", mData);
57+
items.pushMap(primaryClip);
58+
59+
eventData.putArray("items", items);
60+
4661
return eventData;
4762
}
4863
}

packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js

+36-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import * as React from 'react';
2020
import {useContext, useState} from 'react';
2121
import {
2222
Button,
23+
Image,
2324
Platform,
2425
StyleSheet,
2526
Text,
@@ -415,7 +416,9 @@ class TextEventsExample extends React.Component<{...}, $FlowFixMeState> {
415416
onKeyPress={event =>
416417
this.updateText('onKeyPress key: ' + event.nativeEvent.key)
417418
}
418-
onPaste={() => this.updateText('onPaste')}
419+
onPaste={event =>
420+
this.updateText('onPaste type: ' + event.nativeEvent.items[0].type)
421+
}
419422
style={styles.singleLine}
420423
/>
421424
<Text style={styles.eventLabel}>
@@ -847,6 +850,32 @@ function MultilineStyledTextInput({
847850
);
848851
}
849852

853+
function PasteboardTextInput() {
854+
const [pasteboard, setPasteboard] = useState(null);
855+
const {type, data} = pasteboard?.items[0] ?? {};
856+
const isText = type === "text/plain"
857+
const isImage = type && type.startsWith("image/");
858+
859+
return (
860+
<View>
861+
<ExampleTextInput
862+
onPaste={event => setPasteboard(event.nativeEvent)}
863+
placeholder="Paste text or image"
864+
multiline={true}>
865+
</ExampleTextInput>
866+
{type && (
867+
<Text>{"Type: " + type}</Text>
868+
)}
869+
{isText && (
870+
<Text>{"Data: " + data}</Text>
871+
)}
872+
{isImage && (
873+
<Image source={{uri: data}} style={{width: "100%", height: 300}} />
874+
)}
875+
</View>
876+
);
877+
}
878+
850879
module.exports = ([
851880
{
852881
title: 'Auto-focus & select text on focus',
@@ -1150,4 +1179,10 @@ module.exports = ([
11501179
);
11511180
},
11521181
},
1182+
{
1183+
title: 'Pasteboard',
1184+
render: function (): React.Element<any> {
1185+
return <PasteboardTextInput />;
1186+
},
1187+
},
11531188
]: Array<RNTesterModuleExample>);

0 commit comments

Comments
 (0)