blob: 7f513eb3d2843fa24fba216935be545173dc38ee [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.layoutgenerator
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.buildCodeBlock
import com.squareup.kotlinpoet.joinToCode
import java.io.File
/**
* Generate the registry: a mapping from `LayoutSelector` to `Layout`.
*
* For each generated layout, a selector is created describing what the layout can and cannot do.
* It is mapped to a `Layout`, an object describing the parameters needed to act on the layout.
*/
internal fun generateRegistry(
packageName: String,
layouts: Map<File, LayoutProperties>,
outputSourceDir: File,
) {
outputSourceDir.mkdirs()
val file = FileSpec.builder(packageName, "GeneratedLayouts")
file.addComment(LicenseComment)
val generatedLayouts = PropertySpec.builder(
"generatedLayouts",
LayoutsMap,
KModifier.INTERNAL
).apply {
initializer(buildInitializer(layouts))
}.build()
file.addProperty(generatedLayouts)
file.build().writeTo(outputSourceDir)
}
private fun buildInitializer(layouts: Map<File, LayoutProperties>): CodeBlock {
return buildCodeBlock {
addStatement("mapOf(")
withIndent {
add(
layouts.map {
it.key to createFileInitializer(it.key, it.value.mainViewId)
}
.sortedBy { it.first.nameWithoutExtension }
.map { it.second }
.joinToCode("")
)
}
addStatement(")")
}
}
private fun createFileInitializer(layout: File, mainViewId: String): CodeBlock = buildCodeBlock {
val viewType = layout.nameWithoutExtension.toLayoutType()
ValidSize.values().forEach { width ->
ValidSize.values().forEach { height ->
addLayout(
resourceName = makeSimpleResourceName(layout, width, height),
viewType = viewType,
width = width,
height = height,
canResize = false,
mainViewId = "R.id.$mainViewId",
sizeViewId = null
)
addLayout(
resourceName = makeComplexResourceName(layout, width, height),
viewType = viewType,
width = width,
height = height,
canResize = true,
mainViewId = "R.id.$mainViewId",
sizeViewId = "R.id.sizeView"
)
}
}
}
private fun CodeBlock.Builder.addLayout(
resourceName: String,
viewType: String,
width: ValidSize,
height: ValidSize,
canResize: Boolean,
mainViewId: String,
sizeViewId: String?
) {
addStatement(
"%T(type = %M, width = %M, height = %M, canResize = $canResize) to ",
LayoutSelector,
makeViewType(viewType),
width.toValue(),
height.toValue(),
)
withIndent {
addStatement("%T(", LayoutIds)
withIndent {
addStatement("layoutId = R.layout.$resourceName,")
addStatement("mainViewId = $mainViewId,")
addStatement("sizeViewId = $sizeViewId,")
}
addStatement("),")
}
}
private val LayoutSelector = ClassName("androidx.glance.appwidget", "LayoutSelector")
private val LayoutIds = ClassName("androidx.glance.appwidget", "LayoutIds")
private val LayoutsMap = Map::class.asTypeName().parameterizedBy(LayoutSelector, LayoutIds)
private const val LayoutSpecSize = "androidx.glance.appwidget.LayoutSelector.Size"
private val WrapValue = MemberName("$LayoutSpecSize", "Wrap")
private val FixedValue = MemberName("$LayoutSpecSize", "Fixed")
private val MatchValue = MemberName("$LayoutSpecSize", "MatchParent")
private val ExpandValue = MemberName("$LayoutSpecSize", "Expand")
private fun makeViewType(name: String) =
MemberName("androidx.glance.appwidget.LayoutSelector.Type", name)
private fun String.toLayoutType() =
snakeRegex.replace(this) {
it.value.replace("_", "").uppercase()
}.replaceFirstChar { it.uppercaseChar() }
private val snakeRegex = "_[a-zA-Z0-9]".toRegex()
private fun ValidSize.toValue() = when (this) {
ValidSize.Wrap -> WrapValue
ValidSize.Fixed -> FixedValue
ValidSize.Expand -> ExpandValue
ValidSize.Match -> MatchValue
}
internal fun makeSimpleResourceName(file: File, width: ValidSize, height: ValidSize) =
"${file.nameWithoutExtension}_simple_${width.resourceName}_${height.resourceName}"
internal fun makeComplexResourceName(file: File, width: ValidSize, height: ValidSize) =
"${file.nameWithoutExtension}_complex_${width.resourceName}_${height.resourceName}"
fun CodeBlock.Builder.withIndent(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock.Builder {
indent()
apply(builderAction)
unindent()
return this
}
val LicenseComment =
"""
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.
""".trimIndent()