From 80027ce6dba1b632b080dc17a08b9f904d649217 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Mon, 11 Sep 2017 15:44:38 -0700 Subject: [PATCH] ReactRawTextShadowNode does not inherit ReactTextShadowNode anymore Summary: ReactRawTextShadowNode represents "raw" text node (aka textContent), so it cannot have layout or styling, it is just a line of text, a pure string. So, we must not interit it from HUGE ReactTextShadowNode (which represents node with ReactRawTextShadowNode instance inside). We need this change to make future fancy (and valuable from product perspective) refactoring possible. Stay tuned! Reviewed By: achen1 Differential Revision: D5712961 fbshipit-source-id: 009e48046bdf34ddfd40b93175934969af64b98b --- .../react/views/text/ReactRawTextManager.java | 21 +++++--- .../views/text/ReactRawTextShadowNode.java | 41 ++++++++++++++ .../react/views/text/ReactTextShadowNode.java | 52 +++++++++--------- .../text/ReactVirtualTextViewManager.java | 16 +++++- .../textinput/ReactTextInputShadowNode.java | 54 ++++++++++--------- 5 files changed, 125 insertions(+), 59 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextShadowNode.java 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); } }