blob: 72d9d2e5264b600bbb629030dd69f3022e708753 [file] [log] [blame]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.glance.appwidget
import android.content.Context
import android.os.Build
import android.view.ViewGroup
import androidx.glance.Modifier
import androidx.glance.findModifier
import androidx.glance.layout.Dimension
import androidx.glance.layout.HeightModifier
import androidx.glance.layout.WidthModifier
import androidx.glance.unit.dp
/**
* Set of ids defining a layout.
*
* It contains the id of the layout itself, and the ids of the elements within the layout that
* the code may need to access.
*/
internal data class LayoutIds(
val layoutId: Int,
val mainViewId: Int = R.id.glanceView,
val sizeViewId: Int? = null,
)
/**
* The total number of generated layouts.
*/
internal val GeneratedLayoutCount = generatedLayouts.size
/**
* Layout selector.
*
* This class is used to select a particular layout in [generatedLayouts].
*/
internal data class LayoutSelector(
val type: Type,
val width: Size,
val height: Size,
val canResize: Boolean,
) {
internal enum class Size {
Wrap,
Fixed,
Expand,
MatchParent,
}
internal enum class Type {
Row,
Column,
Box,
Text,
List1,
List2,
List3,
ListItem,
CheckBox,
CheckBoxBackport,
Button,
// Note: Java keywords, such as 'switch', can't be used for layout ids.
Swtch,
SwtchBackport
}
}
/**
* Select the layout based on the specification.
*
* @param type Type of layout
* @param modifier Modifier applied to the layout. Modifiers of interest will be extracted to get
* the layout they can be applied on.
*/
internal fun selectLayout(
translationContext: TranslationContext,
type: LayoutSelector.Type,
modifier: Modifier
): LayoutIds {
val context = translationContext.context
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return selectApi31Layout(type, modifier)
}
val widthMod = modifier.findModifier<WidthModifier>()?.width ?: Dimension.Wrap
val heightMod = modifier.findModifier<HeightModifier>()?.height ?: Dimension.Wrap
val width = widthMod.resolveDimension(context).toSpecSize()
val height = heightMod.resolveDimension(context).toSpecSize()
val needResize = width == LayoutSelector.Size.Fixed || height == LayoutSelector.Size.Fixed
return generatedLayouts[LayoutSelector(type, width, height, needResize)]
?: (if (!needResize) generatedLayouts[LayoutSelector(type, width, height, true)] else null)
?: throw IllegalArgumentException(
"Could not find layout for $type, width=$width, height=$height, canResize=$needResize"
)
}
private fun Dimension.toSpecSize(): LayoutSelector.Size =
when (this) {
is Dimension.Dp -> LayoutSelector.Size.Fixed
is Dimension.Wrap -> LayoutSelector.Size.Wrap
is Dimension.Expand -> LayoutSelector.Size.Expand
is Dimension.Fill -> LayoutSelector.Size.MatchParent
else -> LayoutSelector.Size.Fixed
}
private fun Dimension.resolveDimension(context: Context): Dimension {
if (this !is Dimension.Resource) return this
val sizePx = context.resources.getDimension(res)
return when (sizePx.toInt()) {
ViewGroup.LayoutParams.MATCH_PARENT -> Dimension.Fill
ViewGroup.LayoutParams.WRAP_CONTENT -> Dimension.Wrap
else -> Dimension.Dp((sizePx / context.resources.displayMetrics.density).dp)
}
}
/**
* For API 31, we will always select layouts marked as non-resizable, as starting Android S, we
* can always resize views and we want the simplest layout possible.
*/
private fun selectApi31Layout(
type: LayoutSelector.Type,
modifier: Modifier
): LayoutIds {
val widthMod = modifier.findModifier<WidthModifier>()?.width ?: Dimension.Wrap
val heightMod = modifier.findModifier<HeightModifier>()?.height ?: Dimension.Wrap
val width = widthMod.toSpecSize()
val height = heightMod.toSpecSize()
return generatedLayouts[LayoutSelector(type, width, height, canResize = false)]
?: throw IllegalArgumentException(
"Could not find layout for $type, width=$width, height=$height, canResize=false"
)
}