Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2035)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java

Issue 2163693002: [Merge M-53] Credit card editor for PaymentRequest UI. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@2785
Patch Set: Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
index dd3ab51f24561ef5d675491b7420371986e75677..5618878d850083e9b138dff569935798870be581 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
@@ -12,17 +12,22 @@ import android.os.AsyncTask;
import android.os.Handler;
import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
import android.telephony.PhoneNumberFormattingTextWatcher;
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.EditorInfo;
-import android.widget.AutoCompleteTextView;
import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
import android.widget.LinearLayout;
+import android.widget.Spinner;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
@@ -30,6 +35,7 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.EmbedContentViewActivity;
import org.chromium.chrome.browser.payments.ui.PaymentRequestUI.PaymentRequestObserverForTest;
+import org.chromium.chrome.browser.preferences.autofill.CreditCardNumberFormattingTextWatcher;
import org.chromium.chrome.browser.widget.AlwaysDismissedDialog;
import org.chromium.chrome.browser.widget.DualControlLayout;
import org.chromium.chrome.browser.widget.FadingShadow;
@@ -37,10 +43,7 @@ import org.chromium.chrome.browser.widget.FadingShadowView;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import java.util.regex.Pattern;
import javax.annotation.Nullable;
@@ -50,6 +53,8 @@ import javax.annotation.Nullable;
*/
public class EditorView extends AlwaysDismissedDialog
implements OnClickListener, DialogInterface.OnDismissListener {
+ /** The indicator for input fields that are required. */
+ public static final String REQUIRED_FIELD_INDICATOR = "*";
/** Help page that the user is directed to when asking for help. */
private static final String HELP_URL = "https://support.google.com/chrome/answer/142893?hl=en";
@@ -57,17 +62,22 @@ public class EditorView extends AlwaysDismissedDialog
private final Context mContext;
private final PaymentRequestObserverForTest mObserverForTest;
private final Handler mHandler;
- private final AsyncTask<Void, Void, PhoneNumberFormattingTextWatcher> mPhoneFormatterTask;
private final TextView.OnEditorActionListener mEditorActionListener;
private final int mHalfRowMargin;
- private final List<EditorTextField> mEditorTextFields;
+ private final List<Validatable> mCheckableFields;
+ private final List<EditText> mEditableTextFields;
+ private final List<Spinner> mDropdownFields;
+ private final InputFilter mCardNumberInputFilter;
+ private final TextWatcher mCardNumberFormatter;
+ @Nullable private TextWatcher mPhoneFormatter;
private View mLayout;
private EditorModel mEditorModel;
private Button mDoneButton;
private ViewGroup mDataView;
private View mFooter;
- @Nullable private AutoCompleteTextView mPhoneInput;
+ @Nullable private TextView mCardInput;
+ @Nullable private TextView mPhoneInput;
/**
* Builds the editor view.
@@ -80,13 +90,6 @@ public class EditorView extends AlwaysDismissedDialog
mContext = activity;
mObserverForTest = observerForTest;
mHandler = new Handler();
- mPhoneFormatterTask = new AsyncTask<Void, Void, PhoneNumberFormattingTextWatcher>() {
- @Override
- protected PhoneNumberFormattingTextWatcher doInBackground(Void... unused) {
- return new PhoneNumberFormattingTextWatcher();
- }
- }.execute();
-
mEditorActionListener = new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
@@ -95,8 +98,8 @@ public class EditorView extends AlwaysDismissedDialog
return true;
} else if (actionId == EditorInfo.IME_ACTION_NEXT) {
View next = v.focusSearch(View.FOCUS_FORWARD);
- if (next != null && next instanceof AutoCompleteTextView) {
- focusInputField(next);
+ if (next != null) {
+ next.requestFocus();
return true;
}
}
@@ -106,7 +109,43 @@ public class EditorView extends AlwaysDismissedDialog
mHalfRowMargin = activity.getResources().getDimensionPixelSize(
R.dimen.payments_section_large_spacing);
- mEditorTextFields = new ArrayList<>();
+ mCheckableFields = new ArrayList<>();
+ mEditableTextFields = new ArrayList<>();
+ mDropdownFields = new ArrayList<>();
+
+ final Pattern cardNumberPattern = Pattern.compile("^[\\d- ]*$");
+ mCardNumberInputFilter = new InputFilter() {
+ @Override
+ public CharSequence filter(
+ CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+ // Accept deletions.
+ if (start == end) return null;
+
+ // Accept digits, "-", and spaces.
+ if (cardNumberPattern.matcher(source.subSequence(start, end)).matches()) {
+ return null;
+ }
+
+ // Reject everything else.
+ return "";
+ }
+ };
+
+ mCardNumberFormatter = new CreditCardNumberFormattingTextWatcher();
+ new AsyncTask<Void, Void, PhoneNumberFormattingTextWatcher>() {
+ @Override
+ protected PhoneNumberFormattingTextWatcher doInBackground(Void... unused) {
+ return new PhoneNumberFormattingTextWatcher();
+ }
+
+ @Override
+ protected void onPostExecute(PhoneNumberFormattingTextWatcher result) {
+ mPhoneFormatter = result;
+ if (mPhoneInput != null) {
+ mPhoneInput.addTextChangedListener(mPhoneFormatter);
+ }
+ }
+ }.execute();
}
/** Launches the Autofill help page on top of the current Context. */
@@ -158,27 +197,49 @@ public class EditorView extends AlwaysDismissedDialog
}
/**
- * Checks if all of the fields in the form are valid and updates the displayed errors.
+ * Checks if all of the fields in the form are valid and updates the displayed errors. If there
+ * are any invalid fields, makes sure that one of them is focused. Called when user taps [SAVE].
*
* @return Whether all fields contain valid information.
*/
private boolean validateForm() {
- final List<EditorTextField> invalidViews = getViewsWithInvalidInformation();
-
- // Focus the first field that's invalid.
- if (!invalidViews.isEmpty() && !invalidViews.contains(getCurrentFocus())) {
- focusInputField(invalidViews.get(0));
- }
+ final List<Validatable> invalidViews = getViewsWithInvalidInformation(true);
// Iterate over all the fields to update what errors are displayed, which is necessary to
// to clear existing errors on any newly valid fields.
- for (int i = 0; i < mEditorTextFields.size(); i++) {
- EditorTextField fieldView = mEditorTextFields.get(i);
+ for (int i = 0; i < mCheckableFields.size(); i++) {
+ Validatable fieldView = mCheckableFields.get(i);
fieldView.updateDisplayedError(invalidViews.contains(fieldView));
}
+
+ if (!invalidViews.isEmpty()) {
+ // Make sure that focus is on an invalid field.
+ Validatable focusedField = getValidatable(getCurrentFocus());
+ if (invalidViews.contains(focusedField)) {
+ // The focused field is invalid, but it may be scrolled off screen. Scroll to it.
+ focusedField.scrollToAndFocus();
+ } else {
+ // Some fields are invalid, but none of the are focused. Scroll to the first invalid
+ // field and focus it.
+ invalidViews.get(0).scrollToAndFocus();
+ }
+ }
+
return invalidViews.isEmpty();
}
+ /** @return The validatable item for the given view. */
+ private Validatable getValidatable(View v) {
+ if (v instanceof TextView && v.getParent() != null
+ && v.getParent() instanceof EditorTextField) {
+ return (EditorTextField) v.getParent();
+ } else if (v instanceof Spinner && v.getTag() != null) {
+ return (Validatable) v.getTag();
+ } else {
+ return null;
+ }
+ }
+
@Override
public void onClick(View view) {
if (view.getId() == R.id.payments_edit_done_button) {
@@ -219,12 +280,13 @@ public class EditorView extends AlwaysDismissedDialog
* much more human-parsable with inefficient LinearLayouts for half-width controls sharing rows.
*/
private void prepareEditor() {
- removeTextChangedListenerFromPhoneInputField();
-
// Ensure the layout is empty.
+ removeTextChangedListenersAndInputFilters();
mDataView = (ViewGroup) mLayout.findViewById(R.id.contents);
mDataView.removeAllViews();
- mEditorTextFields.clear();
+ mCheckableFields.clear();
+ mEditableTextFields.clear();
+ mDropdownFields.clear();
// Add Views for each of the {@link EditorFields}.
for (int i = 0; i < mEditorModel.getFields().size(); i++) {
@@ -267,10 +329,14 @@ public class EditorView extends AlwaysDismissedDialog
mDataView.addView(mFooter);
}
- private View addFieldViewToEditor(ViewGroup parent, EditorFieldModel fieldModel) {
+ private View addFieldViewToEditor(ViewGroup parent, final EditorFieldModel fieldModel) {
View childView = null;
- if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_DROPDOWN) {
+ if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_ICONS) {
+ childView = new EditorIconsField(mContext, parent, fieldModel).getLayout();
+ } else if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_LABEL) {
+ childView = new EditorLabelField(mContext, parent, fieldModel).getLayout();
+ } else if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_DROPDOWN) {
Runnable prepareEditorRunnable = new Runnable() {
@Override
public void run() {
@@ -280,16 +346,46 @@ public class EditorView extends AlwaysDismissedDialog
}
};
EditorDropdownField dropdownView =
- new EditorDropdownField(mContext, fieldModel, prepareEditorRunnable);
+ new EditorDropdownField(mContext, parent, fieldModel, prepareEditorRunnable);
+ mCheckableFields.add(dropdownView);
+ mDropdownFields.add(dropdownView.getDropdown());
childView = dropdownView.getLayout();
+ } else if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_CHECKBOX) {
+ final CheckBox checkbox = new CheckBox(mLayout.getContext());
+ checkbox.setId(R.id.payments_edit_checkbox);
+ checkbox.setText(fieldModel.getLabel());
+ checkbox.setChecked(fieldModel.isChecked());
+ checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ fieldModel.setIsChecked(isChecked);
+ if (mObserverForTest != null) mObserverForTest.onPaymentRequestReadyToEdit();
+ }
+ });
+
+ childView = checkbox;
} else {
- EditorTextField inputLayout = new EditorTextField(mLayout.getContext(), fieldModel,
- mEditorActionListener, getPhoneFormatter(), mObserverForTest);
- mEditorTextFields.add(inputLayout);
+ InputFilter filter = null;
+ TextWatcher formatter = null;
+ if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_CREDIT_CARD) {
+ filter = mCardNumberInputFilter;
+ formatter = mCardNumberFormatter;
+ } else if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_PHONE) {
+ formatter = mPhoneFormatter;
+ }
- final AutoCompleteTextView input = inputLayout.getEditText();
- if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_PHONE) {
+ EditorTextField inputLayout = new EditorTextField(mContext, fieldModel,
+ mEditorActionListener, filter, formatter, mObserverForTest);
+ mCheckableFields.add(inputLayout);
+
+ EditText input = inputLayout.getEditText();
+ mEditableTextFields.add(input);
+
+ if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_CREDIT_CARD) {
+ assert mCardInput == null;
+ mCardInput = input;
+ } else if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_PHONE) {
assert mPhoneInput == null;
mPhoneInput = input;
}
@@ -322,12 +418,13 @@ public class EditorView extends AlwaysDismissedDialog
show();
// Immediately focus the first invalid field to make it faster to edit.
- final List<EditorTextField> invalidViews = getViewsWithInvalidInformation();
+ final List<Validatable> invalidViews = getViewsWithInvalidInformation(false);
if (!invalidViews.isEmpty()) {
mHandler.post(new Runnable() {
@Override
public void run() {
- focusInputField(invalidViews.get(0));
+ invalidViews.get(0).scrollToAndFocus();
+ if (mObserverForTest != null) mObserverForTest.onPaymentRequestReadyToEdit();
}
});
}
@@ -335,44 +432,45 @@ public class EditorView extends AlwaysDismissedDialog
@Override
public void onDismiss(DialogInterface dialog) {
- removeTextChangedListenerFromPhoneInputField();
+ removeTextChangedListenersAndInputFilters();
mEditorModel.cancel();
if (mObserverForTest != null) mObserverForTest.onPaymentRequestEditorDismissed();
}
- /** @return All the EditorTextFields that exist in the EditorView. */
- @VisibleForTesting
- public List<EditorTextField> getEditorTextFields() {
- return mEditorTextFields;
- }
-
- private void removeTextChangedListenerFromPhoneInputField() {
- if (mPhoneInput != null) mPhoneInput.removeTextChangedListener(getPhoneFormatter());
- mPhoneInput = null;
- }
+ private void removeTextChangedListenersAndInputFilters() {
+ if (mCardInput != null) {
+ mCardInput.removeTextChangedListener(mCardNumberFormatter);
+ mCardInput.setFilters(new InputFilter[0]); // Null is not allowed.
+ mCardInput = null;
+ }
- /** Immediately returns the phone formatter or null if it has not initialized yet. */
- private PhoneNumberFormattingTextWatcher getPhoneFormatter() {
- try {
- return mPhoneFormatterTask.get(0, TimeUnit.MILLISECONDS);
- } catch (CancellationException | ExecutionException | InterruptedException
- | TimeoutException e) {
- return null;
+ if (mPhoneInput != null) {
+ mPhoneInput.removeTextChangedListener(mPhoneFormatter);
+ mPhoneInput = null;
}
}
- private List<EditorTextField> getViewsWithInvalidInformation() {
- List<EditorTextField> invalidViews = new ArrayList<>();
- for (int i = 0; i < mEditorTextFields.size(); i++) {
- EditorTextField fieldView = mEditorTextFields.get(i);
- if (!fieldView.getFieldModel().isValid()) invalidViews.add(fieldView);
+ private List<Validatable> getViewsWithInvalidInformation(boolean findAll) {
+ List<Validatable> invalidViews = new ArrayList<>();
+ for (int i = 0; i < mCheckableFields.size(); i++) {
+ Validatable fieldView = mCheckableFields.get(i);
+ if (!fieldView.isValid()) {
+ invalidViews.add(fieldView);
+ if (!findAll) break;
+ }
}
return invalidViews;
}
- private void focusInputField(View view) {
- view.requestFocus();
- view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- if (mObserverForTest != null) mObserverForTest.onPaymentRequestReadyToEdit();
+ /** @return All editable text fields in the editor. Used only for tests. */
+ @VisibleForTesting
+ public List<EditText> getEditableTextFieldsForTest() {
+ return mEditableTextFields;
+ }
+
+ /** @return All dropdown fields in the editor. Used only for tests. */
+ @VisibleForTesting
+ public List<Spinner> getDropdownFieldsForTest() {
+ return mDropdownFields;
}
}

Powered by Google App Engine
This is Rietveld 408576698