package carbon.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.os.Build;
import android.support.annotation.NonNull;
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import carbon.Carbon;
import carbon.R;
import carbon.animation.AnimUtils;
import carbon.drawable.DefaultAccentColorStateList;
import carbon.drawable.DefaultTextSecondaryColorStateList;
import carbon.internal.TypefaceUtils;
public class InputLayout extends RelativeLayout {
public enum LabelStyle {
Floating, Persistent, Hint, IfNotEmpty;
}
public enum ErrorMode {
WhenInvalid, Always, Never;
}
private boolean inDrawableStateChanged = false;
private TextView errorTextView;
ErrorMode errorMode = ErrorMode.WhenInvalid;
boolean required = false;
private String label;
private int minCharacters;
private int maxCharacters = Integer.MAX_VALUE;
private TextView counterTextView;
private LabelStyle labelStyle;
private TextView labelTextView;
private ImageView clearImageView;
private ImageView showPasswordImageView;
private View child;
TransformationMethod transformationMethod;
public InputLayout(Context context) {
super(context);
initInputLayout(null, R.attr.carbon_inputLayoutStyle);
}
public InputLayout(Context context, AttributeSet attrs) {
super(Carbon.getThemedContext(context, attrs, R.styleable.InputLayout, R.attr.carbon_inputLayoutStyle, R.styleable.InputLayout_carbon_theme), attrs);
initInputLayout(attrs, R.attr.carbon_inputLayoutStyle);
}
public InputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(Carbon.getThemedContext(context, attrs, R.styleable.InputLayout, defStyleAttr, R.styleable.InputLayout_carbon_theme), attrs, defStyleAttr);
initInputLayout(attrs, defStyleAttr);
}
public InputLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(Carbon.getThemedContext(context, attrs, R.styleable.InputLayout, defStyleAttr, R.styleable.InputLayout_carbon_theme), attrs, defStyleAttr, defStyleRes);
initInputLayout(attrs, defStyleAttr);
}
private void initInputLayout(AttributeSet attrs, int defStyleAttr) {
View.inflate(getContext(), R.layout.carbon_inputlayout, this);
errorTextView = (TextView) findViewById(R.id.carbon_error);
errorTextView.setTextColor(new DefaultAccentColorStateList(getContext()));
errorTextView.setValid(false);
counterTextView = (TextView) findViewById(R.id.carbon_counter);
counterTextView.setTextColor(new DefaultTextSecondaryColorStateList(getContext()));
labelTextView = (TextView) findViewById(R.id.carbon_label);
labelTextView.setTextColor(new DefaultAccentColorStateList(getContext()));
clearImageView = (ImageView) findViewById(R.id.carbon_clear);
showPasswordImageView = (ImageView) findViewById(R.id.carbon_showPassword);
setAddStatesFromChildren(true);
if (attrs == null)
return;
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.InputLayout, defStyleAttr, 0);
for (int i = 0; i < a.getIndexCount(); i++) {
int attr = a.getIndex(i);
if (!isInEditMode() && attr == R.styleable.InputLayout_carbon_errorFontPath) {
String path = a.getString(attr);
Typeface typeface = TypefaceUtils.getTypeface(getContext(), path);
setErrorTypeface(typeface);
} else if (attr == R.styleable.InputLayout_carbon_errorTextSize) {
setErrorTextSize(a.getDimension(attr, 0));
} else if (attr == R.styleable.InputLayout_carbon_errorFontFamily) {
int textStyle = a.getInt(R.styleable.InputLayout_android_textStyle, 0);
Typeface typeface = TypefaceUtils.getTypeface(getContext(), a.getString(attr), textStyle);
setErrorTypeface(typeface);
} else if (!isInEditMode() && attr == R.styleable.InputLayout_carbon_labelFontPath) {
String path = a.getString(attr);
Typeface typeface = TypefaceUtils.getTypeface(getContext(), path);
setLabelTypeface(typeface);
} else if (attr == R.styleable.InputLayout_carbon_counterTextSize) {
setCounterTextSize(a.getDimension(attr, 0));
} else if (attr == R.styleable.InputLayout_carbon_labelFontFamily) {
int textStyle = a.getInt(R.styleable.InputLayout_android_textStyle, 0);
Typeface typeface = TypefaceUtils.getTypeface(getContext(), a.getString(attr), textStyle);
setLabelTypeface(typeface);
} else if (!isInEditMode() && attr == R.styleable.InputLayout_carbon_counterFontPath) {
String path = a.getString(attr);
Typeface typeface = TypefaceUtils.getTypeface(getContext(), path);
setCounterTypeface(typeface);
} else if (attr == R.styleable.InputLayout_carbon_labelTextSize) {
setLabelTextSize(a.getDimension(attr, 0));
} else if (attr == R.styleable.InputLayout_carbon_counterFontFamily) {
int textStyle = a.getInt(R.styleable.InputLayout_android_textStyle, 0);
Typeface typeface = TypefaceUtils.getTypeface(getContext(), a.getString(attr), textStyle);
setCounterTypeface(typeface);
}
}
String error = a.getString(R.styleable.InputLayout_carbon_error);
setError(error == null ? a.getString(R.styleable.InputLayout_carbon_errorMessage) : error);
setErrorMode(ErrorMode.values()[a.getInt(R.styleable.InputLayout_carbon_errorMode, ErrorMode.WhenInvalid.ordinal())]);
setMinCharacters(a.getInt(R.styleable.InputLayout_carbon_minCharacters, 0));
setMaxCharacters(a.getInt(R.styleable.InputLayout_carbon_maxCharacters, Integer.MAX_VALUE));
setLabelStyle(LabelStyle.values()[a.getInt(R.styleable.InputLayout_carbon_labelStyle, LabelStyle.Floating.ordinal())]);
setLabel(a.getString(R.styleable.InputLayout_carbon_label));
setRequired(a.getBoolean(R.styleable.InputLayout_carbon_required, false));
setShowPasswordButtonEnabled(a.getBoolean(R.styleable.InputLayout_carbon_showPassword, false));
setClearButtonEnabled(a.getBoolean(R.styleable.InputLayout_carbon_showClear, false));
setGravity(a.getInt(R.styleable.InputLayout_android_gravity, Gravity.START));
a.recycle();
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (!"inputLayout".equals(child.getTag())) {
params = setTextView(child, (android.widget.RelativeLayout.LayoutParams) params);
super.addView(child, 1, params);
} else {
// Carry on adding the View...
super.addView(child, index, params);
}
}
private ViewGroup.LayoutParams setTextView(View child, android.widget.RelativeLayout.LayoutParams params) {
this.child = child;
if (child.getId() == NO_ID)
child.setId(R.id.carbon_input);
params.addRule(BELOW, R.id.carbon_label);
android.widget.RelativeLayout.LayoutParams errorTextViewLayoutParams = (android.widget.RelativeLayout.LayoutParams) errorTextView.getLayoutParams();
errorTextViewLayoutParams.addRule(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ? ALIGN_START : ALIGN_LEFT, child.getId());
errorTextViewLayoutParams.addRule(BELOW, child.getId());
android.widget.RelativeLayout.LayoutParams counterTextViewLayoutParams = (android.widget.RelativeLayout.LayoutParams) counterTextView.getLayoutParams();
counterTextViewLayoutParams.addRule(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ? ALIGN_END : ALIGN_RIGHT, child.getId());
counterTextViewLayoutParams.addRule(BELOW, child.getId());
android.widget.RelativeLayout.LayoutParams clearImageViewLayoutParams = (android.widget.RelativeLayout.LayoutParams) clearImageView.getLayoutParams();
clearImageViewLayoutParams.addRule(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ? ALIGN_END : ALIGN_RIGHT, child.getId());
clearImageViewLayoutParams.addRule(ALIGN_BASELINE, child.getId());
android.widget.RelativeLayout.LayoutParams showPasswordImageViewLayoutParams = (android.widget.RelativeLayout.LayoutParams) showPasswordImageView.getLayoutParams();
showPasswordImageViewLayoutParams.addRule(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ? ALIGN_END : ALIGN_RIGHT, child.getId());
showPasswordImageViewLayoutParams.addRule(ALIGN_BASELINE, child.getId());
if (child instanceof EditText) {
final EditText editText = (EditText) child;
if (labelTextView.getText().length() == 0)
labelTextView.setText(editText.getHint());
editText.addOnValidateListener(valid -> updateError(editText, valid));
showPasswordImageView.setOnTouchListener((view, motionEvent) -> {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
transformationMethod = editText.getTransformationMethod();
editText.setTransformationMethod(null);
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
editText.setTransformationMethod(transformationMethod);
}
return true;
});
clearImageView.setOnClickListener(view -> editText.setText(""));
labelTextView.setInAnimator(null);
labelTextView.setOutAnimator(null);
errorTextView.setInAnimator(null);
errorTextView.setOutAnimator(null);
updateError(editText, editText.isValid());
updateHint(editText);
updateCounter(editText);
labelTextView.setInAnimator(AnimUtils.getFlyInAnimator());
labelTextView.setOutAnimator(AnimUtils.getFadeOutAnimator());
errorTextView.setInAnimator(AnimUtils.getFadeInAnimator());
errorTextView.setOutAnimator(AnimUtils.getFadeOutAnimator());
} else if (child instanceof InputView) {
InputView inputView = (InputView) child;
inputView.addOnValidateListener(valid -> updateError(inputView, valid));
labelTextView.setInAnimator(null);
labelTextView.setOutAnimator(null);
errorTextView.setInAnimator(null);
errorTextView.setOutAnimator(null);
updateError(inputView, inputView.isValid());
updateHint(child);
labelTextView.setInAnimator(AnimUtils.getFlyInAnimator());
labelTextView.setOutAnimator(AnimUtils.getFadeOutAnimator());
errorTextView.setInAnimator(AnimUtils.getFadeInAnimator());
errorTextView.setOutAnimator(AnimUtils.getFadeOutAnimator());
}
return params;
}
@Override
protected void drawableStateChanged() {
if (inDrawableStateChanged)
return;
inDrawableStateChanged = true;
super.drawableStateChanged();
updateHint(child);
inDrawableStateChanged = false;
}
private void updateError(ValidStateView validStateView, boolean valid) {
boolean requiredError = required && validStateView.isEmpty();
labelTextView.setValid(!requiredError);
errorTextView.animateVisibility(errorMode == ErrorMode.Always || errorMode == ErrorMode.WhenInvalid && !valid ? VISIBLE : errorMode == ErrorMode.Never ? GONE : INVISIBLE);
}
private void updateCounter(EditText editText) {
boolean counterError = (minCharacters > 0 && editText.length() < minCharacters || maxCharacters < Integer.MAX_VALUE && editText.length() > maxCharacters);
counterTextView.setValid(!counterError);
if (minCharacters > 0 && maxCharacters < Integer.MAX_VALUE) {
counterTextView.setVisibility(VISIBLE);
counterTextView.setText(editText.length() + " / " + minCharacters + "-" + maxCharacters);
} else if (minCharacters > 0) {
counterTextView.setVisibility(VISIBLE);
counterTextView.setText(editText.length() + " / " + minCharacters + "+");
} else if (maxCharacters < Integer.MAX_VALUE) {
counterTextView.setVisibility(VISIBLE);
counterTextView.setText(editText.length() + " / " + maxCharacters);
} else {
counterTextView.setVisibility(GONE);
}
}
private void updateHint(View child) {
if (child == null) {
labelTextView.setVisibility(GONE);
return;
}
if (labelStyle == LabelStyle.Persistent || labelStyle == LabelStyle.Floating && child.isFocused() ||
labelStyle == LabelStyle.IfNotEmpty && (child.isFocused() || child instanceof android.widget.TextView && ((android.widget.TextView) child).getText().length() > 0)) {
labelTextView.animateVisibility(VISIBLE);
if (child instanceof EditText)
((EditText) child).setHint(null);
} else if (labelStyle != LabelStyle.Hint) {
labelTextView.animateVisibility(INVISIBLE);
if (child instanceof EditText)
((EditText) child).setHint(label + (required ? "*" : ""));
} else {
labelTextView.setVisibility(GONE);
}
}
public boolean isRequired() {
return required;
}
/**
* Sets it the underlying InputView has to be not empty. Adds an asterisk to hint text and label
* text
*
* @param required
*/
public void setRequired(boolean required) {
this.required = required;
updateHint(child);
}
public void setError(String text) {
errorTextView.setText(text);
}
/**
* Changes error message mode. Nothing will be shown until error text is set. Can be set from
* xml using `carbon_errorMode`.
*
* @param errorMode error message mode
*/
public void setErrorMode(@NonNull ErrorMode errorMode) {
this.errorMode = errorMode;
errorTextView.setVisibility(errorMode == ErrorMode.WhenInvalid ? INVISIBLE : errorMode == ErrorMode.Always ? VISIBLE : GONE);
}
/**
* Deprecated use {@link carbon.widget.InputLayout.setErrorMode} instead.
*
* @param errorVisible true to make error messages show when the underlying InputView is
* invalid
*/
@Deprecated
public void setErrorEnabled(boolean errorVisible) {
setErrorMode(errorVisible ? ErrorMode.WhenInvalid : ErrorMode.Never);
}
public float getErrorTextSize() {
return errorTextView.getTextSize();
}
public void setErrorTextSize(float errorTextSize) {
errorTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, errorTextSize);
}
public float getCounterTextSize() {
return counterTextView.getTextSize();
}
public void setCounterTextSize(float counterTextSize) {
counterTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, counterTextSize);
}
public float getLabelTextSize() {
return labelTextView.getTextSize();
}
public void setLabelTextSize(float labelTextSize) {
labelTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize);
}
public Typeface getErrorTypeface() {
return errorTextView.getTypeface();
}
public void setErrorTypeface(Typeface errorTypeface) {
errorTextView.setTypeface(errorTypeface);
}
public Typeface getCounterTypeface() {
return counterTextView.getTypeface();
}
public void setCounterTypeface(Typeface counterTypeface) {
counterTextView.setTypeface(counterTypeface);
}
public Typeface getLabelTypeface() {
return labelTextView.getTypeface();
}
public void setLabelTypeface(Typeface labelTypeface) {
labelTextView.setTypeface(labelTypeface);
}
public String getLabel() {
return labelTextView.getText().toString();
}
public void setLabel(String label) {
this.label = label;
labelTextView.setText(label + (required ? "*" : ""));
if (child != null)
updateHint(child);
}
public LabelStyle getLabelStyle() {
return labelStyle;
}
public void setLabelStyle(LabelStyle labelStyle) {
this.labelStyle = labelStyle;
if (child != null)
updateHint(child);
}
public int getMinCharacters() {
return minCharacters;
}
public void setMinCharacters(int minCharacters) {
this.minCharacters = minCharacters;
}
public int getMaxCharacters() {
return maxCharacters;
}
public void setMaxCharacters(int maxCharacters) {
this.maxCharacters = maxCharacters;
}
public boolean isShowPasswordButtonEnabled() {
return showPasswordImageView.getVisibility() == VISIBLE;
}
public void setShowPasswordButtonEnabled(boolean b) {
showPasswordImageView.setVisibility(b ? VISIBLE : GONE);
if (b)
setClearButtonEnabled(false);
}
public boolean isClearButtonEnabled() {
return clearImageView.getVisibility() == VISIBLE;
}
public void setClearButtonEnabled(boolean b) {
clearImageView.setVisibility(b ? VISIBLE : GONE);
if (b)
setShowPasswordButtonEnabled(false);
}
@Override
public int getBaseline() {
if (child == null)
return super.getBaseline();
return (labelTextView.getVisibility() != GONE ? labelTextView.getMeasuredHeight() + 1 : 0) + child.getBaseline();
}
@Override
public void setGravity(int gravity) {
super.setGravity(gravity);
labelTextView.setGravity(gravity);
}
}