chromium/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchImageControl.java

// Copyright 2016 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.compositor.bottombar.contextualsearch;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.text.TextUtils;
import android.view.animation.Interpolator;

import androidx.core.view.animation.PathInterpolatorCompat;

import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelAnimation;
import org.chromium.chrome.browser.layouts.animation.CompositorAnimator;

/**
 * Controls the image shown in the {@link ContextualSearchBarControl}. Owns animating between the
 * search provider icon and custom image (either a thumbnail or card icon) for the current query.
 */
public class ContextualSearchImageControl {
    /** The {@link ContextualSearchPanel} that this class belongs to. */
    private final ContextualSearchPanel mPanel;

    /** The percentage that the image is visible that is based upon the panel position. */
    private float mVisibilityPercentageBasedOnPanelPosition;

    public ContextualSearchImageControl(ContextualSearchPanel panel) {
        mPanel = panel;
    }

    /**
     * Updates the Bar image when in transition between peeked to expanded states.
     * @param percentage The percentage to the more opened state.
     */
    public void onUpdateFromPeekToExpand(float percentage) {
        if (mCardIconVisible || mThumbnailVisible) {
            mCustomImageVisibilityPercentage = 1.f - percentage;
            mVisibilityPercentageBasedOnPanelPosition = percentage;
        }
    }

    // ============================================================================================
    // Card Icon
    // ============================================================================================

    /** The resource id of the card icon to display. */
    private int mCardIconResourceId;

    /** Whether the card icon is visible. */
    private boolean mCardIconVisible;

    /**
     * @param resId The resource id of the card icon to display.
     */
    void setCardIconResourceId(int resId) {
        mCardIconResourceId = resId;
        mCardIconVisible = true;
        animateCustomImageVisibility(true);
    }

    /**
     * @return The resource id of the card icon to display.
     */
    public int getCardIconResourceId() {
        return mCardIconResourceId;
    }

    /**
     * @return Whether the card icon is visible.
     */
    public boolean getCardIconVisible() {
        return mCardIconVisible;
    }

    // ============================================================================================
    // Thumbnail
    // ============================================================================================

    /** The URL of the thumbnail to display. */
    private String mThumbnailUrl;

    /** Whether the thumbnail is visible. */
    private boolean mThumbnailVisible;

    /**
     * @param thumbnailUrl The URL of the thumbnail to display
     */
    public void setThumbnailUrl(String thumbnailUrl) {
        // If a card icon is showing, the thumbnail should not be shown.
        if (mCardIconVisible) return;

        mThumbnailUrl = thumbnailUrl;
    }

    /**
     * @return The URL used to fetch a thumbnail to display in the Bar. Will return an empty string
     *         if no thumbnail is available.
     */
    public String getThumbnailUrl() {
        return mThumbnailUrl != null ? mThumbnailUrl : "";
    }

    /**
     * @return Whether the thumbnail is visible.
     */
    public boolean getThumbnailVisible() {
        return mThumbnailVisible;
    }

    /**
     * Called when the thumbnail has finished being fetched.
     * @param success Whether fetching the thumbnail was successful.
     */
    public void onThumbnailFetched(boolean success) {
        // Check if the thumbnail URL was cleared before the thumbnail fetch completed. This may
        // occur if the user taps to refine the search.
        mThumbnailVisible = success && !TextUtils.isEmpty(mThumbnailUrl);
        if (!mThumbnailVisible) return;

        animateCustomImageVisibility(true);
    }

    // ============================================================================================
    // Custom image -- either a thumbnail or card icon
    // ============================================================================================

    /** The height and width of the image displayed at the start of the bar in px. */
    private int mBarImageSize;

    /**
     * The custom image visibility percentage, which dictates how and where to draw the custom
     * image. The custom image is not visible at all at 0.f and completely visible at 1.f.
     */
    private float mCustomImageVisibilityPercentage;

    /**
     * Hides the custom image if it is visible. Also resets the thumbnail URL and card icon
     * resource id.
     * @param animate Whether hiding the thumbnail should be animated.
     */
    public void hideCustomImage(boolean animate) {
        if ((mThumbnailVisible || mCardIconVisible) && animate) {
            animateCustomImageVisibility(false);
        } else {
            if (mImageVisibilityAnimator != null) mImageVisibilityAnimator.cancel();
            onCustomImageHidden();
        }
    }

    /**
     * @return The height and width of the image displayed at the start of the bar in px.
     */
    public int getBarImageSize() {
        if (mBarImageSize == 0) {
            mBarImageSize =
                    mPanel.getContext()
                            .getResources()
                            .getDimensionPixelSize(R.dimen.contextual_search_bar_image_size);
        }
        return mBarImageSize;
    }

    /**
     * @return The custom image visibility percentage, which dictates how and where to draw the
     *         custom image. The custom image is not visible at all at 0.f and completely visible at
     *         1.f. The custom image may be either a thumbnail or card icon.
     */
    public float getCustomImageVisibilityPercentage() {
        return mCustomImageVisibilityPercentage;
    }

    /** Called when the custom image finishes hiding to reset thumbnail and card icon values. */
    private void onCustomImageHidden() {
        mCardIconResourceId = 0;
        mCardIconVisible = false;

        mThumbnailUrl = "";
        mThumbnailVisible = false;
        mCustomImageVisibilityPercentage = 0.f;
    }

    // ============================================================================================
    // Thumbnail Animation
    // ============================================================================================

    private CompositorAnimator mImageVisibilityAnimator;

    private Interpolator mCustomImageVisibilityInterpolator;

    private void animateCustomImageVisibility(boolean visible) {
        // If the panel is expanded then #onUpdateFromPeekToExpand() is responsible for setting
        // mCustomImageVisibility and the custom image appearance should not be animated.
        if (visible && mVisibilityPercentageBasedOnPanelPosition > 0.f) return;

        if (mCustomImageVisibilityInterpolator == null) {
            mCustomImageVisibilityInterpolator =
                    PathInterpolatorCompat.create(0.4f, 0.f, 0.6f, 1.f);
        }

        if (mImageVisibilityAnimator != null) mImageVisibilityAnimator.cancel();

        mImageVisibilityAnimator =
                CompositorAnimator.ofFloat(
                        mPanel.getAnimationHandler(),
                        mCustomImageVisibilityPercentage,
                        visible ? 1.f : 0.f,
                        OverlayPanelAnimation.BASE_ANIMATION_DURATION_MS,
                        animator -> {
                            if (mVisibilityPercentageBasedOnPanelPosition > 0.f) return;
                            mCustomImageVisibilityPercentage = animator.getAnimatedValue();
                        });
        mImageVisibilityAnimator.setInterpolator(mCustomImageVisibilityInterpolator);
        mImageVisibilityAnimator.addListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        if (mCustomImageVisibilityPercentage == 0.f) onCustomImageHidden();
                        mImageVisibilityAnimator.removeAllListeners();
                        mImageVisibilityAnimator = null;
                    }
                });
        mImageVisibilityAnimator.start();
    }
}