@@ -585,9 +585,7 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
585585 new SpannableStringBuilder (reactTextUpdate .getText ());
586586
587587 manageSpans (spannableStringBuilder , reactTextUpdate .mContainsMultipleFragments );
588-
589- // Mitigation for https://github.com/facebook/react-native/issues/35936 (S318090)
590- stripAttributeEquivalentSpans (spannableStringBuilder );
588+ stripStyleEquivalentSpans (spannableStringBuilder );
591589
592590 mContainsImages = reactTextUpdate .containsImages ();
593591
@@ -662,28 +660,44 @@ private void manageSpans(
662660 }
663661 }
664662
665- private void stripAttributeEquivalentSpans (SpannableStringBuilder sb ) {
666- // We have already set a font size on the EditText itself. We can safely remove sizing spans
667- // which are the same as the set font size, and not otherwise overlapped.
668- final int effectiveFontSize = mTextAttributes .getEffectiveFontSize ();
669- ReactAbsoluteSizeSpan [] spans = sb .getSpans (0 , sb .length (), ReactAbsoluteSizeSpan .class );
663+ // TODO: Replace with Predicate<T> and lambdas once Java 8 builds in OSS
664+ interface SpanPredicate <T > {
665+ boolean test (T span );
666+ }
667+
668+ /**
669+ * Remove spans from the SpannableStringBuilder which can be represented by TextAppearance
670+ * attributes on the underlying EditText. This works around instability on Samsung devices with
671+ * the presence of spans https://github.com/facebook/react-native/issues/35936 (S318090)
672+ */
673+ private void stripStyleEquivalentSpans (SpannableStringBuilder sb ) {
674+ stripSpansOfKind (
675+ sb ,
676+ ReactAbsoluteSizeSpan .class ,
677+ new SpanPredicate <ReactAbsoluteSizeSpan >() {
678+ @ Override
679+ public boolean test (ReactAbsoluteSizeSpan span ) {
680+ return span .getSize () == mTextAttributes .getEffectiveFontSize ();
681+ }
682+ });
683+ }
670684
671- outerLoop :
672- for (ReactAbsoluteSizeSpan span : spans ) {
673- ReactAbsoluteSizeSpan [] overlappingSpans =
674- sb .getSpans (sb .getSpanStart (span ), sb .getSpanEnd (span ), ReactAbsoluteSizeSpan .class );
685+ private <T > void stripSpansOfKind (
686+ SpannableStringBuilder sb , Class <T > clazz , SpanPredicate <T > shouldStrip ) {
687+ T [] spans = sb .getSpans (0 , sb .length (), clazz );
675688
676- for (ReactAbsoluteSizeSpan overlappingSpan : overlappingSpans ) {
677- if (span .getSize () != effectiveFontSize ) {
678- continue outerLoop ;
679- }
689+ for (T span : spans ) {
690+ if (shouldStrip .test (span )) {
691+ sb .removeSpan (span );
680692 }
681-
682- sb .removeSpan (span );
683693 }
684694 }
685695
686- private void unstripAttributeEquivalentSpans (SpannableStringBuilder workingText ) {
696+ /**
697+ * Copy back styles represented as attributes to the underlying span, for later measurement
698+ * outside the ReactEditText.
699+ */
700+ private void restoreStyleEquivalentSpans (SpannableStringBuilder workingText ) {
687701 int spanFlags = Spannable .SPAN_INCLUSIVE_INCLUSIVE ;
688702
689703 // Set all bits for SPAN_PRIORITY so that this span has the highest possible priority
@@ -1122,7 +1136,7 @@ private void updateCachedSpannable(boolean resetStyles) {
11221136 // - android.app.Activity.dispatchKeyEvent (Activity.java:3447)
11231137 try {
11241138 sb .append (currentText .subSequence (0 , currentText .length ()));
1125- unstripAttributeEquivalentSpans (sb );
1139+ restoreStyleEquivalentSpans (sb );
11261140 } catch (IndexOutOfBoundsException e ) {
11271141 ReactSoftExceptionLogger .logSoftException (TAG , e );
11281142 }
0 commit comments