diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLetterSpacingSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLetterSpacingSpan.java index 3b9cf58e33d..d537cd5dccc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLetterSpacingSpan.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomLetterSpacingSpan.java @@ -37,6 +37,10 @@ public class CustomLetterSpacingSpan extends MetricAffectingSpan implements Reac apply(paint); } + public float getSpacing() { + return mLetterSpacing; + } + private void apply(TextPaint paint) { if (!Float.isNaN(mLetterSpacing)) { paint.setLetterSpacing(mLetterSpacing); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomStyleSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomStyleSpan.java index b249126cf95..7866390bfa0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomStyleSpan.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomStyleSpan.java @@ -71,6 +71,10 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan { return mFontFamily; } + public @Nullable String getFontFeatureSettings() { + return mFeatureSettings; + } + private static void apply( Paint paint, int style, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java index f1591a5fe35..82c9f5d75ac 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java @@ -31,8 +31,6 @@ public class ReactTextUpdate { private final int mSelectionEnd; private final int mJustificationMode; - public boolean mContainsMultipleFragments; - /** * @deprecated Use a non-deprecated constructor for ReactTextUpdate instead. This one remains * because it's being used by a unit test that isn't currently open source. @@ -142,13 +140,11 @@ public class ReactTextUpdate { int jsEventCounter, int textAlign, int textBreakStrategy, - int justificationMode, - boolean containsMultipleFragments) { + int justificationMode) { ReactTextUpdate reactTextUpdate = new ReactTextUpdate( text, jsEventCounter, false, textAlign, textBreakStrategy, justificationMode); - reactTextUpdate.mContainsMultipleFragments = containsMultipleFragments; return reactTextUpdate; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java index ecbf0d45489..d86cf9a095a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java @@ -106,6 +106,11 @@ public class TextLayoutManagerMapBuffer { MapBuffer fragment = fragments.getMapBuffer((short) 0); MapBuffer textAttributes = fragment.getMapBuffer(FR_KEY_TEXT_ATTRIBUTES); + + if (!textAttributes.contains(TextAttributeProps.TA_KEY_LAYOUT_DIRECTION)) { + return false; + } + return TextAttributeProps.getLayoutDirection( textAttributes.getString(TextAttributeProps.TA_KEY_LAYOUT_DIRECTION)) == LayoutDirection.RTL; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK index c3930446837..ef99a74bf37 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK @@ -9,7 +9,8 @@ rn_android_library( "pfh:ReactNative_CommonInfrastructurePlaceholder", "supermodule:xplat/default/public.react_native.infra", ], - language = "JAVA", + language = "KOTLIN", + pure_kotlin = False, required_for_source_only_abi = True, visibility = [ "PUBLIC", @@ -29,7 +30,9 @@ rn_android_library( react_native_target("java/com/facebook/react/views/imagehelper:imagehelper"), react_native_target("java/com/facebook/react/views/scroll:scroll"), react_native_target("java/com/facebook/react/views/text:text"), + react_native_target("java/com/facebook/react/common/mapbuffer:mapbuffer"), react_native_target("java/com/facebook/react/views/view:view"), + react_native_target("java/com/facebook/react/config:config"), ], exported_deps = [ react_native_dep("third-party/android/androidx:appcompat"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index 77ab6bb365b..b078c8f87b4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -11,6 +11,8 @@ import static com.facebook.react.uimanager.UIManagerHelper.getReactContext; import static com.facebook.react.views.text.TextAttributeProps.UNSET; import android.content.Context; +import android.graphics.Color; +import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; @@ -50,15 +52,19 @@ import com.facebook.react.views.text.CustomLetterSpacingSpan; import com.facebook.react.views.text.CustomLineHeightSpan; import com.facebook.react.views.text.CustomStyleSpan; import com.facebook.react.views.text.ReactAbsoluteSizeSpan; +import com.facebook.react.views.text.ReactBackgroundColorSpan; +import com.facebook.react.views.text.ReactForegroundColorSpan; import com.facebook.react.views.text.ReactSpan; +import com.facebook.react.views.text.ReactStrikethroughSpan; import com.facebook.react.views.text.ReactTextUpdate; import com.facebook.react.views.text.ReactTypefaceUtils; +import com.facebook.react.views.text.ReactUnderlineSpan; import com.facebook.react.views.text.TextAttributes; import com.facebook.react.views.text.TextInlineImageSpan; import com.facebook.react.views.text.TextLayoutManager; import com.facebook.react.views.view.ReactViewBackgroundManager; import java.util.ArrayList; -import java.util.List; +import java.util.Objects; /** * A wrapper around the EditText that lets us better control what happens when an EditText gets @@ -82,7 +88,6 @@ public class ReactEditText extends AppCompatEditText // *TextChanged events should be triggered. This is less expensive than removing the text // listeners and adding them back again after the text change is completed. protected boolean mIsSettingTextFromJS; - protected boolean mIsSettingTextFromCacheUpdate = false; private int mDefaultGravityHorizontal; private int mDefaultGravityVertical; @@ -363,7 +368,7 @@ public class ReactEditText extends AppCompatEditText } super.onSelectionChanged(selStart, selEnd); - if (!mIsSettingTextFromCacheUpdate && mSelectionWatcher != null && hasFocus()) { + if (mSelectionWatcher != null && hasFocus()) { mSelectionWatcher.onSelectionChanged(selStart, selEnd); } } @@ -482,6 +487,14 @@ public class ReactEditText extends AppCompatEditText } } + @Override + public void setFontFeatureSettings(String fontFeatureSettings) { + if (!Objects.equals(fontFeatureSettings, getFontFeatureSettings())) { + super.setFontFeatureSettings(fontFeatureSettings); + mTypefaceDirty = true; + } + } + public void maybeUpdateTypeface() { if (!mTypefaceDirty) { return; @@ -493,6 +506,17 @@ public class ReactEditText extends AppCompatEditText ReactTypefaceUtils.applyStyles( getTypeface(), mFontStyle, mFontWeight, mFontFamily, getContext().getAssets()); setTypeface(newTypeface); + + // Match behavior of CustomStyleSpan and enable SUBPIXEL_TEXT_FLAG when setting anything + // nonstandard + if (mFontStyle != UNSET + || mFontWeight != UNSET + || mFontFamily != null + || getFontFeatureSettings() != null) { + setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG); + } else { + setPaintFlags(getPaintFlags() & (~Paint.SUBPIXEL_TEXT_FLAG)); + } } // VisibleForTesting from {@link TextInputEventsTestCase}. @@ -554,10 +578,8 @@ public class ReactEditText extends AppCompatEditText SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(reactTextUpdate.getText()); - manageSpans(spannableStringBuilder, reactTextUpdate.mContainsMultipleFragments); - - // Mitigation for https://github.com/facebook/react-native/issues/35936 (S318090) - stripAbsoluteSizeSpans(spannableStringBuilder); + manageSpans(spannableStringBuilder); + stripStyleEquivalentSpans(spannableStringBuilder); mContainsImages = reactTextUpdate.containsImages(); @@ -585,7 +607,7 @@ public class ReactEditText extends AppCompatEditText } // Update cached spans (in Fabric only). - updateCachedSpannable(false); + updateCachedSpannable(); } /** @@ -594,8 +616,7 @@ public class ReactEditText extends AppCompatEditText * will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes * them. */ - private void manageSpans( - SpannableStringBuilder spannableStringBuilder, boolean skipAddSpansForMeasurements) { + private void manageSpans(SpannableStringBuilder spannableStringBuilder) { Object[] spans = getText().getSpans(0, length(), Object.class); for (int spanIdx = 0; spanIdx < spans.length; spanIdx++) { Object span = spans[spanIdx]; @@ -623,33 +644,170 @@ public class ReactEditText extends AppCompatEditText spannableStringBuilder.setSpan(span, spanStart, spanEnd, spanFlags); } } + } - // In Fabric only, apply necessary styles to entire span - // If the Spannable was constructed from multiple fragments, we don't apply any spans that could - // impact the whole Spannable, because that would override "local" styles per-fragment - if (!skipAddSpansForMeasurements) { - addSpansForMeasurement(getText()); + // TODO: Replace with Predicate and lambdas once Java 8 builds in OSS + interface SpanPredicate { + boolean test(T span); + } + + /** + * Remove spans from the SpannableStringBuilder which can be represented by TextAppearance + * attributes on the underlying EditText. This works around instability on Samsung devices with + * the presence of spans https://github.com/facebook/react-native/issues/35936 (S318090) + */ + private void stripStyleEquivalentSpans(SpannableStringBuilder sb) { + stripSpansOfKind( + sb, + ReactAbsoluteSizeSpan.class, + new SpanPredicate() { + @Override + public boolean test(ReactAbsoluteSizeSpan span) { + return span.getSize() == mTextAttributes.getEffectiveFontSize(); + } + }); + + stripSpansOfKind( + sb, + ReactBackgroundColorSpan.class, + new SpanPredicate() { + @Override + public boolean test(ReactBackgroundColorSpan span) { + return span.getBackgroundColor() == mReactBackgroundManager.getBackgroundColor(); + } + }); + + stripSpansOfKind( + sb, + ReactForegroundColorSpan.class, + new SpanPredicate() { + @Override + public boolean test(ReactForegroundColorSpan span) { + return span.getForegroundColor() == getCurrentTextColor(); + } + }); + + stripSpansOfKind( + sb, + ReactStrikethroughSpan.class, + new SpanPredicate() { + @Override + public boolean test(ReactStrikethroughSpan span) { + return (getPaintFlags() & Paint.STRIKE_THRU_TEXT_FLAG) != 0; + } + }); + + stripSpansOfKind( + sb, + ReactUnderlineSpan.class, + new SpanPredicate() { + @Override + public boolean test(ReactUnderlineSpan span) { + return (getPaintFlags() & Paint.UNDERLINE_TEXT_FLAG) != 0; + } + }); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + stripSpansOfKind( + sb, + CustomLetterSpacingSpan.class, + new SpanPredicate() { + @Override + public boolean test(CustomLetterSpacingSpan span) { + return span.getSpacing() == mTextAttributes.getEffectiveLetterSpacing(); + } + }); + } + + stripSpansOfKind( + sb, + CustomStyleSpan.class, + new SpanPredicate() { + @Override + public boolean test(CustomStyleSpan span) { + return span.getStyle() == mFontStyle + && Objects.equals(span.getFontFamily(), mFontFamily) + && span.getWeight() == mFontWeight + && Objects.equals(span.getFontFeatureSettings(), getFontFeatureSettings()); + } + }); + } + + private void stripSpansOfKind( + SpannableStringBuilder sb, Class clazz, SpanPredicate shouldStrip) { + T[] spans = sb.getSpans(0, sb.length(), clazz); + + for (T span : spans) { + if (shouldStrip.test(span)) { + sb.removeSpan(span); + } } } - private void stripAbsoluteSizeSpans(SpannableStringBuilder sb) { - // We have already set a font size on the EditText itself. We can safely remove sizing spans - // which are the same as the set font size, and not otherwise overlapped. - final int effectiveFontSize = mTextAttributes.getEffectiveFontSize(); - ReactAbsoluteSizeSpan[] spans = sb.getSpans(0, sb.length(), ReactAbsoluteSizeSpan.class); + /** + * Copy styles represented as attributes to the underlying span, for later measurement or other + * usage outside the ReactEditText. + */ + private void addSpansFromStyleAttributes(SpannableStringBuilder workingText) { + int spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE; - outerLoop: - for (ReactAbsoluteSizeSpan span : spans) { - ReactAbsoluteSizeSpan[] overlappingSpans = - sb.getSpans(sb.getSpanStart(span), sb.getSpanEnd(span), ReactAbsoluteSizeSpan.class); + // Set all bits for SPAN_PRIORITY so that this span has the highest possible priority + // (least precedence). This ensures the span is behind any overlapping spans. + spanFlags |= Spannable.SPAN_PRIORITY; - for (ReactAbsoluteSizeSpan overlappingSpan : overlappingSpans) { - if (span.getSize() != effectiveFontSize) { - continue outerLoop; - } + workingText.setSpan( + new ReactAbsoluteSizeSpan(mTextAttributes.getEffectiveFontSize()), + 0, + workingText.length(), + spanFlags); + + workingText.setSpan( + new ReactForegroundColorSpan(getCurrentTextColor()), 0, workingText.length(), spanFlags); + + int backgroundColor = mReactBackgroundManager.getBackgroundColor(); + if (backgroundColor != Color.TRANSPARENT) { + workingText.setSpan( + new ReactBackgroundColorSpan(backgroundColor), 0, workingText.length(), spanFlags); + } + + if ((getPaintFlags() & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { + workingText.setSpan(new ReactStrikethroughSpan(), 0, workingText.length(), spanFlags); + } + + if ((getPaintFlags() & Paint.UNDERLINE_TEXT_FLAG) != 0) { + workingText.setSpan(new ReactUnderlineSpan(), 0, workingText.length(), spanFlags); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + float effectiveLetterSpacing = mTextAttributes.getEffectiveLetterSpacing(); + if (!Float.isNaN(effectiveLetterSpacing)) { + workingText.setSpan( + new CustomLetterSpacingSpan(effectiveLetterSpacing), + 0, + workingText.length(), + spanFlags); } + } - sb.removeSpan(span); + if (mFontStyle != UNSET + || mFontWeight != UNSET + || mFontFamily != null + || getFontFeatureSettings() != null) { + workingText.setSpan( + new CustomStyleSpan( + mFontStyle, + mFontWeight, + getFontFeatureSettings(), + mFontFamily, + getContext().getAssets()), + 0, + workingText.length(), + spanFlags); + } + + float lineHeight = mTextAttributes.getEffectiveLineHeight(); + if (!Float.isNaN(lineHeight)) { + workingText.setSpan(new CustomLineHeightSpan(lineHeight), 0, workingText.length(), spanFlags); } } @@ -669,73 +827,6 @@ public class ReactEditText extends AppCompatEditText return true; } - // This is hacked in for Fabric. When we delete non-Fabric code, we might be able to simplify or - // clean this up a bit. - private void addSpansForMeasurement(Spannable spannable) { - if (!mFabricViewStateManager.hasStateWrapper()) { - return; - } - - boolean originalDisableTextDiffing = mDisableTextDiffing; - mDisableTextDiffing = true; - - int start = 0; - int end = spannable.length(); - - // Remove duplicate spans we might add here - Object[] spans = spannable.getSpans(0, length(), Object.class); - for (Object span : spans) { - int spanFlags = spannable.getSpanFlags(span); - boolean isInclusive = - (spanFlags & Spanned.SPAN_INCLUSIVE_INCLUSIVE) == Spanned.SPAN_INCLUSIVE_INCLUSIVE - || (spanFlags & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) == Spanned.SPAN_INCLUSIVE_EXCLUSIVE; - if (isInclusive - && span instanceof ReactSpan - && spannable.getSpanStart(span) == start - && spannable.getSpanEnd(span) == end) { - spannable.removeSpan(span); - } - } - - List ops = new ArrayList<>(); - - if (!Float.isNaN(mTextAttributes.getLetterSpacing())) { - ops.add( - new TextLayoutManager.SetSpanOperation( - start, end, new CustomLetterSpacingSpan(mTextAttributes.getLetterSpacing()))); - } - ops.add( - new TextLayoutManager.SetSpanOperation( - start, end, new ReactAbsoluteSizeSpan((int) mTextAttributes.getEffectiveFontSize()))); - if (mFontStyle != UNSET || mFontWeight != UNSET || mFontFamily != null) { - ops.add( - new TextLayoutManager.SetSpanOperation( - start, - end, - new CustomStyleSpan( - mFontStyle, - mFontWeight, - null, // TODO: do we need to support FontFeatureSettings / fontVariant? - mFontFamily, - getReactContext(ReactEditText.this).getAssets()))); - } - if (!Float.isNaN(mTextAttributes.getEffectiveLineHeight())) { - ops.add( - new TextLayoutManager.SetSpanOperation( - start, end, new CustomLineHeightSpan(mTextAttributes.getEffectiveLineHeight()))); - } - - int priority = 0; - for (TextLayoutManager.SetSpanOperation op : ops) { - // Actual order of calling {@code execute} does NOT matter, - // but the {@code priority} DOES matter. - op.execute(spannable, priority); - priority++; - } - - mDisableTextDiffing = originalDisableTextDiffing; - } - protected boolean showSoftKeyboard() { return mInputMethodManager.showSoftInput(this, 0); } @@ -1000,7 +1091,9 @@ public class ReactEditText extends AppCompatEditText float effectiveLetterSpacing = mTextAttributes.getEffectiveLetterSpacing(); if (!Float.isNaN(effectiveLetterSpacing)) { - setLetterSpacing(effectiveLetterSpacing); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setLetterSpacing(effectiveLetterSpacing); + } } } @@ -1015,7 +1108,7 @@ public class ReactEditText extends AppCompatEditText * TextLayoutManager.java with some very minor modifications. There's some duplication between * here and TextLayoutManager, so there might be an opportunity for refactor. */ - private void updateCachedSpannable(boolean resetStyles) { + private void updateCachedSpannable() { // Noops in non-Fabric if (mFabricViewStateManager == null || !mFabricViewStateManager.hasStateWrapper()) { return; @@ -1025,12 +1118,6 @@ public class ReactEditText extends AppCompatEditText return; } - if (resetStyles) { - mIsSettingTextFromCacheUpdate = true; - addSpansForMeasurement(getText()); - mIsSettingTextFromCacheUpdate = false; - } - Editable currentText = getText(); boolean haveText = currentText != null && currentText.length() > 0; @@ -1088,11 +1175,9 @@ public class ReactEditText extends AppCompatEditText // Measure something so we have correct height, even if there's no string. sb.append("I"); } - - // Make sure that all text styles are applied when we're measurable the hint or "blank" text - addSpansForMeasurement(sb); } + addSpansFromStyleAttributes(sb); TextLayoutManager.setCachedSpannabledForTag(getId(), sb); } @@ -1107,7 +1192,7 @@ public class ReactEditText extends AppCompatEditText private class TextWatcherDelegator implements TextWatcher { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { - if (!mIsSettingTextFromCacheUpdate && !mIsSettingTextFromJS && mListeners != null) { + if (!mIsSettingTextFromJS && mListeners != null) { for (TextWatcher listener : mListeners) { listener.beforeTextChanged(s, start, count, after); } @@ -1121,23 +1206,20 @@ public class ReactEditText extends AppCompatEditText TAG, "onTextChanged[" + getId() + "]: " + s + " " + start + " " + before + " " + count); } - if (!mIsSettingTextFromCacheUpdate) { - if (!mIsSettingTextFromJS && mListeners != null) { - for (TextWatcher listener : mListeners) { - listener.onTextChanged(s, start, before, count); - } + if (!mIsSettingTextFromJS && mListeners != null) { + for (TextWatcher listener : mListeners) { + listener.onTextChanged(s, start, before, count); } - - updateCachedSpannable( - !mIsSettingTextFromJS && !mIsSettingTextFromState && start == 0 && before == 0); } + updateCachedSpannable(); + onContentSizeChange(); } @Override public void afterTextChanged(Editable s) { - if (!mIsSettingTextFromCacheUpdate && !mIsSettingTextFromJS && mListeners != null) { + if (!mIsSettingTextFromJS && mListeners != null) { for (TextWatcher listener : mListeners) { listener.afterTextChanged(s); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index f3134f9cb1d..d34961afda5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -13,6 +13,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.graphics.BlendMode; import android.graphics.BlendModeColorFilter; +import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Build; @@ -44,6 +45,8 @@ import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.common.MapBuilder; +import com.facebook.react.common.mapbuffer.MapBuffer; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.BaseViewManager; import com.facebook.react.uimanager.FabricViewStateManager; @@ -66,9 +69,11 @@ import com.facebook.react.views.text.DefaultStyleValuesUtil; import com.facebook.react.views.text.ReactBaseTextShadowNode; import com.facebook.react.views.text.ReactTextUpdate; import com.facebook.react.views.text.ReactTextViewManagerCallback; +import com.facebook.react.views.text.ReactTypefaceUtils; import com.facebook.react.views.text.TextAttributeProps; import com.facebook.react.views.text.TextInlineImageSpan; import com.facebook.react.views.text.TextLayoutManager; +import com.facebook.react.views.text.TextLayoutManagerMapBuffer; import com.facebook.react.views.text.TextTransform; import com.facebook.yoga.YogaConstants; import java.lang.reflect.Field; @@ -83,6 +88,12 @@ public class ReactTextInputManager extends BaseViewManager 1; - int textBreakStrategy = TextAttributeProps.getTextBreakStrategy(paragraphAttributes.getString("textBreakStrategy")); @@ -1313,7 +1347,37 @@ public class ReactTextInputManager extends BaseViewManager component. * Represents what to render and how to render. diff --git a/ReactCommon/react/renderer/components/text/conversions.h b/ReactCommon/react/renderer/components/text/conversions.h index 712b8f2fde2..a4a90aa37c9 100644 --- a/ReactCommon/react/renderer/components/text/conversions.h +++ b/ReactCommon/react/renderer/components/text/conversions.h @@ -26,21 +26,13 @@ inline folly::dynamic toDynamic(ParagraphState const ¶graphState) { return newState; } -// constants for Text State serialization -constexpr static MapBuffer::Key TX_STATE_KEY_ATTRIBUTED_STRING = 0; -constexpr static MapBuffer::Key TX_STATE_KEY_PARAGRAPH_ATTRIBUTES = 1; -// Used for TextInput -constexpr static MapBuffer::Key TX_STATE_KEY_HASH = 2; -constexpr static MapBuffer::Key TX_STATE_KEY_MOST_RECENT_EVENT_COUNT = 3; - inline MapBuffer toMapBuffer(ParagraphState const ¶graphState) { auto builder = MapBufferBuilder(); auto attStringMapBuffer = toMapBuffer(paragraphState.attributedString); builder.putMapBuffer(TX_STATE_KEY_ATTRIBUTED_STRING, attStringMapBuffer); auto paMapBuffer = toMapBuffer(paragraphState.paragraphAttributes); builder.putMapBuffer(TX_STATE_KEY_PARAGRAPH_ATTRIBUTES, paMapBuffer); - // TODO: Used for TextInput - builder.putInt(TX_STATE_KEY_HASH, 1234); + builder.putInt(TX_STATE_KEY_HASH, attStringMapBuffer.getInt(AS_KEY_HASH)); return builder.build(); } #endif diff --git a/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.cpp b/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.cpp index a2deb59db33..c57e8b7970f 100644 --- a/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.cpp +++ b/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.cpp @@ -10,6 +10,11 @@ #include #include +#ifdef ANDROID +#include +#include +#endif + #include namespace facebook { @@ -89,6 +94,23 @@ folly::dynamic AndroidTextInputState::getDynamic() const { } return newState; } + +MapBuffer AndroidTextInputState::getMapBuffer() const { + auto builder = MapBufferBuilder(); + // See comment in getDynamic block. + if (cachedAttributedStringId == 0) { + builder.putInt(TX_STATE_KEY_MOST_RECENT_EVENT_COUNT, mostRecentEventCount); + + auto attStringMapBuffer = toMapBuffer(attributedString); + builder.putMapBuffer(TX_STATE_KEY_ATTRIBUTED_STRING, attStringMapBuffer); + auto paMapBuffer = toMapBuffer(paragraphAttributes); + builder.putMapBuffer(TX_STATE_KEY_PARAGRAPH_ATTRIBUTES, paMapBuffer); + + builder.putInt(TX_STATE_KEY_HASH, attStringMapBuffer.getInt(AS_KEY_HASH)); + } + return builder.build(); +} + #endif } // namespace react diff --git a/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.h b/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.h index 4dc50ac9410..f46d6829e1a 100644 --- a/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.h +++ b/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputState.h @@ -94,9 +94,7 @@ class AndroidTextInputState final { AndroidTextInputState const &previousState, folly::dynamic const &data); folly::dynamic getDynamic() const; - MapBuffer getMapBuffer() const { - return MapBufferBuilder::EMPTY(); - }; + MapBuffer getMapBuffer() const; }; } // namespace react