/**
* 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 android.text.Spannable;
import android.text.TextUtils;
import android.view.Gravity;
import android.widget.TextView;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.ThemedReactContext;
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.ReactPropGroup;
import com.facebook.yoga.YogaConstants;
import javax.annotation.Nullable;
/**
* Manages instances of spannable {@link TextView}.
*
* This is a "shadowing" view manager, which means that the {@link NativeViewHierarchyManager} will
* not manage children of native {@link TextView} instances returned by this manager. Instead we use
* @{link ReactTextShadowNode} hierarchy to calculate a {@link Spannable} text representing the
* whole text subtree.
*/
@ReactModule(name = ReactTextViewManager.REACT_CLASS)
public class ReactTextViewManager extends BaseViewManager<ReactTextView, ReactTextShadowNode> {
@VisibleForTesting
public static final String REACT_CLASS = "RCTText";
private static final int[] SPACING_TYPES = {
Spacing.ALL, Spacing.LEFT, Spacing.RIGHT, Spacing.TOP, Spacing.BOTTOM,
};
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public ReactTextView createViewInstance(ThemedReactContext context) {
return new ReactTextView(context);
}
// maxLines can only be set in master view (block), doesn't really make sense to set in a span
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = ViewDefaults.NUMBER_OF_LINES)
public void setNumberOfLines(ReactTextView view, int numberOfLines) {
view.setNumberOfLines(numberOfLines);
}
@ReactProp(name = ViewProps.ELLIPSIZE_MODE)
public void setEllipsizeMode(ReactTextView view, @Nullable String ellipsizeMode) {
if (ellipsizeMode == null || ellipsizeMode.equals("tail")) {
view.setEllipsizeLocation(TextUtils.TruncateAt.END);
} else if (ellipsizeMode.equals("head")) {
view.setEllipsizeLocation(TextUtils.TruncateAt.START);
} else if (ellipsizeMode.equals("middle")) {
view.setEllipsizeLocation(TextUtils.TruncateAt.MIDDLE);
} else {
throw new JSApplicationIllegalArgumentException("Invalid ellipsizeMode: " + ellipsizeMode);
}
}
@ReactProp(name = ViewProps.TEXT_ALIGN_VERTICAL)
public void setTextAlignVertical(ReactTextView view, @Nullable String textAlignVertical) {
if (textAlignVertical == null || "auto".equals(textAlignVertical)) {
view.setGravityVertical(Gravity.NO_GRAVITY);
} else if ("top".equals(textAlignVertical)) {
view.setGravityVertical(Gravity.TOP);
} else if ("bottom".equals(textAlignVertical)) {
view.setGravityVertical(Gravity.BOTTOM);
} else if ("center".equals(textAlignVertical)) {
view.setGravityVertical(Gravity.CENTER_VERTICAL);
} else {
throw new JSApplicationIllegalArgumentException("Invalid textAlignVertical: " + textAlignVertical);
}
}
@ReactProp(name = "selectable")
public void setSelectable(ReactTextView view, boolean isSelectable) {
view.setTextIsSelectable(isSelectable);
}
@ReactProp(name = "selectionColor", customType = "Color")
public void setSelectionColor(ReactTextView view, @Nullable Integer color) {
if (color == null) {
view.setHighlightColor(DefaultStyleValuesUtil.getDefaultTextColorHighlight(view.getContext()));
} else {
view.setHighlightColor(color);
}
}
@ReactPropGroup(names = {
ViewProps.BORDER_RADIUS,
ViewProps.BORDER_TOP_LEFT_RADIUS,
ViewProps.BORDER_TOP_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_LEFT_RADIUS
}, defaultFloat = YogaConstants.UNDEFINED)
public void setBorderRadius(ReactTextView view, int index, float borderRadius) {
if (!YogaConstants.isUndefined(borderRadius)) {
borderRadius = PixelUtil.toPixelFromDIP(borderRadius);
}
if (index == 0) {
view.setBorderRadius(borderRadius);
} else {
view.setBorderRadius(borderRadius, index - 1);
}
}
@ReactProp(name = "borderStyle")
public void setBorderStyle(ReactTextView view, @Nullable String borderStyle) {
view.setBorderStyle(borderStyle);
}
@ReactPropGroup(names = {
ViewProps.BORDER_WIDTH,
ViewProps.BORDER_LEFT_WIDTH,
ViewProps.BORDER_RIGHT_WIDTH,
ViewProps.BORDER_TOP_WIDTH,
ViewProps.BORDER_BOTTOM_WIDTH,
}, defaultFloat = YogaConstants.UNDEFINED)
public void setBorderWidth(ReactTextView view, int index, float width) {
if (!YogaConstants.isUndefined(width)) {
width = PixelUtil.toPixelFromDIP(width);
}
view.setBorderWidth(SPACING_TYPES[index], width);
}
@ReactPropGroup(names = {
"borderColor", "borderLeftColor", "borderRightColor", "borderTopColor", "borderBottomColor"
}, customType = "Color")
public void setBorderColor(ReactTextView view, int index, Integer color) {
float rgbComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int)color & 0x00FFFFFF);
float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int)color >>> 24);
view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent);
}
@ReactProp(name = "includeFontPadding", defaultBoolean = true)
public void setIncludeFontPadding(ReactTextView view, boolean includepad) {
view.setIncludeFontPadding(includepad);
}
@Override
public void updateExtraData(ReactTextView view, Object extraData) {
ReactTextUpdate update = (ReactTextUpdate) extraData;
if (update.containsImages()) {
Spannable spannable = update.getText();
TextInlineImageSpan.possiblyUpdateInlineImageSpans(spannable, view);
}
view.setText(update);
}
@Override
public ReactTextShadowNode createShadowNodeInstance() {
return new ReactTextShadowNode();
}
@Override
public Class<ReactTextShadowNode> getShadowNodeClass() {
return ReactTextShadowNode.class;
}
@Override
protected void onAfterUpdateTransaction(ReactTextView view) {
super.onAfterUpdateTransaction(view);
view.updateView();
}
}