chromium/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowPrompt.java

// Copyright 2019 The Chromium Authors
// 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.autofill;

import android.content.Context;
import android.text.Editable;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.AutofillUiUtils.ErrorType;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.text.EmptyTextWatcher;

/**
 * Prompt that asks users to confirm the expiration date before saving card to Google.
 * TODO(crbug.com/40579040) - Confirm if the month and year needs to be pre-populated in case
 * partial data is available.
 */
public class AutofillExpirationDateFixFlowPrompt extends AutofillSaveCardPromptBase
        implements EmptyTextWatcher {
    /**
     * An interface to handle the interaction with an AutofillExpirationDateFixFlowPrompt object.
     */
    public interface AutofillExpirationDateFixFlowPromptDelegate
            extends AutofillSaveCardPromptBaseDelegate {
        /**
         * Called when user accepted/confirmed the prompt.
         *
         * @param month expiration date month.
         * @param year expiration date year.
         */
        void onUserAcceptExpirationDate(String month, String year);
    }

    /**
     * Create a prompt dialog for the use of infobar. This dialog does not include legal lines.
     *
     * @param context The current context.
     * @param delegate A {@link AutofillExpirationDateFixFlowPromptDelegate} to handle events.
     * @param title Title of the dialog prompt.
     * @param drawableId Drawable id on the title.
     * @param cardLabel Label representing a card which will be saved.
     * @param confirmButtonLabel Label for the confirm button.
     * @return The prompt to confirm expiration data.
     */
    public static AutofillExpirationDateFixFlowPrompt createAsInfobarFixFlowPrompt(
            Context context,
            AutofillExpirationDateFixFlowPromptDelegate delegate,
            String title,
            int drawableId,
            String cardLabel,
            String confirmButtonLabel) {
        return new AutofillExpirationDateFixFlowPrompt(
                context, delegate, title, drawableId, cardLabel, confirmButtonLabel, false);
    }

    private final AutofillExpirationDateFixFlowPromptDelegate mDelegate;

    private final EditText mMonthInput;
    private final EditText mYearInput;
    private final TextView mErrorMessage;

    private boolean mDidFocusOnMonth;
    private boolean mDidFocusOnYear;

    /** Fix flow prompt to confirm expiration date before saving the card to Google. */
    private AutofillExpirationDateFixFlowPrompt(
            Context context,
            AutofillExpirationDateFixFlowPromptDelegate delegate,
            String title,
            int drawableId,
            String cardLabel,
            String confirmButtonLabel,
            boolean filledConfirmButton) {
        super(
                context,
                delegate,
                R.layout.autofill_expiration_date_fix_flow,
                R.layout.icon_after_title_view,
                title,
                drawableId,
                confirmButtonLabel,
                filledConfirmButton);
        mDelegate = delegate;
        mErrorMessage = mDialogView.findViewById(R.id.error_message);
        // Infobar: show masked card number only.
        TextView cardDetailsMasked = mDialogView.findViewById(R.id.cc_details_masked);
        cardDetailsMasked.setText(cardLabel);
        mDialogView.findViewById(R.id.message_divider).setVisibility(View.GONE);
        mDialogView.findViewById(R.id.google_pay_logo).setVisibility(View.GONE);

        mMonthInput = mDialogView.findViewById(R.id.cc_month_edit);
        mMonthInput.addTextChangedListener(this);
        mMonthInput.setOnFocusChangeListener(
                (view, hasFocus) -> {
                    mDidFocusOnMonth |= hasFocus;
                });

        mYearInput = mDialogView.findViewById(R.id.cc_year_edit);
        mYearInput.addTextChangedListener(this);
        mYearInput.setOnFocusChangeListener(
                (view, hasFocus) -> {
                    mDidFocusOnYear |= hasFocus;
                });
    }

    @Override
    public void afterTextChanged(Editable s) {
        validate();
    }

    @Override
    public void onClick(PropertyModel model, int buttonType) {
        if (buttonType == ModalDialogProperties.ButtonType.POSITIVE) {
            String monthString = mMonthInput.getText().toString().trim();
            String yearString = mYearInput.getText().toString().trim();
            mDelegate.onUserAcceptExpirationDate(monthString, yearString);
            mModalDialogManager.dismissDialog(model, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        } else if (buttonType == ModalDialogProperties.ButtonType.NEGATIVE) {
            mModalDialogManager.dismissDialog(model, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
        }
    }

    @Override
    public void onDismiss(PropertyModel model, int dismissalCause) {
        // Do not call onUserDismiss if dialog was dismissed either because the user
        // accepted to save the card or was dismissed by native code.
        if (dismissalCause == DialogDismissalCause.NEGATIVE_BUTTON_CLICKED) {
            mDelegate.onUserDismiss();
        }
        // Call whenever the dialog is dismissed.
        mDelegate.onPromptDismissed();
    }

    /**
     * Validates the values of the input fields to determine whether the submit button should be
     * enabled. Also displays a detailed error message and highlights the fields for which the value
     * is wrong. Finally checks whether the focus should move to the next field.
     */
    private void validate() {
        @ErrorType
        int errorType =
                AutofillUiUtils.getExpirationDateErrorType(
                        mMonthInput, mYearInput, mDidFocusOnMonth, mDidFocusOnYear);
        mDialogModel.set(
                ModalDialogProperties.POSITIVE_BUTTON_DISABLED, errorType != ErrorType.NONE);
        AutofillUiUtils.showDetailedErrorMessage(errorType, mContext, mErrorMessage);
        AutofillUiUtils.updateColorForInputs(
                errorType, mContext, mMonthInput, mYearInput, /* cvcInput= */ null);
        moveFocus(errorType);
    }

    /**
     * Moves the focus to the next field based on the value of the fields and the specified type of
     * error found for the expiration date field(s).
     *
     * @param errorType The type of error detected.
     */
    private void moveFocus(@ErrorType int errorType) {
        if (mMonthInput.isFocused()
                && mMonthInput.getText().length() == AutofillUiUtils.EXPIRATION_FIELDS_LENGTH) {
            // The user just finished typing in the month field and if there are no errors in the
            // month, then move focus to the year input.
            if (errorType != ErrorType.EXPIRATION_MONTH) {
                mYearInput.requestFocus();
                mDidFocusOnYear = true;
            }
        }
    }
}