chromium/chrome/android/java/src/org/chromium/chrome/browser/autofill/save_card/AutofillSaveCardBottomSheetCoordinator.java

// Copyright 2023 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.save_card;

import android.content.Context;
import android.net.Uri;
import android.view.View;

import androidx.browser.customtabs.CustomTabsIntent;

import org.chromium.chrome.browser.layouts.LayoutStateProvider;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.components.autofill.payments.AutofillSaveCardUiInfo;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;

/**
 * Coordinator of the autofill save card UI.
 *
 * <p>This component shows a bottom sheet to let the user choose to save a payment card (either
 * locally or uploaded).
 */
public class AutofillSaveCardBottomSheetCoordinator {
    /** Native callbacks from the save card bottom sheet. */
    public interface NativeDelegate {
        /** Called when the save card bottom sheet is shown to the user. */
        void onUiShown();

        /** Called when the user accepts the save card bottom sheet. */
        void onUiAccepted();

        /** Called when the user cancels the save card bottom sheet. */
        void onUiCanceled();

        /** Called when the user ignores the save card bottom sheet. */
        void onUiIgnored();
    }

    private final Context mContext;
    private final AutofillSaveCardBottomSheetView mView;
    private final AutofillSaveCardBottomSheetMediator mMediator;
    private PropertyModel mModel;

    /**
     * Creates the coordinator.
     *
     * @param context The context for this component.
     * @param uiInfo An object providing initial values for the bottom sheet model.
     * @param skipLoadingForFixFlow When true, loading is skipped due to the fix flow.
     * @param bottomSheetController The bottom sheet controller where this bottom sheet will be
     *     shown.
     * @param layoutStateProvider The LayoutStateProvider used to detect when the bottom sheet needs
     *     to be hidden after a change of layout (e.g. to the tab switcher).
     * @param tabModel The TabModel used to detect when the bottom sheet needs to be hidden after a
     *     tab change.
     * @param delegate The native callbacks for user actions.
     */
    public AutofillSaveCardBottomSheetCoordinator(
            Context context,
            AutofillSaveCardUiInfo uiInfo,
            boolean skipLoadingForFixFlow,
            BottomSheetController bottomSheetController,
            LayoutStateProvider layoutStateProvider,
            TabModel tabModel,
            NativeDelegate delegate) {
        mContext = context;
        mView = new AutofillSaveCardBottomSheetView(context);

        mModel =
                new PropertyModel.Builder(AutofillSaveCardBottomSheetProperties.ALL_KEYS)
                        .with(AutofillSaveCardBottomSheetProperties.TITLE, uiInfo.getTitleText())
                        .with(
                                AutofillSaveCardBottomSheetProperties.DESCRIPTION,
                                uiInfo.getDescriptionText())
                        .with(
                                AutofillSaveCardBottomSheetProperties.LOGO_ICON,
                                uiInfo.isForUpload() ? uiInfo.getLogoIcon() : 0)
                        .with(
                                AutofillSaveCardBottomSheetProperties.CARD_DESCRIPTION,
                                uiInfo.getCardDescription())
                        .with(
                                AutofillSaveCardBottomSheetProperties.CARD_ICON,
                                uiInfo.getCardDetail().issuerIconDrawableId)
                        .with(
                                AutofillSaveCardBottomSheetProperties.CARD_LABEL,
                                uiInfo.getCardDetail().label)
                        .with(
                                AutofillSaveCardBottomSheetProperties.CARD_SUB_LABEL,
                                uiInfo.getCardDetail().subLabel)
                        .with(
                                AutofillSaveCardBottomSheetProperties.LEGAL_MESSAGE,
                                new AutofillSaveCardBottomSheetProperties.LegalMessage(
                                        uiInfo.getLegalMessageLines(), this::openLegalMessageLink))
                        .with(
                                AutofillSaveCardBottomSheetProperties.ACCEPT_BUTTON_LABEL,
                                uiInfo.getConfirmText())
                        .with(
                                AutofillSaveCardBottomSheetProperties.CANCEL_BUTTON_LABEL,
                                uiInfo.getCancelText())
                        .with(AutofillSaveCardBottomSheetProperties.SHOW_LOADING_STATE, false)
                        .with(
                                AutofillSaveCardBottomSheetProperties.LOADING_DESCRIPTION,
                                uiInfo.getLoadingDescription())
                        .build();
        PropertyModelChangeProcessor.create(
                mModel, mView, AutofillSaveCardBottomSheetViewBinder::bind);

        mMediator =
                new AutofillSaveCardBottomSheetMediator(
                        new AutofillSaveCardBottomSheetContent(
                                mView.mContentView, mView.mScrollView),
                        new AutofillSaveCardBottomSheetLifecycle(
                                bottomSheetController, layoutStateProvider, tabModel),
                        bottomSheetController,
                        mModel,
                        delegate,
                        uiInfo.isForUpload(),
                        skipLoadingForFixFlow);

        mView.mAcceptButton.setOnClickListener(
                (View button) -> {
                    mMediator.onAccepted();
                });
        mView.mCancelButton.setOnClickListener(
                (View button) -> {
                    mMediator.onCanceled();
                });
    }

    /**
     * Request to show the bottom sheet.
     *
     * <p>Calls {@link AutofillSaveCardBottomSheetBridge#onUiShown} if the bottom sheet was shown.
     */
    public void requestShowContent() {
        mMediator.requestShowContent();
    }

    /** Hides this component hiding the bottom sheet if needed. */
    public void hide(@StateChangeReason int hideReason) {
        mMediator.hide(hideReason);
    }

    AutofillSaveCardBottomSheetView getAutofillSaveCardBottomSheetViewForTesting() {
        return mView;
    }

    PropertyModel getPropertyModelForTesting() {
        return mModel;
    }

    void openLegalMessageLink(String url) {
        new CustomTabsIntent.Builder()
                .setShowTitle(true)
                .build()
                .launchUrl(mContext, Uri.parse(url));
    }
}