chromium/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchCaptionControl.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.content.Context;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.Px;

import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelAnimation;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelTextViewInflater;
import org.chromium.chrome.browser.layouts.animation.CompositorAnimator;
import org.chromium.ui.interpolators.Interpolators;
import org.chromium.ui.resources.dynamics.DynamicResourceLoader;

/**
 * Controls the Caption View that is shown at the bottom of the {@link ContextualSearchBarControl}
 * and used as a dynamic resource.
 */
public class ContextualSearchCaptionControl extends OverlayPanelTextViewInflater {
    private static final float ANIMATION_PERCENTAGE_ZERO = 0.f;
    private static final float ANIMATION_PERCENTAGE_COMPLETE = 1.f;

    /** The caption View. */
    private TextView mCaption;

    /** The text for the caption when the Bar is peeking. */
    private String mPeekingCaptionText;

    /** Whether there is a caption when the Bar is peeking. */
    private boolean mHasPeekingCaption;

    /** Whether the caption for the expanded Bar is showing. */
    private boolean mShowingExpandedCaption;

    /** Whether the expanded caption should be shown. */
    private final boolean mShouldShowExpandedCaption;

    /** The {@link ContextualSearchPanel} that this class belongs to. */
    private final ContextualSearchPanel mPanel;

    /** The caption visibility. */
    private boolean mIsVisible;

    /**
     * The caption animation percentage, which controls how and where to draw. It is
     * ANIMATION_PERCENTAGE_COMPLETE when the Contextual Search bar is peeking and
     * ANIMATION_PERCENTAGE_ZERO when it is expanded.
     */
    private float mAnimationPercentage = ANIMATION_PERCENTAGE_ZERO;

    /** The animator responsible for transitioning the caption. */
    private CompositorAnimator mTransitionAnimator;

    /**
     * Whether a new snapshot has been captured by the system yet - this is false when we have
     * something to show, but cannot yet show it.
     */
    private boolean mDidCapture;

    /**
     * @param panel The panel.
     * @param context The Android Context used to inflate the View.
     * @param container The container View used to inflate the View.
     * @param resourceLoader The resource loader that will handle the snapshot capturing.
     * @param shouldShowExpandedCaption Whether the "Open in new tab" caption should be shown when
     *     the panel is expanded.
     */
    public ContextualSearchCaptionControl(
            ContextualSearchPanel panel,
            Context context,
            ViewGroup container,
            DynamicResourceLoader resourceLoader,
            boolean shouldShowExpandedCaption) {
        super(
                panel,
                R.layout.contextual_search_caption_view,
                R.id.contextual_search_caption_view,
                context,
                container,
                resourceLoader,
                R.dimen.contextual_search_end_padding,
                R.dimen.contextual_search_padded_button_width);
        mShouldShowExpandedCaption = shouldShowExpandedCaption;
        mPanel = panel;
    }

    /**
     * Sets the caption to display in the bottom of the control.
     *
     * @param caption The string displayed as a caption to help explain results, e.g. a Quick
     *     Answer.
     */
    public void setCaption(String caption) {
        mPeekingCaptionText = sanitizeText(caption);
        mHasPeekingCaption = true;

        if (mShowingExpandedCaption) return;

        mDidCapture = false;

        inflate();

        mCaption.setText(sanitizeText(caption));

        invalidate();
        show();
    }

    /**
     * Updates the caption when in transition between peeked to expanded states.
     * @param percentage The percentage to the more opened state.
     */
    @Override
    public void onUpdateFromPeekToExpand(float percentage) {
        super.onUpdateFromPeekToExpand(percentage);
        if (mHasPeekingCaption) {
            if (mTransitionAnimator != null) mTransitionAnimator.cancel();
            mAnimationPercentage = 1.f - percentage;
        }
    }

    /** Hides the caption. */
    public void hide() {
        mIsVisible = false;
        mAnimationPercentage = ANIMATION_PERCENTAGE_ZERO;
        mHasPeekingCaption = false;
    }

    /** Shows the caption. */
    private void show() {
        mIsVisible = true;
    }

    /**
     * Controls whether the caption is visible and can be rendered.
     * The caption must be visible in order to draw it and take a snapshot.
     * Even though the caption is visible the user might not be able to see it due to a
     * completely transparent opacity associated with an animation percentage of zero.
     * @return Whether the caption is visible or not.
     */
    public boolean getIsVisible() {
        return mIsVisible;
    }

    /**
     * Gets the animation percentage which controls the drawing of the caption and how high to
     * position it in the Bar.
     * @return The current percentage ranging from 0.0 to 1.0.
     */
    public float getAnimationPercentage() {
        // If we don't yet have a snapshot captured, stay at zero.  See crbug.com/608914.
        if (!mDidCapture) return ANIMATION_PERCENTAGE_ZERO;

        return mAnimationPercentage;
    }

    /**
     * @return The text currently showing in the caption view.
     */
    public CharSequence getCaptionText() {
        return mCaption.getText();
    }

    /** @return whether there's already a visible caption. */
    boolean hasCaption() {
        return getIsVisible() && !TextUtils.isEmpty(getCaptionText());
    }

    /**
     * @return the caption's TextView height if it is visible.
     */
    @Px
    int getTextViewHeight() {
        return getIsVisible() ? mCaption.getHeight() : 0;
    }

    // ========================================================================================
    // OverlayPanelTextViewInflater overrides
    // ========================================================================================

    @Override
    protected TextView getTextView() {
        return mCaption;
    }

    // ========================================================================================
    // OverlayPanelInflater overrides
    // ========================================================================================

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        View view = getView();
        mCaption = view.findViewById(R.id.contextual_search_caption);
    }

    @Override
    protected void onCaptureEnd() {
        super.onCaptureEnd();
        if (mDidCapture) return;

        mDidCapture = true;
        animateTransitionIn();
    }

    // ============================================================================================
    // Search Caption Animation
    // ============================================================================================

    private void animateTransitionIn() {
        mTransitionAnimator =
                CompositorAnimator.ofFloat(
                        mOverlayPanel.getAnimationHandler(),
                        ANIMATION_PERCENTAGE_ZERO,
                        ANIMATION_PERCENTAGE_COMPLETE,
                        OverlayPanelAnimation.BASE_ANIMATION_DURATION_MS,
                        null);
        mTransitionAnimator.addUpdateListener(
                animator -> mAnimationPercentage = animator.getAnimatedValue());
        mTransitionAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);

        mTransitionAnimator.start();
    }
}