Skip to content

Commit b1ceea4

Browse files
fabOnReactfacebook-github-bot
authored andcommitted
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 54d70cf commit b1ceea4

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;
@@ -181,6 +186,35 @@ public boolean performAccessibilityAction(View host, int action, Bundle args) {
181186
}
182187
};
183188
ViewCompat.setAccessibilityDelegate(this, editTextAccessibilityDelegate);
189+
ActionMode.Callback customActionModeCallback =
190+
new ActionMode.Callback() {
191+
/*
192+
* Editor onCreateActionMode adds the cut, copy, paste, share, autofill,
193+
* and paste as plain text items to the context menu.
194+
*/
195+
@Override
196+
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
197+
menu.removeItem(android.R.id.pasteAsPlainText);
198+
return true;
199+
}
200+
201+
@Override
202+
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
203+
return true;
204+
}
205+
206+
@Override
207+
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
208+
return false;
209+
}
210+
211+
@Override
212+
public void onDestroyActionMode(ActionMode mode) {}
213+
};
214+
setCustomSelectionActionModeCallback(customActionModeCallback);
215+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
216+
setCustomInsertionActionModeCallback(customActionModeCallback);
217+
}
184218
}
185219

186220
@Override
@@ -269,6 +303,37 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
269303
return inputConnection;
270304
}
271305

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

0 commit comments

Comments
 (0)