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