Skip to content

Commit

Permalink
Issue#11068 [Android] Fix issue of duplicating characters when replac…
Browse files Browse the repository at this point in the history
…ing letters to lowercase or uppercase in TextInput
  • Loading branch information
fknives committed Mar 26, 2023
1 parent 92b8981 commit e9845c0
Showing 1 changed file with 47 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -623,10 +623,13 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
if (reactTextUpdate.getText().length() == 0) {
setText(null);
} else {
// When we update text, we trigger onChangeText code that will
// try to update state if the wrapper is available. Temporarily disable
// to prevent an infinite loop.
boolean shouldKeepCompositeSpan = length() == spannableStringBuilder.length();

getText().replace(0, length(), spannableStringBuilder);

if (shouldKeepCompositeSpan) {
attachCompositeSpansToTextFrom(spannableStringBuilder);
}
}
mDisableTextDiffing = false;

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

/**
* Remove and/or add {@link Spanned.SPAN_EXCLUSIVE_EXCLUSIVE} spans, since they should only exist
* as long as the text they cover is the same. All other spans will remain the same, since they
* will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes
* Remove and/or add {@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE} spans, since they should only exist
* 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
* keyboard suggestions. Without keeping these Spans, suggestions default to be put after the current selection position,
* possibly resulting in letter duplication.
*/
private void manageSpans(SpannableStringBuilder spannableStringBuilder) {
Object[] spans = getText().getSpans(0, length(), Object.class);
Expand All @@ -653,6 +659,8 @@ private void manageSpans(SpannableStringBuilder spannableStringBuilder) {
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) {
Expand All @@ -667,6 +675,12 @@ 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 (isComposing) {
spannableStringBuilder.setSpan(span, spanStart, spanEnd, spanFlags);
continue;
}

// Make sure the span is removed from existing text, otherwise the spans we set will be
// ignored or it will cover text that has changed.
getText().removeSpan(span);
Expand Down Expand Up @@ -841,6 +855,33 @@ private void addSpansFromStyleAttributes(SpannableStringBuilder workingText) {
}
}

/**
* Attaches the {@link Spanned#SPAN_COMPOSING} from {@param spannableStringBuilder} to {@link ReactEditText#getText}
*
* See {@link ReactEditText#manageSpans} for more details.
* Also https://github.com/facebook/react-native/issues/11068
*/
private void attachCompositeSpansToTextFrom(SpannableStringBuilder spannableStringBuilder) {
Editable text = getText();
if (text == null) {
return;
}
Object[] spans = spannableStringBuilder.getSpans(0, length(), Object.class);
for (Object span : spans) {
int spanFlags = spannableStringBuilder.getSpanFlags(span);
boolean isComposing = (spanFlags & Spanned.SPAN_COMPOSING) == Spanned.SPAN_COMPOSING;

if (!isComposing) {
continue;
}

final int spanStart = spannableStringBuilder.getSpanStart(span);
final int spanEnd = spannableStringBuilder.getSpanEnd(span);

text.setSpan(span, spanStart, spanEnd, spanFlags);
}
}

private static boolean sameTextForSpan(
final Editable oldText,
final SpannableStringBuilder newText,
Expand Down

0 comments on commit e9845c0

Please sign in to comment.