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 
17 package com.android.launcher3.responsive
18 
19 import android.content.res.TypedArray
20 import android.util.Log
21 import com.android.launcher3.R
22 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
23 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
24 import com.android.launcher3.util.ResourceHelper
25 
26 class ResponsiveCellSpecsProvider(groupOfSpecs: List<ResponsiveSpecGroup<CellSpec>>) {
27     private val groupOfSpecs: List<ResponsiveSpecGroup<CellSpec>>
28 
29     init {
30         this.groupOfSpecs =
31             groupOfSpecs
32                 .onEach { group ->
33                     check(group.widthSpecs.isEmpty() && group.heightSpecs.isNotEmpty()) {
34                         "$LOG_TAG is invalid, only heightSpecs are allowed - " +
35                             "width list size = ${group.widthSpecs.size}; " +
36                             "height list size = ${group.heightSpecs.size}."
37                     }
38                 }
39                 .sortedBy { it.aspectRatio }
40     }
41 
42     fun getSpecsByAspectRatio(aspectRatio: Float): ResponsiveSpecGroup<CellSpec> {
43         check(aspectRatio > 0f) { "Invalid aspect ratio! The value should be bigger than 0." }
44 
45         val specsGroup = groupOfSpecs.firstOrNull { aspectRatio <= it.aspectRatio }
46         check(specsGroup != null) { "No available spec with aspectRatio within $aspectRatio." }
47 
48         return specsGroup
49     }
50 
51     fun getCalculatedSpec(aspectRatio: Float, availableHeightSpace: Int): CalculatedCellSpec {
52         val specsGroup = getSpecsByAspectRatio(aspectRatio)
53         val spec = specsGroup.getSpec(DimensionType.HEIGHT, availableHeightSpace)
54         return CalculatedCellSpec(availableHeightSpace, spec)
55     }
56 
57     fun getCalculatedSpec(
58         aspectRatio: Float,
59         availableHeightSpace: Int,
60         workspaceCellSpec: CalculatedCellSpec
61     ): CalculatedCellSpec {
62         val specsGroup = getSpecsByAspectRatio(aspectRatio)
63         val spec = specsGroup.getSpec(DimensionType.HEIGHT, availableHeightSpace)
64         return CalculatedCellSpec(availableHeightSpace, spec, workspaceCellSpec)
65     }
66 
67     companion object {
68         private const val LOG_TAG = "ResponsiveCellSpecsProvider"
69         @JvmStatic
70         fun create(resourceHelper: ResourceHelper): ResponsiveCellSpecsProvider {
71             val parser = ResponsiveSpecsParser(resourceHelper)
72             val specs = parser.parseXML(ResponsiveSpecType.Cell, ::CellSpec)
73             return ResponsiveCellSpecsProvider(specs)
74         }
75     }
76 }
77 
78 data class CellSpec(
79     override val maxAvailableSize: Int,
80     override val dimensionType: DimensionType,
81     override val specType: ResponsiveSpecType,
82     val iconSize: SizeSpec,
83     val iconTextSize: SizeSpec,
84     val iconDrawablePadding: SizeSpec
85 ) : IResponsiveSpec {
86     init {
<lambda>null87         check(isValid()) { "Invalid CellSpec found." }
88     }
89 
90     constructor(
91         responsiveSpecType: ResponsiveSpecType,
92         attrs: TypedArray,
93         specs: Map<String, SizeSpec>
94     ) : this(
95         maxAvailableSize =
96             attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
97         dimensionType =
98             DimensionType.entries[
99                     attrs.getInt(
100                         R.styleable.ResponsiveSpec_dimensionType,
101                         DimensionType.HEIGHT.ordinal
102                     )],
103         specType = responsiveSpecType,
104         iconSize = specs.getOrError(SizeSpec.XmlTags.ICON_SIZE),
105         iconTextSize = specs.getOrError(SizeSpec.XmlTags.ICON_TEXT_SIZE),
106         iconDrawablePadding = specs.getOrError(SizeSpec.XmlTags.ICON_DRAWABLE_PADDING)
107     )
108 
isValidnull109     fun isValid(): Boolean {
110         if (maxAvailableSize <= 0) {
111             logError("The property maxAvailableSize must be higher than 0.")
112             return false
113         }
114 
115         // All specs need to be individually valid
116         if (!allSpecsAreValid()) {
117             logError("Specs must be either Fixed Size or Match Workspace!")
118             return false
119         }
120 
121         if (!isValidFixedSize()) {
122             logError("The total Fixed Size used must be lower or equal to $maxAvailableSize.")
123             return false
124         }
125 
126         return true
127     }
128 
isValidFixedSizenull129     private fun isValidFixedSize(): Boolean {
130         val totalSize = iconSize.fixedSize + iconTextSize.fixedSize + iconDrawablePadding.fixedSize
131         return totalSize <= maxAvailableSize
132     }
133 
allSpecsAreValidnull134     private fun allSpecsAreValid(): Boolean {
135         return (iconSize.fixedSize > 0f || iconSize.matchWorkspace) &&
136             (iconTextSize.fixedSize >= 0f || iconTextSize.matchWorkspace) &&
137             (iconDrawablePadding.fixedSize >= 0f || iconDrawablePadding.matchWorkspace)
138     }
139 
logErrornull140     private fun logError(message: String) {
141         Log.e(LOG_TAG, "$LOG_TAG#isValid - $message - $this")
142     }
143 
144     companion object {
145         const val LOG_TAG = "CellSpec"
146     }
147 }
148 
149 data class CalculatedCellSpec(
150     val availableSpace: Int,
151     val spec: CellSpec,
152     val iconSize: Int,
153     val iconTextSize: Int,
154     val iconDrawablePadding: Int
155 ) {
156     constructor(
157         availableSpace: Int,
158         spec: CellSpec
159     ) : this(
160         availableSpace = availableSpace,
161         spec = spec,
162         iconSize = spec.iconSize.getCalculatedValue(availableSpace),
163         iconTextSize = spec.iconTextSize.getCalculatedValue(availableSpace),
164         iconDrawablePadding = spec.iconDrawablePadding.getCalculatedValue(availableSpace)
165     )
166 
167     constructor(
168         availableSpace: Int,
169         spec: CellSpec,
170         workspaceCellSpec: CalculatedCellSpec
171     ) : this(
172         availableSpace = availableSpace,
173         spec = spec,
174         iconSize = getCalculatedValue(availableSpace, spec.iconSize, workspaceCellSpec.iconSize),
175         iconTextSize =
176             getCalculatedValue(availableSpace, spec.iconTextSize, workspaceCellSpec.iconTextSize),
177         iconDrawablePadding =
178             getCalculatedValue(
179                 availableSpace,
180                 spec.iconDrawablePadding,
181                 workspaceCellSpec.iconDrawablePadding
182             )
183     )
184 
185     companion object {
186         private const val LOG_TAG = "CalculatedCellSpec"
getCalculatedValuenull187         private fun getCalculatedValue(
188             availableSpace: Int,
189             spec: SizeSpec,
190             workspaceValue: Int
191         ): Int =
192             if (spec.matchWorkspace) workspaceValue else spec.getCalculatedValue(availableSpace)
193     }
194 
195     override fun toString(): String {
196         return "$LOG_TAG(" +
197             "availableSpace=$availableSpace, iconSize=$iconSize, " +
198             "iconTextSize=$iconTextSize, iconDrawablePadding=$iconDrawablePadding, " +
199             "${CellSpec.LOG_TAG}.maxAvailableSize=${spec.maxAvailableSize}" +
200             ")"
201     }
202 }
203