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 <Text> 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
This commit is contained in:
Valentin Shergin 2017-09-11 15:44:38 -07:00 committed by Facebook Github Bot
parent 393bf88be6
commit 80027ce6db
5 changed files with 125 additions and 59 deletions

View File

@ -9,16 +9,19 @@
package com.facebook.react.views.text; package com.facebook.react.views.text;
import android.view.View;
import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext; 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 * Manages raw text nodes (aka {@code textContent} in terms of DOM).
* operation will throw an {@link IllegalStateException} * 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) @ReactModule(name = ReactRawTextManager.REACT_CLASS)
public class ReactRawTextManager extends ReactTextViewManager { public class ReactRawTextManager extends ViewManager<View, ReactRawTextShadowNode> {
@VisibleForTesting @VisibleForTesting
public static final String REACT_CLASS = "RCTRawText"; public static final String REACT_CLASS = "RCTRawText";
@ -30,15 +33,19 @@ public class ReactRawTextManager extends ReactTextViewManager {
@Override @Override
public ReactTextView createViewInstance(ThemedReactContext context) { 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 @Override
public void updateExtraData(ReactTextView view, Object extraData) { public void updateExtraData(View view, Object extraData) {}
@Override
public Class<ReactRawTextShadowNode> getShadowNodeClass() {
return ReactRawTextShadowNode.class;
} }
@Override @Override
public ReactTextShadowNode createShadowNodeInstance() { public ReactRawTextShadowNode createShadowNodeInstance() {
return new ReactVirtualTextShadowNode(); return new ReactRawTextShadowNode();
} }
} }

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
*
* <p>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;
}
}

View File

