/**
* 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.flat;
import javax.annotation.Nullable;
import android.annotation.TargetApi;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.widget.EditText;
import com.facebook.yoga.YogaMeasureMode;
import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaNodeAPI;
import com.facebook.yoga.YogaMeasureOutput;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.uimanager.PixelUtil;
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.text.ReactTextUpdate;
import com.facebook.react.views.view.MeasureUtil;
import static com.facebook.react.views.text.ReactTextShadowNode.PROP_TEXT;
import static com.facebook.react.views.text.ReactTextShadowNode.UNSET;
public class RCTTextInput extends RCTVirtualText implements AndroidView, YogaMeasureFunction {
@Nullable private String mText;
private int mJsEventCount = UNSET;
private boolean mPaddingChanged = false;
private int mNumberOfLines = UNSET;
private @Nullable EditText mEditText;
public RCTTextInput() {
forceMountToView();
setMeasureFunction(this);
}
@Override
protected void notifyChanged(boolean shouldRemeasure) {
super.notifyChanged(shouldRemeasure);
// needed to trigger onCollectExtraUpdates
markUpdated();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void setThemedContext(ThemedReactContext themedContext) {
super.setThemedContext(themedContext);
mEditText = new EditText(themedContext);
// This is needed to fix an android bug since 4.4.3 which will throw an NPE in measure,
// setting the layoutParams fixes it: https://code.google.com/p/android/issues/detail?id=75877
mEditText.setLayoutParams(
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
setDefaultPadding(Spacing.START, mEditText.getPaddingStart());
setDefaultPadding(Spacing.TOP, mEditText.getPaddingTop());
setDefaultPadding(Spacing.END, mEditText.getPaddingEnd());
setDefaultPadding(Spacing.BOTTOM, mEditText.getPaddingBottom());
}
@Override
public long measure(
YogaNodeAPI node,
float width,
YogaMeasureMode widthMode,
float height,
YogaMeasureMode heightMode) {
// measure() should never be called before setThemedContext()
EditText editText = Assertions.assertNotNull(mEditText);
int fontSize = getFontSize();
editText.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
fontSize == UNSET ?
(int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)) : fontSize);
editText.setPadding(
(int) Math.ceil(getPadding(Spacing.START)),
(int) Math.ceil(getPadding(Spacing.TOP)),
(int) Math.ceil(getPadding(Spacing.END)),
(int) Math.ceil(getPadding(Spacing.BOTTOM)));
if (mNumberOfLines != UNSET) {
editText.setLines(mNumberOfLines);
}
editText.measure(
MeasureUtil.getMeasureSpec(width, widthMode),
MeasureUtil.getMeasureSpec(height, heightMode));
return YogaMeasureOutput.make(editText.getMeasuredWidth(), editText.getMeasuredHeight());
}
@Override
public boolean isVirtual() {
return false;
}
@Override
public boolean isVirtualAnchor() {
return true;
}
@Override
public void setBackgroundColor(int backgroundColor) {
// suppress, this is handled by a ViewManager
}
@Override
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
super.onCollectExtraUpdates(uiViewOperationQueue);
if (mJsEventCount != UNSET) {
ReactTextUpdate reactTextUpdate =
new ReactTextUpdate(
getText(),
mJsEventCount,
false,
getPadding(Spacing.START),
getPadding(Spacing.TOP),
getPadding(Spacing.END),
getPadding(Spacing.BOTTOM),
UNSET);
// TODO: the Float.NaN should be replaced with the real line height see D3592781
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
}
}
@ReactProp(name = "mostRecentEventCount")
public void setMostRecentEventCount(int mostRecentEventCount) {
mJsEventCount = mostRecentEventCount;
}
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = Integer.MAX_VALUE)
public void setNumberOfLines(int numberOfLines) {
mNumberOfLines = numberOfLines;
notifyChanged(true);
}
@ReactProp(name = PROP_TEXT)
public void setText(@Nullable String text) {
mText = text;
notifyChanged(true);
}
@Override
public void setPadding(int spacingType, float padding) {
super.setPadding(spacingType, padding);
mPaddingChanged = true;
dirty();
}
@Override
public boolean isPaddingChanged() {
return mPaddingChanged;
}
@Override
public void resetPaddingChanged() {
mPaddingChanged = false;
}
@Override
boolean shouldAllowEmptySpans() {
return true;
}
@Override
boolean isEditable() {
return true;
}
@Override
protected void performCollectText(SpannableStringBuilder builder) {
if (mText != null) {
builder.append(mText);
}
super.performCollectText(builder);
}
@Override
public boolean needsCustomLayoutForChildren() {
return false;
}
}