// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.payments.ui;
import android.text.TextUtils;
import android.util.Pair;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.preferences.autofill.AutofillProfileBridge.DropdownKeyValue;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Representation of a single input text field in an editor. Can be used, for example, for a phone
* input field.
*/
public class EditorFieldModel {
/**
* The interface to be implemented by the field validator.
*/
public interface EditorFieldValidator {
/**
* Called to check the validity of the field value.
*
* @param value The value of the field to check.
* @return True if the value is valid.
*/
boolean isValid(@Nullable CharSequence value);
}
private static final int INPUT_TYPE_HINT_MIN_INCLUSIVE = 0;
/** Text input with no special formatting rules, e.g., a city, a suburb, or a company name. */
private static final int INPUT_TYPE_HINT_NONE = 0;
/** Indicates a phone field. */
public static final int INPUT_TYPE_HINT_PHONE = 1;
/** Indicates an email field. */
public static final int INPUT_TYPE_HINT_EMAIL = 2;
/** Indicates a multi-line address field that may include numbers. */
public static final int INPUT_TYPE_HINT_STREET_LINES = 3;
/** Indicates a person's name. */
public static final int INPUT_TYPE_HINT_PERSON_NAME = 4;
/** Indicates a region or an administrative area, e.g., a state or a province. */
public static final int INPUT_TYPE_HINT_REGION = 5;
/** Indicates an alpha-numeric value, e.g., postal code or sorting code. */
public static final int INPUT_TYPE_HINT_ALPHA_NUMERIC = 6;
/** Indicates a credit card input. */
public static final int INPUT_TYPE_HINT_CREDIT_CARD = 7;
private static final int INPUT_TYPE_HINT_MAX_TEXT_INPUT_EXCLUSIVE = 8;
/** Indicates a dropdown. */
public static final int INPUT_TYPE_HINT_DROPDOWN = 8;
/** Indicates a list of icons. */
public static final int INPUT_TYPE_HINT_ICONS = 9;
/** Indicates a checkbox. */
public static final int INPUT_TYPE_HINT_CHECKBOX = 10;
/**
* Indicates a label, e.g., for a server credit card.
*
* TOP_LABEL
* MID_LABEL [ICON]
* BOTTOM_LABEL
*
* Example:
*
* Visa***1234
* First Last [VISA]
* Exp: 03/2021
*/
public static final int INPUT_TYPE_HINT_LABEL = 11;
private static final int INPUT_TYPE_HINT_MAX_EXCLUSIVE = 12;
private final int mInputTypeHint;
@Nullable private List<Integer> mIconResourceIds;
@Nullable private List<Integer> mIconDescriptionsForAccessibility;
@Nullable private List<DropdownKeyValue> mDropdownKeyValues;
@Nullable private Set<String> mDropdownKeys;
@Nullable private List<CharSequence> mSuggestions;
@Nullable private EditorFieldValidator mValidator;
@Nullable private CharSequence mRequiredErrorMessage;
@Nullable private CharSequence mInvalidErrorMessage;
@Nullable private CharSequence mErrorMessage;
@Nullable private CharSequence mLabel;
@Nullable private CharSequence mMidLabel;
@Nullable private CharSequence mBottomLabel;
@Nullable private CharSequence mValue;
@Nullable private Callback<Pair<String, Runnable>> mDropdownCallback;
@Nullable private Runnable mIconAction;
private int mLabelIconResourceId;
private int mActionIconResourceId;
private int mActionDescriptionForAccessibility;
private boolean mIsChecked = false;
private boolean mIsFullLine = true;
/**
* Constructs a label to show in the editor. This can be, for example, description of a server
* credit card and its icon. Layout:
*
* topLabel
* midLabel iconId
* bottomLabel
*
* @param topLabel Top label.
* @param midLabel Middle label.
* @param bottomLabel Bottom label.
* @param iconId Icon.
*/
public static EditorFieldModel createLabel(
CharSequence topLabel, CharSequence midLabel, CharSequence bottomLabel, int iconId) {
assert topLabel != null;
assert midLabel != null;
assert bottomLabel != null;
EditorFieldModel result = new EditorFieldModel(INPUT_TYPE_HINT_LABEL);
result.mLabel = topLabel;
result.mMidLabel = midLabel;
result.mBottomLabel = bottomLabel;
result.mLabelIconResourceId = iconId;
return result;
}
/**
* Constructs a checkbox to show in the editor. It's unchecked by default.
*
* @param checkboxLabel The label for the checkbox.
*/
public static EditorFieldModel createCheckbox(CharSequence checkboxLabel) {
assert checkboxLabel != null;
EditorFieldModel result = new EditorFieldModel(INPUT_TYPE_HINT_CHECKBOX);
result.mLabel = checkboxLabel;
return result;
}
/**
* Constructs a list of icons to show in the editor. This can be, for example, the list of
* accepted credit cards.
*
* @param label The label for the icons.
* @param iconIds The list of drawable resources to display, in this order.
* @param descIds The list of string identifiers for descriptions of the icons. This is for
* accessibility.
*/
public static EditorFieldModel createIconList(CharSequence label, List<Integer> iconIds,
List<Integer> descIds) {
assert label != null;
assert iconIds != null;
assert descIds != null;
EditorFieldModel result = new EditorFieldModel(INPUT_TYPE_HINT_ICONS);
result.mLabel = label;
result.mIconResourceIds = iconIds;
result.mIconDescriptionsForAccessibility = descIds;
return result;
}
/**
* Constructs a dropdown field model.
*
* @param label The human-readable label for user to understand the type of data
* that should be entered into this field.
* @param dropdownKeyValues The keyed values to display in the dropdown.
*/
public static EditorFieldModel createDropdown(
@Nullable CharSequence label, List<DropdownKeyValue> dropdownKeyValues) {
assert dropdownKeyValues != null;
EditorFieldModel result = new EditorFieldModel(INPUT_TYPE_HINT_DROPDOWN);
result.mLabel = label;
result.mDropdownKeyValues = dropdownKeyValues;
result.mDropdownKeys = new HashSet<>();
for (int i = 0; i < result.mDropdownKeyValues.size(); i++) {
result.mDropdownKeys.add(result.mDropdownKeyValues.get(i).getKey());
}
return result;
}
/**
* Constructs a dropdown field model with a validator.
*
* @param label The human-readable label for user to understand the type of data
* that should be entered into this field.
* @param dropdownKeyValues The keyed values to display in the dropdown.
* @param validator The validator for the values in this field.
* @param requiredErrorMessage The error message that indicates to the user that they
* cannot leave this field empty.
*/
public static EditorFieldModel createDropdown(
@Nullable CharSequence label, List<DropdownKeyValue> dropdownKeyValues,
EditorFieldValidator validator,
CharSequence invalidErrorMessage) {
assert dropdownKeyValues != null;
assert validator != null;
assert invalidErrorMessage != null;
EditorFieldModel result = createDropdown(label, dropdownKeyValues);
result.mValidator = validator;
result.mInvalidErrorMessage = invalidErrorMessage;
return result;
}
/** Constructs a text input field model without any special text formatting hints. */
public static EditorFieldModel createTextInput() {
return new EditorFieldModel(INPUT_TYPE_HINT_NONE);
}
/**
* Constructs a text input field model.
*
* @param inputTypeHint The type of input. For example, INPUT_TYPE_HINT_PHONE.
*/
public static EditorFieldModel createTextInput(int inputTypeHint) {
EditorFieldModel result = new EditorFieldModel(inputTypeHint);
assert result.isTextField();
return result;
}
/**
* Constructs a text input field model.
*
* @param inputTypeHint The type of input. For example, INPUT_TYPE_HINT_PHONE.
* @param label The human-readable label for user to understand the type of data
* that should be entered into this field.
* @param suggestions Optional set of values to suggest to the user.
* @param validator Optional validator for the values in this field.
* @param requiredErrorMessage The optional error message that indicates to the user that they
* cannot leave this field empty.
* @param invalidErrorMessage The optional error message that indicates to the user that the
* value they have entered is not valid.
* @param value Optional initial value of this field.
*/
public static EditorFieldModel createTextInput(int inputTypeHint, CharSequence label,
@Nullable Set<CharSequence> suggestions, @Nullable EditorFieldValidator validator,
@Nullable CharSequence requiredErrorMessage, @Nullable CharSequence invalidErrorMessage,
@Nullable CharSequence value) {
assert label != null;
EditorFieldModel result = new EditorFieldModel(inputTypeHint);
assert result.isTextField();
result.mSuggestions = suggestions == null ? null : new ArrayList<CharSequence>(suggestions);
result.mValidator = validator;
result.mInvalidErrorMessage = invalidErrorMessage;
result.mRequiredErrorMessage = requiredErrorMessage;
result.mLabel = label;
result.mValue = value;
return result;
}
/**
* Adds an icon to a text input field. The icon can be tapped to perform an action, e.g., launch
* a credit card scanner.
*
* @param icon The drawable resource for the icon.
* @param description The string resource for the human readable description of the action.
* @param action The callback to invoke when the icon is tapped.
*/
public void addActionIcon(int icon, int description, Runnable action) {
assert isTextField();
mActionIconResourceId = icon;
mActionDescriptionForAccessibility = description;
mIconAction = action;
}
private EditorFieldModel(int inputTypeHint) {
assert isTextField();
mInputTypeHint = inputTypeHint;
}
/** @return The action icon resource identifier, for example, R.drawable.ocr_card. */
public int getActionIconResourceId() {
assert isTextField();
return mActionIconResourceId;
}
/** @return The string resource for the human readable description of the action. */
public int getActionDescriptionForAccessibility() {
assert isTextField();
return mActionDescriptionForAccessibility;
}
/** @return The action to invoke when the icon has been tapped. */
public Runnable getIconAction() {
assert isTextField();
return mIconAction;
}
private boolean isTextField() {
return mInputTypeHint >= INPUT_TYPE_HINT_MIN_INCLUSIVE
&& mInputTypeHint < INPUT_TYPE_HINT_MAX_TEXT_INPUT_EXCLUSIVE;
}
/** @return The type of input, for example, INPUT_TYPE_HINT_PHONE. */
public int getInputTypeHint() {
return mInputTypeHint;
}
/** @return Whether the checkbox is checked. */
public boolean isChecked() {
assert mInputTypeHint == INPUT_TYPE_HINT_CHECKBOX;
return mIsChecked;
}
/** Sets the checkbox state. */
public void setIsChecked(boolean isChecked) {
assert mInputTypeHint == INPUT_TYPE_HINT_CHECKBOX;
mIsChecked = isChecked;
}
/** @return The list of icons resource identifiers to display. */
public List<Integer> getIconResourceIds() {
assert mInputTypeHint == INPUT_TYPE_HINT_ICONS;
return mIconResourceIds;
}
/** @return The list of string identifiers of the descriptions of the displayed icons. This is
* for the screen reader. */
public List<Integer> getIconDescriptionsForAccessibility() {
assert mInputTypeHint == INPUT_TYPE_HINT_ICONS;
return mIconDescriptionsForAccessibility;
}
/** @return The dropdown key-value pairs. */
public List<DropdownKeyValue> getDropdownKeyValues() {
assert mInputTypeHint == INPUT_TYPE_HINT_DROPDOWN;
return mDropdownKeyValues;
}
/** @return The dropdown keys. */
public Set<String> getDropdownKeys() {
assert mInputTypeHint == INPUT_TYPE_HINT_DROPDOWN;
return mDropdownKeys;
}
/** Updates the dropdown key values. */
public void setDropdownKeyValues(List<DropdownKeyValue> dropdownKeyValues) {
assert mInputTypeHint == INPUT_TYPE_HINT_DROPDOWN;
mDropdownKeyValues = dropdownKeyValues;
}
/** @return The human-readable label for this field. */
public CharSequence getLabel() {
return mLabel;
}
/** @return The human-readable mid-level label for this field. */
public CharSequence getMidLabel() {
assert mInputTypeHint == INPUT_TYPE_HINT_LABEL;
return mMidLabel;
}
/** @return The human-readable lower-level label for this field. */
public CharSequence getBottomLabel() {
assert mInputTypeHint == INPUT_TYPE_HINT_LABEL;
return mBottomLabel;
}
/** @return The icon to show next to the label. */
public int getLabelIconResourceId() {
assert mInputTypeHint == INPUT_TYPE_HINT_LABEL;
return mLabelIconResourceId;
}
/**
* Updates the label.
*
* @param label The new label to use.
*/
public void setLabel(CharSequence label) {
mLabel = label;
}
/** @return Suggested values for this field. Can be null. */
@Nullable public List<CharSequence> getSuggestions() {
return mSuggestions;
}
/** @return The error message for the last validation. Can be null if no error was reported. */
@Nullable public CharSequence getErrorMessage() {
return mErrorMessage;
}
/** @return The value that the user has typed into the field or the key of the value that the
* user has selected in the dropdown. Can be null. */
@Nullable public CharSequence getValue() {
return mValue;
}
/**
* Updates the value of this field. Does not trigger validation or update the last error
* message. Can be called on a dropdown to initialize it, but will not fire the dropdown
* callback.
*
* @param value The new value that the user has typed in or the initial key for the dropdown.
*/
public void setValue(@Nullable CharSequence userTypedValueOrInitialDropdownKey) {
mValue = userTypedValueOrInitialDropdownKey;
}
/**
* Updates the dropdown selection and fires the dropdown callback.
*
* @param key The new dropdown key.
* @param callback The callback to invoke when the change has been processed.
*/
public void setDropdownKey(String key, Runnable callback) {
assert mInputTypeHint == INPUT_TYPE_HINT_DROPDOWN;
mValue = key;
if (mDropdownCallback != null) {
mDropdownCallback.onResult(new Pair<String, Runnable>(key, callback));
}
}
/** @return Whether or not the field is required. */
public boolean isRequired() {
return !TextUtils.isEmpty(mRequiredErrorMessage);
}
/**
* Updates the required error message.
*
* @param message The error message to use if this field is required, but empty. If null, then
* this field is optional.
*/
public void setRequiredErrorMessage(@Nullable CharSequence message) {
mRequiredErrorMessage = message;
}
/**
* Returns true if the field value is valid. Also updates the error message.
*
* @return Whether the field value is valid.
*/
public boolean isValid() {
if (isRequired()
&& (TextUtils.isEmpty(mValue) || TextUtils.getTrimmedLength(mValue) == 0)) {
mErrorMessage = mRequiredErrorMessage;
return false;
}
if (mValidator != null && !mValidator.isValid(mValue)) {
mErrorMessage = mInvalidErrorMessage;
return false;
}
mErrorMessage = null;
return true;
}
/**
* Sets the dropdown callback.
*
* @param callback The callback to invoke when the dropdown selection has changed. The first
* element in the callback pair is the dropdown key. The second element is the
* callback to invoke after the dropdown change has been processed.
*/
public void setDropdownCallback(Callback<Pair<String, Runnable>> callback) {
assert mInputTypeHint == INPUT_TYPE_HINT_DROPDOWN;
mDropdownCallback = callback;
}
/** @return True if the input field should take up the full line, instead of sharing with other
* input fields. This is the default.*/
public boolean isFullLine() {
return mIsFullLine;
}
/**
* Sets whether this input field should take up the full line. All fields take up the full line
* by default.
*
* @param isFullLine Whether the input field should take up the full line.
*/
public void setIsFullLine(boolean isFullLine) {
mIsFullLine = isFullLine;
}
}