chromium/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEmptyCoordinator.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.tasks.tab_management;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import org.chromium.base.Callback;
import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.modelutil.ListObservable;
import org.chromium.ui.modelutil.ListObservable.ListObserver;

/**
 * Empty coordinator that is responsible for showing an empty state view in tab switcher when we are
 * in no tab state.
 */
// @TODO(crbug.com/40910476) Add instrumentation test for TabListEmptyCoordinator class.
class TabListEmptyCoordinator {
    private ViewGroup mRootView;
    private View mEmptyView;
    private TextView mEmptyStateHeading;
    private TextView mEmptyStateSubheading;
    private ImageView mImageView;
    private Context mContext;
    private TabListModel mModel;
    private ListObserver<Void> mListObserver;
    private Callback<Runnable> mRunOnItemAnimatorFinished;
    private boolean mIsTabSwitcherShowing;
    private boolean mIsListObserverAttached;

    public TabListEmptyCoordinator(
            ViewGroup rootView, TabListModel model, Callback<Runnable> runOnItemAnimatorFinished) {
        mRootView = rootView;
        mContext = rootView.getContext();
        mRunOnItemAnimatorFinished = runOnItemAnimatorFinished;

        // Observe TabListModel to determine when to add / remove empty state view.
        mModel = model;
        mListObserver =
                new ListObserver<Void>() {
                    @Override
                    public void onItemRangeInserted(ListObservable source, int index, int count) {
                        updateEmptyView();
                    }

                    @Override
                    public void onItemRangeRemoved(ListObservable source, int index, int count) {
                        updateEmptyView();
                    }
                };
    }

    public void initializeEmptyStateView(
            int imageResId, int emptyHeadingStringResId, int emptySubheadingStringResId) {
        if (mEmptyView != null) {
            return;
        }
        // Initialize empty state resource.
        mEmptyView =
                (ViewGroup)
                        android.view.LayoutInflater.from(mContext)
                                .inflate(R.layout.empty_state_view, null);
        mEmptyStateHeading = mEmptyView.findViewById(R.id.empty_state_text_title);
        mEmptyStateSubheading = mEmptyView.findViewById(R.id.empty_state_text_description);
        mImageView = mEmptyView.findViewById(R.id.empty_state_icon);

        // Set empty state properties.
        setEmptyStateImageRes(imageResId);
        setEmptyStateViewText(emptyHeadingStringResId, emptySubheadingStringResId);
    }

    private void setEmptyStateViewText(
            int emptyHeadingStringResId, int emptySubheadingStringResId) {
        mEmptyStateHeading.setText(emptyHeadingStringResId);
        mEmptyStateSubheading.setText(emptySubheadingStringResId);
    }

    private void setEmptyStateImageRes(int imageResId) {
        mImageView.setImageResource(imageResId);
    }

    private boolean isEmptyViewAttached() {
        return mEmptyView != null && mEmptyView.getParent() != null;
    }

    private boolean isInEmptyState() {
        return mModel.size() == 0 && mIsTabSwitcherShowing;
    }

    private void updateEmptyView() {
        if (isEmptyViewAttached()) {
            if (isInEmptyState()) {
                mRunOnItemAnimatorFinished.onResult(
                        () -> {
                            // Re-check requirements since this is now async.
                            if (isEmptyViewAttached() && isInEmptyState()) {
                                setEmptyViewVisibility(View.VISIBLE);
                            }
                        });
            } else {
                setEmptyViewVisibility(View.GONE);
            }
        }
    }

    public void setIsTabSwitcherShowing(boolean isShowing) {
        mIsTabSwitcherShowing = isShowing;
        if (mIsTabSwitcherShowing) {
            attachListObserver();
            updateEmptyView();
        } else {
            updateEmptyView();
            removeListObserver();
        }
    }

    public void attachListObserver() {
        if (mListObserver != null && !getIsListObserverAttached()) {
            mModel.addObserver(mListObserver);
            mIsListObserverAttached = true;
        }
    }

    public void removeListObserver() {
        if (mListObserver != null && getIsListObserverAttached()) {
            mModel.removeObserver(mListObserver);
            mIsListObserverAttached = false;
        }
    }

    public void attachEmptyView() {
        if (mEmptyView != null && mEmptyView.getParent() == null) {
            mRootView.addView(mEmptyView);
        }
        setEmptyViewVisibility(View.GONE);
    }

    public void destroyEmptyView() {
        if (mEmptyView != null && mEmptyView.getParent() != null) {
            mRootView.removeView(mEmptyView);
        }
        mEmptyView = null;
    }

    public void setEmptyViewVisibility(int isVisible) {
        mEmptyView.setVisibility(isVisible);
    }

    private boolean getIsListObserverAttached() {
        return mIsListObserverAttached;
    }
}