Skip to content

Commit e9845c0

Browse files
committed
Issue#11068 [Android] Fix issue of duplicating characters when replacing letters to lowercase or uppercase in TextInput
1 parent 92b8981 commit e9845c0

File tree

1 file changed

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

1 file changed

+47
-6
lines changed

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

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -623,10 +623,13 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
623623
if (reactTextUpdate.getText().length() == 0) {
624624
setText(null);
625625
} else {
626-
// When we update text, we trigger onChangeText code that will
627-
// try to update state if the wrapper is available. Temporarily disable
628-
// to prevent an infinite loop.
626+
boolean shouldKeepCompositeSpan = length() == spannableStringBuilder.length();
627+
629628
getText().replace(0, length(), spannableStringBuilder);
629+
630+
if (shouldKeepCompositeSpan) {
631+
attachCompositeSpansToTextFrom(spannableStringBuilder);
632+
}
630633
}
631634
mDisableTextDiffing = false;
632635

@@ -641,10 +644,13 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
641644
}
642645

643646
/**
644-
* Remove and/or add {@link Spanned.SPAN_EXCLUSIVE_EXCLUSIVE} spans, since they should only exist
645-
* as long as the text they cover is the same. All other spans will remain the same, since they
646-
* will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes
647+
* Remove and/or add {@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE} spans, since they should only exist
648+
* as long as the text they cover is the same unless they are {@link Spanned#SPAN_COMPOSING}.
649+
* All other spans will remain the same, since they will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes
647650
* them.
651+
* Keep copy of {@link Spanned#SPAN_COMPOSING} Spans in {@param spannableStringBuilder}, because they are important for
652+
* keyboard suggestions. Without keeping these Spans, suggestions default to be put after the current selection position,
653+
* possibly resulting in letter duplication.
648654
*/
649655
private void manageSpans(SpannableStringBuilder spannableStringBuilder) {
650656
Object[] spans = getText().getSpans(0, length(), Object.class);
@@ -653,6 +659,8 @@ private void manageSpans(SpannableStringBuilder spannableStringBuilder) {
653659
int spanFlags = getText().getSpanFlags(span);
654660
boolean isExclusiveExclusive =
655661
(spanFlags & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
662+
boolean isComposing =
663+
(spanFlags & Spanned.SPAN_COMPOSING) == Spanned.SPAN_COMPOSING;
656664

657665
// Remove all styling spans we might have previously set
658666
if (span instanceof ReactSpan) {
@@ -667,6 +675,12 @@ private void manageSpans(SpannableStringBuilder spannableStringBuilder) {
667675
final int spanStart = getText().getSpanStart(span);
668676
final int spanEnd = getText().getSpanEnd(span);
669677

678+
// We keep a copy of Composing spans
679+
if (isComposing) {
680+
spannableStringBuilder.setSpan(span, spanStart, spanEnd, spanFlags);
681+
continue;
682+
}
683+
670684
// Make sure the span is removed from existing text, otherwise the spans we set will be
671685
// ignored or it will cover text that has changed.
672686
getText().removeSpan(span);
@@ -841,6 +855,33 @@ private void addSpansFromStyleAttributes(SpannableStringBuilder workingText) {
841855
}
842856
}
843857

858+
/**
859+
* Attaches the {@link Spanned#SPAN_COMPOSING} from {@param spannableStringBuilder} to {@link ReactEditText#getText}
860+
*
861+
* See {@link ReactEditText#manageSpans} for more details.
862+
* Also https://github.com/facebook/react-native/issues/11068
863+
*/
864+
private void attachCompositeSpansToTextFrom(SpannableStringBuilder spannableStringBuilder) {
865+
Editable text = getText();
866+
if (text == null) {
867+
return;
868+
}
869+
Object[] spans = spannableStringBuilder.getSpans(0, length(), Object.class);
870+
for (Object span : spans) {
871+
int spanFlags = spannableStringBuilder.getSpanFlags(span);
872+
boolean isComposing = (spanFlags & Spanned.SPAN_COMPOSING) == Spanned.SPAN_COMPOSING;
873+
874+
if (!isComposing) {
875+
continue;
876+
}
877+
878+
final int spanStart = spannableStringBuilder.getSpanStart(span);
879+
final int spanEnd = spannableStringBuilder.getSpanEnd(span);
880+
881+
text.setSpan(span, spanStart, spanEnd, spanFlags);
882+
}
883+
}
884+
844885
private static boolean sameTextForSpan(
845886
final Editable oldText,
846887
final SpannableStringBuilder newText,

0 commit comments

Comments
 (0)