Skip to content

Commit 5b45e97

Browse files
fabOnReactLuna Wei
authored and
Luna Wei
committed
Remove option to paste rich text from Android EditText context menu (#38189)
Summary: Text is copy pasted as rich text on Android TextInput instead of Plain Text. ### What is the root cause of that problem? Android EditText and iOS UITextField/UITextView have different copy/paste behavior. - Android TextInput copies/pastes rich text - iOS UITextField copies/pastes plain text. | iOS (react-native) | Android (react-native) | | ----------- | ----------- | | <video src="https://user-images.githubusercontent.com/24992535/249170968-8fde35f0-a53c-4c5c-bd89-ee822c08eadf.mp4" width="350" /> | <video src="https://user-images.githubusercontent.com/24992535/249171968-bf0915a0-4060-4586-b267-7c2b463d76f6.mov" width="350" /> | ### What changes do you think we should make in order to solve the problem? The issue is a bug in react-native #31442: 1) The JavaScript TextInput and ReactEditText Android state are not in sync 2) The TextInput Android Native state over-rides the JavaScript state. 3) onChangeText passes a plain text string to JavaScript, not rich text (text with spans and styles). More info at Expensify/App#21411 (comment) The solution consists of: 1) **Over-riding onTextContextMenuItem in ReactEditText to copy/paste plain text instead of rich-text** (https://stackoverflow.com/a/45319485/7295772). 2) **Removing the `Paste as plaintext` option from the insert and selection context menu** fixes #31442 ## Changelog: [ANDROID] [FIXED] - Remove option to paste rich text from Android EditText context menu Pull Request resolved: #38189 Test Plan: #### Reproducing the issue on Android https://user-images.githubusercontent.com/24992535/249185416-76f8a687-1aca-4dc9-9abe-3d73d6e2893c.mp4 #### Fixing the issue on Android Sourcecode https://github.com/fabriziobertoglio1987/text-input-cursor-flickering-android/blob/fix-copy-paste/app/src/main/java/com/example/myapplication/CustomEditText.java https://user-images.githubusercontent.com/24992535/249486339-95449bb9-71b6-430c-8207-f5672f034fa9.mp4 #### Testing the solution on React Native https://github.com/Expensify/App/assets/24992535/b302237b-99e5-44a2-996d-8bc50bbbc95c Reviewed By: mdvacca Differential Revision: D47824730 Pulled By: NickGerleman fbshipit-source-id: 35525e7d52e664b0f78649d23941262ee45a00cd
1 parent a601b22 commit 5b45e97

File tree

1 file changed

+65
-0
lines changed
  • packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput

1 file changed

+65
-0
lines changed

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import static com.facebook.react.uimanager.UIManagerHelper.getReactContext;
1111
import static com.facebook.react.views.text.TextAttributeProps.UNSET;
1212

13+
import android.content.ClipData;
14+
import android.content.ClipboardManager;
1315
import android.content.Context;
1416
import android.graphics.Color;
1517
import android.graphics.Paint;
@@ -28,8 +30,11 @@
2830
import android.text.method.KeyListener;
2931
import android.text.method.QwertyKeyListener;
3032
import android.util.TypedValue;
33+
import android.view.ActionMode;
3134
import android.view.Gravity;
3235
import android.view.KeyEvent;
36+
import android.view.Menu;
37+
import android.view.MenuItem;
3338
import android.view.MotionEvent;
3439
import android.view.View;
3540
import android.view.accessibility.AccessibilityNodeInfo;
@@ -180,6 +185,35 @@ public boolean performAccessibilityAction(View host, int action, Bundle args) {
180185
}
181186
};
182187
ViewCompat.setAccessibilityDelegate(this, editTextAccessibilityDelegate);
188+
ActionMode.Callback customActionModeCallback =
189+
new ActionMode.Callback() {
190+
/*
191+
* Editor onCreateActionMode adds the cut, copy, paste, share, autofill,
192+
* and paste as plain text items to the context menu.
193+
*/
194+
@Override
195+
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
196+
menu.removeItem(android.R.id.pasteAsPlainText);
197+
return true;
198+
}
199+
200+
@Override
201+
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
202+
return true;
203+
}
204+
205+
@Override
206+
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
207+
return false;
208+
}
209+
210+
@Override
211+
public void onDestroyActionMode(ActionMode mode) {}
212+
};
213+
setCustomSelectionActionModeCallback(customActionModeCallback);
214+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
215+
setCustomInsertionActionModeCallback(customActionModeCallback);
216+
}
183217
}
184218

185219
@Override
@@ -268,6 +302,37 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
268302
return inputConnection;
269303
}
270304

305+
/*
306+
* Called when a context menu option for the text view is selected.
307+
* React Native replaces copy (as rich text) with copy as plain text.
308+
*/
309+
@Override
310+
public boolean onTextContextMenuItem(int id) {
311+
if (id == android.R.id.paste) {
312+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
313+
id = android.R.id.pasteAsPlainText;
314+
} else {
315+
ClipboardManager clipboard =
316+
(ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
317+
ClipData previousClipData = clipboard.getPrimaryClip();
318+
if (previousClipData != null) {
319+
for (int i = 0; i < previousClipData.getItemCount(); i++) {
320+
final CharSequence text = previousClipData.getItemAt(i).coerceToText(getContext());
321+
final CharSequence paste = (text instanceof Spanned) ? text.toString() : text;
322+
if (paste != null) {
323+
ClipData clipData = ClipData.newPlainText(null, text);
324+
clipboard.setPrimaryClip(clipData);
325+
}
326+
}
327+
boolean actionPerformed = super.onTextContextMenuItem(id);
328+
clipboard.setPrimaryClip(previousClipData);
329+
return actionPerformed;
330+
}
331+
}
332+
}
333+
return super.onTextContextMenuItem(id);
334+
}
335+
271336
@Override
272337
public void clearFocus() {
273338
setFocusableInTouchMode(false);

0 commit comments

Comments
 (0)