chromium/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionsListAnimationDriver.java

// Copyright 2024 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.omnibox.suggestions;

import androidx.annotation.NonNull;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;

import org.chromium.base.supplier.Supplier;
import org.chromium.ui.InsetObserver;
import org.chromium.ui.InsetObserver.WindowInsetsAnimationListener;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.PropertyModel;

import java.util.List;

/**
 * "Driver" class that synchronizes the omnibox suggestions list appearance animation with the IME
 * appearance animation.
 */
public class SuggestionsListAnimationDriver implements WindowInsetsAnimationListener {

    @NonNull private final WindowAndroid mWindowAndroid;
    @NonNull private final PropertyModel mListPropertyModel;
    private WindowInsetsAnimationCompat mAnimation;
    @NonNull private final Supplier<Float> mOmniboxVerticalTranslationSupplier;
    private Runnable mShowSuggestionsListCallback;
    private final float mAdditionalVerticalOffset;

    /**
     * Construct a new SuggestionsListAnimationDriver
     *
     * @param windowAndroid WindowAndroid in which the driver is operating.
     * @param listPropertyModel Property model for the suggestions list view being animated.
     * @param omniboxVerticalTranslationSupplier Supplier of the current translation of the omnibox,
     *     used to synchronize the position of the suggestions list to match.
     * @param showSuggestionsListCallback Callback that shows the suggestions list when invoked.
     * @param additionalVerticalOffset Vertical compensation that should be added to vertical
     *     translation of the suggestions list during animation. The magnitude of the compensation
     *     will scale inversely with animation progress, from 100% at start to 0% at end.
     */
    public SuggestionsListAnimationDriver(
            @NonNull WindowAndroid windowAndroid,
            @NonNull PropertyModel listPropertyModel,
            @NonNull Supplier<Float> omniboxVerticalTranslationSupplier,
            @NonNull Runnable showSuggestionsListCallback,
            int additionalVerticalOffset) {
        mWindowAndroid = windowAndroid;
        mListPropertyModel = listPropertyModel;
        mOmniboxVerticalTranslationSupplier = omniboxVerticalTranslationSupplier;
        mShowSuggestionsListCallback = showSuggestionsListCallback;
        mAdditionalVerticalOffset = additionalVerticalOffset;
    }

    void onOmniboxSessionStateChange(boolean active) {
        if (active) {
            InsetObserver insetObserver = mWindowAndroid.getInsetObserver();
            insetObserver.addWindowInsetsAnimationListener(this);
        } else {
            removeInsetListener();
        }
    }

    private void removeInsetListener() {
        InsetObserver insetObserver = mWindowAndroid.getInsetObserver();
        insetObserver.removeWindowInsetsAnimationListener(this);
    }

    @Override
    public void onPrepare(@NonNull WindowInsetsAnimationCompat animation) {
        if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) == 0) {
            return;
        }

        mAnimation = animation;
        mShowSuggestionsListCallback.run();
        mListPropertyModel.set(SuggestionListProperties.ALPHA, 0.0f);
    }

    @Override
    public void onStart(
            @NonNull WindowInsetsAnimationCompat animation,
            @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds) {}

    @Override
    public void onEnd(@NonNull WindowInsetsAnimationCompat animation) {
        if (animation != mAnimation) return;
        removeInsetListener();
        mAnimation = null;
        mListPropertyModel.set(SuggestionListProperties.ALPHA, 1.0f);
        mListPropertyModel.set(SuggestionListProperties.CHILD_TRANSLATION_Y, 0.0f);
    }

    @Override
    public void onProgress(
            @NonNull WindowInsetsCompat windowInsetsCompat,
            @NonNull List<WindowInsetsAnimationCompat> runningAnimations) {
        if (mAnimation == null || !runningAnimations.contains(mAnimation)) return;

        float interpolatedFraction = mAnimation.getInterpolatedFraction();
        mListPropertyModel.set(SuggestionListProperties.ALPHA, interpolatedFraction);
        float verticalTranslationOfOmnibox = mOmniboxVerticalTranslationSupplier.get();
        if (verticalTranslationOfOmnibox > 0.0f
                || mListPropertyModel.get(SuggestionListProperties.CHILD_TRANSLATION_Y) > 0.0f) {
            mListPropertyModel.set(
                    SuggestionListProperties.CHILD_TRANSLATION_Y,
                    verticalTranslationOfOmnibox
                            + mAdditionalVerticalOffset * (1.0f - interpolatedFraction));
        }
    }
}