chromium/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/mini/MiniPlayerCoordinator.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.readaloud.player.mini;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewStub;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import org.chromium.chrome.browser.browser_controls.BottomControlsStacker;
import org.chromium.chrome.browser.layouts.LayoutManager;
import org.chromium.chrome.browser.readaloud.ReadAloudMiniPlayerSceneLayer;
import org.chromium.chrome.browser.readaloud.player.PlayerCoordinator;
import org.chromium.chrome.browser.readaloud.player.R;
import org.chromium.chrome.browser.readaloud.player.VisibilityState;
import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
import org.chromium.chrome.browser.user_education.UserEducationHelper;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;

/** Coordinator responsible for Read Aloud mini player lifecycle. */
public class MiniPlayerCoordinator {
    private static ViewStub sViewStubForTesting;
    private final PropertyModelChangeProcessor<PropertyModel, MiniPlayerLayout, PropertyKey>
            mPlayerModelChangeProcessor;
    private final PropertyModelChangeProcessor<
                    PropertyModel, MiniPlayerViewBinder.ViewHolder, PropertyKey>
            mMiniPlayerModelChangeProcessor;
    private final MiniPlayerMediator mMediator;
    private final MiniPlayerLayout mLayout;
    // Compositor layer to be shown during show and hide while browser controls are
    // resizing.
    private final ReadAloudMiniPlayerSceneLayer mSceneLayer;
    private final PlayerCoordinator mPlayerCoordinator;
    private final UserEducationHelper mUserEducationHelper;
    /*  View-inflation-capable Context for read_aloud_playback isolated split */
    private final Context mContext;

    /**
     * @param activity App activity containing a placeholder FrameLayout with ID
     *     R.id.readaloud_mini_player.
     * @param context View-inflation-capable Context for read_aloud_playback isolated split.
     * @param sharedModel Player UI property model for properties shared with expanded player.
     * @param bottomControlsStacker Allows observing and changing browser controls heights.
     * @param layoutManager Involved in showing the compositor view.
     * @param playerCoordinator PlayerCoordinator to be notified of mini player updates.
     */
    public MiniPlayerCoordinator(
            Activity activity,
            Context context,
            PropertyModel sharedModel,
            BottomControlsStacker bottomControlsStacker,
            @Nullable LayoutManager layoutManager,
            PlayerCoordinator playerCoordinator,
            UserEducationHelper userEducationHelper) {
        this(
                context,
                sharedModel,
                new MiniPlayerMediator(bottomControlsStacker),
                inflateLayout(activity, context),
                new ReadAloudMiniPlayerSceneLayer(bottomControlsStacker.getBrowserControls()),
                layoutManager,
                playerCoordinator,
                userEducationHelper);
    }

    private static MiniPlayerLayout inflateLayout(Activity activity, Context context) {
        ViewStub stub =
                sViewStubForTesting != null
                        ? sViewStubForTesting
                        : activity.findViewById(R.id.readaloud_mini_player_stub);
        assert stub != null;
        stub.setLayoutResource(R.layout.readaloud_mini_player_layout);
        stub.setLayoutInflater(LayoutInflater.from(context));
        return (MiniPlayerLayout) stub.inflate();
    }

    @VisibleForTesting
    MiniPlayerCoordinator(
            Context context,
            PropertyModel sharedModel,
            MiniPlayerMediator mediator,
            MiniPlayerLayout layout,
            ReadAloudMiniPlayerSceneLayer sceneLayer,
            @Nullable LayoutManager layoutManager,
            PlayerCoordinator playerCoordinator,
            UserEducationHelper userEducationHelper) {
        mContext = context;
        mMediator = mediator;
        mMediator.setCoordinator(this);
        mLayout = layout;
        assert layout != null;
        mSceneLayer = sceneLayer;
        sceneLayer.setIsVisible(true);
        if (layoutManager != null) {
            layoutManager.addSceneOverlay(sceneLayer);
        }
        mPlayerCoordinator = playerCoordinator;
        mUserEducationHelper = userEducationHelper;
        mPlayerModelChangeProcessor =
                PropertyModelChangeProcessor.create(
                        sharedModel, mLayout, MiniPlayerViewBinder::bindPlayerProperties);
        mMiniPlayerModelChangeProcessor =
                PropertyModelChangeProcessor.create(
                        mMediator.getModel(),
                        new MiniPlayerViewBinder.ViewHolder(layout, sceneLayer),
                        MiniPlayerViewBinder::bindMiniPlayerProperties);
    }

    public void destroy() {
        mLayout.destroy();
    }

    /**
     * Show the mini player if it isn't already showing.
     * @param animate True if the transition should be animated. If false, the mini player will
     *         instantly appear.
     */
    public void show(boolean animate) {
        mMediator.show(animate);
    }

    /** Returns the mini player visibility state. */
    public @VisibilityState int getVisibility() {
        return mMediator.getVisibility();
    }

    /**
     * Dismiss the mini player.
     *
     * @param animate True if the transition should be animated. If false, the mini
     *                player will
     *                instantly disappear (though web contents resizing may lag
     *                behind).
     */
    public void dismiss(boolean animate) {
        mMediator.dismiss(animate);
    }

    void onShown(@Nullable View iphAnchorView) {
        if (iphAnchorView != null) {

            mUserEducationHelper.requestShowIPH(
                    new IPHCommandBuilder(
                                    mContext.getResources(),
                                    FeatureConstants.READ_ALOUD_EXPANDED_PLAYER_FEATURE,
                                    /* stringId= */ R.string.readaloud_expanded_player_iph,
                                    /* accessibilityStringId= */ R.string
                                            .readaloud_expanded_player_iph)
                            .setAnchorView(iphAnchorView)
                            .build());
        }
        mPlayerCoordinator.onMiniPlayerShown();
    }

    public static void setViewStubForTesting(ViewStub stub) {
        sViewStubForTesting = stub;
    }
}