Chromium Code Reviews| 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 710f836417449b1aab0e968f2e5408b76577a8cc..0b72acc05bf3c1a98824028552ddffd3a0f2412d 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. */ |
| @@ -163,22 +202,39 @@ public class EditorView extends AlwaysDismissedDialog |
| * @return Whether all fields contain valid information. |
| */ |
| private boolean validateForm() { |
| - final List<EditorTextField> invalidViews = getViewsWithInvalidInformation(); |
| + final List<Validatable> invalidViews = getViewsWithInvalidInformation(true); |
| // Focus the first field that's invalid. |
| - if (!invalidViews.isEmpty() && !invalidViews.contains(getCurrentFocus())) { |
| - focusInputField(invalidViews.get(0)); |
| + if (!invalidViews.isEmpty()) { |
| + Validatable focusedField = getValidatable(getCurrentFocus()); |
| + if (invalidViews.contains(focusedField)) { |
| + focusedField.scrollToAndFocus(); |
| + } else { |
| + invalidViews.get(0).scrollToAndFocus(); |
|
gone
2016/07/14 22:36:36
Kind of weird... should leave a comment about why
please use gerrit instead
2016/07/15 00:24:37
Done.
|
| + } |
| } |
| // 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)); |
| } |
| 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 +275,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 +324,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 +341,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; |
| + } |
| + |
| + EditorTextField inputLayout = new EditorTextField(mContext, fieldModel, |
| + mEditorActionListener, filter, formatter, mObserverForTest); |
| + mCheckableFields.add(inputLayout); |
| - final AutoCompleteTextView input = inputLayout.getEditText(); |
| - if (fieldModel.getInputTypeHint() == EditorFieldModel.INPUT_TYPE_HINT_PHONE) { |
| + 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 +413,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 +427,43 @@ public class EditorView extends AlwaysDismissedDialog |
| @Override |
| public void onDismiss(DialogInterface dialog) { |
| - removeTextChangedListenerFromPhoneInputField(); |
| + removeTextChangedListenersAndInputFilters(); |
| mEditorModel.cancel(); |
| if (mObserverForTest != null) mObserverForTest.onPaymentRequestEditorDismissed(); |
| } |
| + private void removeTextChangedListenersAndInputFilters() { |
| + if (mCardInput != null) { |
| + mCardInput.removeTextChangedListener(mCardNumberFormatter); |
| + mCardInput.setFilters(new InputFilter[0]); // Null is not allowed. |
|
gone
2016/07/14 22:36:36
for clarity:
if (mCardInput != null) {
mCardI
please use gerrit instead
2016/07/15 00:24:37
Done.
|
| + } |
| - /** @return All the EditorTextFields that exist in the EditorView. */ |
| - @VisibleForTesting |
| - public List<EditorTextField> getEditorTextFields() { |
| - return mEditorTextFields; |
| - } |
| + if (mPhoneInput != null) mPhoneInput.removeTextChangedListener(mPhoneFormatter); |
| - private void removeTextChangedListenerFromPhoneInputField() { |
| - if (mPhoneInput != null) mPhoneInput.removeTextChangedListener(getPhoneFormatter()); |
| + mCardInput = null; |
| mPhoneInput = 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; |
| + 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 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); |
| - } |
| - return invalidViews; |
| + /** @return All editable text fields in the editor. Used only for tests. */ |
| + @VisibleForTesting |
| + public List<EditText> getEditableTextFieldsForTest() { |
| + return mEditableTextFields; |
| } |
| - private void focusInputField(View view) { |
| - view.requestFocus(); |
| - view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); |
| - if (mObserverForTest != null) mObserverForTest.onPaymentRequestReadyToEdit(); |
| + /** @return All dropdown fields in the editor. Used only for tests. */ |
| + @VisibleForTesting |
| + public List<Spinner> getDropdownFieldsForTest() { |
| + return mDropdownFields; |
| } |
| } |