1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.allapps;
18 
19 import static android.view.View.VISIBLE;
20 
21 import static com.android.app.animation.Interpolators.DECELERATE_1_7;
22 import static com.android.app.animation.Interpolators.INSTANT;
23 import static com.android.app.animation.Interpolators.clampToProgress;
24 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
25 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
26 
27 import android.animation.ObjectAnimator;
28 import android.animation.TimeInterpolator;
29 import android.view.View;
30 import android.view.animation.Interpolator;
31 
32 import com.android.launcher3.R;
33 
34 /** Coordinates the transition between Search and A-Z in All Apps. */
35 public class SearchTransitionController extends RecyclerViewAnimationController {
36 
37     // Interpolator when the user taps the QSB while already in All Apps.
38     private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DECELERATE_1_7;
39     // Interpolator when the user taps the QSB from home screen, so transition to all apps is
40     // happening simultaneously.
41     private static final Interpolator INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = INSTANT;
42 
43     private boolean mSkipNextAnimationWithinAllApps;
44 
SearchTransitionController(ActivityAllAppsContainerView<?> allAppsContainerView)45     public SearchTransitionController(ActivityAllAppsContainerView<?> allAppsContainerView) {
46         super(allAppsContainerView);
47     }
48 
49     /**
50      * Starts the transition to or from search state. If a transition is already in progress, the
51      * animation will start from that point with the new duration, and the previous onEndRunnable
52      * will not be called.
53      *
54      * @param goingToSearch true if will be showing search results, otherwise will be showing a-z
55      * @param duration time in ms for the animation to run
56      * @param onEndRunnable will be called when the animation finishes, unless another animation is
57      *                      scheduled in the meantime
58      */
59     @Override
animateToState(boolean goingToSearch, long duration, Runnable onEndRunnable)60     protected void animateToState(boolean goingToSearch, long duration, Runnable onEndRunnable) {
61         super.animateToState(goingToSearch, duration, onEndRunnable);
62         if (!goingToSearch) {
63             mAnimator.addListener(forSuccessCallback(() -> {
64                 mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(false);
65                 mAllAppsContainerView.getFloatingHeaderView().reset(false /* animate */);
66                 mAllAppsContainerView.getAppsRecyclerViewContainer().setTranslationY(0);
67             }));
68         }
69         mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(true);
70         mAllAppsContainerView.getFloatingHeaderView().setVisibility(VISIBLE);
71         mAllAppsContainerView.getFloatingHeaderView().maybeSetTabVisibility(VISIBLE);
72         mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(VISIBLE);
73         getRecyclerView().setVisibility(VISIBLE);
74     }
75 
76     @Override
getRecyclerView()77     protected SearchRecyclerView getRecyclerView() {
78         return mAllAppsContainerView.getSearchRecyclerView();
79     }
80 
81     @Override
onProgressUpdated(float searchToAzProgress)82     protected int onProgressUpdated(float searchToAzProgress) {
83         int searchHeight = super.onProgressUpdated(searchToAzProgress);
84 
85         FloatingHeaderView headerView = mAllAppsContainerView.getFloatingHeaderView();
86 
87         // Add predictions + app divider height to account for predicted apps which will now be in
88         // the Search RV instead of the floating header view. Note `getFloatingRowsHeight` returns 0
89         // when predictions are not shown.
90         int appsTranslationY = searchHeight + headerView.getFloatingRowsHeight();
91 
92         if (headerView.usingTabs()) {
93             // Move tabs below the search results, and fade them out in 20% of the animation.
94             headerView.setTranslationY(searchHeight);
95             headerView.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f));
96 
97             // Account for the additional padding added for the tabs.
98             appsTranslationY +=
99                     headerView.getTabsAdditionalPaddingBottom()
100                             + mAllAppsContainerView.getResources().getDimensionPixelOffset(
101                                     R.dimen.all_apps_tabs_margin_top)
102                             - headerView.getPaddingTop();
103         }
104 
105         View appsContainer = mAllAppsContainerView.getAppsRecyclerViewContainer();
106         appsContainer.setTranslationY(appsTranslationY);
107         // Fade apps out with tabs (in 20% of the total animation).
108         appsContainer.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f));
109         return searchHeight;
110     }
111 
112     /**
113      * Should only animate if the view is not an app icon or if the app row is complete.
114      */
115     @Override
shouldAnimate(View view, boolean hasDecorationInfo, boolean appRowComplete)116     protected boolean shouldAnimate(View view, boolean hasDecorationInfo, boolean appRowComplete) {
117         return !isAppIcon(view) || appRowComplete;
118     }
119 
120     @Override
getInterpolator()121     protected TimeInterpolator getInterpolator() {
122         TimeInterpolator timeInterpolator =
123                 mAllAppsContainerView.isInAllApps()
124                         ? INTERPOLATOR_WITHIN_ALL_APPS : INTERPOLATOR_TRANSITIONING_TO_ALL_APPS;
125         if (mSkipNextAnimationWithinAllApps) {
126             timeInterpolator = INSTANT;
127             mSkipNextAnimationWithinAllApps = false;
128         }
129         return timeInterpolator;
130     }
131 
132     /**
133      * This should only be called from {@code LauncherSearchSessionManager} when app restarts due to
134      * theme changes.
135      */
setSkipAnimationWithinAllApps(boolean skip)136     public void setSkipAnimationWithinAllApps(boolean skip) {
137         mSkipNextAnimationWithinAllApps = skip;
138     }
139 }
140