chromium/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/TargetSelectorCoordinator.java

// Copyright 2021 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.multiwindow;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ListView;

import org.chromium.base.Callback;
import org.chromium.components.favicon.LargeIconBridge;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.ModelListAdapter;
import org.chromium.ui.modelutil.PropertyModel;

import java.util.Iterator;
import java.util.List;

/** Coordinator to construct the move target selector dialog. */
public class TargetSelectorCoordinator {
    private static final int TYPE_ENTRY = 0;

    // Last selector dialog instance. This is used to prevent the user from interacting with
    // multiple instances of selector UI.
    @SuppressLint("StaticFieldLeak")
    static TargetSelectorCoordinator sPrevInstance;

    private final Context mContext;
    private final Callback<InstanceInfo> mMoveCallback;

    private final ModelList mModelList = new ModelList();
    private final UiUtils mUiUtils;
    private final View mDialogView;
    private final ModalDialogManager mModalDialogManager;

    private PropertyModel mDialog;
    private InstanceInfo mSelectedItem;
    private int mCurrentId; // ID for the current instance.

    /**
     * Show 'move window' modal dialog UI.
     * @param context Context to use to build the dialog.
     * @param modalDialogManager {@link ModalDialogManager} object.
     * @param iconBridge An object that fetches favicons from local DB.
     * @param moveCallback Action to take when asked to open a chosen instance.
     * @param instanceInfo List of {@link InstanceInfo} for available Chrome instances.
     */
    public static void showDialog(
            Context context,
            ModalDialogManager modalDialogManager,
            LargeIconBridge iconBridge,
            Callback<InstanceInfo> moveCallback,
            List<InstanceInfo> instanceInfo) {
        new TargetSelectorCoordinator(context, modalDialogManager, iconBridge, moveCallback)
                .showDialog(instanceInfo);
    }

    private TargetSelectorCoordinator(
            Context context,
            ModalDialogManager modalDialogManager,
            LargeIconBridge iconBridge,
            Callback<InstanceInfo> moveCallback) {
        mContext = context;
        mModalDialogManager = modalDialogManager;
        mMoveCallback = moveCallback;
        mUiUtils = new UiUtils(mContext, iconBridge);

        ModelListAdapter adapter = new ModelListAdapter(mModelList);
        adapter.registerType(
                TYPE_ENTRY,
                parentView ->
                        LayoutInflater.from(mContext)
                                .inflate(R.layout.instance_switcher_item, null),
                TargetSelectorItemViewBinder::bind);
        mDialogView = LayoutInflater.from(context).inflate(R.layout.target_selector_dialog, null);
        ((ListView) mDialogView.findViewById(R.id.list_view)).setAdapter(adapter);
    }

    private void showDialog(List<InstanceInfo> items) {
        UiUtils.closeOpenDialogs();
        sPrevInstance = this;
        mDialog = createDialog(items);
        mModalDialogManager.showDialog(mDialog, ModalDialogType.APP);
    }

    private PropertyModel createDialog(List<InstanceInfo> items) {
        for (InstanceInfo info : items) {
            if (info.type == InstanceInfo.Type.CURRENT) {
                mSelectedItem = info;
                mCurrentId = info.instanceId;
            }
            PropertyModel itemModel = generateListItem(info);
            mModelList.add(new ModelListAdapter.ListItem(0, itemModel));
        }
        ModalDialogProperties.Controller controller =
                new ModalDialogProperties.Controller() {
                    @Override
                    public void onDismiss(
                            PropertyModel model, @DialogDismissalCause int dismissalCause) {
                        sPrevInstance = null;
                    }

                    @Override
                    public void onClick(PropertyModel model, int buttonType) {
                        switch (buttonType) {
                            case ModalDialogProperties.ButtonType.POSITIVE:
                                dismissDialog(DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
                                mMoveCallback.onResult(mSelectedItem);
                                break;
                            case ModalDialogProperties.ButtonType.NEGATIVE:
                                dismissDialog(DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
                                break;
                            default:
                        }
                    }
                };
        Resources resources = mContext.getResources();
        String title = mContext.getString(R.string.menu_move_to_other_window);
        return new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
                .with(ModalDialogProperties.CONTROLLER, controller)
                .with(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, true)
                .with(ModalDialogProperties.CUSTOM_VIEW, mDialogView)
                .with(ModalDialogProperties.TITLE, title)
                .with(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, true)
                .with(
                        ModalDialogProperties.BUTTON_STYLES,
                        ModalDialogProperties.ButtonStyles.PRIMARY_FILLED_NEGATIVE_OUTLINE)
                .with(
                        ModalDialogProperties.POSITIVE_BUTTON_TEXT,
                        resources,
                        R.string.target_selector_move)
                .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, resources, R.string.cancel)
                .with(
                        ModalDialogProperties.DIALOG_STYLES,
                        ModalDialogProperties.DialogStyles.DIALOG_WHEN_LARGE)
                .build();
    }

    void dismissDialog(@DialogDismissalCause int cause) {
        mModalDialogManager.dismissDialog(mDialog, cause);
    }

    private PropertyModel generateListItem(InstanceInfo item) {
        String title = mUiUtils.getItemTitle(item);
        String desc = mUiUtils.getItemDesc(item);
        PropertyModel model =
                new PropertyModel.Builder(TargetSelectorItemProperties.ALL_KEYS)
                        .with(TargetSelectorItemProperties.TITLE, title)
                        .with(TargetSelectorItemProperties.DESC, desc)
                        .with(TargetSelectorItemProperties.INSTANCE_ID, item.instanceId)
                        .with(
                                TargetSelectorItemProperties.CHECK_TARGET,
                                item.type == InstanceInfo.Type.CURRENT)
                        .with(
                                TargetSelectorItemProperties.CLICK_LISTENER,
                                (view) -> selectInstance(item))
                        .build();
        mUiUtils.setFavicon(model, TargetSelectorItemProperties.FAVICON, item);
        return model;
    }

    private void selectInstance(InstanceInfo clickedItem) {
        int instanceId = clickedItem.instanceId;
        if (mSelectedItem.instanceId == instanceId) return;
        // Do not allow the target to be the current one.
        mDialog.set(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, mCurrentId == instanceId);
        Iterator<ListItem> it = mModelList.iterator();
        while (it.hasNext()) {
            ListItem li = it.next();
            int id = li.model.get(TargetSelectorItemProperties.INSTANCE_ID);
            if (id == mSelectedItem.instanceId) {
                li.model.set(TargetSelectorItemProperties.CHECK_TARGET, false);
            } else if (id == instanceId) {
                li.model.set(TargetSelectorItemProperties.CHECK_TARGET, true);
            }
        }
        mSelectedItem = clickedItem;
    }
}