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

// Copyright 2018 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.content.res.Resources;
import android.text.Editable;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.TextView.BufferType;

import androidx.core.text.TextUtilsCompat;
import androidx.core.view.ViewCompat;

import org.chromium.chrome.R;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.text.EmptyTextWatcher;

import java.util.Locale;

/** Prompt that asks users to confirm user's name before saving card to Google. */
public class AutofillNameFixFlowPrompt extends AutofillSaveCardPromptBase
        implements EmptyTextWatcher {
    /**
     * An interface to handle the interaction with
     * an AutofillNameFixFlowPrompt object.
     */
    public interface AutofillNameFixFlowPromptDelegate extends AutofillSaveCardPromptBaseDelegate {
        /**
         * Called when user accepted/confirmed the prompt.
         *
         * @param name Card holder name.
         */
        void onUserAcceptCardholderName(String name);
    }

    /**
     * Create a dialog prompt for the use of infobar. This prompt does not include legal lines.
     *
     * @param context The current context.
     * @param delegate A {@link AutofillNameFixFlowPromptDelegate} to handle events.
     * @param inferredName Name inferred from the account. Empty string for user to fill in.
     * @param title Title of the prompt.
     * @param drawableId Drawable id on the title.
     * @param confirmButtonLabel Label for the confirm button.
     * @return A {@link AutofillNameFixFlowPrompt} to confirm name.
     */
    public static AutofillNameFixFlowPrompt createAsInfobarFixFlowPrompt(
            Context context,
            AutofillNameFixFlowPromptDelegate delegate,
            String inferredName,
            String title,
            int drawableId,
            String confirmButtonLabel) {
        return new AutofillNameFixFlowPrompt(
                context, delegate, inferredName, title, drawableId, confirmButtonLabel, false);
    }

    private final AutofillNameFixFlowPromptDelegate mDelegate;

    private final EditText mUserNameInput;
    private final ImageView mNameFixFlowTooltipIcon;
    private PopupWindow mNameFixFlowTooltipPopup;

    /** Fix flow prompt to confirm user name before saving the card to Google. */
    private AutofillNameFixFlowPrompt(
            Context context,
            AutofillNameFixFlowPromptDelegate delegate,
            String inferredName,
            String title,
            int drawableId,
            String confirmButtonLabel,
            boolean filledConfirmButton) {
        super(
                context,
                delegate,
                R.layout.autofill_name_fixflow,
                /* customTitleLayoutId= */ Resources.ID_NULL,
                title,
                drawableId,
                confirmButtonLabel,
                filledConfirmButton);
        mDelegate = delegate;
        // Dialog of infobar doesn't show any details of the cc.
        mDialogView.findViewById(R.id.cc_details).setVisibility(View.GONE);
        mUserNameInput = mDialogView.findViewById(R.id.cc_name_edit);
        mUserNameInput.setText(inferredName, BufferType.EDITABLE);
        mNameFixFlowTooltipIcon = mDialogView.findViewById(R.id.cc_name_tooltip_icon);
        mDialogModel.set(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, inferredName.isEmpty());

        // Do not show tooltip if inferred name is empty.
        if (TextUtils.isEmpty(inferredName)) {
            mNameFixFlowTooltipIcon.setVisibility(View.GONE);
        } else {
            mNameFixFlowTooltipIcon.setOnClickListener((view) -> onTooltipIconClicked());
        }

        // Hitting the "submit" button on the software keyboard should submit, unless the name field
        // is empty.
        mUserNameInput.setOnEditorActionListener(
                (view, actionId, event) -> {
                    if (actionId == EditorInfo.IME_ACTION_DONE) {
                        if (mUserNameInput.getText().toString().trim().length() != 0) {
                            onClick(mDialogModel, ModalDialogProperties.ButtonType.POSITIVE);
                        }
                        return true;
                    }
                    return false;
                });
        mUserNameInput.addTextChangedListener(this);
    }

    @Override
    public void afterTextChanged(Editable s) {
        mDialogModel.set(
                ModalDialogProperties.POSITIVE_BUTTON_DISABLED,
                mUserNameInput.getText().toString().trim().isEmpty());
    }

    /**
     * Handle tooltip icon clicked. If tooltip is already opened, don't show another. Otherwise
     * create a new one.
     */
    private void onTooltipIconClicked() {
        if (mNameFixFlowTooltipPopup != null) return;

        mNameFixFlowTooltipPopup = new PopupWindow(mContext);
        Runnable dismissAction =
                () -> {
                    mNameFixFlowTooltipPopup = null;
                };
        boolean isLeftToRight =
                TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
                        == ViewCompat.LAYOUT_DIRECTION_LTR;
        AutofillUiUtils.showTooltip(
                mContext,
                mNameFixFlowTooltipPopup,
                R.string.autofill_save_card_prompt_cardholder_name_tooltip,
                new AutofillUiUtils.OffsetProvider() {
                    @Override
                    public int getXOffset(TextView textView) {
                        int xOffset =
                                mNameFixFlowTooltipIcon.getLeft() - textView.getMeasuredWidth();
                        return Math.max(0, xOffset);
                    }

                    @Override
                    public int getYOffset(TextView textView) {
                        return 0;
                    }
                },
                // If the layout is right to left then anchor on the edit text field else anchor on
                // the tooltip icon, which would be on the left.
                isLeftToRight ? mUserNameInput : mNameFixFlowTooltipIcon,
                dismissAction);
    }

    @Override
    public void onClick(PropertyModel model, int buttonType) {
        if (buttonType == ModalDialogProperties.ButtonType.POSITIVE) {
            mDelegate.onUserAcceptCardholderName(mUserNameInput.getText().toString());
            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();
    }
}