From f32f142fa2e6e4f9d73629d775a0eb2aa49d4044 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Fri, 4 Aug 2023 12:58:17 +0300 Subject: [PATCH] Issue#11068 PR#38649 [Android] Feature Flag the keeping of Composing Spans --- .../react/config/ReactFeatureFlags.java | 3 ++ .../react/views/textinput/ReactEditText.java | 32 ++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index b084ccbb43d1b7..c6dbb3394b8cd9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -160,4 +160,7 @@ public class ReactFeatureFlags { /** Clean yoga node when does not change. */ public static boolean enableCleanParagraphYogaNode = false; + + /** Enable keeping Composing Spans on Text input change if the new text has the same length. */ + public static boolean enableComposingSpanRestorationOnSameLength = false; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index 0bdcec1b9df508..10d05460650152 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -9,6 +9,7 @@ import static com.facebook.react.uimanager.UIManagerHelper.getReactContext; import static com.facebook.react.views.text.TextAttributeProps.UNSET; +import static com.facebook.react.config.ReactFeatureFlags.enableComposingSpanRestorationOnSameLength; import android.content.ClipData; import android.content.ClipboardManager; @@ -50,6 +51,7 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactSoftExceptionLogger; import com.facebook.react.common.build.ReactBuildConfig; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.uimanager.FabricViewStateManager; import com.facebook.react.uimanager.ReactAccessibilityDelegate; import com.facebook.react.uimanager.UIManagerModule; @@ -721,20 +723,20 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) { * as long as the text they cover is the same unless they are {@link Spanned#SPAN_COMPOSING}. * All other spans will remain the same, since they will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes * them. - * Keep copy of {@link Spanned#SPAN_COMPOSING} Spans in {@param spannableStringBuilder}, because they are important for + * When {@link ReactFeatureFlags#enableComposingSpanRestorationOnSameLength} is enabled, + * keep copy of {@link Spanned#SPAN_COMPOSING} Spans in {@param spannableStringBuilder}, because they are important for * keyboard suggestions. Without keeping these Spans, suggestions default to be put after the current selection position, * possibly resulting in letter duplication (ex. Samsung Keyboard). */ private void manageSpans(SpannableStringBuilder spannableStringBuilder) { Object[] spans = getText().getSpans(0, length(), Object.class); - boolean shouldKeepComposingSpans = length() == spannableStringBuilder.length(); + boolean shouldKeepComposingSpans = enableComposingSpanRestorationOnSameLength + && length() == spannableStringBuilder.length(); for (int spanIdx = 0; spanIdx < spans.length; spanIdx++) { Object span = spans[spanIdx]; int spanFlags = getText().getSpanFlags(span); boolean isExclusiveExclusive = (spanFlags & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; - boolean isComposing = - (spanFlags & Spanned.SPAN_COMPOSING) == Spanned.SPAN_COMPOSING; // Remove all styling spans we might have previously set if (span instanceof ReactSpan) { @@ -749,10 +751,13 @@ private void manageSpans(SpannableStringBuilder spannableStringBuilder) { final int spanStart = getText().getSpanStart(span); final int spanEnd = getText().getSpanEnd(span); - // We keep a copy of Composing spans - if (shouldKeepComposingSpans && isComposing) { - spannableStringBuilder.setSpan(span, spanStart, spanEnd, spanFlags); - continue; + if (shouldKeepComposingSpans) { + // We keep a copy of Composing spans + boolean isComposing = (spanFlags & Spanned.SPAN_COMPOSING) == Spanned.SPAN_COMPOSING; + if (isComposing) { + spannableStringBuilder.setSpan(span, spanStart, spanEnd, spanFlags); + continue; + } } // Make sure the span is removed from existing text, otherwise the spans we set will be @@ -883,13 +888,18 @@ private void addSpansFromStyleAttributes(SpannableStringBuilder workingText) { } /** - * Attaches the {@link Spanned#SPAN_COMPOSING} from {@param spannableStringBuilder} to {@link ReactEditText#getText} - * if they are the same length. + * When {@link ReactFeatureFlags#enableComposingSpanRestorationOnSameLength} is enabled, this + * function attaches the {@link Spanned#SPAN_COMPOSING} from {@param spannableStringBuilder} to + * {@link ReactEditText#getText} if they are the same length. * * See {@link ReactEditText#manageSpans} for more details. - * Also https://github.com/facebook/react-native/issues/11068 + * Also this GitHub issue */ private void attachCompositeSpansToTextFrom(SpannableStringBuilder spannableStringBuilder) { + if (!enableComposingSpanRestorationOnSameLength) { + return; + } + Editable text = getText(); if (text == null || text.length() != spannableStringBuilder.length()) { return;