@@ -585,9 +585,7 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
585
585
new SpannableStringBuilder (reactTextUpdate .getText ());
586
586
587
587
manageSpans (spannableStringBuilder , reactTextUpdate .mContainsMultipleFragments );
588
-
589
- // Mitigation for https://github.com/facebook/react-native/issues/35936 (S318090)
590
- stripAttributeEquivalentSpans (spannableStringBuilder );
588
+ stripStyleEquivalentSpans (spannableStringBuilder );
591
589
592
590
mContainsImages = reactTextUpdate .containsImages ();
593
591
@@ -662,28 +660,44 @@ private void manageSpans(
662
660
}
663
661
}
664
662
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
+ }
670
684
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 );
675
688
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 );
680
692
}
681
-
682
- sb .removeSpan (span );
683
693
}
684
694
}
685
695
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 ) {
687
701
int spanFlags = Spannable .SPAN_INCLUSIVE_INCLUSIVE ;
688
702
689
703
// Set all bits for SPAN_PRIORITY so that this span has the highest possible priority
@@ -1122,7 +1136,7 @@ private void updateCachedSpannable(boolean resetStyles) {
1122
1136
// - android.app.Activity.dispatchKeyEvent (Activity.java:3447)
1123
1137
try {
1124
1138
sb .append (currentText .subSequence (0 , currentText .length ()));
1125
- unstripAttributeEquivalentSpans (sb );
1139
+ restoreStyleEquivalentSpans (sb );
1126
1140
} catch (IndexOutOfBoundsException e ) {
1127
1141
ReactSoftExceptionLogger .logSoftException (TAG , e );
1128
1142
}
0 commit comments