From f9e94217b8254d60c5a98b0b4b8ec8b142255bee Mon Sep 17 00:00:00 2001 From: malilex Date: Thu, 7 Mar 2024 18:02:02 +0300 Subject: [PATCH 01/13] feat(sdds-acore/theme-builder): Fetching task was created. Theme file deserialization was implemented. Color, gradients, typography and dimen tokens generators were implemented for view system. --- gradle/libs.versions.toml | 4 + playground/theme-builder/build.gradle.kts | 9 + .../theme-builder/new_theme_scheme.json | 170 ++++++++++++++++++ .../src/main/res/values/dimens.xml | 4 + playground/theme-builder/theme_scheme.json | 139 ++++++++++++++ .../plugin_theme_builder/build.gradle.kts | 12 +- .../plugin/themebuilder/FetchThemeTask.kt | 43 +++++ .../plugin/themebuilder/GenerateThemeTask.kt | 95 ++++++++++ .../themebuilder/ThemeBuilderExtension.kt | 21 +++ .../plugin/themebuilder/ThemeBuilderPlugin.kt | 53 +++++- .../plugin/themebuilder/ThemeBuilderTarget.kt | 26 +++ .../themebuilder/internal/FileProvider.kt | 38 ++++ .../themebuilder/internal/StringUtils.kt | 19 ++ .../internal/builder/XmlDocumentBuilder.kt | 87 +++++++++ .../builder/XmlDocumentBuilderFactory.kt | 12 ++ .../themebuilder/internal/dimens/DimenData.kt | 16 ++ .../internal/dimens/DimensAggregator.kt | 16 ++ .../internal/generator/BaseGenerator.kt | 18 ++ .../internal/generator/ColorGenerator.kt | 29 +++ .../internal/generator/DimenGenerator.kt | 32 ++++ .../internal/generator/GradientGenerator.kt | 78 ++++++++ .../internal/generator/TypographyGenerator.kt | 87 +++++++++ .../ExcludeNonAndroidPlatformTokens.kt | 54 ++++++ .../internal/serializer/Serializer.kt | 34 ++++ .../internal/token/ColorTokens.kt | 97 ++++++++++ .../internal/token/ShadowTokens.kt | 34 ++++ .../themebuilder/internal/token/Theme.kt | 16 ++ .../themebuilder/internal/token/Token.kt | 50 ++++++ .../internal/token/TypographyTokens.kt | 59 ++++++ 29 files changed, 1346 insertions(+), 6 deletions(-) create mode 100644 playground/theme-builder/new_theme_scheme.json create mode 100644 playground/theme-builder/src/main/res/values/dimens.xml create mode 100644 playground/theme-builder/theme_scheme.json create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/FetchThemeTask.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderExtension.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderTarget.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/FileProvider.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/StringUtils.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilder.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilderFactory.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/dimens/DimenData.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/dimens/DimensAggregator.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/BaseGenerator.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorGenerator.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/DimenGenerator.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientGenerator.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/ExcludeNonAndroidPlatformTokens.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/Serializer.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ColorTokens.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShadowTokens.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/Theme.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/Token.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/TypographyTokens.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2a607140..53f6a587 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,8 @@ test-robolectric = "4.9" test-androidX-rules = "1.5.0" test-androidX-runner = "1.5.2" +kotlinSerialization = "1.4.0" + plugin-androidCacheFix = "3.0.1" plugin-gradleNexusPublish = "1.3.0" plugin-gradlePluginPublish = "1.2.1" @@ -40,6 +42,7 @@ base-androidX-compose-uiTooling = { module = "androidx.compose.ui:ui-tooling" } base-androidX-compose-uiTooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } base-kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "global-kotlin" } +base-kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } base-staticAnalysis-ktlint = { module = "com.pinterest:ktlint", version.ref = "staticAnalysis-ktlint" } base-staticAnalysis-detekt-api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "staticAnalysis-detekt" } @@ -75,6 +78,7 @@ android-lib = { id = "com.android.library", version.ref = "global-gradle" } android-app = { id = "com.android.application", version.ref = "global-gradle" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "global-kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "global-kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "global-kotlin" } paparazzi = { id = "app.cash.paparazzi", version.ref = "test-paparazzi" } jgp = { id = "java-gradle-plugin" } android-cache-fix = { id = "org.gradle.android.cache-fix", version.ref= "plugin-androidCacheFix" } diff --git a/playground/theme-builder/build.gradle.kts b/playground/theme-builder/build.gradle.kts index 7fc22534..6aad0f88 100644 --- a/playground/theme-builder/build.gradle.kts +++ b/playground/theme-builder/build.gradle.kts @@ -1,8 +1,17 @@ +import com.sdds.plugin.themebuilder.ThemeBuilderExtension +import com.sdds.plugin.themebuilder.ThemeBuilderTarget + @Suppress("DSL_SCOPE_VIOLATION") plugins { id("convention.android-lib") + id(libs.plugins.themebuilder.get().pluginId) } android { namespace = "com.sdds.playground.themebuilder" } + +configure { + themeUrl.set("file://${projectDir.path}/new_theme_scheme.json") + target.set(ThemeBuilderTarget.COMPOSE.value) +} diff --git a/playground/theme-builder/new_theme_scheme.json b/playground/theme-builder/new_theme_scheme.json new file mode 100644 index 00000000..5296cd6b --- /dev/null +++ b/playground/theme-builder/new_theme_scheme.json @@ -0,0 +1,170 @@ +{ + "name" : "theme-name", + "version" : "0.0.1", + "platforms": ["ios", "android", "web"], + "colors" : { + "category": ["text", "surfaces", "backgrounds"], + "mode" : ["dark", "light"], + "subcategory" : ["on-dark", "on-light", "default", "inverse"] + }, + "shadows": { + "directions": ["up", "down"], + "kind": ["hard", "soft"], + "sizes": ["l", "m", "s", "xs", "xxs"] + }, + "typography": { + "sizes": ["l", "m", "s", "xs", "xxs"], + "kind": ["display", "body", "text", "h1", "h2", "h3", "h4", "h5"], + "screen": ["screen-l", "screen-m", "screen-s"] + }, + "tokens": [ + { + "displayName": "surfaceAccent", + "name": "dark.inverse.surface.accent", + "platform": "android", + "tags" : [ + "dark", + "surface", + "inverse" + ], + "type": "sweep-gradient", + "value" : { + "locations": [ 0, 0.3, 0.7, 1 ], + "colors" : ["#000", "#fff"], + "startAngle": 0, + "endAngle" : 360 + }, + "description": "Цвет текста", + "enabled": true + }, + { + "displayName": "surfaceTransparentAccent", + "name": "dark.on-light.surface.transparent-accent", + "platform": "android", + "tags" : [ + "dark", + "surface", + "on-light" + ], + "type": "color", + "value" : { + "origin": "#FFFFFF1F" + }, + "description": "Прозрачная акцентная поверхность", + "enabled": true + }, + { + "displayName": "textAccent", + "name": "on-dark.text.accent", + "platform": "web", + "tags": [ + "dark", + "on-dark", + "text", + "gradient" + ], + "type": "linear-gradient", + "value" : { + "origin": "linear-gradient(122.62deg, #4817C1 0%, #C505D6 100%)" + }, + "description": "Цвет текста", + "enabled": true + }, + { + "displayName": "downHardL", + "name": "down.hard.l", + "platform": "android", + "tags": [ + "down", + "hard", + "l" + ], + "type": "shadow", + "value": { + "color": "#000", + "elevation": 16 + }, + "description": "тень android", + "enabled": true + }, + { + "name": "downHardL", + "tags": [ + "down", + "hard", + "l", + "ios" + ], + "platform": "ios", + "type": "shadow", + "value": { + "color": "#000", + "offset": { "width": 0, "height": 2 }, + "opacity": 0.15, + "radius": 1.5 + }, + "description": "Тень ios", + "enabled": true + }, + { + "name": "downHardL", + "platform": "web", + "tags": [ + "down", + "hard", + "l", + "web" + ], + "type": "shadow", + "value": { + "origin" : "0px 6px 12px rgba(0, 0, 0, 0.12)" + }, + "description": "Тень web", + "enabled": true + }, + { + "displayName": "displayL", + "name": "screen-l.display.l", + "platform": "android", + "tags": [ + "screen-l", + "display", + "l" + ], + "type": "typography", + "value": { + "fontFamily": "sans", + "fontWeight": 300, + "fontStyle": "normal", + "textSize": 128, + "letterSpacing": 0.02, + "lineHeight": 128 + }, + "description": "шрифт android", + "enabled": true + }, + { + "displayName": "displayL", + "name": "screen-m.display.l", + "platform": "android", + "tags": [ + "screen-m", + "display", + "l" + ], + "type": "typography", + "value": { + "fontFamily": "sans", + "fontWeight": 300, + "fontStyle": "normal", + "textSize": 96, + "letterSpacing": 0.02, + "lineHeight": 96 + }, + "description": "шрифт android", + "enabled": true + } + ] +} + + \ No newline at end of file diff --git a/playground/theme-builder/src/main/res/values/dimens.xml b/playground/theme-builder/src/main/res/values/dimens.xml new file mode 100644 index 00000000..3261aff6 --- /dev/null +++ b/playground/theme-builder/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/playground/theme-builder/theme_scheme.json b/playground/theme-builder/theme_scheme.json new file mode 100644 index 00000000..426b8f3a --- /dev/null +++ b/playground/theme-builder/theme_scheme.json @@ -0,0 +1,139 @@ +{ + "name" : "theme-name", + "version" : "0.0.2", + "platforms": ["ios", "android", "web"], + "colors" : { + "category": ["text", "surfaces", "backgrounds"], + "mode" : ["dark", "light"], + "subcategory" : ["on-dark", "on-light", "default", "inverse"], + "naming": "subcategory.category.name", + "tokens" : [ + { + "displayName": "surfaceAccent", + "name": "inverse.surface.accent", + "platform": "ios", + "tags" : [ + "dark", + "surface", + "inverse" + ], + "value" : { + "type": "angular-gradient", + "locations": [ 0, 0.3, 0.7, 1 ], + "colors" : ["#000", "#fff"], + "startAngle": 0, + "endAngle" : 360 + }, + "description": "Цвет текста", + "enabled": true + }, + { + "displayName": "surfaceTransparentAccent", + "name": "on-light.surface.transparent-accent", + "platform": "ios", + "tags" : [ + "dark", + "surface", + "on-light" + ], + "value" : { + "origin": "#FFFFFF1F" + }, + "description": "Прозрачная акцентная поверхность", + "enabled": true + }, + { + "displayName": "textAccent", + "name": "on-dark.text.accent", + "platform": "web", + "tags": [ + "dark", + "on-dark", + "text" + ], + "value" : "linear-gradient(122.62deg, #4817C1 0%, #C505D6 100%)", + "description": "Цвет текста", + "enabled": true + } + ] + }, + + "shadows": { + "directions": ["up", "down"], + "types": ["hard", "soft"], + "sizes": ["l", "m", "s", "xs", "xxs"], + "tokens" : [ + { + "displayName": "downHardL", + "name": "down.hard.l", + "tags": [ + "down", + "hard", + "l" + ], + "value": { + "color": "#000", + "elevation": 16 + }, + "description": "тень android", + "enabled": true + }, + { + "name": "downHardL", + "tags": [ + "down", + "hard", + "l", + "ios" + ], + "value": { + "color": "#000", + "offset": { "width": 0, "height": 2 }, + "opacity": 0.15, + "radius": 1.5 + }, + "description": "Тень ios", + "enabled": true + }, + { + "name": "downHardL", + "tags": [ + "down", + "hard", + "l", + "web" + ], + "value": "0px 6px 12px rgba(0, 0, 0, 0.12)", + "description": "Тень web", + "enabled": true + } + ] + }, + + "typography": { + "sizes": ["l", "m", "s", "xs", "xxs"], + "type": ["display", "body", "text", "h1", "h2", "h3", "h4", "h5"], + "screen": ["sceen-l", "screen-m", "screen-s"], + "tokens": [ + { + "displayName": "displayL", + "name": "screen-l.display.l", + "platform": "android", + "tags": [ + "screen-l", + "display", + "l" + ], + "value": { + "font-family": "#000", + "fontSize": 16, + "lineSpacing": 0.02 + }, + "description": "шрифт android", + "enabled": true + } + ] + } +} + + \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/build.gradle.kts b/sdds-core/plugin_theme_builder/build.gradle.kts index 47d3aa8f..5d7e25aa 100644 --- a/sdds-core/plugin_theme_builder/build.gradle.kts +++ b/sdds-core/plugin_theme_builder/build.gradle.kts @@ -1,19 +1,22 @@ import utils.findPropertyOrDefault import utils.versionInfo +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @Suppress("DSL_SCOPE_VIOLATION") plugins { `java-gradle-plugin` `kotlin-dsl` alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) alias(libs.plugins.gradlePluginPublish) id("convention.detekt") id("convention.spotless") } -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 +afterEvaluate { + tasks.withType().configureEach { + kotlinOptions.languageVersion = "1.7" + } } group = "io.github.salute-developers" @@ -40,7 +43,8 @@ publishing { } dependencies { - implementation(libs.base.kotlin.stdlib) + implementation(libs.base.kotlin.serialization.json) + implementation(libs.base.gradle.android) testImplementation(libs.base.test.unit.jUnit) testImplementation(libs.base.test.unit.mockk) } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/FetchThemeTask.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/FetchThemeTask.kt new file mode 100644 index 00000000..610d700d --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/FetchThemeTask.kt @@ -0,0 +1,43 @@ +package com.sdds.plugin.themebuilder + +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import java.net.URL + +/** + * Gradle-task для загрузки файла темы + * @author Малышев Александр on 04.03.2024 + */ +internal abstract class FetchThemeTask : DefaultTask() { + + /** + * Строка URL файла темы + */ + @get:Input + abstract val url: Property + + /** + * Местоположение файла с темой после загрузки + */ + @get:OutputFile + abstract val themeFile: RegularFileProperty + + /** + * Загружает файл с темой + */ + @TaskAction + fun fetch() { + runCatching { URL(url.get()).readBytes() } + .onSuccess { themeContent -> + val themeJSON = themeFile.get().asFile + themeJSON.writeBytes(themeContent) + }.onFailure { + logger.error("Can't fetch theme json", it) + throw it + } + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt new file mode 100644 index 00000000..a7553e5e --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt @@ -0,0 +1,95 @@ +package com.sdds.plugin.themebuilder + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.AppPlugin +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.LibraryPlugin +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilderFactory +import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator +import com.sdds.plugin.themebuilder.internal.generator.ColorGenerator +import com.sdds.plugin.themebuilder.internal.generator.DimenGenerator +import com.sdds.plugin.themebuilder.internal.generator.GradientGenerator +import com.sdds.plugin.themebuilder.internal.generator.TypographyGenerator +import com.sdds.plugin.themebuilder.internal.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.token.ColorToken +import com.sdds.plugin.themebuilder.internal.token.LinearGradientToken +import com.sdds.plugin.themebuilder.internal.token.RadialGradientToken +import com.sdds.plugin.themebuilder.internal.token.ShadowToken +import com.sdds.plugin.themebuilder.internal.token.SweepGradientToken +import com.sdds.plugin.themebuilder.internal.token.Theme +import com.sdds.plugin.themebuilder.internal.token.Token +import com.sdds.plugin.themebuilder.internal.token.TypographyToken +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction + +/** + * + * @author Малышев Александр on 05.03.2024 + */ +abstract class GenerateThemeTask : DefaultTask() { + + @get:InputFile + abstract val themeFile: RegularFileProperty + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + + + private val mainSourceSet = when { + project.plugins.hasPlugin(AppPlugin::class.java) -> + project.extensions.getByType(AppExtension::class.java).sourceSets.maybeCreate("main") + project.plugins.hasPlugin(LibraryPlugin::class.java) -> + project.extensions.getByType(LibraryExtension::class.java).sourceSets.maybeCreate("main") + else -> throw IllegalStateException("The project must have either AppPlugin or LibraryPlugin applied.") + } + + + @TaskAction + fun generate() { + // val outputDir = project.layout.buildDirectory.dir("theme-builder").get().asFile + val outputDir = outputDir.get().asFile + val dimensAggregator = DimensAggregator() + val colorGenerator = ColorGenerator(outputDir, XmlDocumentBuilderFactory) + val gradientGenerator = GradientGenerator(outputDir, XmlDocumentBuilderFactory) + val typographyGenerator = TypographyGenerator(outputDir, dimensAggregator, XmlDocumentBuilderFactory) + val dimensGenerator = DimenGenerator( + outputDir, + dimensAggregator, + XmlDocumentBuilderFactory + ) + + decodeTheme().tokens.onEach { + when (it) { + is ColorToken -> colorGenerator.addToken(it) + is SweepGradientToken -> gradientGenerator.addToken(it) + is LinearGradientToken -> gradientGenerator.addToken(it) + is RadialGradientToken -> gradientGenerator.addToken(it) + is ShadowToken -> {} + is TypographyToken -> typographyGenerator.addToken(it) + } + } + + colorGenerator.generate() + gradientGenerator.generate() + typographyGenerator.generate() + dimensGenerator.generate() + } + + private fun decodeTheme(): Theme { + val readFile = themeFile.get().asFile.readText() + val theme = Serializer.instance.decodeFromString(readFile) + println("Decoded object $theme") + return theme + } +} diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderExtension.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderExtension.kt new file mode 100644 index 00000000..bda80d77 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderExtension.kt @@ -0,0 +1,21 @@ +package com.sdds.plugin.themebuilder + +import org.gradle.api.provider.Property + +/** + * Расширение для плагина ThemeBuilder + * @author Малышев Александр on 04.03.2024 + */ +interface ThemeBuilderExtension { + + /** + * Путь к файлу с темой + */ + val themeUrl: Property + + /** + * Целевой фреймворк для которого будет сгенерирована тема. + * @see ThemeBuilderTarget + */ + val target: Property +} diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderPlugin.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderPlugin.kt index 7924362d..3098b1a6 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderPlugin.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderPlugin.kt @@ -1,14 +1,63 @@ package com.sdds.plugin.themebuilder +import com.android.build.gradle.AppExtension +import com.android.build.gradle.AppPlugin +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.LibraryPlugin +import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.TaskProvider +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.register /** * Плагин для генерации тем и токенов ДС * @author Малышев Александр on 09.02.2024 */ class ThemeBuilderPlugin : Plugin { - override fun apply(target: Project) { - TODO("Добавить реализацию плагина") + override fun apply(project: Project) { + val extension = project.extensions.create("theme-builder", ThemeBuilderExtension::class.java) + val themeOutputFile = project.layout.buildDirectory.file("theme-builder/theme.json") + + val fetchThemeTask = project.registerThemeFetcher(extension, themeOutputFile) + project.registerThemeGenerator(themeOutputFile, fetchThemeTask) + + project.plugins.all { + when (this) { + is AppPlugin -> project.extensions.getByType(AppExtension::class).configureSourceSets() + is LibraryPlugin -> project.extensions.getByType(LibraryExtension::class).configureSourceSets() + } + } + } + + private fun Project.registerThemeFetcher( + extension: ThemeBuilderExtension, + output: Provider): TaskProvider { + return project.tasks.register("fetchTheme") { + if (extension.themeUrl.orNull == null) throw GradleException("Property themeUrl must be set") + url.set(extension.themeUrl) + themeFile.set(output) + } + } + + private fun Project.registerThemeGenerator(input: Provider, fetchTask: Any) { + project.tasks.register("generateTheme") { + themeFile.set(input) + outputDir.set(project.layout.projectDirectory.dir(OUTPUT_RESOURCE_PATH)) + dependsOn(fetchTask) + } + } + + private fun BaseExtension.configureSourceSets() { + this.sourceSets.maybeCreate("main").res.srcDir(OUTPUT_RESOURCE_PATH) + } + + companion object { + const val OUTPUT_RESOURCE_PATH = "build/generated/theme-builder-res" } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderTarget.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderTarget.kt new file mode 100644 index 00000000..79489e25 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderTarget.kt @@ -0,0 +1,26 @@ +package com.sdds.plugin.themebuilder + +import org.gradle.api.provider.Property + +/** + * Целевой фреймворк для которого будет сгенерирована тема. + * @property value название фреймворка + * @author Малышев Александр on 04.03.2024 + */ +enum class ThemeBuilderTarget(val value: String) { + VIEW_SYSTEM("vs"), COMPOSE("compose"), ALL("all"); + + companion object { + + /** + * Возвращает [ThemeBuilderTarget] из [property] + */ + fun ThemeBuilderTarget.fromProperty(property: Property): ThemeBuilderTarget { + return when (property.get()) { + "vs" -> VIEW_SYSTEM + "compose" -> COMPOSE + else -> ALL + } + } + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/FileProvider.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/FileProvider.kt new file mode 100644 index 00000000..27e29808 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/FileProvider.kt @@ -0,0 +1,38 @@ +package com.sdds.plugin.themebuilder.internal + +import java.io.File + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +object FileProvider { + + fun File.valueDir(qualifier: String = ""): File { + val valueDirName = if (qualifier.isNotBlank()) { + "values-$qualifier" + } else { + "values" + } + val valueDir = File("${this.path}/$valueDirName") + if (!valueDir.exists()) valueDir.mkdirs() + return valueDir + } + + fun File.colorsXmlFile(): File = + File("${valueDir().path}/colors.xml") + + fun File.gradientsXmlFile(): File = + File("${valueDir().path}/gradients.xml") + + fun File.dimensFile(): File = + File("${valueDir().path}/dimens.xml") + + fun File.textAppearancesXmlFile( + qualifier: String = "" + ): File = File("${valueDir(qualifier).path}/text-appearances.xml") + + fun File.typographyXmlFile( + qualifier: String = "" + ): File = File("${valueDir(qualifier).path}/typography.xml") +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/StringUtils.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/StringUtils.kt new file mode 100644 index 00000000..3ffce3c6 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/StringUtils.kt @@ -0,0 +1,19 @@ +package com.sdds.plugin.themebuilder.internal + +import org.gradle.configurationcache.extensions.capitalized + +/** + * + * @author Малышев Александр on 06.03.2024 + */ +fun String.toCamelCase(): String { + var ignoreFirst = true + return split("-").joinToString("") { + if (ignoreFirst) { + ignoreFirst = false + it + } else { + it.capitalized() + } + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilder.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilder.kt new file mode 100644 index 00000000..50adc1b7 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilder.kt @@ -0,0 +1,87 @@ +package com.sdds.plugin.themebuilder.internal.builder + +import org.w3c.dom.Document +import org.w3c.dom.Element +import java.io.File +import java.io.FileWriter +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +/** + * + * @author Малышев Александр on 06.03.2024 + */ +open class XmlDocumentBuilder( + private val rootElement: String +) { + + private val document: Document = DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .newDocument() + + private val rootContent: Element by lazy(LazyThreadSafetyMode.NONE) { + document.createElement(rootElement) + .also { document.appendChild(it) } + } + + fun appendToken( + elementName: String, + tokenName: String, + value: String, + format: String? = null, + type: String? = null, + ) { + rootContent.appendToken(elementName, tokenName, value, format, type) + } + + + fun Element.appendToken( + elementName: String, + tokenName: String, + value: String, + format: String? = null, + type: String? = null, + ) { + val colorItem = document.createElement(elementName).apply { + setAttribute("name", tokenName) + format?.let { setAttribute("format", it) } + type?.let { setAttribute("type", it) } + textContent = value + } + appendChild(colorItem) + } + + fun appendComment(comment: String?) { + comment?.let { + rootContent.appendChild(document.createComment(it)) + } + } + + fun wrapWithRegion(regionName: String, content: () -> Unit) { + appendComment("region $regionName") + content.invoke() + appendComment("endregion $regionName") + + } + + fun appendStyle(styleName: String, content: Element.() -> Unit) { + document.createElement("style").apply { + setAttribute("name", styleName) + content(this) + rootContent.appendChild(this) + } + } + + fun build(output: File) { + val transformer = TransformerFactory.newInstance().apply { + setAttribute("indent-number", 4) + }.newTransformer().apply { + setOutputProperty(OutputKeys.ENCODING, "UTF-8") + setOutputProperty(OutputKeys.INDENT, "yes") + } + transformer.transform(DOMSource(document), StreamResult(FileWriter(output))) + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilderFactory.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilderFactory.kt new file mode 100644 index 00000000..966b1b81 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilderFactory.kt @@ -0,0 +1,12 @@ +package com.sdds.plugin.themebuilder.internal.builder + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +object XmlDocumentBuilderFactory { + + + fun create(rootElement: String): XmlDocumentBuilder = + XmlDocumentBuilder(rootElement) +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/dimens/DimenData.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/dimens/DimenData.kt new file mode 100644 index 00000000..5a4abe9c --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/dimens/DimenData.kt @@ -0,0 +1,16 @@ +package com.sdds.plugin.themebuilder.internal.dimens + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +data class DimenData( + val name: String, + val value: Number, + val type: Type +) { + + enum class Type(val suffix: String) { + DP("dp"), SP("sp"), PX("px") + } +} diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/dimens/DimensAggregator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/dimens/DimensAggregator.kt new file mode 100644 index 00000000..1eb69a78 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/dimens/DimensAggregator.kt @@ -0,0 +1,16 @@ +package com.sdds.plugin.themebuilder.internal.dimens + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +class DimensAggregator { + + private val mutableDimens = mutableSetOf() + val dimens: Set + get() = mutableDimens + + fun addDimen(dimenData: DimenData) { + mutableDimens.add(dimenData) + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/BaseGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/BaseGenerator.kt new file mode 100644 index 00000000..c87a2050 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/BaseGenerator.kt @@ -0,0 +1,18 @@ +package com.sdds.plugin.themebuilder.internal.generator + +import com.sdds.plugin.themebuilder.internal.token.Token +import com.sdds.plugin.themebuilder.internal.token.TokenValue + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +internal interface BaseGenerator { + + fun generate() +} + +internal interface TokenGenerator> : BaseGenerator { + + fun addToken(token: T) +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorGenerator.kt new file mode 100644 index 00000000..38aba651 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorGenerator.kt @@ -0,0 +1,29 @@ +package com.sdds.plugin.themebuilder.internal.generator + +import com.sdds.plugin.themebuilder.internal.FileProvider.colorsXmlFile +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilderFactory +import com.sdds.plugin.themebuilder.internal.token.ColorToken +import java.io.File + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +class ColorGenerator( + private val outputDir: File, + private val xmlBuilderFactory: XmlDocumentBuilderFactory +) : TokenGenerator { + + private val xmlDocumentBuilder = xmlBuilderFactory.create("resources") + + override fun addToken(token: ColorToken) { + val tokenValue = token.value ?: return + xmlDocumentBuilder.appendComment(token.description) + xmlDocumentBuilder.appendToken("color", token.xmlName, tokenValue.origin) + } + + override fun generate() { + xmlDocumentBuilder.build(outputDir.colorsXmlFile()) + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/DimenGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/DimenGenerator.kt new file mode 100644 index 00000000..4bb1717c --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/DimenGenerator.kt @@ -0,0 +1,32 @@ +package com.sdds.plugin.themebuilder.internal.generator + +import com.sdds.plugin.themebuilder.internal.FileProvider.dimensFile +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilderFactory +import com.sdds.plugin.themebuilder.internal.dimens.DimenData +import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator +import java.io.File + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +class DimenGenerator( + private val outputDir: File, + private val dimensAggregator: DimensAggregator, + private val xmlBuilderFactory: XmlDocumentBuilderFactory +) : BaseGenerator { + + private val xmlDocumentBuilder: XmlDocumentBuilder = xmlBuilderFactory.create("resources") + + override fun generate() { + dimensAggregator.dimens.forEach { + xmlDocumentBuilder.appendDimen(it) + } + xmlDocumentBuilder.build(outputDir.dimensFile()) + } + + private fun XmlDocumentBuilder.appendDimen(dimen: DimenData) { + appendToken("dimen", dimen.name, "${dimen.value}${dimen.type.suffix}") + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientGenerator.kt new file mode 100644 index 00000000..0389e4b8 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientGenerator.kt @@ -0,0 +1,78 @@ +package com.sdds.plugin.themebuilder.internal.generator + +import com.sdds.plugin.themebuilder.internal.FileProvider.gradientsXmlFile +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilderFactory +import com.sdds.plugin.themebuilder.internal.token.ColorToken +import com.sdds.plugin.themebuilder.internal.token.GradientTokenValue +import com.sdds.plugin.themebuilder.internal.token.LinearGradientToken +import com.sdds.plugin.themebuilder.internal.token.RadialGradientToken +import com.sdds.plugin.themebuilder.internal.token.SweepGradientToken +import com.sdds.plugin.themebuilder.internal.token.Token +import java.io.File + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +class GradientGenerator( + private val outputDir: File, + private val xmlBuilderFactory: XmlDocumentBuilderFactory +) : TokenGenerator> { + + private val xmlDocumentBuilder: XmlDocumentBuilder = xmlBuilderFactory.create("resources") + + override fun addToken(token: Token) { + when (token) { + is LinearGradientToken -> xmlDocumentBuilder.appendLinearGradient(token) + is RadialGradientToken -> xmlDocumentBuilder.appendRadialGradient(token) + is SweepGradientToken -> xmlDocumentBuilder.appendSweepGradient(token) + } + } + + override fun generate() { + xmlDocumentBuilder.build(outputDir.gradientsXmlFile()) + } + + private fun XmlDocumentBuilder.appendBaseGradient(baseName: String, colors: List, positions: List) { + colors.forEachIndexed { index, color -> + appendToken("color", "${baseName}_color_$index", color) + } + positions.forEachIndexed { index, position -> + appendToken( + "item", + "${baseName}_position_$index", + position.toString(), + "float", + "dimen", + ) + } + } + + private fun XmlDocumentBuilder.appendLinearGradient(token: LinearGradientToken) { + val tokenValue = token.value ?: return + val baseTokenName = token.xmlName + + wrapWithRegion(token.description) { + appendBaseGradient(baseTokenName, tokenValue.colors, tokenValue.locations) + } + } + + private fun XmlDocumentBuilder.appendSweepGradient(token: SweepGradientToken) { + val tokenValue = token.value ?: return + val baseTokenName = token.xmlName + + wrapWithRegion(token.description) { + appendBaseGradient(baseTokenName, tokenValue.colors, tokenValue.locations) + } + } + + private fun XmlDocumentBuilder.appendRadialGradient(token: RadialGradientToken) { + val tokenValue = token.value ?: return + val baseTokenName = token.xmlName + + wrapWithRegion(token.description) { + appendBaseGradient(baseTokenName, tokenValue.colors, tokenValue.locations) + } + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt new file mode 100644 index 00000000..6de801f5 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt @@ -0,0 +1,87 @@ +package com.sdds.plugin.themebuilder.internal.generator + +import com.sdds.plugin.themebuilder.internal.FileProvider.textAppearancesXmlFile +import com.sdds.plugin.themebuilder.internal.FileProvider.typographyXmlFile +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilderFactory +import com.sdds.plugin.themebuilder.internal.dimens.DimenData +import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator +import com.sdds.plugin.themebuilder.internal.token.TypographyToken +import com.sdds.plugin.themebuilder.internal.token.toSnakeCase +import java.io.File + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +class TypographyGenerator( + private val outputDir: File, + private val dimensAggregator: DimensAggregator, + private val xmlBuilderFactory: XmlDocumentBuilderFactory +) : TokenGenerator { + + private val textAppearanceXmlBuilders = mutableMapOf() + private val typographyXmlBuilder = XmlDocumentBuilderFactory.create("resources") + + override fun addToken(token: TypographyToken) { + val builder = textAppearanceXmlBuilders[token.screenClass] ?: xmlBuilderFactory.create("resources").also { + textAppearanceXmlBuilders[token.screenClass] = it + } + + val textAppearanceName = "Sdds.TextAppearance.${token.xmlName}" + + builder.appendTypographyToken(token, textAppearanceName) + + with(typographyXmlBuilder) { + if (token.screenClass.isDefault) { + appendStyle("Sdds.Typography.${token.xmlName}") { + appendToken( + elementName = "item", + tokenName = "android:textAppearance", + value = "@style/$textAppearanceName" + ) + } + } + } + } + + override fun generate() { + textAppearanceXmlBuilders.forEach { + // val outputDir = File("${outputDir.path}/${it.key.dirName()}") + // outputDir.mkdirs() + // it.value.build(File("${outputDir.path}/text-appearances.xml")) + it.value.build(outputDir.textAppearancesXmlFile(it.key.qualifier())) + } + // typographyXmlBuilder.build(File("${outputDir.path}/typography.xml")) + typographyXmlBuilder.build(outputDir.typographyXmlFile()) + } + + private fun XmlDocumentBuilder.appendTypographyToken(token: TypographyToken, textAppearanceName: String) { + val tokenValue = token.value ?: return + + val textSizeDimen = "${token.name.toSnakeCase()}_text_size" + val lineHeightDimen = "${token.name.toSnakeCase()}_line_height" + + dimensAggregator.addDimen(DimenData(textSizeDimen, tokenValue.textSize, DimenData.Type.SP)) + dimensAggregator.addDimen(DimenData(lineHeightDimen, tokenValue.lineHeight, DimenData.Type.DP)) + + appendStyle(textAppearanceName) { + appendToken("item", "fontFamily", tokenValue.fontFamily) + appendToken("item", "fontWeight", tokenValue.fontWeight.toString()) + appendToken("item", "android:fontStyle", tokenValue.fontStyle) + appendToken("item", "android:letterSpacing", tokenValue.letterSpacing.toString()) + appendToken("item", "android:textSize", "@dimen/$textSizeDimen") + appendToken("item", "android:lineHeight", "@dimen/$lineHeightDimen") + } + } + + private companion object { + fun TypographyToken.ScreenClass.qualifier(): String { + return when (this) { + TypographyToken.ScreenClass.SMALL -> "small" + TypographyToken.ScreenClass.LARGE -> "large" + else -> "" + } + } + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/ExcludeNonAndroidPlatformTokens.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/ExcludeNonAndroidPlatformTokens.kt new file mode 100644 index 00000000..d52fd330 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/ExcludeNonAndroidPlatformTokens.kt @@ -0,0 +1,54 @@ +package com.sdds.plugin.themebuilder.internal.serializer + +import com.sdds.plugin.themebuilder.internal.token.Token +import com.sdds.plugin.themebuilder.internal.token.TokenPlatform +import com.sdds.plugin.themebuilder.internal.token.TokenValue +import kotlinx.serialization.KSerializer +import kotlinx.serialization.PolymorphicSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +/** + * + * @author Малышев Александр on 05.03.2024 + */ +class FilteringListSerializer(private val elementSerializer: KSerializer) : KSerializer> { + private val listSerializer = ListSerializer(elementSerializer) + override val descriptor: SerialDescriptor = listSerializer.descriptor + + override fun serialize(encoder: Encoder, value: List) { + listSerializer.serialize(encoder, value) + } + + override fun deserialize(decoder: Decoder): List = with(decoder as JsonDecoder) { + decodeJsonElement().jsonArray.mapNotNull { + val currentPlatform = it.getPlatform() + if (currentPlatform == null || currentPlatform != TokenPlatform.ANDROID) { + return@mapNotNull null + } + json.decodeFromJsonElement(elementSerializer, it) + } + } + + private fun JsonElement.getPlatform(): TokenPlatform? { + val platformValue = if (this is JsonObject) { + jsonObject["platform"]?.jsonPrimitive?.content + } else { + null + } + return TokenPlatform.fromString(platformValue) + + } +} + +object ExcludeNonAndroidPlatformTokens : KSerializer>> by FilteringListSerializer( + PolymorphicSerializer(Token::class) +) \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/Serializer.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/Serializer.kt new file mode 100644 index 00000000..a68aa695 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/Serializer.kt @@ -0,0 +1,34 @@ +package com.sdds.plugin.themebuilder.internal.serializer + +import com.sdds.plugin.themebuilder.internal.token.ColorToken +import com.sdds.plugin.themebuilder.internal.token.LinearGradientToken +import com.sdds.plugin.themebuilder.internal.token.RadialGradientToken +import com.sdds.plugin.themebuilder.internal.token.ShadowToken +import com.sdds.plugin.themebuilder.internal.token.SweepGradientToken +import com.sdds.plugin.themebuilder.internal.token.Token +import com.sdds.plugin.themebuilder.internal.token.TypographyToken +import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass + +/** + * + * @author Малышев Александр on 07.03.2024 + */ +object Serializer { + + val instance: Json = Json { + ignoreUnknownKeys = true + serializersModule = SerializersModule { + polymorphic(Token::class) { + subclass(ColorToken::class) + subclass(LinearGradientToken::class) + subclass(RadialGradientToken::class) + subclass(SweepGradientToken::class) + subclass(ShadowToken::class) + subclass(TypographyToken::class) + } + } + } +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ColorTokens.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ColorTokens.kt new file mode 100644 index 00000000..3740fd26 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ColorTokens.kt @@ -0,0 +1,97 @@ +package com.sdds.plugin.themebuilder.internal.token + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ColorTokensScheme( + val category: Set, + val mode: Set, + val subcategories: Set, +) + +/** + * + * @author Малышев Александр on 05.03.2024 + */ +interface GradientTokenValue : TokenValue { + val colors: List + val locations: List +} + +@Serializable +@SerialName("radial-gradient") +data class RadialGradientToken( + override val displayName: String, + override val name: String, + override val platform: TokenPlatform, + override val tags: Set, + override val value: Value?, + override val enabled: Boolean, + override val description: String +) : Token() { + + @Serializable + data class Value( + override val colors: List, + override val locations: List, + val radius: Float + ) : GradientTokenValue +} + +@Serializable +@SerialName("sweep-gradient") +data class SweepGradientToken( + override val displayName: String, + override val name: String, + override val platform: TokenPlatform, + override val tags: Set, + override val value: Value?, + override val enabled: Boolean, + override val description: String +) : Token() { + + @Serializable + data class Value( + override val colors: List, + override val locations: List, + val startAngle: Float, + val endAngle: Float + ) : GradientTokenValue +} + +@Serializable +@SerialName("linear-gradient") +data class LinearGradientToken( + override val displayName: String, + override val name: String, + override val platform: TokenPlatform, + override val tags: Set, + override val value: Value?, + override val enabled: Boolean, + override val description: String +) : Token() { + + @Serializable + data class Value( + override val colors: List, + override val locations: List, + val angle: Float + ) : GradientTokenValue +} + +@Serializable +@SerialName("color") +data class ColorToken( + override val displayName: String, + override val name: String, + override val platform: TokenPlatform, + override val tags: Set, + override val value: Value?, + override val enabled: Boolean, + override val description: String +) : Token() { + + @Serializable + data class Value(val origin: String) : TokenValue +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShadowTokens.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShadowTokens.kt new file mode 100644 index 00000000..6906cfa0 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShadowTokens.kt @@ -0,0 +1,34 @@ +package com.sdds.plugin.themebuilder.internal.token + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * + * @author Малышев Александр on 05.03.2024 + */ +@Serializable +data class ShadowTokensScheme( + val directions: Set, + val types: Set, + val sizes: Set, +) + +@Serializable +@SerialName("shadow") +data class ShadowToken( + override val displayName: String, + override val name: String, + override val platform: TokenPlatform, + override val tags: Set, + override val value: Value?, + override val enabled: Boolean, + override val description: String +) : Token() { + + @Serializable + data class Value( + val color: String, + val elevation: Float, + ) : TokenValue +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/Theme.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/Theme.kt new file mode 100644 index 00000000..b9e1937e --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/Theme.kt @@ -0,0 +1,16 @@ +package com.sdds.plugin.themebuilder.internal.token + +import com.sdds.plugin.themebuilder.internal.serializer.ExcludeNonAndroidPlatformTokens +import kotlinx.serialization.Serializable + +/** + * + * @author Малышев Александр on 05.03.2024 + */ +@Serializable +data class Theme( + val name: String, + val version: String, + @Serializable(with = ExcludeNonAndroidPlatformTokens::class) + val tokens: List> +) diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/Token.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/Token.kt new file mode 100644 index 00000000..9ffced18 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/Token.kt @@ -0,0 +1,50 @@ +package com.sdds.plugin.themebuilder.internal.token + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * + * @author Малышев Александр on 05.03.2024 + */ +@Serializable +abstract class Token { + abstract val displayName: String + abstract val name: String + abstract val platform: TokenPlatform + abstract val tags: Set + abstract val value: Value? + abstract val enabled: Boolean + abstract val description: String + + open val xmlName: String by lazy { + name.toSnakeCase() + } +} + +fun String.toSnakeCase(): String { + return replace("[.-]+".toRegex(), "_") +} + +interface TokenValue + +@Serializable +enum class TokenPlatform { + @SerialName("android") + ANDROID, + @SerialName("web") + WEB, + @SerialName("ios") + IOS; + + companion object { + + fun fromString(value: String?): TokenPlatform? = + when (value) { + "android" -> ANDROID + "ios" -> IOS + "web" -> WEB + else -> null + } + } +} diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/TypographyTokens.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/TypographyTokens.kt new file mode 100644 index 00000000..07752d49 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/TypographyTokens.kt @@ -0,0 +1,59 @@ +package com.sdds.plugin.themebuilder.internal.token + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.gradle.configurationcache.extensions.capitalized + +@Serializable +data class TypographyTokensScheme( + val sizes: List, + val type: List, + val screen: List, +) + +@Serializable +@SerialName("typography") +data class TypographyToken( + override val displayName: String, + override val name: String, + override val platform: TokenPlatform, + override val tags: Set, + override val value: Value?, + override val enabled: Boolean, + override val description: String +) : Token() { + + override val xmlName: String by lazy { + val nameTokens = name.split(".") + val nameTokensExcludeScreen = nameTokens.subList(1, nameTokens.size) + nameTokensExcludeScreen.joinToString("") { it.capitalized() } + } + + val screenClass: ScreenClass by lazy { + when(name.split(".").first()) { + "screen-s" -> ScreenClass.SMALL + "screen-m" -> ScreenClass.MEDIUM + "screen-l" -> ScreenClass.LARGE + else -> ScreenClass.UNKNOWN + } + } + + @Serializable + data class Value( + val fontFamily: String, + val fontWeight: Int, + val fontStyle: String, + val textSize: Float, + val letterSpacing: Float, + val lineHeight: Float, + ) : TokenValue + + enum class ScreenClass(val value: String) { + SMALL("screen-s"), + MEDIUM("screen-m"), + LARGE("screen-l"), + UNKNOWN(""); + + val isDefault: Boolean get() = this == UNKNOWN || this == MEDIUM + } +} \ No newline at end of file From dfeac83b533e13427eb4c714bccd41568a3f02d5 Mon Sep 17 00:00:00 2001 From: malilex Date: Tue, 12 Mar 2024 18:27:11 +0300 Subject: [PATCH 02/13] feat(sdds-acore/theme-builder): Color, gradients, typography tokens generators were implemented for compose. --- gradle/libs.versions.toml | 2 + playground/theme-builder/build.gradle.kts | 12 + .../theme-builder/new_theme_scheme.json | 69 ++++- .../src/main/res/values/styles.xml | 7 + .../plugin_theme_builder/build.gradle.kts | 1 + .../plugin/themebuilder/FetchThemeTask.kt | 2 +- .../plugin/themebuilder/GenerateThemeTask.kt | 65 +++-- .../plugin/themebuilder/ThemeBuilderPlugin.kt | 27 +- .../plugin/themebuilder/ThemeBuilderTarget.kt | 14 +- .../themebuilder/internal/FileProvider.kt | 38 --- .../themebuilder/internal/StringUtils.kt | 19 -- .../internal/builder/XmlDocumentBuilder.kt | 68 +++-- .../builder/XmlDocumentBuilderFactory.kt | 12 - .../themebuilder/internal/dimens/DimenData.kt | 13 +- .../internal/dimens/DimensAggregator.kt | 15 +- .../internal/factory/GeneratorFactory.kt | 87 +++++++ .../factory/XmlDocumentBuilderFactory.kt | 15 ++ .../internal/generator/BaseGenerator.kt | 14 +- .../internal/generator/ColorGenerator.kt | 96 ++++++- .../internal/generator/DimenGenerator.kt | 28 +- .../internal/generator/GradientGenerator.kt | 244 ++++++++++++++++-- .../internal/generator/TypographyGenerator.kt | 160 +++++++++--- .../ExcludeNonAndroidPlatformTokens.kt | 13 +- .../internal/serializer/Serializer.kt | 7 +- .../internal/token/ColorTokens.kt | 134 ++++++++-- .../internal/token/ShadowTokens.kt | 24 +- .../themebuilder/internal/token/Theme.kt | 9 +- .../themebuilder/internal/token/Token.kt | 63 ++++- .../internal/token/TypographyTokens.kt | 55 +++- .../internal/utils/CommonUtils.kt | 8 + .../internal/utils/FileProvider.kt | 56 ++++ .../internal/utils/StringUtils.kt | 9 + 32 files changed, 1111 insertions(+), 275 deletions(-) create mode 100644 playground/theme-builder/src/main/res/values/styles.xml delete mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/FileProvider.kt delete mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/StringUtils.kt delete mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilderFactory.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/GeneratorFactory.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/XmlDocumentBuilderFactory.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/utils/CommonUtils.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/utils/FileProvider.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/utils/StringUtils.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 53f6a587..f191e411 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ test-androidX-rules = "1.5.0" test-androidX-runner = "1.5.2" kotlinSerialization = "1.4.0" +kotlinPoet = "1.12.0" plugin-androidCacheFix = "3.0.1" plugin-gradleNexusPublish = "1.3.0" @@ -43,6 +44,7 @@ base-androidX-compose-uiTooling-preview = { module = "androidx.compose.ui:ui-too base-kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "global-kotlin" } base-kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } +base-kotlin-poet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoet"} base-staticAnalysis-ktlint = { module = "com.pinterest:ktlint", version.ref = "staticAnalysis-ktlint" } base-staticAnalysis-detekt-api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "staticAnalysis-detekt" } diff --git a/playground/theme-builder/build.gradle.kts b/playground/theme-builder/build.gradle.kts index 6aad0f88..06601418 100644 --- a/playground/theme-builder/build.gradle.kts +++ b/playground/theme-builder/build.gradle.kts @@ -15,3 +15,15 @@ configure { themeUrl.set("file://${projectDir.path}/new_theme_scheme.json") target.set(ThemeBuilderTarget.COMPOSE.value) } + +dependencies { + implementation("sdds-core:uikit") + implementation("sdds-core:uikit-compose") + + implementation(libs.base.androidX.core) + implementation(libs.base.androidX.appcompat) + + val composeBom = platform(libs.base.androidX.compose.bom) + implementation(composeBom) + implementation(libs.base.androidX.compose.foundation) +} diff --git a/playground/theme-builder/new_theme_scheme.json b/playground/theme-builder/new_theme_scheme.json index 5296cd6b..e46ac8e9 100644 --- a/playground/theme-builder/new_theme_scheme.json +++ b/playground/theme-builder/new_theme_scheme.json @@ -29,12 +29,14 @@ ], "type": "sweep-gradient", "value" : { - "locations": [ 0, 0.3, 0.7, 1 ], + "locations": [ 0, 0.7, 1 ], "colors" : ["#000", "#fff"], "startAngle": 0, - "endAngle" : 360 + "endAngle" : 360, + "centerX": 0.5, + "centerY": 0.5 }, - "description": "Цвет текста", + "description": "Акцентный sweep градиент поверхности", "enabled": true }, { @@ -53,6 +55,46 @@ "description": "Прозрачная акцентная поверхность", "enabled": true }, + { + "displayName": "surfaceTertiary", + "name": "light.on-dark.surface.tertiary", + "platform": "android", + "tags": [ + "light", + "on-dark", + "surface", + "tertiary" + ], + "type": "linear-gradient", + "value" : { + "locations": [ 0, 0.7, 1 ], + "colors" : ["#000", "#fff"], + "angle": 90 + }, + "description": "Третичный линейный градиент поверхности", + "enabled": true + }, + { + "displayName": "surfaceSecondary", + "name": "light.on-dark.surface.secondary", + "platform": "android", + "tags": [ + "light", + "on-dark", + "text", + "gradient" + ], + "type": "radial-gradient", + "value" : { + "locations": [ 0, 0.7, 1 ], + "colors" : ["#000", "#fff"], + "radius": 0.8, + "centerX": 0.5, + "centerY": 0.5 + }, + "description": "Вторичный радиальный градиент поверхности", + "enabled": true + }, { "displayName": "textAccent", "name": "on-dark.text.accent", @@ -163,6 +205,27 @@ }, "description": "шрифт android", "enabled": true + }, + { + "displayName": "displayL", + "name": "screen-s.display.l", + "platform": "android", + "tags": [ + "screen-s", + "display", + "l" + ], + "type": "typography", + "value": { + "fontFamily": "sans", + "fontWeight": 300, + "fontStyle": "normal", + "textSize": 72, + "letterSpacing": 0.02, + "lineHeight": 72 + }, + "description": "шрифт android", + "enabled": true } ] } diff --git a/playground/theme-builder/src/main/res/values/styles.xml b/playground/theme-builder/src/main/res/values/styles.xml new file mode 100644 index 00000000..a1cc563b --- /dev/null +++ b/playground/theme-builder/src/main/res/values/styles.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-output.xml new file mode 100644 index 00000000..85342b3a --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-output.xml @@ -0,0 +1,13 @@ + + + + diff --git a/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-small-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-small-output.xml new file mode 100644 index 00000000..1a9c5983 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-small-output.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-typography-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-typography-output.xml new file mode 100644 index 00000000..6b50e4ea --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-typography-output.xml @@ -0,0 +1,8 @@ + + + + From f3e7e39d55635157e1dc524479d235bd4fd17adc Mon Sep 17 00:00:00 2001 From: malilex Date: Tue, 19 Mar 2024 13:31:00 +0300 Subject: [PATCH 04/13] feat(sdds-acore/theme-builder): Shape and shadows (partialy) generators were implemented. --- .../theme-builder/new_theme_scheme.json | 57 ++++++++- .../src/main/res/values/attrs.xml | 6 + .../plugin/themebuilder/GenerateThemeTask.kt | 8 +- .../internal/builder/KtFileBuilder.kt | 21 +++- .../internal/factory/GeneratorFactory.kt | 30 +++++ .../internal/generator/ShadowGenerator.kt | 96 ++++++++++++++ .../internal/generator/ShapeGenerator.kt | 118 ++++++++++++++++++ .../internal/serializer/Serializer.kt | 2 + .../internal/token/ShadowTokens.kt | 8 +- .../themebuilder/internal/token/ShapeToken.kt | 53 ++++++++ .../internal/utils/FileProvider.kt | 12 ++ .../internal/generator/ShadowGeneratorTest.kt | 84 +++++++++++++ .../internal/generator/ShapeGeneratorTest.kt | 87 +++++++++++++ .../resources/inputs/test-shadow-input.json | 19 +++ .../resources/inputs/test-shape-input.json | 47 +++++++ .../shadow-outputs/TestShadowOutputKt.txt | 33 +++++ .../shadow-outputs/test-shadow-output.xml | 9 ++ .../shape-outputs/TestShapeOutputKt.txt | 22 ++++ .../shape-outputs/test-shape-output.xml | 19 +++ 19 files changed, 720 insertions(+), 11 deletions(-) create mode 100644 playground/theme-builder/src/main/res/values/attrs.xml create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGenerator.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGenerator.kt create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShapeToken.kt create mode 100644 sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGeneratorTest.kt create mode 100644 sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGeneratorTest.kt create mode 100644 sdds-core/plugin_theme_builder/src/test/resources/inputs/test-shadow-input.json create mode 100644 sdds-core/plugin_theme_builder/src/test/resources/inputs/test-shape-input.json create mode 100644 sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/TestShadowOutputKt.txt create mode 100644 sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/test-shadow-output.xml create mode 100644 sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/TestShapeOutputKt.txt create mode 100644 sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/test-shape-output.xml diff --git a/playground/theme-builder/new_theme_scheme.json b/playground/theme-builder/new_theme_scheme.json index e46ac8e9..9495bebb 100644 --- a/playground/theme-builder/new_theme_scheme.json +++ b/playground/theme-builder/new_theme_scheme.json @@ -17,6 +17,10 @@ "kind": ["display", "body", "text", "h1", "h2", "h3", "h4", "h5"], "screen": ["screen-l", "screen-m", "screen-s"] }, + "shapes": { + "sizes": [ "xxs", "xs", "s", "m", "l", "xl", "xxl"], + "kind": [ "round" ] + }, "tokens": [ { "displayName": "surfaceAccent", @@ -123,10 +127,12 @@ ], "type": "shadow", "value": { - "color": "#000", - "elevation": 16 + "color": "#99000000", + "dX": 0.0, + "dY": 1.0, + "radius": 4 }, - "description": "тень android", + "description": "Тень downHardL для android", "enabled": true }, { @@ -226,6 +232,51 @@ }, "description": "шрифт android", "enabled": true + }, + { + "displayName": "roundXs", + "name": "round.xs", + "platform": "android", + "tags": [ + "round", + "xs" + ], + "type": "round-shape", + "value": { + "cornerRadius": 6.0 + }, + "description": "Форма со скругленными углами размером XS", + "enabled": true + }, + { + "displayName": "roundS", + "name": "round.s", + "platform": "android", + "tags": [ + "round", + "s" + ], + "type": "round-shape", + "value": { + "cornerRadius": 8.0 + }, + "description": "Форма со скругленными углами размером S", + "enabled": true + }, + { + "displayName": "roundXs", + "name": "round.l", + "platform": "android", + "tags": [ + "round", + "l" + ], + "type": "round-shape", + "value": { + "cornerRadius": 16.0 + }, + "description": "Форма со скругленными углами размером L", + "enabled": true } ] } diff --git a/playground/theme-builder/src/main/res/values/attrs.xml b/playground/theme-builder/src/main/res/values/attrs.xml new file mode 100644 index 00000000..6ee0f072 --- /dev/null +++ b/playground/theme-builder/src/main/res/values/attrs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt index 1a547f48..e0f2b83d 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt @@ -8,6 +8,7 @@ import com.sdds.plugin.themebuilder.internal.serializer.Serializer import com.sdds.plugin.themebuilder.internal.token.ColorToken import com.sdds.plugin.themebuilder.internal.token.LinearGradientToken import com.sdds.plugin.themebuilder.internal.token.RadialGradientToken +import com.sdds.plugin.themebuilder.internal.token.RoundedShapeToken import com.sdds.plugin.themebuilder.internal.token.ShadowToken import com.sdds.plugin.themebuilder.internal.token.SweepGradientToken import com.sdds.plugin.themebuilder.internal.token.Theme @@ -83,6 +84,8 @@ abstract class GenerateThemeTask : DefaultTask() { private val gradientGenerator by unsafeLazy { generatorFactory.createGradientGenerator() } private val typographyGenerator by unsafeLazy { generatorFactory.createTypographyGenerator() } private val dimensGenerator by unsafeLazy { generatorFactory.createDimensGenerator() } + private val shapesGenerator by unsafeLazy { generatorFactory.createShapesGenerator() } + private val shadowGenerator by unsafeLazy { generatorFactory.createShadowGenerator() } /** * Генерирует файлы с токенами @@ -95,7 +98,8 @@ abstract class GenerateThemeTask : DefaultTask() { is SweepGradientToken -> gradientGenerator.addToken(it) is LinearGradientToken -> gradientGenerator.addToken(it) is RadialGradientToken -> gradientGenerator.addToken(it) - is ShadowToken -> {} + is ShadowToken -> shadowGenerator.addToken(it) + is RoundedShapeToken -> shapesGenerator.addToken(it) is TypographyToken -> typographyGenerator.addToken(it) } } @@ -103,6 +107,8 @@ abstract class GenerateThemeTask : DefaultTask() { colorGenerator.generate() gradientGenerator.generate() typographyGenerator.generate() + shapesGenerator.generate() + shadowGenerator.generate() dimensGenerator.generate() } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/KtFileBuilder.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/KtFileBuilder.kt index beec99f0..2985c1dd 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/KtFileBuilder.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/KtFileBuilder.kt @@ -122,7 +122,11 @@ internal class KtFileBuilder( val TypeTextStyle = ClassName("androidx.compose.ui.text", listOf("TextStyle")) val TypeFontWeight = ClassName("androidx.compose.ui.text.font", "FontWeight") val TypeSp = ClassName("androidx.compose.ui.unit", "sp") + val TypeDp = ClassName("androidx.compose.ui.unit", "Dp") + val TypeDpExtension = ClassName("androidx.compose.ui.unit", "dp") val TypeListOfColors = List::class.asClassName().parameterizedBy(TypeColor) + val TypeRoundRectShape = ClassName("androidx.compose.foundation.shape", listOf("RoundedCornerShape")) + val TypeCornerSize = ClassName("androidx.compose.foundation.shape", listOf("CornerSize")) /** * Добавляет kotlin объект с названием [name], описанием [description] @@ -153,11 +157,18 @@ internal class KtFileBuilder( ): String { return buildString { append(constructorName) - appendReproducibleNewLine("(") - initializers.forEach { - append(DEFAULT_FILE_INDENT) - append(it) - appendReproducibleNewLine(",") + if (initializers.size > 1) { + appendReproducibleNewLine("(") + initializers.forEach { + append(DEFAULT_FILE_INDENT) + append(it) + appendReproducibleNewLine(",") + } + } else { + append("(") + initializers.forEach { + append(it) + } } append(")") } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/GeneratorFactory.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/GeneratorFactory.kt index 5d8551c1..2e74a865 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/GeneratorFactory.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/GeneratorFactory.kt @@ -6,6 +6,8 @@ import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator import com.sdds.plugin.themebuilder.internal.generator.ColorGenerator import com.sdds.plugin.themebuilder.internal.generator.DimenGenerator import com.sdds.plugin.themebuilder.internal.generator.GradientGenerator +import com.sdds.plugin.themebuilder.internal.generator.ShadowGenerator +import com.sdds.plugin.themebuilder.internal.generator.ShapeGenerator import com.sdds.plugin.themebuilder.internal.generator.TypographyGenerator import com.sdds.plugin.themebuilder.internal.utils.ResourceReferenceProvider import java.io.File @@ -82,4 +84,32 @@ internal class GeneratorFactory( xmlDocumentBuilderFactory, ) } + + /** + * Создает генератор форм [ShapeGenerator] + */ + fun createShapesGenerator(): ShapeGenerator { + return ShapeGenerator( + OutputLocation.Directory(outputDir), + outputResDir, + target, + xmlDocumentBuilderFactory, + ktFileBuilderFactory, + dimensAggregator, + resourceReferenceProvider, + ) + } + + /** + * Создает генератор теней [ShadowGenerator] + */ + fun createShadowGenerator(): ShadowGenerator { + return ShadowGenerator( + OutputLocation.Directory(outputDir), + outputResDir, + target, + xmlDocumentBuilderFactory, + ktFileBuilderFactory, + ) + } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGenerator.kt new file mode 100644 index 00000000..eb7684b3 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGenerator.kt @@ -0,0 +1,96 @@ +package com.sdds.plugin.themebuilder.internal.generator + +import com.sdds.plugin.themebuilder.ThemeBuilderTarget +import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isComposeOrAll +import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isViewSystemOrAll +import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder +import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Companion.appendObject +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder.ElementName +import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory +import com.sdds.plugin.themebuilder.internal.factory.XmlDocumentBuilderFactory +import com.sdds.plugin.themebuilder.internal.token.ShadowToken +import com.sdds.plugin.themebuilder.internal.utils.FileProvider.shadowsXmlFile +import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy +import java.io.File + +/** + * Генератор токенов теней + * @param outputLocation локация для сохранения kt-файла с токенами + * @param outputResDir директория для сохранения xml-файла с токенами + * @param target целевой фреймворк + * @param xmlBuilderFactory фабрика делегата построения xml файлов + * @param ktFileBuilderFactory фабрика делегата построения kt файлов + * @author Малышев Александр on 07.03.2024 + */ +internal class ShadowGenerator( + private val outputLocation: KtFileBuilder.OutputLocation, + private val outputResDir: File, + private val target: ThemeBuilderTarget, + private val xmlBuilderFactory: XmlDocumentBuilderFactory, + private val ktFileBuilderFactory: KtFileBuilderFactory, +) : TokenGenerator { + + private val xmlDocumentBuilder by unsafeLazy { xmlBuilderFactory.create() } + private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create("ShadowTokens") } + private val rootShadows by unsafeLazy { ktFileBuilder.rootObject("ShadowTokens") } + private var needGenerateCompose: Boolean = false + private var needGenerateViewSystem: Boolean = false + + /** + * @see TokenGenerator.addToken + */ + override fun addToken(token: ShadowToken) { + when (target) { + ThemeBuilderTarget.VIEW_SYSTEM -> addViewSystemToken(token) + ThemeBuilderTarget.COMPOSE -> addComposeToken(token) + ThemeBuilderTarget.ALL -> { + addViewSystemToken(token) + addComposeToken(token) + } + } + } + + /** + * @see TokenGenerator.generate + */ + override fun generate() { + if (needGenerateViewSystem) { + xmlDocumentBuilder.build(outputResDir.shadowsXmlFile()) + } + + if (needGenerateCompose) { + ktFileBuilder.addImport(KtFileBuilder.TypeDpExtension) + ktFileBuilder.addImport(KtFileBuilder.TypeCornerSize) + ktFileBuilder.build(outputLocation) + } + } + + private fun addViewSystemToken(token: ShadowToken) = with(xmlDocumentBuilder) { + val tokenValue = token.value ?: return + + wrapWithRegion(token.description) { + appendElement(ElementName.DIMEN, "shadow_${token.xmlName}_dx", "${tokenValue.dX}dp") + appendElement(ElementName.DIMEN, "shadow_${token.xmlName}_dY", "${tokenValue.dY}dp") + appendElement(ElementName.COLOR, "shadow_${token.xmlName}_color", tokenValue.color) + appendElement(ElementName.DIMEN, "shadow_${token.xmlName}_radius", "${tokenValue.radius}dp") + } + if (!needGenerateViewSystem && target.isViewSystemOrAll) needGenerateViewSystem = true + } + + private fun addComposeToken(token: ShadowToken) = with(ktFileBuilder) { + val tokenValue = token.value ?: return + + rootShadows.appendObject(token.ktName, token.description) { + appendProperty("dX", KtFileBuilder.TypeDp, "${tokenValue.dX}.dp", token.description) + appendProperty("dY", KtFileBuilder.TypeDp, "${tokenValue.dY}.dp", token.description) + appendProperty("radius", KtFileBuilder.TypeDp, "${tokenValue.radius}.dp", token.description) + appendProperty( + "color", + KtFileBuilder.TypeColor, + "Color(${tokenValue.color.replace("#", "0x")})", + token.description, + ) + } + if (!needGenerateCompose && target.isComposeOrAll) needGenerateCompose = true + } +} diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGenerator.kt new file mode 100644 index 00000000..cd4400b7 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGenerator.kt @@ -0,0 +1,118 @@ +package com.sdds.plugin.themebuilder.internal.generator + +import com.sdds.plugin.themebuilder.ThemeBuilderTarget +import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isComposeOrAll +import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isViewSystemOrAll +import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder.ElementName +import com.sdds.plugin.themebuilder.internal.dimens.DimenData +import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator +import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory +import com.sdds.plugin.themebuilder.internal.factory.XmlDocumentBuilderFactory +import com.sdds.plugin.themebuilder.internal.token.RoundedShapeToken +import com.sdds.plugin.themebuilder.internal.token.ShapeTokenValue +import com.sdds.plugin.themebuilder.internal.token.Token +import com.sdds.plugin.themebuilder.internal.utils.FileProvider.shapesXmlFile +import com.sdds.plugin.themebuilder.internal.utils.ResourceReferenceProvider +import com.sdds.plugin.themebuilder.internal.utils.techToSnakeCase +import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy +import java.io.File + +/** + * Генератор токенов форм + * @param outputLocation локация для сохранения kt-файла с токенами + * @param outputResDir директория для сохранения xml-файла с токенами + * @param target целевой фреймворк + * @param xmlBuilderFactory фабрика делегата построения xml файлов + * @param ktFileBuilderFactory фабрика делегата построения kt файлов + * @param dimensAggregator агрегатор размеров + * @param resourceReferenceProvider провайдер ссылок на ресурсы + * @author Малышев Александр on 07.03.2024 + */ +internal class ShapeGenerator( + private val outputLocation: KtFileBuilder.OutputLocation, + private val outputResDir: File, + private val target: ThemeBuilderTarget, + private val xmlBuilderFactory: XmlDocumentBuilderFactory, + private val ktFileBuilderFactory: KtFileBuilderFactory, + private val dimensAggregator: DimensAggregator, + private val resourceReferenceProvider: ResourceReferenceProvider, +) : TokenGenerator> { + + private val xmlDocumentBuilder by unsafeLazy { xmlBuilderFactory.create() } + private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create("ShapeTokens") } + private val rootRoundShapes by unsafeLazy { ktFileBuilder.rootObject("RoundShapeTokens") } + private var needGenerateCompose: Boolean = false + private var needGenerateViewSystem: Boolean = false + private var needCreateStyle: Boolean = true + + /** + * @see TokenGenerator.addToken + */ + override fun addToken(token: Token) { + when (target) { + ThemeBuilderTarget.VIEW_SYSTEM -> addViewSystemToken(token) + ThemeBuilderTarget.COMPOSE -> addComposeToken(token) + ThemeBuilderTarget.ALL -> { + addViewSystemToken(token) + addComposeToken(token) + } + } + } + + /** + * @see TokenGenerator.generate + */ + override fun generate() { + if (needGenerateViewSystem) { + xmlDocumentBuilder.build(outputResDir.shapesXmlFile()) + } + + if (needGenerateCompose) { + ktFileBuilder.addImport(KtFileBuilder.TypeDpExtension) + ktFileBuilder.addImport(KtFileBuilder.TypeCornerSize) + ktFileBuilder.build(outputLocation) + } + } + + private fun addViewSystemToken(token: Token) = with(xmlDocumentBuilder) { + val roundedShapeToken = token as? RoundedShapeToken ?: return + val tokenValue = roundedShapeToken.value ?: return + val cornerSize = DimenData( + name = "${roundedShapeToken.name.techToSnakeCase()}_corner_size", + value = tokenValue.cornerRadius, + type = DimenData.Type.DP, + ) + dimensAggregator.addDimen(cornerSize) + if (needCreateStyle) { + needCreateStyle = false + appendStyle("Shape") + appendStyle("Shape.Round") { + appendElement(ElementName.ITEM, "cornerFamily", "rounded", usePrefix = false) + } + } + appendComment(roundedShapeToken.description) + appendStyle("Shape.${roundedShapeToken.xmlName}") { + appendElement( + ElementName.ITEM, + "cornerSize", + resourceReferenceProvider.dimen(cornerSize), + usePrefix = false, + ) + } + if (!needGenerateViewSystem && target.isViewSystemOrAll) needGenerateViewSystem = true + } + + private fun addComposeToken(token: Token) = with(ktFileBuilder) { + val roundedShapeToken = token as? RoundedShapeToken ?: return + val tokenValue = roundedShapeToken.value ?: return + + val value = "${tokenValue.cornerRadius}.dp" + val initializer = KtFileBuilder.createConstructorCall( + "RoundedCornerShape", + "CornerSize($value)", + ) + rootRoundShapes.appendProperty(token.ktName, KtFileBuilder.TypeRoundRectShape, initializer, token.description) + if (!needGenerateCompose && target.isComposeOrAll) needGenerateCompose = true + } +} diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/Serializer.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/Serializer.kt index e97bf01e..46e86d90 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/Serializer.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/serializer/Serializer.kt @@ -3,6 +3,7 @@ package com.sdds.plugin.themebuilder.internal.serializer import com.sdds.plugin.themebuilder.internal.token.ColorToken import com.sdds.plugin.themebuilder.internal.token.LinearGradientToken import com.sdds.plugin.themebuilder.internal.token.RadialGradientToken +import com.sdds.plugin.themebuilder.internal.token.RoundedShapeToken import com.sdds.plugin.themebuilder.internal.token.ShadowToken import com.sdds.plugin.themebuilder.internal.token.SweepGradientToken import com.sdds.plugin.themebuilder.internal.token.Token @@ -30,6 +31,7 @@ object Serializer { subclass(RadialGradientToken::class) subclass(SweepGradientToken::class) subclass(ShadowToken::class) + subclass(RoundedShapeToken::class) subclass(TypographyToken::class) } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShadowTokens.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShadowTokens.kt index 7bdc8f79..6d7e716d 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShadowTokens.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShadowTokens.kt @@ -36,11 +36,15 @@ internal data class ShadowToken( /** * Значение токена тени * @property color цвет тени - * @property elevation Z-координата тени + * @property dX смещение по X координате + * @property dY смещение по Y координате + * @property radius радиус тени */ @Serializable internal data class Value( val color: String, - val elevation: Float, + val dX: Float, + val dY: Float, + val radius: Float, ) : TokenValue } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShapeToken.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShapeToken.kt new file mode 100644 index 00000000..fc72e000 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/token/ShapeToken.kt @@ -0,0 +1,53 @@ +package com.sdds.plugin.themebuilder.internal.token + +import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.gradle.configurationcache.extensions.capitalized + +/** + * Значение токена формы + * @author Малышев Александр on 15.03.2024 + */ +internal interface ShapeTokenValue : TokenValue { + + /** + * Радиус скругления углов + */ + val cornerRadius: Float +} + +/** + * Токен формы + * @see Token + */ +@Serializable +@SerialName("round-shape") +internal data class RoundedShapeToken( + override val displayName: String, + override val name: String, + override val platform: TokenPlatform, + override val tags: Set, + override val value: Value?, + override val enabled: Boolean, + override val description: String, +) : Token() { + + /** + * @see Token.xmlName + */ + override val xmlName: String by unsafeLazy { + val nameTokens = name.split(".") + val nameTokensExcludeScreen = nameTokens.subList(1, nameTokens.size) + nameTokensExcludeScreen.joinToString("") { it.capitalized() } + } + + /** + * Значение токена round-shape + * @property cornerRadius радиус скругления углов формы + */ + @Serializable + data class Value( + override val cornerRadius: Float, + ) : ShapeTokenValue +} diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/utils/FileProvider.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/utils/FileProvider.kt index e7c0508a..098673cc 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/utils/FileProvider.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/utils/FileProvider.kt @@ -56,6 +56,18 @@ object FileProvider { qualifier: String = "", ): File = File("${valuesDir(qualifier).path}/typography.xml") + /** + * XML файл для токенов форм + */ + fun File.shapesXmlFile(): File = + File("${valuesDir().path}/shapes.xml") + + /** + * XML файл для токенов теней + */ + fun File.shadowsXmlFile(): File = + File("${valuesDir().path}/shadows.xml") + /** * Возвращает экземпляр [FileWriter] для текущего файла */ diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGeneratorTest.kt new file mode 100644 index 00000000..bb977ae8 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGeneratorTest.kt @@ -0,0 +1,84 @@ +package com.sdds.plugin.themebuilder.internal.generator + +import com.sdds.plugin.themebuilder.ThemeBuilderTarget +import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder +import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator +import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory +import com.sdds.plugin.themebuilder.internal.factory.XmlDocumentBuilderFactory +import com.sdds.plugin.themebuilder.internal.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.token.ShadowToken +import com.sdds.plugin.themebuilder.internal.utils.FileProvider +import com.sdds.plugin.themebuilder.internal.utils.FileProvider.fileWriter +import com.sdds.plugin.themebuilder.internal.utils.FileProvider.shadowsXmlFile +import com.sdds.plugin.themebuilder.internal.utils.getResourceAsText +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import kotlinx.serialization.decodeFromString +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.io.ByteArrayOutputStream +import java.io.File + +/** + * Unit тесты [ShadowGenerator] + * @author Малышев Александр on 13.03.2024 + */ +class ShadowGeneratorTest { + + private lateinit var outputKt: ByteArrayOutputStream + private lateinit var mockOutputResDir: File + private lateinit var mockDimenAggregator: DimensAggregator + private lateinit var underTest: ShadowGenerator + + @Before + fun setUp() { + mockkObject( + PropertySpec, + TypeSpec, + FileProvider, + ) + outputKt = ByteArrayOutputStream() + mockOutputResDir = mockk(relaxed = true) + mockDimenAggregator = mockk(relaxed = true) + underTest = ShadowGenerator( + outputLocation = KtFileBuilder.OutputLocation.Stream(outputKt), + outputResDir = mockOutputResDir, + target = ThemeBuilderTarget.ALL, + xmlBuilderFactory = XmlDocumentBuilderFactory("thmbldr"), + ktFileBuilderFactory = KtFileBuilderFactory("com.test"), + ) + } + + @After + fun tearDown() { + clearAllMocks(true) + unmockkObject( + PropertySpec, + TypeSpec, + FileProvider, + ) + } + + @Test + fun `ShadowGenerator добавляет токен и генерирует файлы для compose и view system`() { + val input = getResourceAsText("inputs/test-shadow-input.json") + val shadowToken = Serializer.instance.decodeFromString(input) + val outputXml = ByteArrayOutputStream() + val shadowsXmlFile = mockk(relaxed = true) + every { shadowsXmlFile.fileWriter() } returns outputXml.writer() + every { mockOutputResDir.shadowsXmlFile() } returns shadowsXmlFile + + underTest.addToken(shadowToken) + underTest.generate() + + assertEquals(getResourceAsText("shadow-outputs/test-shadow-output.xml"), outputXml.toString()) + assertEquals(getResourceAsText("shadow-outputs/TestShadowOutputKt.txt"), outputKt.toString()) + } +} diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGeneratorTest.kt new file mode 100644 index 00000000..222dbc9d --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGeneratorTest.kt @@ -0,0 +1,87 @@ +package com.sdds.plugin.themebuilder.internal.generator + +import com.sdds.plugin.themebuilder.ThemeBuilderTarget +import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder +import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator +import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory +import com.sdds.plugin.themebuilder.internal.factory.XmlDocumentBuilderFactory +import com.sdds.plugin.themebuilder.internal.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.token.RoundedShapeToken +import com.sdds.plugin.themebuilder.internal.utils.FileProvider +import com.sdds.plugin.themebuilder.internal.utils.FileProvider.fileWriter +import com.sdds.plugin.themebuilder.internal.utils.FileProvider.shapesXmlFile +import com.sdds.plugin.themebuilder.internal.utils.ResourceReferenceProvider +import com.sdds.plugin.themebuilder.internal.utils.getResourceAsText +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import kotlinx.serialization.decodeFromString +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.io.ByteArrayOutputStream +import java.io.File + +/** + * Unit тесты [ShapeGenerator] + * @author Малышев Александр on 13.03.2024 + */ +class ShapeGeneratorTest { + + private lateinit var outputKt: ByteArrayOutputStream + private lateinit var mockOutputResDir: File + private lateinit var mockDimenAggregator: DimensAggregator + private lateinit var underTest: ShapeGenerator + + @Before + fun setUp() { + mockkObject( + PropertySpec, + TypeSpec, + FileProvider, + ) + outputKt = ByteArrayOutputStream() + mockOutputResDir = mockk(relaxed = true) + mockDimenAggregator = mockk(relaxed = true) + underTest = ShapeGenerator( + outputLocation = KtFileBuilder.OutputLocation.Stream(outputKt), + outputResDir = mockOutputResDir, + target = ThemeBuilderTarget.ALL, + xmlBuilderFactory = XmlDocumentBuilderFactory("thmbldr"), + ktFileBuilderFactory = KtFileBuilderFactory("com.test"), + dimensAggregator = mockDimenAggregator, + resourceReferenceProvider = ResourceReferenceProvider("thmbldr"), + ) + } + + @After + fun tearDown() { + clearAllMocks(true) + unmockkObject( + PropertySpec, + TypeSpec, + FileProvider, + ) + } + + @Test + fun `ShapeGenerator добавляет токен и генерирует файлы для compose и view system`() { + val input = getResourceAsText("inputs/test-shape-input.json") + val shapeTokens = Serializer.instance.decodeFromString>(input) + val outputXml = ByteArrayOutputStream() + val shapesXmlFile = mockk(relaxed = true) + every { shapesXmlFile.fileWriter() } returns outputXml.writer() + every { mockOutputResDir.shapesXmlFile() } returns shapesXmlFile + + shapeTokens.forEach { underTest.addToken(it) } + underTest.generate() + + assertEquals(getResourceAsText("shape-outputs/test-shape-output.xml"), outputXml.toString()) + assertEquals(getResourceAsText("shape-outputs/TestShapeOutputKt.txt"), outputKt.toString()) + } +} diff --git a/sdds-core/plugin_theme_builder/src/test/resources/inputs/test-shadow-input.json b/sdds-core/plugin_theme_builder/src/test/resources/inputs/test-shadow-input.json new file mode 100644 index 00000000..11cc8176 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/inputs/test-shadow-input.json @@ -0,0 +1,19 @@ +{ + "displayName": "downHardL", + "name": "down.hard.l", + "platform": "android", + "tags": [ + "down", + "hard", + "l" + ], + "type": "shadow", + "value": { + "color": "#99000000", + "dX": 0.0, + "dY": 1.0, + "radius": 4 + }, + "description": "Тень downHardL для android", + "enabled": true +} \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/test/resources/inputs/test-shape-input.json b/sdds-core/plugin_theme_builder/src/test/resources/inputs/test-shape-input.json new file mode 100644 index 00000000..b04e6249 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/inputs/test-shape-input.json @@ -0,0 +1,47 @@ +[ + { + "displayName": "roundXs", + "name": "round.xs", + "platform": "android", + "tags": [ + "round", + "xs" + ], + "type": "round-shape", + "value": { + "cornerRadius": 6.0 + }, + "description": "Форма со скругленными углами размером XS", + "enabled": true + }, + { + "displayName": "roundS", + "name": "round.s", + "platform": "android", + "tags": [ + "round", + "s" + ], + "type": "round-shape", + "value": { + "cornerRadius": 8.0 + }, + "description": "Форма со скругленными углами размером S", + "enabled": true + }, + { + "displayName": "roundXs", + "name": "round.l", + "platform": "android", + "tags": [ + "round", + "l" + ], + "type": "round-shape", + "value": { + "cornerRadius": 16.0 + }, + "description": "Форма со скругленными углами размером L", + "enabled": true + } +] \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/TestShadowOutputKt.txt b/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/TestShadowOutputKt.txt new file mode 100644 index 00000000..40bf112d --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/TestShadowOutputKt.txt @@ -0,0 +1,33 @@ +package com.test + +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +public object ShadowTokens { + /** + * Тень downHardL для android + */ + public object DownHardL { + /** + * Тень downHardL для android + */ + public val dX: Dp = 0.0.dp + + /** + * Тень downHardL для android + */ + public val dY: Dp = 1.0.dp + + /** + * Тень downHardL для android + */ + public val radius: Dp = 4.0.dp + + /** + * Тень downHardL для android + */ + public val color: Color = Color(0x99000000) + } +} diff --git a/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/test-shadow-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/test-shadow-output.xml new file mode 100644 index 00000000..7d4068f9 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/test-shadow-output.xml @@ -0,0 +1,9 @@ + + + + 0.0dp + 1.0dp + #99000000 + 4.0dp + + diff --git a/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/TestShapeOutputKt.txt b/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/TestShapeOutputKt.txt new file mode 100644 index 00000000..f6c0cff6 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/TestShapeOutputKt.txt @@ -0,0 +1,22 @@ +package com.test + +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.unit.dp + +public object RoundShapeTokens { + /** + * Форма со скругленными углами размером XS + */ + public val RoundXs: RoundedCornerShape = RoundedCornerShape(CornerSize(6.0.dp)) + + /** + * Форма со скругленными углами размером S + */ + public val RoundS: RoundedCornerShape = RoundedCornerShape(CornerSize(8.0.dp)) + + /** + * Форма со скругленными углами размером L + */ + public val RoundL: RoundedCornerShape = RoundedCornerShape(CornerSize(16.0.dp)) +} diff --git a/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/test-shape-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/test-shape-output.xml new file mode 100644 index 00000000..7872d4ec --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/test-shape-output.xml @@ -0,0 +1,19 @@ + + + + + + + + + + From f93fe95626ead9366fb12d2ddead48db10bfb2ff Mon Sep 17 00:00:00 2001 From: malilex Date: Tue, 19 Mar 2024 16:41:57 +0300 Subject: [PATCH 05/13] feat(sdds-acore/theme-builder): Generators were refactored. --- gradle/libs.versions.toml | 2 +- .../internal/generator/BaseGenerator.kt | 63 ++++++++++++++++++- .../internal/generator/ColorGenerator.kt | 53 +++++++--------- .../internal/generator/DimenGenerator.kt | 4 +- .../internal/generator/GradientGenerator.kt | 51 +++++++-------- .../internal/generator/ShadowGenerator.kt | 57 +++++++---------- .../internal/generator/ShapeGenerator.kt | 61 ++++++++---------- .../internal/generator/TypographyGenerator.kt | 62 ++++++++---------- 8 files changed, 183 insertions(+), 170 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f191e411..2d76314f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ test-robolectric = "4.9" test-androidX-rules = "1.5.0" test-androidX-runner = "1.5.2" -kotlinSerialization = "1.4.0" +kotlinSerialization = "1.3.2" kotlinPoet = "1.12.0" plugin-androidCacheFix = "3.0.1" diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/BaseGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/BaseGenerator.kt index 3e7bd8be..ac63633a 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/BaseGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/BaseGenerator.kt @@ -1,5 +1,8 @@ package com.sdds.plugin.themebuilder.internal.generator +import com.sdds.plugin.themebuilder.ThemeBuilderTarget +import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isComposeOrAll +import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isViewSystemOrAll import com.sdds.plugin.themebuilder.internal.token.Token import com.sdds.plugin.themebuilder.internal.token.TokenValue @@ -19,10 +22,66 @@ internal interface BaseGenerator { * Интерфейс генератора токенов * @author Малышев Александр on 07.03.2024 */ -internal interface TokenGenerator> : BaseGenerator { +internal abstract class TokenGenerator>( + private val target: ThemeBuilderTarget, +) : BaseGenerator { + + private var needGenerateCompose: Boolean = false + private var needGenerateViewSystem: Boolean = false /** * Добавляет [token] для генерации данных */ - fun addToken(token: T) + fun addToken(token: T) { + val result = when (target) { + ThemeBuilderTarget.VIEW_SYSTEM -> addViewSystemToken(token) + ThemeBuilderTarget.COMPOSE -> addComposeToken(token) + ThemeBuilderTarget.ALL -> addViewSystemToken(token) && addComposeToken(token) + } + + if (!needGenerateViewSystem && target.isViewSystemOrAll && result) { + needGenerateViewSystem = true + } + + if (!needGenerateCompose && target.isComposeOrAll && result) { + needGenerateCompose = true + } + } + + /** + * @see BaseGenerator.generate + */ + final override fun generate() { + if (needGenerateViewSystem) { + generateViewSystem() + } + + if (needGenerateCompose) { + generateCompose() + } + } + + /** + * Добавляет токен [token] для ViewSystem. + * @return статус добавления токена + */ + protected open fun addViewSystemToken(token: T): Boolean = false + + /** + * Добавляет токен [token] для Compose. + * @return статус добавления токена + */ + protected open fun addComposeToken(token: T): Boolean = false + + /** + * Генерирует файл с токенами для ViewSystem, если хотя бы один токен + * был успешно добавлен + */ + protected open fun generateViewSystem() = Unit + + /** + * Генерирует файл с токенами для Compose, если хотя бы один токен + * был успешно добавлен + */ + protected open fun generateCompose() = Unit } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorGenerator.kt index 84b4724b..a320e476 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorGenerator.kt @@ -1,8 +1,6 @@ package com.sdds.plugin.themebuilder.internal.generator import com.sdds.plugin.themebuilder.ThemeBuilderTarget -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isComposeOrAll -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isViewSystemOrAll import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder.ElementName import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory @@ -24,63 +22,54 @@ import java.io.File internal class ColorGenerator( private val outputLocation: KtFileBuilder.OutputLocation, private val outputResDir: File, - private val target: ThemeBuilderTarget, + target: ThemeBuilderTarget, private val xmlBuilderFactory: XmlDocumentBuilderFactory, private val ktFileBuilderFactory: KtFileBuilderFactory, -) : TokenGenerator { +) : TokenGenerator(target) { private val xmlDocumentBuilder by unsafeLazy { xmlBuilderFactory.create() } private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create("ColorTokens") } private val lightBuilder by unsafeLazy { ktFileBuilder.rootObject("LightColorTokens") } private val darkBuilder by unsafeLazy { ktFileBuilder.rootObject("DarkColorTokens") } - private var needGenerateCompose: Boolean = false - private var needGenerateViewSystem: Boolean = false /** - * @see TokenGenerator.addToken + * @see TokenGenerator.generateViewSystem */ - override fun addToken(token: ColorToken) { - when (target) { - ThemeBuilderTarget.VIEW_SYSTEM -> addViewSystemToken(token) - ThemeBuilderTarget.COMPOSE -> addComposeToken(token) - ThemeBuilderTarget.ALL -> { - addViewSystemToken(token) - addComposeToken(token) - } - } + override fun generateViewSystem() { + xmlDocumentBuilder.build(outputResDir.colorsXmlFile()) } /** - * @see TokenGenerator.generate + * @see TokenGenerator.generateCompose */ - override fun generate() { - if (needGenerateViewSystem) { - xmlDocumentBuilder.build(outputResDir.colorsXmlFile()) - } - - if (needGenerateCompose) { - ktFileBuilder.build(outputLocation) - } + override fun generateCompose() { + ktFileBuilder.build(outputLocation) } - private fun addViewSystemToken(token: ColorToken) { - val tokenValue = token.value ?: return + /** + * @see TokenGenerator.addViewSystemToken + */ + override fun addViewSystemToken(token: ColorToken): Boolean { + val tokenValue = token.value ?: return false xmlDocumentBuilder.appendComment(token.description) xmlDocumentBuilder.appendElement(ElementName.COLOR, token.xmlName, tokenValue.origin) - if (!needGenerateViewSystem && target.isViewSystemOrAll) needGenerateViewSystem = true + return true } - private fun addComposeToken(token: ColorToken) = with(ktFileBuilder) { - val tokenValue = token.value ?: return + /** + * @see TokenGenerator.addComposeToken + */ + override fun addComposeToken(token: ColorToken): Boolean = with(ktFileBuilder) { + val tokenValue = token.value ?: return false val root = if (token.tags.contains("dark")) { darkBuilder } else if (token.tags.contains("light")) { lightBuilder } else { - return + return false } val value = "Color(${tokenValue.origin.replace("#", "0x")})" root.appendProperty(token.ktName, KtFileBuilder.TypeColor, value, token.description) - if (!needGenerateCompose && target.isComposeOrAll) needGenerateCompose = true + return true } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/DimenGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/DimenGenerator.kt index 4999799c..fcc87e5a 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/DimenGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/DimenGenerator.kt @@ -31,7 +31,9 @@ internal class DimenGenerator( dimensAggregator.dimens.forEach { xmlDocumentBuilder.appendDimen(it) } - xmlDocumentBuilder.build(outputResDir.dimensFile()) + if (dimensAggregator.dimens.isNotEmpty()) { + xmlDocumentBuilder.build(outputResDir.dimensFile()) + } } private fun XmlDocumentBuilder.appendDimen(dimen: DimenData) { diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientGenerator.kt index 5a62f2f7..099e7c07 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientGenerator.kt @@ -1,8 +1,6 @@ package com.sdds.plugin.themebuilder.internal.generator import com.sdds.plugin.themebuilder.ThemeBuilderTarget -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isComposeOrAll -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isViewSystemOrAll import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Companion.appendObject import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder @@ -32,62 +30,55 @@ import java.io.File internal class GradientGenerator( private val outputLocation: KtFileBuilder.OutputLocation, private val outputResDir: File, - private val target: ThemeBuilderTarget, + target: ThemeBuilderTarget, private val xmlBuilderFactory: XmlDocumentBuilderFactory, private val ktFileBuilderFactory: KtFileBuilderFactory, -) : TokenGenerator> { +) : TokenGenerator>(target) { private val xmlDocumentBuilder by unsafeLazy { xmlBuilderFactory.create() } private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create("GradientTokens") } private val lightBuilder by unsafeLazy { ktFileBuilder.rootObject("LightGradientTokens") } private val darkBuilder by unsafeLazy { ktFileBuilder.rootObject("DarkGradientTokens") } - private var needGenerateCompose: Boolean = false - private var needGenerateViewSystem: Boolean = false /** - * @see TokenGenerator.addToken + * @see TokenGenerator.generateViewSystem */ - override fun addToken(token: Token) { - when (target) { - ThemeBuilderTarget.VIEW_SYSTEM -> addViewSystemToken(token) - ThemeBuilderTarget.COMPOSE -> addComposeToken(token) - ThemeBuilderTarget.ALL -> { - addViewSystemToken(token) - addComposeToken(token) - } - } + override fun generateViewSystem() { + super.generateViewSystem() + xmlDocumentBuilder.build(outputResDir.gradientsXmlFile()) } /** - * @see TokenGenerator.generate + * @see TokenGenerator.generateCompose */ - override fun generate() { - if (needGenerateViewSystem) { - xmlDocumentBuilder.build(outputResDir.gradientsXmlFile()) - } - - if (needGenerateCompose) { - ktFileBuilder.build(outputLocation) - } + override fun generateCompose() { + super.generateCompose() + ktFileBuilder.build(outputLocation) } - private fun addViewSystemToken(token: Token) { + /** + * @see TokenGenerator.addViewSystemToken + */ + override fun addViewSystemToken(token: Token): Boolean { val result = when (token) { is LinearGradientToken -> xmlDocumentBuilder.appendLinearGradient(token) is RadialGradientToken -> xmlDocumentBuilder.appendRadialGradient(token) is SweepGradientToken -> xmlDocumentBuilder.appendSweepGradient(token) else -> false } - if (result && !needGenerateViewSystem && target.isViewSystemOrAll) needGenerateViewSystem = true + return result } - private fun addComposeToken(token: Token) = with(ktFileBuilder) { + /** + * @see TokenGenerator.addComposeToken + */ + override fun addComposeToken(token: Token): Boolean = with(ktFileBuilder) { val builder = if (token.tags.contains("dark")) { darkBuilder } else if (token.tags.contains("light")) { lightBuilder } else { - return + return false } val result = when (token) { is LinearGradientToken -> builder.appendLinearGradient(token) @@ -95,7 +86,7 @@ internal class GradientGenerator( is SweepGradientToken -> builder.appendSweepGradient(token) else -> false } - if (result && !needGenerateCompose && target.isComposeOrAll) needGenerateCompose = true + return result } private fun XmlDocumentBuilder.appendBaseGradient(baseName: String, colors: List, positions: List) { diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGenerator.kt index eb7684b3..6bbb7e3b 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowGenerator.kt @@ -1,8 +1,6 @@ package com.sdds.plugin.themebuilder.internal.generator import com.sdds.plugin.themebuilder.ThemeBuilderTarget -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isComposeOrAll -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isViewSystemOrAll import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Companion.appendObject import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder.ElementName @@ -25,48 +23,38 @@ import java.io.File internal class ShadowGenerator( private val outputLocation: KtFileBuilder.OutputLocation, private val outputResDir: File, - private val target: ThemeBuilderTarget, + target: ThemeBuilderTarget, private val xmlBuilderFactory: XmlDocumentBuilderFactory, private val ktFileBuilderFactory: KtFileBuilderFactory, -) : TokenGenerator { +) : TokenGenerator(target) { private val xmlDocumentBuilder by unsafeLazy { xmlBuilderFactory.create() } private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create("ShadowTokens") } private val rootShadows by unsafeLazy { ktFileBuilder.rootObject("ShadowTokens") } - private var needGenerateCompose: Boolean = false - private var needGenerateViewSystem: Boolean = false /** - * @see TokenGenerator.addToken + * @see TokenGenerator.generateViewSystem */ - override fun addToken(token: ShadowToken) { - when (target) { - ThemeBuilderTarget.VIEW_SYSTEM -> addViewSystemToken(token) - ThemeBuilderTarget.COMPOSE -> addComposeToken(token) - ThemeBuilderTarget.ALL -> { - addViewSystemToken(token) - addComposeToken(token) - } - } + override fun generateViewSystem() { + super.generateViewSystem() + xmlDocumentBuilder.build(outputResDir.shadowsXmlFile()) } /** - * @see TokenGenerator.generate + * @see TokenGenerator.generateCompose */ - override fun generate() { - if (needGenerateViewSystem) { - xmlDocumentBuilder.build(outputResDir.shadowsXmlFile()) - } - - if (needGenerateCompose) { - ktFileBuilder.addImport(KtFileBuilder.TypeDpExtension) - ktFileBuilder.addImport(KtFileBuilder.TypeCornerSize) - ktFileBuilder.build(outputLocation) - } + override fun generateCompose() { + super.generateCompose() + ktFileBuilder.addImport(KtFileBuilder.TypeDpExtension) + ktFileBuilder.addImport(KtFileBuilder.TypeCornerSize) + ktFileBuilder.build(outputLocation) } - private fun addViewSystemToken(token: ShadowToken) = with(xmlDocumentBuilder) { - val tokenValue = token.value ?: return + /** + * @see TokenGenerator.addViewSystemToken + */ + override fun addViewSystemToken(token: ShadowToken): Boolean = with(xmlDocumentBuilder) { + val tokenValue = token.value ?: return@with false wrapWithRegion(token.description) { appendElement(ElementName.DIMEN, "shadow_${token.xmlName}_dx", "${tokenValue.dX}dp") @@ -74,11 +62,14 @@ internal class ShadowGenerator( appendElement(ElementName.COLOR, "shadow_${token.xmlName}_color", tokenValue.color) appendElement(ElementName.DIMEN, "shadow_${token.xmlName}_radius", "${tokenValue.radius}dp") } - if (!needGenerateViewSystem && target.isViewSystemOrAll) needGenerateViewSystem = true + return@with true } - private fun addComposeToken(token: ShadowToken) = with(ktFileBuilder) { - val tokenValue = token.value ?: return + /** + * @see TokenGenerator.addComposeToken + */ + override fun addComposeToken(token: ShadowToken): Boolean = with(ktFileBuilder) { + val tokenValue = token.value ?: return@with false rootShadows.appendObject(token.ktName, token.description) { appendProperty("dX", KtFileBuilder.TypeDp, "${tokenValue.dX}.dp", token.description) @@ -91,6 +82,6 @@ internal class ShadowGenerator( token.description, ) } - if (!needGenerateCompose && target.isComposeOrAll) needGenerateCompose = true + return@with true } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGenerator.kt index cd4400b7..2229095e 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeGenerator.kt @@ -1,8 +1,6 @@ package com.sdds.plugin.themebuilder.internal.generator import com.sdds.plugin.themebuilder.ThemeBuilderTarget -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isComposeOrAll -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isViewSystemOrAll import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder.ElementName import com.sdds.plugin.themebuilder.internal.dimens.DimenData @@ -32,52 +30,42 @@ import java.io.File internal class ShapeGenerator( private val outputLocation: KtFileBuilder.OutputLocation, private val outputResDir: File, - private val target: ThemeBuilderTarget, + target: ThemeBuilderTarget, private val xmlBuilderFactory: XmlDocumentBuilderFactory, private val ktFileBuilderFactory: KtFileBuilderFactory, private val dimensAggregator: DimensAggregator, private val resourceReferenceProvider: ResourceReferenceProvider, -) : TokenGenerator> { +) : TokenGenerator>(target) { private val xmlDocumentBuilder by unsafeLazy { xmlBuilderFactory.create() } private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create("ShapeTokens") } private val rootRoundShapes by unsafeLazy { ktFileBuilder.rootObject("RoundShapeTokens") } - private var needGenerateCompose: Boolean = false - private var needGenerateViewSystem: Boolean = false private var needCreateStyle: Boolean = true /** - * @see TokenGenerator.addToken + * @see TokenGenerator.generateViewSystem */ - override fun addToken(token: Token) { - when (target) { - ThemeBuilderTarget.VIEW_SYSTEM -> addViewSystemToken(token) - ThemeBuilderTarget.COMPOSE -> addComposeToken(token) - ThemeBuilderTarget.ALL -> { - addViewSystemToken(token) - addComposeToken(token) - } - } + override fun generateViewSystem() { + super.generateViewSystem() + xmlDocumentBuilder.build(outputResDir.shapesXmlFile()) } /** - * @see TokenGenerator.generate + * @see TokenGenerator.generateCompose */ - override fun generate() { - if (needGenerateViewSystem) { - xmlDocumentBuilder.build(outputResDir.shapesXmlFile()) - } - - if (needGenerateCompose) { - ktFileBuilder.addImport(KtFileBuilder.TypeDpExtension) - ktFileBuilder.addImport(KtFileBuilder.TypeCornerSize) - ktFileBuilder.build(outputLocation) - } + override fun generateCompose() { + super.generateCompose() + ktFileBuilder.addImport(KtFileBuilder.TypeDpExtension) + ktFileBuilder.addImport(KtFileBuilder.TypeCornerSize) + ktFileBuilder.build(outputLocation) } - private fun addViewSystemToken(token: Token) = with(xmlDocumentBuilder) { - val roundedShapeToken = token as? RoundedShapeToken ?: return - val tokenValue = roundedShapeToken.value ?: return + /** + * @see TokenGenerator.addViewSystemToken + */ + override fun addViewSystemToken(token: Token): Boolean = with(xmlDocumentBuilder) { + val roundedShapeToken = token as? RoundedShapeToken ?: return@with false + val tokenValue = roundedShapeToken.value ?: return@with false val cornerSize = DimenData( name = "${roundedShapeToken.name.techToSnakeCase()}_corner_size", value = tokenValue.cornerRadius, @@ -100,12 +88,15 @@ internal class ShapeGenerator( usePrefix = false, ) } - if (!needGenerateViewSystem && target.isViewSystemOrAll) needGenerateViewSystem = true + return@with true } - private fun addComposeToken(token: Token) = with(ktFileBuilder) { - val roundedShapeToken = token as? RoundedShapeToken ?: return - val tokenValue = roundedShapeToken.value ?: return + /** + * @see TokenGenerator.addComposeToken + */ + override fun addComposeToken(token: Token): Boolean = with(ktFileBuilder) { + val roundedShapeToken = token as? RoundedShapeToken ?: return@with false + val tokenValue = roundedShapeToken.value ?: return@with false val value = "${tokenValue.cornerRadius}.dp" val initializer = KtFileBuilder.createConstructorCall( @@ -113,6 +104,6 @@ internal class ShapeGenerator( "CornerSize($value)", ) rootRoundShapes.appendProperty(token.ktName, KtFileBuilder.TypeRoundRectShape, initializer, token.description) - if (!needGenerateCompose && target.isComposeOrAll) needGenerateCompose = true + return@with true } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt index 133927b1..732d2ee7 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt @@ -1,8 +1,6 @@ package com.sdds.plugin.themebuilder.internal.generator import com.sdds.plugin.themebuilder.ThemeBuilderTarget -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isComposeOrAll -import com.sdds.plugin.themebuilder.ThemeBuilderTarget.Companion.isViewSystemOrAll import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder.ElementName @@ -30,12 +28,12 @@ import java.io.File internal class TypographyGenerator( private val outputLocation: KtFileBuilder.OutputLocation, private val outputResDir: File, - private val target: ThemeBuilderTarget, + target: ThemeBuilderTarget, private val dimensAggregator: DimensAggregator, private val xmlBuilderFactory: XmlDocumentBuilderFactory, private val ktFileBuilderFactory: KtFileBuilderFactory, private val resourceReferenceProvider: ResourceReferenceProvider, -) : TokenGenerator { +) : TokenGenerator(target) { private val textAppearanceXmlBuilders = mutableMapOf() private val typographyXmlBuilder by unsafeLazy { xmlBuilderFactory.create() } @@ -43,44 +41,34 @@ internal class TypographyGenerator( private val largeBuilder by unsafeLazy { ktFileBuilder.rootObject("TypographyLargeTokens") } private val mediumBuilder by unsafeLazy { ktFileBuilder.rootObject("TypographyMediumTokens") } private val smallBuilder by unsafeLazy { ktFileBuilder.rootObject("TypographySmallTokens") } - private var needGenerateCompose: Boolean = false - private var needGenerateViewSystem: Boolean = false private var needDeclareStyle: Boolean = true /** - * @see TokenGenerator.addToken + * @see TokenGenerator.generateViewSystem */ - override fun addToken(token: TypographyToken) { - when (target) { - ThemeBuilderTarget.VIEW_SYSTEM -> addViewSystemToken(token) - ThemeBuilderTarget.COMPOSE -> addComposeToken(token) - ThemeBuilderTarget.ALL -> { - addViewSystemToken(token) - addComposeToken(token) - } + override fun generateViewSystem() { + super.generateViewSystem() + textAppearanceXmlBuilders.forEach { + it.value.build(outputResDir.textAppearancesXmlFile(it.key.qualifier())) } + typographyXmlBuilder.build(outputResDir.typographyXmlFile()) } /** - * @see TokenGenerator.generate + * @see TokenGenerator.generateCompose */ - override fun generate() { - if (needGenerateViewSystem) { - textAppearanceXmlBuilders.forEach { - it.value.build(outputResDir.textAppearancesXmlFile(it.key.qualifier())) - } - typographyXmlBuilder.build(outputResDir.typographyXmlFile()) - } - - if (needGenerateCompose) { - ktFileBuilder.addImport(KtFileBuilder.TypeSp) - ktFileBuilder.addImport(KtFileBuilder.TypeFontWeight) - ktFileBuilder.build(outputLocation) - } + override fun generateCompose() { + super.generateCompose() + ktFileBuilder.addImport(KtFileBuilder.TypeSp) + ktFileBuilder.addImport(KtFileBuilder.TypeFontWeight) + ktFileBuilder.build(outputLocation) } - private fun addViewSystemToken(token: TypographyToken) { - val tokenValue = token.value ?: return + /** + * @see TokenGenerator.addViewSystemToken + */ + override fun addViewSystemToken(token: TypographyToken): Boolean { + val tokenValue = token.value ?: return false val builder = textAppearanceXmlBuilders[token.screenClass] ?: xmlBuilderFactory.create().also { textAppearanceXmlBuilders[token.screenClass] = it } @@ -106,12 +94,14 @@ internal class TypographyGenerator( } builder.appendComment(token.description) builder.appendTypographyToken(token, tokenValue, textAppearanceName) - - if (!needGenerateViewSystem && target.isViewSystemOrAll) needGenerateViewSystem = true + return true } - private fun addComposeToken(token: TypographyToken) = with(ktFileBuilder) { - val tokenValue = token.value ?: return + /** + * @see TokenGenerator.addComposeToken + */ + override fun addComposeToken(token: TypographyToken): Boolean = with(ktFileBuilder) { + val tokenValue = token.value ?: return@with false when (token.screenClass) { TypographyToken.ScreenClass.SMALL -> smallBuilder.addTypographyToken( token.ktName, @@ -127,7 +117,7 @@ internal class TypographyGenerator( else -> mediumBuilder.addTypographyToken(token.ktName, token.description, tokenValue) } - if (!needGenerateCompose && target.isComposeOrAll) needGenerateCompose = true + return@with true } private fun XmlDocumentBuilder.appendTypographyToken( From b5d6bfd76fadfb144214bfaf24c1a2df3ecbf9ea Mon Sep 17 00:00:00 2001 From: Sergey Velesko Date: Wed, 27 Mar 2024 15:32:04 +0300 Subject: [PATCH 06/13] docs(sdds-acore/theme-builder): Updated theme-builder readme --- sdds-core/plugin_theme_builder/README.MD | 56 +++++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/sdds-core/plugin_theme_builder/README.MD b/sdds-core/plugin_theme_builder/README.MD index 98264d0e..b959b6be 100644 --- a/sdds-core/plugin_theme_builder/README.MD +++ b/sdds-core/plugin_theme_builder/README.MD @@ -3,7 +3,59 @@ Плагин генерирует тему и токены на основе метаданных. ## Установка -TBD +
+Добавить зависимость в gradle + +```groovy +[plugins] +themebuilder = { id = "TBD", version.ref = "TBD" } +``` +
+ +
+ +Подключить плагин в build.gradle модуля + +```kotlin +plugins { + id(libs.plugins.themebuilder.get().pluginId) +} +``` +
+
+ +Далее в `build.gradle` необходимо сконфигурировать плагин через экстеншн `ThemeBuilderExtension`. В нем указать: + +- `themeUrl`, по которому находится json схема темы +- `target` - `COMPOSE`|`VIEW_SYSTEM`|`ALL` +- `packageName` - пакет для темы Compose, если указан таргет `ALL` или `COMPOSE` +- `resourcesPrefix` - префикс для названий ресурсов, если таргет `ALL` или `VIEW_SYSTEM`. Можно не указывать, если он уже указан в android конфигурации (см. пример) + +
+Пример + +```kotlin +plugins { + ... + id(libs.plugins.themebuilder.get().pluginId) +} + +android { + namespace = "com.example.app.themebuilder" + resourcePrefix = "thmbldr" +} + +configure { + themeUrl.set("file://${projectDir.path}/new_theme_scheme.json") + target.set(ThemeBuilderTarget.ALL) + packageName.set("com.example.app.themebuilder.tokens") +} +``` +
## Использование -TBD +Запускаем `./gradlew :your_module:build`. + +Плагин скачает json-схему темы по ссылке `themeUrl` и сгенерирует темы под таргеты, указанные в `target`. По пути `your_module/build/generated/` должны появиться папки: + - `your_module/build/generated/theme-builder` - тема для Compose + - `your_module/build/generated/theme-builder-res` - тема для ViewSystem \ No newline at end of file From 317f61426cd3da3c031099c0745f3912af684ad2 Mon Sep 17 00:00:00 2001 From: malilex Date: Fri, 29 Mar 2024 15:50:32 +0300 Subject: [PATCH 07/13] feat(sdds-acore/theme-builder): Json theme decoding was fixed. Unused attrs were removed from playground:theme-builder. Typography generator was changed to support textStyle and lineHeight attributes. --- playground/build.gradle.kts | 5 +++++ .../src/main/res/values/attrs.xml | 6 ------ .../plugin/themebuilder/GenerateThemeTask.kt | 12 +++++------ .../internal/builder/KtFileBuilder.kt | 6 ++++-- .../internal/builder/XmlDocumentBuilder.kt | 20 ++++++++++++++++++- .../internal/factory/KtFileBuilderFactory.kt | 5 ++--- .../internal/generator/TypographyGenerator.kt | 4 +++- .../color-outputs/test-color-output.xml | 2 +- .../gradient-outputs/test-gradient-output.xml | 2 +- .../shadow-outputs/test-shadow-output.xml | 2 +- .../shape-outputs/test-shape-output.xml | 2 +- .../src/test/resources/test-dimens-output.xml | 2 +- .../test-appearances-large-output.xml | 6 +++--- .../test-appearances-output.xml | 6 +++--- .../test-appearances-small-output.xml | 6 +++--- .../test-typography-output.xml | 2 +- 16 files changed, 53 insertions(+), 35 deletions(-) delete mode 100644 playground/theme-builder/src/main/res/values/attrs.xml diff --git a/playground/build.gradle.kts b/playground/build.gradle.kts index 63878c48..7cb0901b 100644 --- a/playground/build.gradle.kts +++ b/playground/build.gradle.kts @@ -19,6 +19,11 @@ buildscript { } dependencies { + configurations.all { + resolutionStrategy { + force(libs.base.kotlin.serialization.json) + } + } classpath(libs.base.gradle.android) classpath(libs.base.gradle.kotlin) classpath(libs.base.gradle.detekt) diff --git a/playground/theme-builder/src/main/res/values/attrs.xml b/playground/theme-builder/src/main/res/values/attrs.xml deleted file mode 100644 index 6ee0f072..00000000 --- a/playground/theme-builder/src/main/res/values/attrs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt index e0f2b83d..8bcea621 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/GenerateThemeTask.kt @@ -15,7 +15,7 @@ import com.sdds.plugin.themebuilder.internal.token.Theme import com.sdds.plugin.themebuilder.internal.token.TypographyToken import com.sdds.plugin.themebuilder.internal.utils.ResourceReferenceProvider import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy -import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.decodeFromStream import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty @@ -112,10 +112,8 @@ abstract class GenerateThemeTask : DefaultTask() { dimensGenerator.generate() } - private fun decodeTheme(): Theme { - val readFile = themeFile.get().asFile.readText() - val theme = Serializer.instance.decodeFromString(readFile) - logger.debug("decoded theme $theme") - return theme - } + private fun decodeTheme(): Theme = + themeFile.get().asFile.inputStream().use { stream -> + Serializer.instance.decodeFromStream(stream) + }.also { logger.debug("decoded theme $it") } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/KtFileBuilder.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/KtFileBuilder.kt index 2985c1dd..34f618a7 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/KtFileBuilder.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/KtFileBuilder.kt @@ -39,7 +39,8 @@ internal class KtFileBuilder( } /** - * Добавляет kotlin свойство с именем [name], типом [typeName] и инициализатором [initializer] + * Добавляет kotlin свойство с именем [name], типом [typeName], инициализатором [initializer] + * и описанием (документацией) [description] * @return [TypeSpec.Builder] */ fun TypeSpec.Builder.appendProperty( @@ -50,7 +51,8 @@ internal class KtFileBuilder( ) = appendProperty(name, typeName.asTypeName(), initializer, description, this) /** - * Добавляет kotlin свойство с именем [name], типом [typeName] и инициализатором [initializer] + * Добавляет kotlin свойство с именем [name], типом [typeName], инициализатором [initializer] + * и описанием (документацией) [description] * @return [TypeSpec.Builder] */ fun TypeSpec.Builder.appendProperty( diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilder.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilder.kt index d005e234..b5909846 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilder.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/builder/XmlDocumentBuilder.kt @@ -26,6 +26,7 @@ internal open class XmlDocumentBuilder(private val tokenPrefix: String) { private val rootContent: Element by unsafeLazy { document.createElement("resources") + .apply { setAttribute("xmlns:tools", "http://schemas.android.com/tools") } .also { document.appendChild(it) } } @@ -38,6 +39,7 @@ internal open class XmlDocumentBuilder(private val tokenPrefix: String) { * @param value значение токена * @param format формат xml элемента * @param type тип xml элемента + * @param targetApi целевая версия */ fun appendElement( elementName: ElementName, @@ -45,9 +47,10 @@ internal open class XmlDocumentBuilder(private val tokenPrefix: String) { value: String, format: ElementFormat? = null, type: ElementType? = null, + targetApi: TargetApi? = null, usePrefix: Boolean = true, ) { - rootContent.appendElement(elementName, tokenName, value, format, type, usePrefix) + rootContent.appendElement(elementName, tokenName, value, format, type, targetApi, usePrefix) } /** @@ -57,6 +60,7 @@ internal open class XmlDocumentBuilder(private val tokenPrefix: String) { * @param value значение токена * @param format формат xml элемента * @param type тип xml элемента + * @param targetApi целевая версия * @param usePrefix если true - добавляет префикс к имени токена */ fun Element.appendElement( @@ -65,6 +69,7 @@ internal open class XmlDocumentBuilder(private val tokenPrefix: String) { value: String, format: ElementFormat? = null, type: ElementType? = null, + targetApi: TargetApi? = null, usePrefix: Boolean = true, ) { val nameAttr = tokenName.withPrefixIfNeed(tokenPrefix.takeIf { usePrefix }) @@ -72,6 +77,7 @@ internal open class XmlDocumentBuilder(private val tokenPrefix: String) { setAttribute("name", nameAttr) format?.let { setAttribute("format", it.value) } type?.let { setAttribute("type", it.value) } + targetApi?.let { setAttribute("tools:targetApi", targetApi.value) } textContent = value } appendChild(element) @@ -147,4 +153,16 @@ internal open class XmlDocumentBuilder(private val tokenPrefix: String) { enum class ElementType(val value: String) { DIMEN("dimen"), } + + /** + * Версия api + * @param value строковое название + */ + enum class TargetApi(val value: String) { + + /** + * Android SDK 28 + */ + P("p"), + } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/KtFileBuilderFactory.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/KtFileBuilderFactory.kt index ad01fc00..bdce4597 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/KtFileBuilderFactory.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/factory/KtFileBuilderFactory.kt @@ -1,10 +1,9 @@ package com.sdds.plugin.themebuilder.internal.factory import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder -import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder /** - * Фабрика для [XmlDocumentBuilder] + * Фабрика для [KtFileBuilder] * @param packageName название пакета, куда будет сохранен файл * @author Малышев Александр on 07.03.2024 */ @@ -13,7 +12,7 @@ internal class KtFileBuilderFactory( ) { /** - * Создает [XmlDocumentBuilder] + * Создает [KtFileBuilder] */ fun create(fileName: String): KtFileBuilder = KtFileBuilder(packageName, fileName) } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt index 732d2ee7..e37ae74e 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyGenerator.kt @@ -4,6 +4,7 @@ import com.sdds.plugin.themebuilder.ThemeBuilderTarget import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder.ElementName +import com.sdds.plugin.themebuilder.internal.builder.XmlDocumentBuilder.TargetApi import com.sdds.plugin.themebuilder.internal.dimens.DimenData import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory @@ -142,7 +143,7 @@ internal class TypographyGenerator( appendStyle(textAppearanceName) { appendElement(ElementName.ITEM, "fontFamily", tokenValue.fontFamily, usePrefix = false) appendElement(ElementName.ITEM, "fontWeight", tokenValue.fontWeight.toString(), usePrefix = false) - appendElement(ElementName.ITEM, "android:fontStyle", tokenValue.fontStyle, usePrefix = false) + appendElement(ElementName.ITEM, "android:textStyle", tokenValue.fontStyle, usePrefix = false) appendElement( ElementName.ITEM, "android:letterSpacing", @@ -159,6 +160,7 @@ internal class TypographyGenerator( ElementName.ITEM, "android:lineHeight", resourceReferenceProvider.dimen(lineHeightDimen), + targetApi = TargetApi.P, usePrefix = false, ) } diff --git a/sdds-core/plugin_theme_builder/src/test/resources/color-outputs/test-color-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/color-outputs/test-color-output.xml index b0e7b19d..f087ba78 100644 --- a/sdds-core/plugin_theme_builder/src/test/resources/color-outputs/test-color-output.xml +++ b/sdds-core/plugin_theme_builder/src/test/resources/color-outputs/test-color-output.xml @@ -1,5 +1,5 @@ - + #FFFFFF1F diff --git a/sdds-core/plugin_theme_builder/src/test/resources/gradient-outputs/test-gradient-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/gradient-outputs/test-gradient-output.xml index ff261ed6..dacef80d 100644 --- a/sdds-core/plugin_theme_builder/src/test/resources/gradient-outputs/test-gradient-output.xml +++ b/sdds-core/plugin_theme_builder/src/test/resources/gradient-outputs/test-gradient-output.xml @@ -1,5 +1,5 @@ - + #000 #fff diff --git a/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/test-shadow-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/test-shadow-output.xml index 7d4068f9..fe59969d 100644 --- a/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/test-shadow-output.xml +++ b/sdds-core/plugin_theme_builder/src/test/resources/shadow-outputs/test-shadow-output.xml @@ -1,5 +1,5 @@ - + 0.0dp 1.0dp diff --git a/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/test-shape-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/test-shape-output.xml index 7872d4ec..8ca63a77 100644 --- a/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/test-shape-output.xml +++ b/sdds-core/plugin_theme_builder/src/test/resources/shape-outputs/test-shape-output.xml @@ -1,5 +1,5 @@ - + diff --git a/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-output.xml index 85342b3a..8fe4daca 100644 --- a/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-output.xml +++ b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-output.xml @@ -1,13 +1,13 @@ - + diff --git a/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-small-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-small-output.xml index 1a9c5983..8fb6ee3e 100644 --- a/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-small-output.xml +++ b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-appearances-small-output.xml @@ -1,12 +1,12 @@ - + diff --git a/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-typography-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-typography-output.xml index 6b50e4ea..2a3e257d 100644 --- a/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-typography-output.xml +++ b/sdds-core/plugin_theme_builder/src/test/resources/typography-outputs/test-typography-output.xml @@ -1,5 +1,5 @@ - + + diff --git a/sdds-core/plugin_theme_builder/src/test/resources/theme-outputs/test-theme-output.xml b/sdds-core/plugin_theme_builder/src/test/resources/theme-outputs/test-theme-output.xml new file mode 100644 index 00000000..255d6a6e --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/test/resources/theme-outputs/test-theme-output.xml @@ -0,0 +1,6 @@ + + + +