@ -9,11 +9,6 @@
package com.facebook.react.views.text; package com.facebook.react.views.text;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.Build; import android.os.Build;
import android.text.BoringLayout; import android.text.BoringLayout;
@ -30,27 +25,28 @@ import android.text.style.StrikethroughSpan;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
import android.view.Gravity; import android.view.Gravity;
import android.widget.TextView; 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.infer.annotation.Assertions;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.LayoutShadowNode; 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.PixelUtil;
import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.uimanager.UIViewOperationQueue;
import com.facebook.react.uimanager.ViewDefaults; import com.facebook.react.uimanager.ViewDefaults;
import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp; 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. * {@link ReactShadowNode} class for spannable text view.
@ -107,13 +103,15 @@ public class ReactTextShadowNode extends LayoutShadowNode {
ReactTextShadowNode textShadowNode, ReactTextShadowNode textShadowNode,
SpannableStringBuilder sb, SpannableStringBuilder sb,
List<SetSpanOperation> ops) { List<SetSpanOperation> ops) {
int start = sb.length(); int start = sb.length();
if (textShadowNode.mText != null) {
sb.append(textShadowNode.mText);
}
for (int i = 0, length = textShadowNode.getChildCount(); i < length; i++) { for (int i = 0, length = textShadowNode.getChildCount(); i < length; i++) {
ReactShadowNode child = textShadowNode.getChildAt(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); buildSpannedFromShadowNode((ReactTextShadowNode) child, sb, ops);
} else if (child instanceof ReactTextInlineImageShadowNode) { } else if (child instanceof ReactTextInlineImageShadowNode) {
// We make the image take up 1 character in the span and put a corresponding character into // 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(); SpannableStringBuilder sb = new SpannableStringBuilder();
// TODO(5837930): Investigate whether it's worth optimizing this part and do it if so // 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 // 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 // a new spannable will be wiped out
List<SetSpanOperation> ops = new ArrayList<>(); List<SetSpanOperation> ops = new ArrayList<>();
buildSpannedFromShadowNode(textShadowNode, sb, ops); buildSpannedFromShadowNode(textShadowNode, sb, ops);
if (text != null) {
sb.append(text);
}
if (textShadowNode.mFontSize == UNSET) { if (textShadowNode.mFontSize == UNSET) {
sb.setSpan( sb.setSpan(
new AbsoluteSizeSpan(textShadowNode.mAllowFontScaling new AbsoluteSizeSpan(textShadowNode.mAllowFontScaling
@ -375,7 +380,6 @@ public class ReactTextShadowNode extends LayoutShadowNode {
* <Text style={{fontFamily="serif}}>Bold Text</Text> * <Text style={{fontFamily="serif}}>Bold Text</Text>
*/ */
private @Nullable String mFontFamily = null; private @Nullable String mFontFamily = null;
private @Nullable String mText = null;
private @Nullable Spannable mPreparedSpannableText; private @Nullable Spannable mPreparedSpannableText;
@ -415,7 +419,7 @@ public class ReactTextShadowNode extends LayoutShadowNode {
if (isVirtual()) { if (isVirtual()) {
return; return;
} }
mPreparedSpannableText = spannedFromShadowNode(this); mPreparedSpannableText = spannedFromShadowNode(this, null);
markUpdated(); 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) @ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = UNSET)
public void setNumberOfLines(int numberOfLines) { public void setNumberOfLines(int numberOfLines) {
mNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines; mNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines;

View File

@ -11,13 +11,14 @@ package com.facebook.react.views.text;
import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule; 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 * Manages raw text nodes. Since they are used only as a virtual nodes any type of native view
* operation will throw an {@link IllegalStateException} * operation will throw an {@link IllegalStateException}
*/ */
@ReactModule(name = ReactVirtualTextViewManager.REACT_CLASS) @ReactModule(name = ReactVirtualTextViewManager.REACT_CLASS)
public class ReactVirtualTextViewManager extends ReactRawTextManager { public class ReactVirtualTextViewManager extends ReactTextViewManager {
@VisibleForTesting @VisibleForTesting
public static final String REACT_CLASS = "RCTVirtualText"; public static final String REACT_CLASS = "RCTVirtualText";
@ -26,4 +27,17 @@ public class ReactVirtualTextViewManager extends ReactRawTextManager {
public String getName() { public String getName() {
return REACT_CLASS; 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();
}
} }

View File

@ -9,21 +9,11 @@
package com.facebook.react.views.textinput; package com.facebook.react.views.textinput;
import javax.annotation.Nullable;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import android.os.Build; import android.os.Build;
import android.text.Layout; import android.text.Layout;
import android.text.Spannable;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText; 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.infer.annotation.Assertions;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.common.annotations.VisibleForTesting; 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.ThemedReactContext;
import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.uimanager.UIViewOperationQueue;
import com.facebook.react.uimanager.ViewDefaults; import com.facebook.react.uimanager.ViewDefaults;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp; 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.ReactTextShadowNode;
import com.facebook.react.views.text.ReactTextUpdate; 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 @VisibleForTesting
public class ReactTextInputShadowNode extends ReactTextShadowNode implements public class ReactTextInputShadowNode extends ReactTextShadowNode implements
@ -45,6 +39,10 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements
private @Nullable EditText mEditText; private @Nullable EditText mEditText;
private int mMostRecentEventCount = UNSET; private int mMostRecentEventCount = UNSET;
@VisibleForTesting public static final String PROP_TEXT = "text";
private @Nullable String mText = null;
public ReactTextInputShadowNode() { public ReactTextInputShadowNode() {
mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ?
0 : Layout.BREAK_STRATEGY_SIMPLE; 0 : Layout.BREAK_STRATEGY_SIMPLE;
@ -114,6 +112,16 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements
mMostRecentEventCount = mostRecentEventCount; mMostRecentEventCount = mostRecentEventCount;
} }
@ReactProp(name = PROP_TEXT)
public void setText(@Nullable String text) {
mText = text;
markUpdated();
}
public @Nullable String getText() {
return mText;
}
@Override @Override
public void setTextBreakStrategy(@Nullable String textBreakStrategy) { public void setTextBreakStrategy(@Nullable String textBreakStrategy) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
@ -136,10 +144,9 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements
super.onCollectExtraUpdates(uiViewOperationQueue); super.onCollectExtraUpdates(uiViewOperationQueue);
if (mMostRecentEventCount != UNSET) { if (mMostRecentEventCount != UNSET) {
Spannable preparedSpannableText = spannedFromShadowNode(this);
ReactTextUpdate reactTextUpdate = ReactTextUpdate reactTextUpdate =
new ReactTextUpdate( new ReactTextUpdate(
preparedSpannableText, spannedFromShadowNode(this, getText()),
mMostRecentEventCount, mMostRecentEventCount,
mContainsImages, mContainsImages,
getPadding(Spacing.LEFT), getPadding(Spacing.LEFT),
@ -147,8 +154,7 @@ public class ReactTextInputShadowNode extends ReactTextShadowNode implements
getPadding(Spacing.RIGHT), getPadding(Spacing.RIGHT),
getPadding(Spacing.BOTTOM), getPadding(Spacing.BOTTOM),
mTextAlign, mTextAlign,
mTextBreakStrategy mTextBreakStrategy);
);
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
} }
} }