diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextManager.java index 339496f2a65..35bdf2a3c32 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextManager.java @@ -9,16 +9,19 @@ package com.facebook.react.views.text; +import android.view.View; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManager; /** - * Manages raw text nodes. Since they are used only as a virtual nodes any type of native view - * operation will throw an {@link IllegalStateException} + * Manages raw text nodes (aka {@code textContent} in terms of DOM). + * Since they are used only as a virtual nodes, any type of native view + * operation will throw an {@link IllegalStateException}. */ @ReactModule(name = ReactRawTextManager.REACT_CLASS) -public class ReactRawTextManager extends ReactTextViewManager { +public class ReactRawTextManager extends ViewManager { @VisibleForTesting public static final String REACT_CLASS = "RCTRawText"; @@ -30,15 +33,19 @@ public class ReactRawTextManager extends ReactTextViewManager { @Override public ReactTextView createViewInstance(ThemedReactContext context) { - throw new IllegalStateException("RKRawText doesn't map into a native view"); + throw new IllegalStateException("Attempt to create a native view for RCTRawText"); } @Override - public void updateExtraData(ReactTextView view, Object extraData) { + public void updateExtraData(View view, Object extraData) {} + + @Override + public Class getShadowNodeClass() { + return ReactRawTextShadowNode.class; } @Override - public ReactTextShadowNode createShadowNodeInstance() { - return new ReactVirtualTextShadowNode(); + public ReactRawTextShadowNode createShadowNodeInstance() { + return new ReactRawTextShadowNode(); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextShadowNode.java new file mode 100644 index 00000000000..b97a5bf544a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextShadowNode.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + * + *

This source code is licensed under the BSD-style license found in the LICENSE file in the root + * directory of this source tree. An additional grant of patent rights can be found in the PATENTS + * file in the same directory. + */ +package com.facebook.react.views.text; + +import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.react.uimanager.ReactShadowNode; +import com.facebook.react.uimanager.annotations.ReactProp; +import javax.annotation.Nullable; + +/** + * {@link ReactShadowNode} class for pure raw text node + * (aka {@code textContent} in terms of DOM). + * Raw text node can only have simple string value without any attributes, + * properties or state. + */ +public class ReactRawTextShadowNode extends ReactShadowNode { + + @VisibleForTesting public static final String PROP_TEXT = "text"; + + private @Nullable String mText = null; + + @ReactProp(name = PROP_TEXT) + public void setText(@Nullable String text) { + mText = text; + markUpdated(); + } + + public @Nullable String getText() { + return mText; + } + + @Override + public boolean isVirtual() { + return true; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index 4d05948d1dc..e95f5080e3e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -9,11 +9,6 @@ package com.facebook.react.views.text; -import javax.annotation.Nullable; - -import java.util.ArrayList; -import java.util.List; - import android.graphics.Typeface; import android.os.Build; import android.text.BoringLayout; @@ -30,27 +25,28 @@ import android.text.style.StrikethroughSpan; import android.text.style.UnderlineSpan; import android.view.Gravity; import android.widget.TextView; - -import com.facebook.yoga.YogaDirection; -import com.facebook.yoga.YogaConstants; -import com.facebook.yoga.YogaMeasureMode; -import com.facebook.yoga.YogaMeasureFunction; -import com.facebook.yoga.YogaNode; -import com.facebook.yoga.YogaMeasureOutput; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.LayoutShadowNode; -import com.facebook.react.uimanager.ReactShadowNode; -import com.facebook.react.uimanager.Spacing; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ReactShadowNode; +import com.facebook.react.uimanager.Spacing; import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.uimanager.ViewDefaults; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.yoga.YogaConstants; +import com.facebook.yoga.YogaDirection; +import com.facebook.yoga.YogaMeasureFunction; +import com.facebook.yoga.YogaMeasureMode; +import com.facebook.yoga.YogaMeasureOutput; +import com.facebook.yoga.YogaNode; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; /** * {@link ReactShadowNode} class for spannable text view. @@ -107,13 +103,15 @@ public class ReactTextShadowNode extends LayoutShadowNode { ReactTextShadowNode textShadowNode, SpannableStringBuilder sb, List ops) { + int start = sb.length(); - if (textShadowNode.mText != null) { - sb.append(textShadowNode.mText); - } + for (int i = 0, length = textShadowNode.getChildCount(); i < length; i++) { ReactShadowNode child = textShadowNode.getChildAt(i); - if (child instanceof ReactTextShadowNode) { + + if (child instanceof ReactRawTextShadowNode) { + sb.append(((ReactRawTextShadowNode) child).getText()); + } else if (child instanceof ReactTextShadowNode) { buildSpannedFromShadowNode((ReactTextShadowNode) child, sb, ops); } else if (child instanceof ReactTextInlineImageShadowNode) { // We make the image take up 1 character in the span and put a corresponding character into @@ -182,8 +180,10 @@ public class ReactTextShadowNode extends LayoutShadowNode { } } - protected static Spannable spannedFromShadowNode(ReactTextShadowNode textShadowNode) { + protected static Spannable spannedFromShadowNode( + ReactTextShadowNode textShadowNode, String text) { SpannableStringBuilder sb = new SpannableStringBuilder(); + // TODO(5837930): Investigate whether it's worth optimizing this part and do it if so // The {@link SpannableStringBuilder} implementation require setSpan operation to be called @@ -191,6 +191,11 @@ public class ReactTextShadowNode extends LayoutShadowNode { // a new spannable will be wiped out List ops = new ArrayList<>(); buildSpannedFromShadowNode(textShadowNode, sb, ops); + + if (text != null) { + sb.append(text); + } + if (textShadowNode.mFontSize == UNSET) { sb.setSpan( new AbsoluteSizeSpan(textShadowNode.mAllowFontScaling @@ -375,7 +380,6 @@ public class ReactTextShadowNode extends LayoutShadowNode { * Bold Text */ private @Nullable String mFontFamily = null; - private @Nullable String mText = null; private @Nullable Spannable mPreparedSpannableText; @@ -415,7 +419,7 @@ public class ReactTextShadowNode extends LayoutShadowNode { if (isVirtual()) { return; } - mPreparedSpannableText = spannedFromShadowNode(this); + mPreparedSpannableText = spannedFromShadowNode(this, null); markUpdated(); } @@ -428,12 +432,6 @@ public class ReactTextShadowNode extends LayoutShadowNode { } } - @ReactProp(name = PROP_TEXT) - public void setText(@Nullable String text) { - mText = text; - markUpdated(); - } - @ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = UNSET) public void setNumberOfLines(int numberOfLines) { mNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactVirtualTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactVirtualTextViewManager.java index 9fd290107c0..c86439c2b05 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactVirtualTextViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactVirtualTextViewManager.java @@ -11,13 +11,14 @@ package com.facebook.react.views.text; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.uimanager.ThemedReactContext; /** * Manages raw text nodes. Since they are used only as a virtual nodes any type of native view * operation will throw an {@link IllegalStateException} */ @ReactModule(name = ReactVirtualTextViewManager.REACT_CLASS) -public class ReactVirtualTextViewManager extends ReactRawTextManager { +public class ReactVirtualTextViewManager extends ReactTextViewManager { @VisibleForTesting public static final String REACT_CLASS = "RCTVirtualText"; @@ -26,4 +27,17 @@ public class ReactVirtualTextViewManager extends ReactRawTextManager { public String getName() { return REACT_CLASS; } + + @Override + public ReactTextView createViewInstance(ThemedReactContext context) { + throw new IllegalStateException("Attempt to create a native view for RCTVirtualText"); + } + + @Override + public void updateExtraData(ReactTextView view, Object extraData) {} + + @Override + public ReactTextShadowNode createShadowNodeInstance() { + return new ReactVirtualTextShadowNode(); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java index d73a0a9cde6..9fb09471e1f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java @@ -9,21 +9,11 @@ package com.facebook.react.views.textinput; -import javax.annotation.Nullable; -import javax.annotation.OverridingMethodsMustInvokeSuper; - import android.os.Build; import android.text.Layout; -import android.text.Spannable; import android.util.TypedValue; import android.view.ViewGroup; import android.widget.EditText; - -import com.facebook.yoga.YogaDirection; -import com.facebook.yoga.YogaMeasureMode; -import com.facebook.yoga.YogaMeasureFunction; -import com.facebook.yoga.YogaNode; -import com.facebook.yoga.YogaMeasureOutput; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.common.annotations.VisibleForTesting; @@ -32,11 +22,15 @@ import com.facebook.react.uimanager.Spacing; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.uimanager.ViewDefaults; -import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.views.view.MeasureUtil; import com.facebook.react.views.text.ReactTextShadowNode; import com.facebook.react.views.text.ReactTextUpdate; +import com.facebook.react.views.view.MeasureUtil; +import com.facebook.yoga.YogaMeasureFunction; +import com.facebook.yoga.YogaMeasureMode; +import com.facebook.yoga.YogaMeasureOutput; +import com.facebook.yoga.YogaNode; +import javax.annotation.Nullable; @VisibleForTesting public class ReactTextInputShadowNode extends ReactTextShadowNode implements @@ -45,6 +39,10 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements private @Nullable EditText mEditText; private int mMostRecentEventCount = UNSET; + @VisibleForTesting public static final String PROP_TEXT = "text"; + + private @Nullable String mText = null; + public ReactTextInputShadowNode() { mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_SIMPLE; @@ -114,6 +112,16 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements mMostRecentEventCount = mostRecentEventCount; } + @ReactProp(name = PROP_TEXT) + public void setText(@Nullable String text) { + mText = text; + markUpdated(); + } + + public @Nullable String getText() { + return mText; + } + @Override public void setTextBreakStrategy(@Nullable String textBreakStrategy) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { @@ -136,19 +144,17 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements super.onCollectExtraUpdates(uiViewOperationQueue); if (mMostRecentEventCount != UNSET) { - Spannable preparedSpannableText = spannedFromShadowNode(this); ReactTextUpdate reactTextUpdate = - new ReactTextUpdate( - preparedSpannableText, - mMostRecentEventCount, - mContainsImages, - getPadding(Spacing.LEFT), - getPadding(Spacing.TOP), - getPadding(Spacing.RIGHT), - getPadding(Spacing.BOTTOM), - mTextAlign, - mTextBreakStrategy - ); + new ReactTextUpdate( + spannedFromShadowNode(this, getText()), + mMostRecentEventCount, + mContainsImages, + getPadding(Spacing.LEFT), + getPadding(Spacing.TOP), + getPadding(Spacing.RIGHT), + getPadding(Spacing.BOTTOM), + mTextAlign, + mTextBreakStrategy); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); } }