1 /*
<lambda>null2  * Copyright (C) 2023 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 package com.android.quickstep.util.unfold
17 
18 import android.graphics.Point
19 import android.view.ViewGroup
20 import com.android.app.animation.Interpolators.LINEAR
21 import com.android.app.animation.Interpolators.clampToProgress
22 import com.android.launcher3.CellLayout
23 import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY
24 import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATION
25 import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
26 import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y
27 import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY
28 import com.android.launcher3.Workspace
29 import com.android.launcher3.anim.PendingAnimation
30 import com.android.launcher3.uioverrides.QuickstepLauncher
31 import com.android.launcher3.util.HorizontalInsettableView
32 
33 private typealias ViewGroupAction = (ViewGroup, Boolean) -> Unit
34 
35 object UnfoldAnimationBuilder {
36 
37     private val CLIP_CHILDREN: ViewGroupAction = ViewGroup::setClipChildren
38     private val CLIP_TO_PADDING: ViewGroupAction = ViewGroup::setClipToPadding
39 
40     data class RestoreInfo(val action: ViewGroupAction, var target: ViewGroup, var value: Boolean)
41 
42     // Percentage of the width of the quick search bar that will be reduced
43     // from the both sides of the bar when progress is 0
44     private const val MAX_WIDTH_INSET_FRACTION = 0.04f
45 
46     // Scale factor for the whole workspace and hotseat
47     private const val SCALE_LAUNCHER_FROM = 0.92f
48 
49     // Translation factor for all the items on the homescreen
50     private const val TRANSLATION_PERCENTAGE = 0.08f
51 
52     private fun setClipChildren(
53         target: ViewGroup,
54         value: Boolean,
55         restoreList: MutableList<RestoreInfo>
56     ) {
57         val originalValue = target.clipChildren
58         if (originalValue != value) {
59             target.clipChildren = value
60             restoreList.add(RestoreInfo(CLIP_CHILDREN, target, originalValue))
61         }
62     }
63 
64     private fun setClipToPadding(
65         target: ViewGroup,
66         value: Boolean,
67         restoreList: MutableList<RestoreInfo>
68     ) {
69         val originalValue = target.clipToPadding
70         if (originalValue != value) {
71             target.clipToPadding = value
72             restoreList.add(RestoreInfo(CLIP_TO_PADDING, target, originalValue))
73         }
74     }
75 
76     private fun addChildrenAnimation(
77         itemsContainer: ViewGroup,
78         isVerticalFold: Boolean,
79         screenSize: Point,
80         anim: PendingAnimation
81     ) {
82         val tempLocation = IntArray(2)
83         for (i in 0 until itemsContainer.childCount) {
84             val child = itemsContainer.getChildAt(i)
85 
86             child.getLocationOnScreen(tempLocation)
87             if (isVerticalFold) {
88                 val viewCenterX = tempLocation[0] + child.width / 2
89                 val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX
90                 anim.addFloat(
91                     child,
92                     VIEW_TRANSLATE_X,
93                     distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE,
94                     0f,
95                     LINEAR
96                 )
97             } else {
98                 val viewCenterY = tempLocation[1] + child.height / 2
99                 val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY
100                 anim.addFloat(
101                     child,
102                     VIEW_TRANSLATE_Y,
103                     distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE,
104                     0f,
105                     LINEAR
106                 )
107             }
108         }
109     }
110 
111     /**
112      * Builds an animation for the unfold experience and adds it to the provided PendingAnimation
113      */
114     fun buildUnfoldAnimation(
115         launcher: QuickstepLauncher,
116         isVerticalFold: Boolean,
117         screenSize: Point,
118         anim: PendingAnimation
119     ) {
120         val restoreList = ArrayList<RestoreInfo>()
121         val registerViews: (CellLayout) -> Unit = { cellLayout ->
122             setClipChildren(cellLayout, false, restoreList)
123             setClipToPadding(cellLayout, false, restoreList)
124             addChildrenAnimation(cellLayout.shortcutsAndWidgets, isVerticalFold, screenSize, anim)
125         }
126 
127         val workspace: Workspace<*> = launcher.workspace
128         val hotseat = launcher.hotseat
129 
130         // Animation icons from workspace for all orientations
131         workspace.forEachVisiblePage { registerViews(it as CellLayout) }
132         setClipChildren(workspace, false, restoreList)
133         setClipToPadding(workspace, true, restoreList)
134 
135         // Workspace scale
136         launcher.workspace.setPivotToScaleWithSelf(launcher.hotseat)
137         val interpolator = clampToProgress(LINEAR, 0f, 1f)
138         anim.addFloat(
139             workspace,
140             WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_UNFOLD_ANIMATION],
141             SCALE_LAUNCHER_FROM,
142             1f,
143             interpolator
144         )
145         anim.addFloat(
146             hotseat,
147             HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_UNFOLD_ANIMATION],
148             SCALE_LAUNCHER_FROM,
149             1f,
150             interpolator
151         )
152 
153         if (isVerticalFold) {
154             if (hotseat.qsb is HorizontalInsettableView) {
155                 anim.addFloat(
156                     hotseat.qsb as HorizontalInsettableView,
157                     HorizontalInsettableView.HORIZONTAL_INSETS,
158                     MAX_WIDTH_INSET_FRACTION,
159                     0f,
160                     LINEAR
161                 )
162             }
163             registerViews(hotseat)
164         }
165         anim.addEndListener { restoreList.forEach { it.action(it.target, it.value) } }
166     }
167 }
168