@@ -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,19 +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+ }
670667
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+ }
684+
685+ private <T > void stripSpansOfKind (
686+ SpannableStringBuilder sb , Class <T > clazz , SpanPredicate <T > isEquivalentToAttributes ) {
687+ T [] spans = sb .getSpans (0 , sb .length (), clazz );
671688 outerLoop :
672- for (ReactAbsoluteSizeSpan span : spans ) {
673- ReactAbsoluteSizeSpan [] overlappingSpans =
674- sb .getSpans (sb .getSpanStart (span ), sb .getSpanEnd (span ), ReactAbsoluteSizeSpan .class );
689+ for (T span : spans ) {
690+ if (!isEquivalentToAttributes .test (span )) {
691+ continue ;
692+ }
675693
676- for (ReactAbsoluteSizeSpan overlappingSpan : overlappingSpans ) {
677- if (span .getSize () != effectiveFontSize ) {
694+ int priority = sb .getSpanFlags (span ) & Spannable .SPAN_PRIORITY ;
695+ T [] overlappingSpans = sb .getSpans (sb .getSpanStart (span ), sb .getSpanEnd (span ), clazz );
696+
697+ // Do not strip the span if removing it should show a non-equivalent span under it
698+ for (T overlappingSpan : overlappingSpans ) {
699+ int overlappingPriority = sb .getSpanFlags (overlappingSpan ) & Spanned .SPAN_PRIORITY ;
700+ if (!isEquivalentToAttributes .test (overlappingSpan ) && priority < overlappingPriority ) {
678701 continue outerLoop ;
679702 }
680703 }
@@ -683,7 +706,11 @@ private void stripAttributeEquivalentSpans(SpannableStringBuilder sb) {
683706 }
684707 }
685708
686- private void unstripAttributeEquivalentSpans (SpannableStringBuilder workingText ) {
709+ /**
710+ * Copy back styles represented as attributes to the underlying span, for later measurement
711+ * outside the ReactEditText.
712+ */
713+ private void restoreStyleEquivalentSpans (SpannableStringBuilder workingText ) {
687714 int spanFlags = Spannable .SPAN_INCLUSIVE_INCLUSIVE ;
688715
689716 // Set all bits for SPAN_PRIORITY so that this span has the highest possible priority
@@ -1122,7 +1149,7 @@ private void updateCachedSpannable(boolean resetStyles) {
11221149 // - android.app.Activity.dispatchKeyEvent (Activity.java:3447)
11231150 try {
11241151 sb .append (currentText .subSequence (0 , currentText .length ()));
1125- unstripAttributeEquivalentSpans (sb );
1152+ restoreStyleEquivalentSpans (sb );
11261153 } catch (IndexOutOfBoundsException e ) {
11271154 ReactSoftExceptionLogger .logSoftException (TAG , e );
11281155 }
0 commit comments