From ea01d2fad8a81fbfed2c9d7ec1f9c091178e3dd8 Mon Sep 17 00:00:00 2001 From: raininforest Date: Tue, 5 May 2026 18:42:24 +0300 Subject: [PATCH] feat(sdds-acore/theme-builder): multitenant mode was supported --- playground/theme-builder/build.gradle.kts | 13 +- playground/theme-builder/json/latest.zip | Bin 0 -> 106741 bytes playground/theme-builder/json/latest_gold.zip | Bin 0 -> 107433 bytes playground/theme-builder/json/test_theme.zip | Bin 48241 -> 0 bytes .../plugin/themebuilder/GenerateThemeTask.kt | 270 +++++++++++++----- .../themebuilder/ThemeBuilderExtension.kt | 79 ++++- .../plugin/themebuilder/ThemeBuilderPlugin.kt | 144 ++++++---- .../plugin/themebuilder/ThemeBuilderSource.kt | 31 +- .../internal/builder/KtFileBuilder.kt | 32 +++ .../internal/factory/GeneratorFactory.kt | 15 +- .../internal/generator/ColorTokenGenerator.kt | 129 ++++++--- .../internal/generator/FontTokenGenerator.kt | 34 ++- .../generator/GradientTokenGenerator.kt | 117 +++++--- .../generator/ShadowTokenGenerator.kt | 71 +++-- .../internal/generator/ShapeTokenGenerator.kt | 67 +++-- .../generator/SpacingTokenGenerator.kt | 78 +++-- .../generator/TypographyTokenGenerator.kt | 158 ++++++---- .../generator/data/ColorTokenResult.kt | 3 +- .../generator/data/GradientTokenResult.kt | 4 +- .../generator/data/ShadowTokenResult.kt | 4 +- .../generator/data/ShapeTokenResult.kt | 5 +- .../generator/data/SpacingTokenResult.kt | 5 +- .../generator/data/TypographyTokenResult.kt | 4 +- .../compose/ComposeColorAttributeGenerator.kt | 211 ++++++++++++-- .../ComposeGradientAttributeGenerator.kt | 246 ++++++++++++---- .../ComposeShadowAttributeGenerator.kt | 72 +++-- .../compose/ComposeShapeAttributeGenerator.kt | 65 +++-- .../ComposeSpacingAttributeGenerator.kt | 65 +++-- .../theme/compose/ComposeSubThemeGenerator.kt | 162 +++-------- .../theme/compose/ComposeThemeGenerator.kt | 11 +- .../ComposeTypographyAttributeGenerator.kt | 156 ++++++---- .../themebuilder/internal/tenant/Tenant.kt | 8 + .../ComposeColorAttributeGeneratorTest.kt | 19 +- .../ComposeGradientAttributeGeneratorTest.kt | 64 +++-- .../ComposeShapeAttributeGeneratorTest.kt | 17 +- ...ComposeTypographyAttributeGeneratorTest.kt | 61 ++-- .../generator/ColorTokenGeneratorTest.kt | 7 +- .../generator/FontTokenGeneratorTest.kt | 73 ++--- .../generator/GradientTokenGeneratorTest.kt | 48 ++-- .../generator/ShadowTokenGeneratorTest.kt | 31 +- .../generator/ShapeTokenGeneratorTest.kt | 9 +- .../generator/SpacingTokenGeneratorTest.kt | 19 +- .../generator/TypographyTokenGeneratorTest.kt | 97 ++++--- .../theme/ComposeThemeGeneratorTest.kt | 48 ++-- .../attrs-outputs/ColorsOutputKt.txt | 32 +++ .../attrs-outputs/GradientsOutputKt.txt | 33 +++ 46 files changed, 1905 insertions(+), 912 deletions(-) create mode 100644 playground/theme-builder/json/latest.zip create mode 100644 playground/theme-builder/json/latest_gold.zip delete mode 100644 playground/theme-builder/json/test_theme.zip create mode 100644 sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/tenant/Tenant.kt diff --git a/playground/theme-builder/build.gradle.kts b/playground/theme-builder/build.gradle.kts index ee122f5044..9c14c37524 100644 --- a/playground/theme-builder/build.gradle.kts +++ b/playground/theme-builder/build.gradle.kts @@ -15,9 +15,16 @@ android { } themeBuilder { - themeSource { - url("file://${projectDir.path}/json/test_theme.zip") - name("sdds_serv") + themeSources(baseAlias = "SddsServ") { + defaultSourceFromUrl( + name = "sdds_serv", + url = "file://${projectDir.path}/json/latest.zip", + ) + sourceFromUrl( + name = "sdds_serv", + url = "file://${projectDir.path}/json/latest_gold.zip", + tenant = "Gold", + ) } componentSource { url("file://${projectDir.path}/json/test_components.zip") diff --git a/playground/theme-builder/json/latest.zip b/playground/theme-builder/json/latest.zip new file mode 100644 index 0000000000000000000000000000000000000000..eb9305837cc08d9720028129114fec4e394f8700 GIT binary patch literal 106741 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 literal 0 HcmV?d00001 diff --git a/playground/theme-builder/json/latest_gold.zip b/playground/theme-builder/json/latest_gold.zip new file mode 100644 index 0000000000000000000000000000000000000000..890d6014d7095e565a9042cc7000ce8a240c6a2e GIT binary patch literal 107433 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 Sl(PXp3&8gXcpL`s+y4ON4nJW4 literal 0 HcmV?d00001 diff --git a/playground/theme-builder/json/test_theme.zip b/playground/theme-builder/json/test_theme.zip deleted file mode 100644 index 429d7418650f2fa4868a1e6130a3db373ec35ba8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48241 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

O!qu*hYrp;Q8=?q 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 + + /** + * Список мета-файлов темы */ @get:InputFile abstract val metaFile: RegularFileProperty /** - * Путь до json-файла с цветами + * Список файлов с токенами цветов */ - @get:InputFile - abstract val colorFile: RegularFileProperty + @get:InputFiles + abstract val colorFiles: ConfigurableFileCollection /** - * Путь до json-файла с типографикой + * Список файлов с токенами типографики */ - @get:InputFile - abstract val typographyFile: RegularFileProperty + @get:InputFiles + abstract val typographyFiles: ConfigurableFileCollection /** - * Путь до json-файла со шрифтами + * Список файлов с токенами шрифтов */ - @get:InputFile - abstract val fontFile: RegularFileProperty + @get:InputFiles + abstract val fontFiles: ConfigurableFileCollection /** - * Путь до json-файла с тенями + * Список файлов с токенами теней */ - @get:InputFile - abstract val shadowFile: RegularFileProperty + @get:InputFiles + abstract val shadowFiles: ConfigurableFileCollection /** - * Путь до json-файла с отступами + * Список файлов с токенами отступов */ - @get:InputFile - abstract val spacingFile: RegularFileProperty + @get:InputFiles + abstract val spacingFiles: ConfigurableFileCollection /** - * Путь до json-файла с градиентами + * Список файлов с токенами градиентов */ - @get:InputFile - abstract val gradientFile: RegularFileProperty + @get:InputFiles + abstract val gradientFiles: ConfigurableFileCollection /** - * Путь до json-файла с формами + * Список файлов с токенами форм */ - @get:InputFile - abstract val shapeFile: RegularFileProperty + @get:InputFiles + abstract val shapeFiles: ConfigurableFileCollection /** * Название пакета для файлов kotlin @@ -218,48 +229,111 @@ abstract class GenerateThemeTask : DefaultTask() { } private val themeGenerator by unsafeLazy { generatorFactory.createThemeGenerator() } - private val colorGenerator by unsafeLazy { - generatorFactory.createColorGenerator(colors, palette) - } - private val gradientGenerator by unsafeLazy { - generatorFactory.createGradientGenerator( - gradients, - palette, - ) - } - private val fontGenerator by unsafeLazy { generatorFactory.createFontGenerator(fonts) } - private val typographyGenerator by unsafeLazy { - generatorFactory.createTypographyGenerator(typography) - } private val dimensGenerator by unsafeLazy { generatorFactory.createDimensGenerator() } - private val shapesGenerator by unsafeLazy { generatorFactory.createShapesGenerator(shapes) } - private val shadowGenerator by unsafeLazy { generatorFactory.createShadowGenerator(shadows, palette) } - private val spacingGenerator by unsafeLazy { generatorFactory.createSpacingGenerator(spacing) } /** * Генерирует файлы с токенами */ @TaskAction + @Suppress("CyclomaticComplexMethod") fun generate() { - collectTokens() - generateAll() - } + checkCollectionSize() + val tenants = themeTenants.get() + val tenantDataMap = tenants.mapIndexed { index, tenant -> + TenantData( + colorFile = colorFiles.files.elementAt(index), + gradientFile = gradientFiles.files.elementAt(index), + fontFile = fontFiles.files.elementAt(index), + typographyFile = typographyFiles.files.elementAt(index), + shapeFile = shapeFiles.files.elementAt(index), + shadowFile = shadowFiles.files.elementAt(index), + spacingFile = spacingFiles.files.elementAt(index), + tenant = tenant, + ) + }.associateBy { Tenant(it.tenant) } + + val metaFileTokens = decodeMeta().tokens + + val defaultTenant = tenantDataMap[Tenant.Default] + ?: throw ThemeBuilderException("Theme must have default tenant") + + val defaultTenantColors = colors(defaultTenant.colorFile) + val defaultTenantGradients = gradients(defaultTenant.gradientFile) + val defaultTenantFonts = fonts(defaultTenant.fontFile) + val defaultTenantTypography = typography(defaultTenant.typographyFile) + val defaultTenantShapes = shapes(defaultTenant.shapeFile) + val defaultTenantShadows = shadows(defaultTenant.shadowFile) + val defaultTenantSpacings = spacing(defaultTenant.spacingFile) + + val allColors = mutableMapOf>() + .apply { this[Tenant.Default] = defaultTenantColors } + val allGradients = mutableMapOf>>() + .apply { this[Tenant.Default] = defaultTenantGradients } + val allFonts = mutableMapOf>() + .apply { this[Tenant.Default] = defaultTenantFonts } + val allTypography = mutableMapOf>() + .apply { this[Tenant.Default] = defaultTenantTypography } + val allShapes = mutableMapOf>() + .apply { this[Tenant.Default] = defaultTenantShapes } + val allShadows = mutableMapOf>>() + .apply { this[Tenant.Default] = defaultTenantShadows } + val allSpacings = mutableMapOf>() + .apply { this[Tenant.Default] = defaultTenantSpacings } + + tenantDataMap + .filter { it.key != Tenant.Default } + .forEach { tenantEntry -> + val tenant = tenantEntry.key + + val tenantColors = colors(tenantEntry.value.colorFile) + val tenantDiffColors = tenantToDefaultDiff(defaultTenantColors, tenantColors) + allColors[tenant] = tenantDiffColors + + val tenantGradients = gradients(tenantEntry.value.gradientFile) + val tenantDiffGradients = tenantToDefaultDiff(defaultTenantGradients, tenantGradients) + allGradients[tenant] = tenantDiffGradients + + val tenantFonts = fonts(tenantEntry.value.fontFile) + val tenantDiffFonts = tenantToDefaultDiff(defaultTenantFonts, tenantFonts) + allFonts[tenant] = tenantDiffFonts + + val tenantTypography = typography(tenantEntry.value.typographyFile) + val tenantDiffTypography = tenantToDefaultDiff(defaultTenantTypography, tenantTypography) + allTypography[tenant] = tenantDiffTypography + + val tenantShapes = shapes(tenantEntry.value.shapeFile) + val tenantDiffShapes = tenantToDefaultDiff(defaultTenantShapes, tenantShapes) + allShapes[tenant] = tenantDiffShapes + + val tenantShadows = shadows(tenantEntry.value.shadowFile) + val tenantDiffShadows = tenantToDefaultDiff(defaultTenantShadows, tenantShadows) + allShadows[tenant] = tenantDiffShadows + + val tenantSpacings = spacing(tenantEntry.value.spacingFile) + val tenantDiffSpacings = tenantToDefaultDiff(defaultTenantSpacings, tenantSpacings) + allSpacings[tenant] = tenantDiffSpacings + } - private fun collectTokens() { - decodeBase().tokens.onEach { - when (it) { - is ColorToken -> colorGenerator.addToken(it) - is GradientToken -> gradientGenerator.addToken(it) - is ShadowToken -> shadowGenerator.addToken(it) - is ShapeToken -> shapesGenerator.addToken(it) - is TypographyToken -> typographyGenerator.addToken(it) - is FontToken -> fontGenerator.addToken(it) - is SpacingToken -> spacingGenerator.addToken(it) + val colorGenerator = generatorFactory.createColorGenerator(allColors, palette) + val gradientGenerator = generatorFactory.createGradientGenerator(allGradients, palette) + val fontGenerator = generatorFactory.createFontGenerator(allFonts) + val typographyGenerator = generatorFactory.createTypographyGenerator(allTypography) + val shapesGenerator = generatorFactory.createShapesGenerator(allShapes) + val shadowGenerator = generatorFactory.createShadowGenerator(allShadows, palette) + val spacingGenerator = generatorFactory.createSpacingGenerator(allSpacings) + + metaFileTokens.onEach { token -> + when (token) { + is ColorToken -> colorGenerator.addToken(token) + is GradientToken -> gradientGenerator.addToken(token) + is ShadowToken -> shadowGenerator.addToken(token) + is ShapeToken -> shapesGenerator.addToken(token) + is TypographyToken -> typographyGenerator.addToken(token) + is FontToken -> fontGenerator.addToken(token) + is SpacingToken -> spacingGenerator.addToken(token) } } - } - private fun generateAll() { colorGenerator.generate().also(themeGenerator::setColorTokenData) gradientGenerator.generate().also(themeGenerator::setGradientTokenData) typographyGenerator.generate().also(themeGenerator::setTypographyTokenData) @@ -272,7 +346,67 @@ abstract class GenerateThemeTask : DefaultTask() { themeGenerator.generate() } - private fun decodeBase(): Theme = + private fun tenantToDefaultDiff( + defaultTokens: Map, + tenantTokens: Map, + ): Map { + return tenantTokens.filter { + defaultTokens[it.key] != it.value + } + } + + @Suppress("ThrowsCount") + private fun checkCollectionSize() { + val tenantSize = themeTenants.get().size + if (colorFiles.files.size != tenantSize) { + throw ThemeBuilderException( + "colorFiles count and themeTenants count must be the same", + ) + } + if (gradientFiles.files.size != tenantSize) { + throw ThemeBuilderException( + "gradientFiles count and themeTenants count must be the same", + ) + } + if (fontFiles.files.size != tenantSize) { + throw ThemeBuilderException( + "fontFiles count and themeTenants count must be the same", + ) + } + if (typographyFiles.files.size != tenantSize) { + throw ThemeBuilderException( + "typographyFiles count and themeTenants count must be the same", + ) + } + if (shapeFiles.files.size != tenantSize) { + throw ThemeBuilderException( + "shapeFiles count and themeTenants count must be the same", + ) + } + if (shadowFiles.files.size != tenantSize) { + throw ThemeBuilderException( + "shadowFiles count and themeTenants count must be the same", + ) + } + if (spacingFiles.files.size != tenantSize) { + throw ThemeBuilderException( + "spacingFiles count and themeTenants count must be the same", + ) + } + } + + private data class TenantData( + val tenant: String, + val colorFile: File, + val gradientFile: File, + val fontFile: File, + val typographyFile: File, + val shapeFile: File, + val shadowFile: File, + val spacingFile: File, + ) + + private fun decodeMeta(): Theme = metaFile.get().asFile.decode(Serializer.meta) .let { theme -> if (ignoreDisabledTokens.get()) { @@ -283,38 +417,38 @@ abstract class GenerateThemeTask : DefaultTask() { } .also { logger.debug("decoded base $it") } - private val colors: Map by unsafeLazy { - colorFile.get().asFile.decode>() + private fun colors(file: File): Map { + return file.decode>() .also { logger.debug("decoded colors $it") } } - private val shapes: Map by unsafeLazy { - shapeFile.get().asFile.decode>() + private fun shapes(file: File): Map { + return file.decode>() .also { logger.debug("decoded shapes $it") } } - private val gradients: Map> by unsafeLazy { - gradientFile.get().asFile.decode>>() + private fun gradients(file: File): Map> { + return file.decode>>() .also { logger.debug("decoded gradients $it") } } - private val shadows: Map> by unsafeLazy { - shadowFile.get().asFile.decode>>() + private fun shadows(file: File): Map> { + return file.decode>>() .also { logger.debug("decoded shadows $it") } } - private val spacing: Map by unsafeLazy { - spacingFile.get().asFile.decode>() + private fun spacing(file: File): Map { + return file.decode>() .also { logger.debug("decoded spacing $it") } } - private val typography: Map by unsafeLazy { - typographyFile.get().asFile.decode>() + private fun typography(file: File): Map { + return file.decode>() .also { logger.debug("decoded typography $it") } } - private val fonts: Map by unsafeLazy { - fontFile.get().asFile.decode>() + private fun fonts(file: File): Map { + return file.decode>() .also { logger.debug("decoded fonts $it") } } 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 index d80c3221e7..c3b235dc96 100644 --- 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 @@ -16,7 +16,8 @@ open class ThemeBuilderExtension { internal var resourcesPrefix: String? = null internal var viewThemeParents: Set = emptySet() internal var viewShapeAppearanceConfig: Set = emptySet() - internal var themeSource: ThemeBuilderSource? = null + private var themeSource: ThemeBuilderSource? = null + private var themeSources: ThemeBuilderSources? = null internal var componentSource: ThemeBuilderSource? = null internal var componentsSource: String? = null internal var paletteUrl: String = DEFAULT_PALETTE_URL @@ -107,6 +108,30 @@ open class ThemeBuilderExtension { } } + /** + * Конфигурирует источники темы + */ + fun themeSources(baseAlias: String = "", sourceBuilder: ThemeSourcesBuilder.() -> Unit) { + val builder = ThemeSourcesBuilder(baseAlias).apply(sourceBuilder) + if (!builder.defaultSourceWasDefined) { + throw ThemeBuilderException("Default source must be defined when use multitenant mode") + } + this.themeSources = ThemeBuilderSources( + baseAlias = baseAlias, + sources = builder.sources, + ) + } + + internal fun getThemeSources(): ThemeBuilderSources { + val themeSrc = themeSource + val themeSrcs = themeSources + return when { + themeSrc != null -> ThemeBuilderSources(baseAlias = themeSrc.themeName, sources = listOf(themeSrc)) + themeSrcs != null && themeSrcs.sources.isNotEmpty() -> themeSrcs + else -> throw ThemeBuilderException("themeSource(s) must be set") + } + } + /** * Устанавливает View фреймворк для генерации темы и токенов * @@ -219,6 +244,58 @@ open class ThemeBuilderExtension { } } +/** + * Класс, описывающий источники теы + */ +class ThemeSourcesBuilder(private val baseAlias: String) { + internal val sources = mutableListOf() + internal var defaultSourceWasDefined = false + + /** + * Устанавливает базовый источник темы с помощью [name] и [version] темы + */ + fun defaultSource( + name: String, + version: String = ThemeSourceBuilder.VERSION_LATEST, + ) { + source(name, version, "") + defaultSourceWasDefined = true + } + + /** + * Устанавливает базовый источник темы с помощью ссылки [url] + */ + fun defaultSourceFromUrl( + name: String, + url: String, + ) { + sourceFromUrl(name, url, "") + defaultSourceWasDefined = true + } + + /** + * Устанавливает источник тенанта [tenant] темы с помощью [name] и [version]. + */ + fun source( + name: String, + version: String = ThemeSourceBuilder.VERSION_LATEST, + tenant: String, + ) { + sources.add(ThemeBuilderSource.withNameAndVersion(name, version, baseAlias, tenant)) + } + + /** + * Устанавливает источник тенанта [tenant] темы с помощью ссылки [url]. + */ + fun sourceFromUrl( + name: String, + url: String, + tenant: String = "", + ) { + sources.add(ThemeBuilderSource.withUrl(url, name, tenant)) + } +} + /** * Конфигуратор источника темы */ 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 eb99f83947..0d0f2acbd5 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 @@ -18,6 +18,7 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.register +import java.io.File /** * Плагин для генерации тем и токенов ДС @@ -25,7 +26,6 @@ import org.gradle.kotlin.dsl.register */ class ThemeBuilderPlugin : Plugin { override fun apply(project: Project) { - val themeZip = project.layout.buildDirectory.file("$THEME_PATH/theme.zip") val componentsZip = project.layout.buildDirectory.file("$COMPONENTS_PATH/components.zip") val paletteJson = project.layout.buildDirectory.file("$THEME_PATH/$PALETTE_JSON_NAME") val extension = project.themeBuilderExt() @@ -33,11 +33,34 @@ class ThemeBuilderPlugin : Plugin { project.afterEvaluate { project.registerClean(extension) - val unzipThemeTask = registerFetchAndUnzipTheme(extension, themeZip, paletteJson) + val themeSources = extension.getThemeSources() + + val fetchPaletteTask = registerPaletteFetcher( + taskName = "fetchPalette", + paletteUrl = extension.paletteUrl, + paletteOutput = paletteJson, + ) + + val unzipTasks = mutableListOf>() + themeSources.sources.forEach { source -> + val tenantId = source.tenant + val tenantIdForPath = if (tenantId.isEmpty()) "default" else tenantId.lowercase() + val themeZip = project.layout.buildDirectory.file("$THEME_PATH/$tenantIdForPath/theme.zip") + + val unzipThemeTask = registerFetchAndUnzipTheme( + source = source, + themeOutputZip = themeZip, + themeId = tenantId, + fetchPaletteTask = fetchPaletteTask, + ) + unzipTasks.add(unzipThemeTask) + } + registerThemeBuilder( extension = extension, - unzipThemeTask = unzipThemeTask, + unzipThemeTasks = unzipTasks, dependOnPreBuild = extension.autoGenerate, + themeSources = themeSources, ) val fetchComponentsTask = registerFetchAndUnzipComponents(extension, componentsZip) @@ -78,27 +101,23 @@ class ThemeBuilderPlugin : Plugin { } private fun Project.registerFetchAndUnzipTheme( - extension: ThemeBuilderExtension, themeOutputZip: Provider, - paletteOutputJson: Provider, + source: ThemeBuilderSource, + themeId: String, + fetchPaletteTask: TaskProvider, ): TaskProvider { - val source = getThemeSource(extension) + val themeIdPathToken = if (themeId.isEmpty()) "default" else themeId.lowercase() - val fetchPaletteTask = registerPaletteFetcher( - taskName = "fetchPalette", - paletteUrl = extension.paletteUrl, - paletteOutput = paletteOutputJson, - ) val fetchThemeTask = registerFileFetcher( - taskName = "fetchTheme", + taskName = "fetchTheme$themeId", url = getThemeUrl(source), output = themeOutputZip, dependsOnTask = fetchPaletteTask, ) val unzipTask = registerUnzip( - taskName = "unpackThemeFiles", + taskName = "unpackThemeFiles$themeId", zipFile = themeOutputZip, - outputPath = THEME_PATH, + outputPath = "$THEME_PATH$themeIdPathToken", dependsOnTask = fetchThemeTask, ) return unzipTask @@ -129,22 +148,48 @@ class ThemeBuilderPlugin : Plugin { private fun Project.registerThemeBuilder( extension: ThemeBuilderExtension, - unzipThemeTask: Any, + unzipThemeTasks: List, dependOnPreBuild: Boolean, + themeSources: ThemeBuilderSources, ) { + val tenants = mutableListOf() + val colorFiles = mutableListOf() + val gradientFiles = mutableListOf() + val typographyFiles = mutableListOf() + val fontFiles = mutableListOf() + val shapeFiles = mutableListOf() + val shadowFiles = mutableListOf() + val spacingFiles = mutableListOf() + + themeSources.sources.forEach { + val themeId = it.tenant.also(tenants::add) + val themeIdPathToken = if (themeId.isEmpty()) "default" else themeId.lowercase() + colorFiles.add(file(getValueFile(themeIdPathToken, TokenValueFile.COLORS))) + gradientFiles.add(file(getValueFile(themeIdPathToken, TokenValueFile.GRADIENTS))) + typographyFiles.add(file(getValueFile(themeIdPathToken, TokenValueFile.TYPOGRAPHY))) + fontFiles.add(file(getValueFile(themeIdPathToken, TokenValueFile.FONTS))) + shapeFiles.add(file(getValueFile(themeIdPathToken, TokenValueFile.SHAPES))) + shadowFiles.add(file(getValueFile(themeIdPathToken, TokenValueFile.SHADOWS))) + spacingFiles.add(file(getValueFile(themeIdPathToken, TokenValueFile.SPACING))) + } + + logger.warn("themeSources=$themeSources") + val generateThemeTask = registerThemeGenerator( extension = extension, paletteFileProvider = getPaletteFile(), - baseFileProvider = getMetaFile(), - colorFileProvider = getValueFile(TokenValueFile.COLORS), - typographyFileProvider = getValueFile(TokenValueFile.TYPOGRAPHY), - fontFileProvider = getValueFile(TokenValueFile.FONTS), - shadowFileProvider = getValueFile(TokenValueFile.SHADOWS), - spacingFileProvider = getValueFile(TokenValueFile.SPACING), - gradientFileProvider = getValueFile(TokenValueFile.GRADIENTS), - shapeFileProvider = getValueFile(TokenValueFile.SHAPES), - unzipTask = unzipThemeTask, + metaFileProvider = getMetaFile(), + tenants = tenants, + colorFiles = colorFiles, + typographyFiles = typographyFiles, + fontFiles = fontFiles, + shadowFiles = shadowFiles, + spacingFiles = spacingFiles, + gradientFiles = gradientFiles, + shapeFiles = shapeFiles, + unzipTasks = unzipThemeTasks, + themeName = themeSources.baseAlias, ) if (dependOnPreBuild) { tasks.named("preBuild").dependsOn(generateThemeTask) @@ -163,9 +208,6 @@ class ThemeBuilderPlugin : Plugin { } } - private fun getThemeSource(extension: ThemeBuilderExtension): ThemeBuilderSource = - extension.themeSource ?: throw GradleException("themeSource must be set") - private fun getThemeUrl(source: ThemeBuilderSource): String { return getSourceUrl(source, BASE_THEME_URL) } @@ -200,11 +242,11 @@ class ThemeBuilderPlugin : Plugin { } private fun Project.getMetaFile(): Provider { - return layout.buildDirectory.file("$THEME_PATH/$META_JSON_NAME") + return layout.buildDirectory.file("${THEME_PATH}default/$META_JSON_NAME") } - private fun Project.getValueFile(fileType: TokenValueFile): Provider { - return layout.buildDirectory.file("$THEME_PATH/android/${fileType.fileName}") + private fun Project.getValueFile(themeIdPathToken: String, fileType: TokenValueFile): String { + return "build/$THEME_PATH$themeIdPathToken/android/${fileType.fileName}" } private fun Project.getComponentConfigFile(fileName: String): Provider { @@ -257,31 +299,35 @@ class ThemeBuilderPlugin : Plugin { } } + @Suppress("SpreadOperator") private fun Project.registerThemeGenerator( extension: ThemeBuilderExtension, paletteFileProvider: Provider, - baseFileProvider: Provider, - colorFileProvider: Provider, - typographyFileProvider: Provider, - fontFileProvider: Provider, - shadowFileProvider: Provider, - spacingFileProvider: Provider, - gradientFileProvider: Provider, - shapeFileProvider: Provider, - unzipTask: Any, + metaFileProvider: Provider, + unzipTasks: List, + tenants: List, + colorFiles: List, + typographyFiles: List, + fontFiles: List, + shadowFiles: List, + spacingFiles: List, + gradientFiles: List, + shapeFiles: List, + themeName: String, ): TaskProvider { return project.tasks.register("generateTheme") { group = TASK_GROUP paletteFile.set(paletteFileProvider) - themeName.set(getThemeSource(extension).themeName) - metaFile.set(baseFileProvider) - colorFile.set(colorFileProvider) - typographyFile.set(typographyFileProvider) - fontFile.set(fontFileProvider) - shadowFile.set(shadowFileProvider) - spacingFile.set(spacingFileProvider) - gradientFile.set(gradientFileProvider) - shapeFile.set(shapeFileProvider) + metaFile.set(metaFileProvider) + this.themeTenants.set(tenants) + this.themeName.set(themeName) + this.colorFiles.setFrom(colorFiles) + this.typographyFiles.setFrom(typographyFiles) + this.fontFiles.setFrom(fontFiles) + this.shadowFiles.setFrom(shadowFiles) + this.spacingFiles.setFrom(spacingFiles) + this.gradientFiles.setFrom(gradientFiles) + this.shapeFiles.setFrom(shapeFiles) packageName.set(extension.ktPackage ?: DEFAULT_KT_PACKAGE) target.set(extension.target) @@ -298,7 +344,7 @@ class ThemeBuilderPlugin : Plugin { dimensionsConfig.set(extension.dimensionsConfig) defaultThemeTypography.set(extension.defaultThemeTypography) ignoreDisabledTokens.set(extension.ignoreDisabledTokens) - dependsOn(unzipTask) + dependsOn(*unzipTasks.toTypedArray()) } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderSource.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderSource.kt index 638c09261b..bc749d64ab 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderSource.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/ThemeBuilderSource.kt @@ -1,9 +1,14 @@ package com.sdds.plugin.themebuilder +internal data class ThemeBuilderSources( + val baseAlias: String = "", + val sources: List, +) + /** * Способ получения темы. */ -internal sealed class ThemeBuilderSource(val themeName: String) { +internal sealed class ThemeBuilderSource(val themeName: String, val tenant: String) { /** * Предпочтительный способ получения темы с помощью названия [remoteName] и версии [version] темы. @@ -12,12 +17,17 @@ internal sealed class ThemeBuilderSource(val themeName: String) { val remoteName: String, val version: String, val alias: String = remoteName, - ) : ThemeBuilderSource(alias) + val suffix: String, + ) : ThemeBuilderSource(alias, suffix) /** * Способ получения темы с помощью ссылки [url]. */ - data class Url(val url: String, val name: String) : ThemeBuilderSource(name) + data class Url( + val url: String, + val name: String, + val suffix: String, + ) : ThemeBuilderSource(name, suffix) companion object { @@ -26,12 +36,21 @@ internal sealed class ThemeBuilderSource(val themeName: String) { /** * Позволяет указать источник получения темы с помощью [name] и [version] */ - fun withNameAndVersion(name: String, version: String, alias: String = name): ThemeBuilderSource = - NameAndVersion(name, version, alias) + fun withNameAndVersion( + name: String, + version: String, + alias: String = name, + suffix: String = "", + ): ThemeBuilderSource = + NameAndVersion(name, version, alias, suffix) /** * Позволяет указать источник получения темы с помощью [url] */ - fun withUrl(url: String, name: String = DEFAULT_THEME_NAME): ThemeBuilderSource = Url(url, name) + fun withUrl( + url: String, + name: String = DEFAULT_THEME_NAME, + suffix: String = "", + ): ThemeBuilderSource = Url(url, name, suffix) } } 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 80c5ea676c..818646bb3d 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 @@ -132,6 +132,38 @@ internal class KtFileBuilder( } .also(rootTypeBuilders::add) + /** + * Создает val свойство в корне файла. + * + * @param name имя свойства + * @param typeName тип свойства + * @param initializer инициализатор свойства + * @param lazy инициализация через lazy делегат + */ + fun appendRootVal( + name: String, + typeName: TypeName, + initializer: String? = null, + modifiers: List? = null, + isMutable: Boolean = false, + setter: Setter? = null, + getter: Getter? = null, + receiver: TypeName? = null, + lazy: Boolean = false, + ) { + appendPropertyInternal( + name = name, + typeName = typeName, + initializer = initializer, + isMutable = isMutable, + modifiers = modifiers?.toKModifiers(), + setter = setter, + getter = getter, + receiver = receiver, + lazy = lazy, + ).also(rootPropBuilders::add) + } + /** * Создает val свойство в корне файла. * 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 a83cd4aa05..7f57fde7dc 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 @@ -20,6 +20,7 @@ import com.sdds.plugin.themebuilder.internal.generator.ShapeTokenGenerator import com.sdds.plugin.themebuilder.internal.generator.SpacingTokenGenerator import com.sdds.plugin.themebuilder.internal.generator.TypographyTokenGenerator import com.sdds.plugin.themebuilder.internal.generator.theme.ThemeGenerator +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.FontTokenValue import com.sdds.plugin.themebuilder.internal.token.GradientTokenValue import com.sdds.plugin.themebuilder.internal.token.ShadowTokenValue @@ -273,7 +274,7 @@ internal class GeneratorFactory( * @return [ColorTokenGenerator] генератор токенов цвета */ fun createColorGenerator( - colors: Map, + colors: Map>, palette: Map>, ): ColorTokenGenerator { return ColorTokenGenerator( @@ -292,7 +293,7 @@ internal class GeneratorFactory( * Создает генератор градиентов [GradientTokenGenerator] */ fun createGradientGenerator( - gradients: Map>, + gradients: Map>>, palette: Map>, ): GradientTokenGenerator { return GradientTokenGenerator( @@ -310,7 +311,7 @@ internal class GeneratorFactory( /** * Создает генератор шрифтов [FontTokenGenerator] */ - fun createFontGenerator(fonts: Map): FontTokenGenerator { + fun createFontGenerator(fonts: Map>): FontTokenGenerator { return FontTokenGenerator( OutputLocation.Directory(outputDir), outputResDir, @@ -330,7 +331,7 @@ internal class GeneratorFactory( * Создает генератор типографии [TypographyTokenGenerator] */ fun createTypographyGenerator( - typography: Map, + typography: Map>, ): TypographyTokenGenerator { return TypographyTokenGenerator( outputLocation = OutputLocation.Directory(outputDir), @@ -362,7 +363,7 @@ internal class GeneratorFactory( /** * Создает генератор форм [ShapeTokenGenerator] */ - fun createShapesGenerator(shapes: Map): ShapeTokenGenerator { + fun createShapesGenerator(shapes: Map>): ShapeTokenGenerator { return ShapeTokenGenerator( outputLocation = OutputLocation.Directory(outputDir), outputResDir = outputResDir, @@ -382,7 +383,7 @@ internal class GeneratorFactory( * Создает генератор теней [ShadowTokenGenerator] */ fun createShadowGenerator( - shadows: Map>, + shadows: Map>>, palette: Map>, ): ShadowTokenGenerator { return ShadowTokenGenerator( @@ -403,7 +404,7 @@ internal class GeneratorFactory( /** * Создает генератор форм [SpacingTokenGenerator] */ - fun createSpacingGenerator(spacings: Map): SpacingTokenGenerator { + fun createSpacingGenerator(spacings: Map>): SpacingTokenGenerator { return SpacingTokenGenerator( outputLocation = OutputLocation.Directory(outputDir), outputResDir = outputResDir, diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorTokenGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorTokenGenerator.kt index 10062320be..d51ccb07e6 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorTokenGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorTokenGenerator.kt @@ -8,6 +8,7 @@ import com.sdds.plugin.themebuilder.internal.exceptions.ThemeBuilderException import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.data.ColorTokenResult +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.ColorToken import com.sdds.plugin.themebuilder.internal.token.colorAttrName import com.sdds.plugin.themebuilder.internal.token.isDark @@ -37,31 +38,47 @@ internal class ColorTokenGenerator( target: ThemeBuilderTarget, private val xmlBuilderFactory: XmlResourcesDocumentBuilderFactory, private val ktFileBuilderFactory: KtFileBuilderFactory, - private val colorTokenValues: Map, + private val colorTokenValues: Map>, private val resourceReferenceProvider: ResourceReferenceProvider, private val palette: Map>, ) : TokenGenerator(target) { private val xmlDocumentBuilder by unsafeLazy { xmlBuilderFactory.create(DEFAULT_ROOT_ATTRIBUTES) } private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create("ColorTokens") } - private val lightBuilder by unsafeLazy { - ktFileBuilder.rootObject(LIGHT_COLOR_TOKENS_NAME, LIGHT_COLOR_TOKENS_DESC) + private val lightBuilders by unsafeLazy { + mutableMapOf( + Tenant.Default to ktFileBuilder.rootObject( + LIGHT_COLOR_TOKENS_NAME, + LIGHT_COLOR_TOKENS_DESC, + ), + ) } - private val darkBuilder by unsafeLazy { - ktFileBuilder.rootObject(DARK_COLOR_TOKENS_NAME, DARK_COLOR_TOKENS_DESC) + private val darkBuilders by unsafeLazy { + mutableMapOf( + Tenant.Default to ktFileBuilder.rootObject( + DARK_COLOR_TOKENS_NAME, + DARK_COLOR_TOKENS_DESC, + ), + ) } - private val composeLightTokenDataCollector = mutableMapOf() - private val composeDarkTokenDataCollector = mutableMapOf() - private val viewLightTokenDataCollector = mutableMapOf() - private val viewDarkTokenDataCollector = mutableMapOf() + private val composeLightTokenDataCollectors = + mutableMapOf>() + private val composeDarkTokenDataCollectors = + mutableMapOf>() + private val viewLightTokenDataCollector = + mutableMapOf() + private val viewDarkTokenDataCollector = + mutableMapOf() override fun collectResult() = ColorTokenResult( tokens = tokens, - composeTokens = ColorTokenResult.TokenData( - light = composeLightTokenDataCollector, - dark = composeDarkTokenDataCollector, - ), + composeTokens = colorTokenValues.mapValues { + ColorTokenResult.TokenData( + light = composeLightTokenDataCollectors[it.key] ?: emptyMap(), + dark = composeDarkTokenDataCollectors[it.key] ?: emptyMap(), + ) + }, viewTokens = ColorTokenResult.TokenData( light = viewLightTokenDataCollector, dark = viewDarkTokenDataCollector, @@ -87,7 +104,7 @@ internal class ColorTokenGenerator( */ @Suppress("ReturnCount") override fun addViewSystemToken(token: ColorToken): Boolean { - val tokenValue = colorTokenValues[token.name] + val tokenValue = colorTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for color token ${token.name}. " + "It should be in android_color.json.", @@ -112,30 +129,61 @@ internal class ColorTokenGenerator( * @see TokenGenerator.addComposeToken */ @Suppress("ReturnCount") - override fun addComposeToken(token: ColorToken): Boolean = with(ktFileBuilder) { - val tokenValue = colorTokenValues[token.name] + override fun addComposeToken(token: ColorToken): Boolean { + colorTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for color token ${token.name}. " + "It should be in android_color.json.", ) - val root = if (token.isDark) { - darkBuilder - } else { - lightBuilder + colorTokenValues.forEach { (tenant, values) -> + val tokenValue = values[token.name] + if (tokenValue != null) { + val root = if (token.isDark) { + darkBuilders.getOrPut(tenant) { + ktFileBuilder.rootObject( + "${DARK_COLOR_TOKENS_NAME}${tenant.name}", + DARK_COLOR_TOKENS_DESC, + ) + } + } else { + lightBuilders.getOrPut(tenant) { + ktFileBuilder.rootObject( + "${LIGHT_COLOR_TOKENS_NAME}${tenant.name}", + LIGHT_COLOR_TOKENS_DESC, + ) + } + } + val resolvedColor = resolveColor( + tokenValue = tokenValue, + tokenName = token.name, + palette = palette, + hexFormat = HexFormat.INT_HEX, + ) + val value = "Color($resolvedColor)" + with(ktFileBuilder) { + root.appendProperty( + token.ktName, + KtFileBuilder.TypeColor, + value, + token.description, + ) + } + token.addComposeTokenData( + token.ktName.decapitalize(Locale.getDefault()), + token.ktName, + token.description, + tenant, + ) + } } - val resolvedColor = resolveColor( - tokenValue = tokenValue, - tokenName = token.name, - palette = palette, - hexFormat = HexFormat.INT_HEX, - ) - val value = "Color($resolvedColor)" - root.appendProperty(token.ktName, KtFileBuilder.TypeColor, value, token.description) - token.addComposeTokenData(token.ktName.decapitalize(Locale.getDefault()), token.ktName, token.description) return true } - private fun ColorToken.addViewTokenData(attrName: String, tokenRef: String, description: String) { + private fun ColorToken.addViewTokenData( + attrName: String, + tokenRef: String, + description: String, + ) { val info = ColorTokenResult.TokenData.ColorInfo(tokenRef, description) if (this.isLight) { viewLightTokenDataCollector[attrName] = info @@ -147,15 +195,26 @@ internal class ColorTokenGenerator( } } - private fun ColorToken.addComposeTokenData(attrName: String, tokenRef: String, description: String) { + private fun ColorToken.addComposeTokenData( + attrName: String, + tokenRef: String, + description: String, + tenant: Tenant, + ) { val info = ColorTokenResult.TokenData.ColorInfo(tokenRef, description) + val lightCollector = composeLightTokenDataCollectors.getOrPut(tenant) { + mutableMapOf() + } + val darkCollector = composeDarkTokenDataCollectors.getOrPut(tenant) { + mutableMapOf() + } if (this.isLight) { - composeLightTokenDataCollector[attrName] = info + lightCollector[attrName] = info } else if (this.isDark) { - composeDarkTokenDataCollector[attrName] = info + darkCollector[attrName] = info } else { - composeLightTokenDataCollector[attrName] = info - composeDarkTokenDataCollector[attrName] = info + lightCollector[attrName] = info + darkCollector[attrName] = info } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/FontTokenGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/FontTokenGenerator.kt index 7c4004ad5d..ead04221f9 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/FontTokenGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/FontTokenGenerator.kt @@ -10,6 +10,7 @@ import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.factory.XmlFontFamilyDocumentBuilderFactory import com.sdds.plugin.themebuilder.internal.fonts.FontData import com.sdds.plugin.themebuilder.internal.fonts.FontsAggregator +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.FontToken import com.sdds.plugin.themebuilder.internal.token.FontTokenValue import com.sdds.plugin.themebuilder.internal.utils.FileProvider.fontDir @@ -44,7 +45,7 @@ internal class FontTokenGenerator( private val ktFileBuilderFactory: KtFileBuilderFactory, namespace: String, private val resPrefix: String, - private val fontTokenValues: Map, + private val fontTokenValues: Map>, private val fontsAggregator: FontsAggregator, private val dimensionsConfig: DimensionsConfig, ) : TokenGenerator(target) { @@ -59,9 +60,8 @@ internal class FontTokenGenerator( } } } - private val ktFileRootObjectBuilder by unsafeLazy { - ktFileBuilder.rootObject(FONT_TOKENS_NAME, FONT_TOKENS_DESC) - } + private val rootObjectBuilders = + mutableMapOf(Tenant.Default to ktFileBuilder.rootObject(FONT_TOKENS_NAME, FONT_TOKENS_DESC)) private val fontDownloader by unsafeLazy { fontDownloaderFactory.create() } override fun collectResult() = "" @@ -74,7 +74,7 @@ internal class FontTokenGenerator( } override fun addViewSystemToken(token: FontToken): Boolean { - val tokenValue = fontTokenValues[token.name] + val tokenValue = fontTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for font token ${token.name}. " + "It should be in android_fontFamily.json.", @@ -97,17 +97,27 @@ internal class FontTokenGenerator( } override fun addComposeToken(token: FontToken): Boolean { - val tokenValue = fontTokenValues[token.name] + fontTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for font token ${token.name}. " + "It should be in android_fontFamily.json.", ) - FontTokenValidator.validate(tokenValue, token.name) - ktFileRootObjectBuilder.addFontFamilyToken( - name = token.ktName, - description = token.description, - tokenValue = tokenValue, - ) + fontTokenValues.forEach { (tenant, values) -> + val tokenValue = values[token.name] + if (tokenValue != null) { + FontTokenValidator.validate(tokenValue, token.name) + val objectName = "${FONT_TOKENS_NAME}${tenant.name}" + val objectBuilder = rootObjectBuilders.getOrPut(tenant) { + ktFileBuilder.rootObject(objectName, FONT_TOKENS_DESC) + } + objectBuilder.addFontFamilyToken( + name = token.ktName, + description = token.description, + tokenValue = tokenValue, + ) + } + } + return true } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientTokenGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientTokenGenerator.kt index bf3aa42406..bfe2489a39 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientTokenGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientTokenGenerator.kt @@ -11,6 +11,7 @@ import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilder import com.sdds.plugin.themebuilder.internal.generator.data.GradientTokenResult import com.sdds.plugin.themebuilder.internal.generator.data.GradientTokenResult.ComposeTokenData import com.sdds.plugin.themebuilder.internal.generator.data.GradientTokenResult.ViewTokenData +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.BackgroundGradientTokenValue import com.sdds.plugin.themebuilder.internal.token.GradientPoint import com.sdds.plugin.themebuilder.internal.token.GradientToken @@ -49,26 +50,37 @@ internal class GradientTokenGenerator( target: ThemeBuilderTarget, private val xmlBuilderFactory: XmlResourcesDocumentBuilderFactory, private val ktFileBuilderFactory: KtFileBuilderFactory, - private val gradientTokenValues: Map>, + private val gradientTokenValues: Map>>, private val palette: Map>, private val resourceReferenceProvider: ResourceReferenceProvider, ) : TokenGenerator(target) { private val composeKtFileBuilder by unsafeLazy { ktFileBuilderFactory.create("GradientTokens") } - private val composeLightBuilder by unsafeLazy { - composeKtFileBuilder.rootObject(LIGHT_GRADIENT_TOKENS_NAME, LIGHT_GRADIENT_TOKENS_DESC) + private val composeLightBuilders by unsafeLazy { + mutableMapOf( + Tenant.Default to composeKtFileBuilder.rootObject( + LIGHT_GRADIENT_TOKENS_NAME, + LIGHT_GRADIENT_TOKENS_DESC, + ), + ) } - private val composeDarkBuilder by unsafeLazy { - composeKtFileBuilder.rootObject(DARK_GRADIENT_TOKENS_NAME, DARK_GRADIENT_TOKENS_DESC) + private val composeDarkBuilders by unsafeLazy { + mutableMapOf( + Tenant.Default to composeKtFileBuilder.rootObject( + DARK_GRADIENT_TOKENS_NAME, + DARK_GRADIENT_TOKENS_DESC, + ), + ) } + private val xmlParametersDocumentBuilder by unsafeLazy { xmlBuilderFactory.create(DEFAULT_ROOT_ATTRIBUTES) } - private val composeKtLightTokenDataCollector = - mutableMapOf>() - private val composeKtDarkTokenDataCollector = - mutableMapOf>() + private val composeKtLightTokenDataCollectors = + mutableMapOf>>() + private val composeKtDarkTokenDataCollectors = + mutableMapOf>>() private val viewXmlDrawableLightTokenDataCollector = mutableMapOf() private val viewXmlDrawableDarkTokenDataCollector = @@ -76,10 +88,12 @@ internal class GradientTokenGenerator( override fun collectResult() = GradientTokenResult( tokens = tokens, - composeTokens = ComposeTokenData( - light = composeKtLightTokenDataCollector, - dark = composeKtDarkTokenDataCollector, - ), + composeTokens = gradientTokenValues.mapValues { + ComposeTokenData( + light = composeKtLightTokenDataCollectors[it.key] ?: emptyMap(), + dark = composeKtDarkTokenDataCollectors[it.key] ?: emptyMap(), + ) + }, viewXmlTokens = ViewTokenData( light = viewXmlDrawableLightTokenDataCollector, dark = viewXmlDrawableDarkTokenDataCollector, @@ -106,7 +120,7 @@ internal class GradientTokenGenerator( * @see TokenGenerator.addViewSystemToken */ override fun addViewSystemToken(token: GradientToken): Boolean { - val tokenValue = gradientTokenValues[token.name] + val tokenValue = gradientTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for gradient token ${token.name}. " + "It should be in android_gradient.json.", @@ -118,12 +132,18 @@ internal class GradientTokenGenerator( * @see TokenGenerator.addComposeToken */ override fun addComposeToken(token: GradientToken): Boolean { - val tokenValues = gradientTokenValues[token.name] + gradientTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for gradient token ${token.name}. " + "It should be in android_gradient.json.", ) - return addKtToken(token, tokenValues) + gradientTokenValues.forEach { (tenant, values) -> + val tokenValues = values[token.name] + if (tokenValues != null) { + addKtToken(token, tokenValues, tenant) + } + } + return true } private fun addXmlViewToken( @@ -219,28 +239,29 @@ internal class GradientTokenGenerator( private fun addKtToken( token: GradientToken, tokenValues: List, - ): Boolean { - val darkLightObjectBuilder = darkLightObjectBuilder(token) + tenant: Tenant, + ) { + val objectBuilder = objectBuilder(token, tenant) val baseTokenName = token.ktName - with(darkLightObjectBuilder) { + with(objectBuilder) { appendObject(baseTokenName, token.description) { if (tokenValues.size > 1) { - appendGradientLayers(tokenValues, token) + appendGradientLayers(tokenValues, token, tenant) } else { - appendGradientLayer(tokenValues.first(), token, null) + appendGradientLayer(tokenValues.first(), token, null, tenant) } } } - return true } private fun TypeSpec.Builder.appendGradientLayers( tokenValues: List, token: GradientToken, + tenant: Tenant, ) { tokenValues.mapIndexed { index, gradient -> appendObject("Layer$index", "Cлой $index") { - appendGradientLayer(gradient, token, index) + appendGradientLayer(gradient, token, index, tenant) } } } @@ -249,36 +270,47 @@ internal class GradientTokenGenerator( tokenValue: GradientTokenValue, token: GradientToken, layerIndex: Int?, + tenant: Tenant, ) { val layerRef = layerIndex?.let { "Layer$it." }.orEmpty() + val objectName = if (token.isDark) { + "${DARK_GRADIENT_TOKENS_NAME}${tenant.name}" + } else { + "${LIGHT_GRADIENT_TOKENS_NAME}${tenant.name}" + } val tokenData = when (tokenValue) { is LinearGradientTokenValue -> appendLinearGradient( token = token, tokenValue = tokenValue, layerRef = layerRef, + objectName = objectName, ) is RadialGradientTokenValue -> appendRadialGradient( token = token, tokenValue = tokenValue, layerRef = layerRef, + objectName = objectName, ) is SweepGradientTokenValue -> appendSweepGradient( token = token, tokenValue = tokenValue, layerRef = layerRef, + objectName = objectName, ) is BackgroundGradientTokenValue -> appendSingleColorBackground( token = token, tokenValue = tokenValue, layerRef = layerRef, + objectName = objectName, ) } token.addKtTokenData( token.attrName(), tokenData, + tenant, ) } @@ -483,11 +515,18 @@ internal class GradientTokenGenerator( } } - private fun darkLightObjectBuilder(token: GradientToken): TypeSpec.Builder { + private fun objectBuilder(token: GradientToken, tenant: Tenant): TypeSpec.Builder { return if (token.isDark) { - composeDarkBuilder + composeDarkBuilders.getOrPut(tenant) { + composeKtFileBuilder.rootObject("${DARK_GRADIENT_TOKENS_NAME}${tenant.name}", DARK_GRADIENT_TOKENS_DESC) + } } else { - composeLightBuilder + composeLightBuilders.getOrPut(tenant) { + composeKtFileBuilder.rootObject( + "${LIGHT_GRADIENT_TOKENS_NAME}${tenant.name}", + LIGHT_GRADIENT_TOKENS_DESC, + ) + } } } @@ -495,6 +534,7 @@ internal class GradientTokenGenerator( token: GradientToken, tokenValue: LinearGradientTokenValue, layerRef: String, + objectName: String, ): ComposeTokenData.Gradient { val baseTokenName = token.ktName LinearGradientTokenValidator.validate(tokenValue, baseTokenName) @@ -527,6 +567,7 @@ internal class GradientTokenGenerator( tokenRefs = tokenRefs, gradientType = ComposeTokenData.GradientType.LINEAR, description = token.description, + tokenObjectName = objectName, ) } @@ -534,6 +575,7 @@ internal class GradientTokenGenerator( token: GradientToken, tokenValue: SweepGradientTokenValue, layerRef: String, + objectName: String, ): ComposeTokenData.Gradient { val baseTokenName = token.ktName SweepGradientTokenValidator.validate(tokenValue, baseTokenName) @@ -563,6 +605,7 @@ internal class GradientTokenGenerator( ), gradientType = ComposeTokenData.GradientType.SWEEP, description = token.description, + tokenObjectName = objectName, ) } @@ -570,6 +613,7 @@ internal class GradientTokenGenerator( token: GradientToken, tokenValue: RadialGradientTokenValue, layerRef: String, + objectName: String, ): ComposeTokenData.Gradient { val baseTokenName = token.ktName RadialGradientTokenValidator.validate(tokenValue, baseTokenName) @@ -609,6 +653,8 @@ internal class GradientTokenGenerator( ), gradientType = ComposeTokenData.GradientType.RADIAL, description = token.description, + tokenObjectName = objectName, + ) } @@ -616,6 +662,7 @@ internal class GradientTokenGenerator( token: GradientToken, tokenValue: BackgroundGradientTokenValue, layerRef: String, + objectName: String, ): ComposeTokenData.Gradient { val baseTokenName = token.ktName val resolvedColor = @@ -634,6 +681,7 @@ internal class GradientTokenGenerator( ), gradientType = ComposeTokenData.GradientType.BACKGROUND, description = token.description, + tokenObjectName = objectName, ) } @@ -689,16 +737,21 @@ internal class GradientTokenGenerator( private fun GradientToken.addKtTokenData( attrName: String, params: ComposeTokenData.Gradient, + tenant: Tenant, ) { - val lightDataCollector = composeKtLightTokenDataCollector - val darkDataCollector = composeKtDarkTokenDataCollector + val lightCollector = composeKtLightTokenDataCollectors.getOrPut(tenant) { + mutableMapOf() + } + val darkCollector = composeKtDarkTokenDataCollectors.getOrPut(tenant) { + mutableMapOf() + } if (this.isLight) { - lightDataCollector.getOrPut(attrName) { mutableListOf() }.add(params) + lightCollector.getOrPut(attrName) { mutableListOf() }.add(params) } else if (this.isDark) { - darkDataCollector.getOrPut(attrName) { mutableListOf() }.add(params) + darkCollector.getOrPut(attrName) { mutableListOf() }.add(params) } else { - lightDataCollector.getOrPut(attrName) { mutableListOf() }.add(params) - darkDataCollector.getOrPut(attrName) { mutableListOf() }.add(params) + lightCollector.getOrPut(attrName) { mutableListOf() }.add(params) + darkCollector.getOrPut(attrName) { mutableListOf() }.add(params) } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowTokenGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowTokenGenerator.kt index 5908a98319..02bb87cc35 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowTokenGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowTokenGenerator.kt @@ -13,6 +13,7 @@ import com.sdds.plugin.themebuilder.internal.exceptions.ThemeBuilderException import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.data.ShadowTokenResult +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.ShadowToken import com.sdds.plugin.themebuilder.internal.token.ShadowTokenValue import com.sdds.plugin.themebuilder.internal.utils.ColorResolver.HexFormat @@ -43,7 +44,7 @@ internal class ShadowTokenGenerator( target: ThemeBuilderTarget, private val xmlBuilderFactory: XmlResourcesDocumentBuilderFactory, private val ktFileBuilderFactory: KtFileBuilderFactory, - private val shadowTokenValues: Map>, + private val shadowTokenValues: Map>>, private val resourceReferenceProvider: ResourceReferenceProvider, private val dimensionsConfig: DimensionsConfig, private val dimensAggregator: DimensAggregator, @@ -53,17 +54,18 @@ internal class ShadowTokenGenerator( private val xmlDocumentBuilder by unsafeLazy { xmlBuilderFactory.create(DEFAULT_ROOT_ATTRIBUTES) } private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create(SHADOW_TOKENS_NAME) } - private val rootShadows by unsafeLazy { ktFileBuilder.rootObject(SHADOW_TOKENS_NAME, SHADOW_TOKENS_DESC) } + private val rootShadows = + mutableMapOf(Tenant.Default to ktFileBuilder.rootObject(SHADOW_TOKENS_NAME, SHADOW_TOKENS_DESC)) private val rFileImport = ClassName(namespace, "R") - private val composeTokenDataCollector = mutableListOf() + private val composeTokenDataCollectors = mutableMapOf>() private val viewTokenDataCollector = mutableListOf() /** * @see TokenGenerator.collectResult */ override fun collectResult() = ShadowTokenResult( - composeTokenDataCollector, + composeTokenDataCollectors, viewTokenDataCollector, ) @@ -93,7 +95,7 @@ internal class ShadowTokenGenerator( * @see TokenGenerator.addViewSystemToken */ override fun addViewSystemToken(token: ShadowToken): Boolean = with(xmlDocumentBuilder) { - val tokenValues = shadowTokenValues[token.name] + val tokenValues = shadowTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for shadow token ${token.name}. " + "It should be in android_shadow.json.", @@ -166,36 +168,48 @@ internal class ShadowTokenGenerator( /** * @see TokenGenerator.addComposeToken */ - override fun addComposeToken(token: ShadowToken): Boolean = with(ktFileBuilder) { - val tokenValues = shadowTokenValues[token.name] + override fun addComposeToken(token: ShadowToken): Boolean { + shadowTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for shadow token ${token.name}. " + "It should be in android_shadow.json.", ) - val useLayerSuffix = tokenValues.size > 1 - val layers = mutableListOf() - tokenValues.forEachIndexed { index, tokenValue -> - ShadowTokenValidator.validate(tokenValue, token.name) - val tokenName = "${token.ktName}Layer${index + 1}".takeIf { useLayerSuffix } ?: token.ktName - rootShadows.appendObject(tokenName, token.description) { - val layerRef = appendShadowProperties(tokenName, tokenValue, token.description) - layers.add(layerRef) + shadowTokenValues.forEach { (tenant, values) -> + val tokenValues = values[token.name] + if (tokenValues != null) { + val useLayerSuffix = tokenValues.size > 1 + val layers = mutableListOf() + tokenValues.forEachIndexed { index, tokenValue -> + ShadowTokenValidator.validate(tokenValue, token.name) + val tokenName = "${token.ktName}Layer${index + 1}".takeIf { useLayerSuffix } ?: token.ktName + val rootShadowsObject = rootShadows.getOrPut(tenant) { + ktFileBuilder.rootObject("${SHADOW_TOKENS_NAME}${tenant.name}", SHADOW_TOKENS_DESC) + } + rootShadowsObject.appendObject(tokenName, token.description) { + val layerRef = appendShadowProperties(tokenName, tokenValue, token.description, tenant) + layers.add(layerRef) + } + } + val composeTokenDataCollector = composeTokenDataCollectors.getOrPut(tenant) { + mutableListOf() + } + composeTokenDataCollector.add( + ShadowTokenResult.TokenData( + tokenTechName = token.name, + layers = layers, + tokenDescription = token.description, + ), + ) } } - composeTokenDataCollector.add( - ShadowTokenResult.TokenData( - tokenTechName = token.name, - layers = layers, - tokenDescription = token.description, - ), - ) - return@with true + return true } private fun TypeSpec.Builder.appendShadowProperties( tokenName: String, tokenValue: ShadowTokenValue, description: String, + tenant: Tenant, ): ShadowTokenResult.ShadowLayer = with(ktFileBuilder) { val offsetXRef = appendShadowProperty( @@ -203,27 +217,31 @@ internal class ShadowTokenGenerator( "offsetX", tokenValue.offsetX, description, + tenant, ) val offsetYRef = appendShadowProperty( tokenName, "offsetY", tokenValue.offsetY, description, + tenant, ) val spreadRadiusRef = appendShadowProperty( tokenName, "spreadRadius", tokenValue.spreadRadius, description, + tenant, ) val blurRadiusRef = appendShadowProperty( tokenName, "blurRadius", tokenValue.blurRadius, description, + tenant, ) val elevationRef = tokenValue.fallbackElevation?.let { - appendShadowProperty(tokenName, "fallbackElevation", it, description) + appendShadowProperty(tokenName, "fallbackElevation", it, description, tenant) } val resolvedColor = resolveColor( tokenValue = tokenValue.color, @@ -239,7 +257,7 @@ internal class ShadowTokenGenerator( ) ShadowTokenResult.ShadowLayer( - colorRef = "$SHADOW_TOKENS_NAME.$tokenName.color", + colorRef = "$SHADOW_TOKENS_NAME${tenant.name}.$tokenName.color", offsetXRef = offsetXRef, offsetYRef = offsetYRef, spreadRef = spreadRadiusRef, @@ -253,13 +271,14 @@ internal class ShadowTokenGenerator( propertyName: String, value: Float, description: String, + tenant: Tenant, ): String = with(ktFileBuilder) { if (dimensionsConfig.fromResources) { shadowPropertyWithResources(tokenName, propertyName, value, description) } else { shadowProperty(propertyName, value, description) } - return "$SHADOW_TOKENS_NAME.$tokenName.$propertyName" + return "$SHADOW_TOKENS_NAME${tenant.name}.$tokenName.$propertyName" } private fun TypeSpec.Builder.shadowProperty( diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeTokenGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeTokenGenerator.kt index 51de8350af..c703f1deb5 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeTokenGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeTokenGenerator.kt @@ -14,18 +14,19 @@ import com.sdds.plugin.themebuilder.internal.exceptions.ThemeBuilderException import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.data.ShapeTokenResult +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.RoundedShapeTokenValue import com.sdds.plugin.themebuilder.internal.token.ShapeToken import com.sdds.plugin.themebuilder.internal.token.ShapeTokenValue import com.sdds.plugin.themebuilder.internal.utils.FileProvider.shapesXmlFile import com.sdds.plugin.themebuilder.internal.utils.ResourceReferenceProvider +import com.sdds.plugin.themebuilder.internal.utils.decapitalized import com.sdds.plugin.themebuilder.internal.utils.techToSnakeCase import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy import com.sdds.plugin.themebuilder.internal.validator.ShapeTokenValidator import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.TypeSpec import java.io.File -import java.util.Locale /** * Генератор токенов форм @@ -48,27 +49,25 @@ internal class ShapeTokenGenerator( private val ktFileBuilderFactory: KtFileBuilderFactory, private val dimensAggregator: DimensAggregator, private val resourceReferenceProvider: ResourceReferenceProvider, - private val shapeTokenValues: Map, + private val shapeTokenValues: Map>, private val dimensionsConfig: DimensionsConfig, namespace: String, ) : TokenGenerator(target) { private val xmlDocumentBuilder by unsafeLazy { xmlBuilderFactory.create(DEFAULT_ROOT_ATTRIBUTES) } private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create(ROUND_SHAPE_TOKENS_NAME) } - private val rootRoundShapes by unsafeLazy { - ktFileBuilder.rootObject(ROUND_SHAPE_TOKENS_NAME, ROUND_SHAPE_TOKENS_DESC) - } + private val rootRoundShapes = + mutableMapOf(Tenant.Default to ktFileBuilder.rootObject(ROUND_SHAPE_TOKENS_NAME, ROUND_SHAPE_TOKENS_DESC)) private val shouldGenerateShapeStyles: Boolean = viewShapeAppearanceConfig.isNotEmpty() private val rFileImport = ClassName(namespace, "R") private var needCreateStyle: Boolean = true - private val composeTokenDataCollector = - mutableListOf() + private val composeTokenDataCollectors = mutableMapOf>() private val viewTokenDataCollector = mutableListOf() override fun collectResult() = ShapeTokenResult( - composeTokens = composeTokenDataCollector, + composeTokens = composeTokenDataCollectors, viewTokens = viewTokenDataCollector, ) @@ -99,7 +98,7 @@ internal class ShapeTokenGenerator( * @see TokenGenerator.addViewSystemToken */ override fun addViewSystemToken(token: ShapeToken): Boolean = with(xmlDocumentBuilder) { - val roundedShapeTokenValue = shapeTokenValues[token.name] as? RoundedShapeTokenValue + val roundedShapeTokenValue = shapeTokenValues[Tenant.Default]?.get(token.name) as? RoundedShapeTokenValue ?: throw ThemeBuilderException( "Can't find value for shape token ${token.name}. " + "It should be in android_shape.json.", @@ -160,27 +159,39 @@ internal class ShapeTokenGenerator( /** * @see TokenGenerator.addComposeToken */ - override fun addComposeToken(token: ShapeToken): Boolean = with(ktFileBuilder) { - val roundedShapeTokenValue = shapeTokenValues[token.name] as? RoundedShapeTokenValue + override fun addComposeToken(token: ShapeToken): Boolean { + shapeTokenValues[Tenant.Default]?.get(token.name) as? RoundedShapeTokenValue ?: throw ThemeBuilderException( "Can't find value for shape token ${token.name}. " + "It should be in android_shape.json.", ) - ShapeTokenValidator.validate(roundedShapeTokenValue, token.name) + shapeTokenValues.forEach { (tenant, values) -> + val tokenValue = values[token.name] + if (tokenValue != null && tokenValue is RoundedShapeTokenValue) { + ShapeTokenValidator.validate(tokenValue, token.name) - if (dimensionsConfig.fromResources) { - rootRoundShapes.addShapeTokenWithResources(token, roundedShapeTokenValue) - } else { - rootRoundShapes.addShapeToken(token, roundedShapeTokenValue) + val rootObject = rootRoundShapes.getOrPut(tenant) { + ktFileBuilder.rootObject("${ROUND_SHAPE_TOKENS_NAME}${tenant.name}", ROUND_SHAPE_TOKENS_DESC) + } + if (dimensionsConfig.fromResources) { + rootObject.addShapeTokenWithResources(token, tokenValue, tenant) + } else { + rootObject.addShapeToken(token, tokenValue) + } + val composeTokenDataCollector = composeTokenDataCollectors.getOrPut(tenant) { + mutableListOf() + } + composeTokenDataCollector.add( + ShapeTokenResult.TokenData( + attrName = token.ktName.decapitalized(), + tokenRefName = token.ktName, + description = token.description, + tokenObjectName = "${ROUND_SHAPE_TOKENS_NAME}${tenant.name}", + ), + ) + } } - composeTokenDataCollector.add( - ShapeTokenResult.TokenData( - attrName = token.ktName.decapitalize(Locale.getDefault()), - tokenRefName = token.ktName, - description = token.description, - ), - ) - return@with true + return true } private fun TypeSpec.Builder.addShapeToken( @@ -200,9 +211,15 @@ internal class ShapeTokenGenerator( private fun TypeSpec.Builder.addShapeTokenWithResources( token: ShapeToken, value: RoundedShapeTokenValue, + tenant: Tenant, ) = with(ktFileBuilder) { + val tenantSuffix = if (tenant == Tenant.Default) { + "" + } else { + "_${tenant.name}" + } val cornerSize = DimenData( - name = "${token.name.techToSnakeCase()}_corner_size", + name = "${token.name.techToSnakeCase()}_corner_size$tenantSuffix", value = value.cornerRadius, type = DimenData.Type.DP, ) diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/SpacingTokenGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/SpacingTokenGenerator.kt index bf5ac956cc..7a4a56c605 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/SpacingTokenGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/SpacingTokenGenerator.kt @@ -9,6 +9,7 @@ import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator import com.sdds.plugin.themebuilder.internal.exceptions.ThemeBuilderException import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.data.SpacingTokenResult +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.SpacingToken import com.sdds.plugin.themebuilder.internal.token.SpacingTokenValue import com.sdds.plugin.themebuilder.internal.utils.ResourceReferenceProvider @@ -22,7 +23,6 @@ import java.io.File /** * Генератор токенов отступов * @param outputLocation локация для сохранения kt-файла с токенами - * @param outputResDir директория для сохранения xml-файла с токенами * @param target целевой фреймворк * @param ktFileBuilderFactory фабрика делегата построения kt файлов * @param dimensAggregator агрегатор размеров @@ -37,22 +37,29 @@ internal class SpacingTokenGenerator( private val ktFileBuilderFactory: KtFileBuilderFactory, private val dimensAggregator: DimensAggregator, private val resourceReferenceProvider: ResourceReferenceProvider, - private val spacingTokenValues: Map, + private val spacingTokenValues: Map>, private val dimensionsConfig: DimensionsConfig, namespace: String, ) : TokenGenerator(target) { private val ktFileBuilder by unsafeLazy { ktFileBuilderFactory.create(SPACING_TOKENS_NAME) } - private val rootSpacings by unsafeLazy { ktFileBuilder.rootObject(SPACING_TOKENS_NAME, SPACING_TOKENS_DESC) } + private val rootSpacings = + mutableMapOf( + Tenant.Default to ktFileBuilder.rootObject( + SPACING_TOKENS_NAME, + SPACING_TOKENS_DESC, + ), + ) private val rFileImport = ClassName(namespace, "R") - private val composeTokenDataCollector = - mutableListOf() + private val composeTokenDataCollectors = + mutableMapOf(Tenant.Default to mutableListOf()) + private val viewTokenDataCollector = mutableListOf() override fun collectResult() = SpacingTokenResult( - composeTokens = composeTokenDataCollector, + composeTokens = composeTokenDataCollectors, viewTokens = viewTokenDataCollector, ) @@ -73,7 +80,7 @@ internal class SpacingTokenGenerator( * @see TokenGenerator.addViewSystemToken */ override fun addViewSystemToken(token: SpacingToken): Boolean { - val tokenValue = spacingTokenValues[token.name] + val tokenValue = spacingTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for spacing token ${token.name}. " + "It should be in android_spacing.json.", @@ -98,28 +105,43 @@ internal class SpacingTokenGenerator( /** * @see TokenGenerator.addComposeToken */ - override fun addComposeToken(token: SpacingToken): Boolean = with(ktFileBuilder) { - val tokenValue = spacingTokenValues[token.name] + override fun addComposeToken(token: SpacingToken): Boolean { + spacingTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for spacing token ${token.name}. " + "It should be in android_spacing.json.", ) - SpacingTokenValidator.validate(tokenValue, token.name) - - if (dimensionsConfig.fromResources) { - rootSpacings.addSpacingTokenWithResources(token, tokenValue) - } else { - rootSpacings.addSpacingToken(token, tokenValue) + spacingTokenValues.forEach { (tenant, values) -> + val tokenValue = values[token.name] + if (tokenValue != null) { + SpacingTokenValidator.validate(tokenValue, token.name) + val rootSpacingObject = rootSpacings.getOrPut(tenant) { + ktFileBuilder.rootObject( + "${SPACING_TOKENS_NAME}${tenant.name}", + SPACING_TOKENS_DESC, + ) + } + if (dimensionsConfig.fromResources) { + rootSpacingObject.addSpacingTokenWithResources(token, tokenValue, tenant) + } else { + rootSpacingObject.addSpacingToken(token, tokenValue) + } + val decapitalizedName = token.ktName.decapitalized() + val composeTokenDataCollector = composeTokenDataCollectors.getOrPut(tenant) { + mutableListOf() + } + composeTokenDataCollector.add( + SpacingTokenResult.TokenData( + attrName = decapitalizedName, + tokenRefName = decapitalizedName, + description = token.description, + tokenObjectName = "${SPACING_TOKENS_NAME}${tenant.name}", + ), + ) + } } - val decapitalizedName = token.ktName.decapitalized() - composeTokenDataCollector.add( - SpacingTokenResult.TokenData( - attrName = decapitalizedName, - tokenRefName = decapitalizedName, - description = token.description, - ), - ) - return@with true + + return true } private fun TypeSpec.Builder.addSpacingToken( @@ -139,9 +161,15 @@ internal class SpacingTokenGenerator( private fun TypeSpec.Builder.addSpacingTokenWithResources( token: SpacingToken, value: SpacingTokenValue, + tenant: Tenant, ) = with(ktFileBuilder) { + val tenantSuffix = if (tenant == Tenant.Default) { + "" + } else { + "_${tenant.name.lowercase()}" + } val dimenValue = DimenData( - name = token.xmlName, + name = "${token.xmlName}$tenantSuffix", value = value.value, type = DimenData.Type.DP, ) diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyTokenGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyTokenGenerator.kt index 9431bb1dc2..4d9a12594f 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyTokenGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyTokenGenerator.kt @@ -17,19 +17,22 @@ import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilder import com.sdds.plugin.themebuilder.internal.fonts.FontsAggregator import com.sdds.plugin.themebuilder.internal.generator.data.TypographyTokenResult import com.sdds.plugin.themebuilder.internal.generator.data.TypographyTokenResult.TypographyInfo +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.TypographyToken import com.sdds.plugin.themebuilder.internal.token.TypographyToken.ScreenClass import com.sdds.plugin.themebuilder.internal.token.TypographyTokenValue import com.sdds.plugin.themebuilder.internal.utils.FileProvider.textAppearancesXmlFile import com.sdds.plugin.themebuilder.internal.utils.FileProvider.typographyXmlFile import com.sdds.plugin.themebuilder.internal.utils.ResourceReferenceProvider +import com.sdds.plugin.themebuilder.internal.utils.decapitalized import com.sdds.plugin.themebuilder.internal.utils.techToSnakeCase import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy import com.sdds.plugin.themebuilder.internal.validator.TypographyTokenValidator import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.TypeSpec import java.io.File -import java.util.Locale +import kotlin.collections.component1 +import kotlin.collections.component2 /** * Генерирует токены типографики. @@ -59,7 +62,7 @@ internal class TypographyTokenGenerator( private val xmlBuilderFactory: XmlResourcesDocumentBuilderFactory, private val ktFileBuilderFactory: KtFileBuilderFactory, private val resourceReferenceProvider: ResourceReferenceProvider, - private val typographyTokenValues: Map, + private val typographyTokenValues: Map>, private val fontsAggregator: FontsAggregator, private val dimensionsConfig: DimensionsConfig, namespace: String, @@ -74,23 +77,38 @@ internal class TypographyTokenGenerator( ktFileBuilderFactory.create("TypographyTokens") .apply { this.addSuppressAnnotation("DEPRECATION") } } - private val largeBuilder by unsafeLazy { - ktFileBuilder.rootObject(TYPOGRAPHY_LARGE_TOKENS_NAME, TYPOGRAPHY_LARGE_TOKENS_DESC) + private val largeBuilders by unsafeLazy { + mutableMapOf( + Tenant.Default to ktFileBuilder.rootObject( + TYPOGRAPHY_LARGE_TOKENS_NAME, + TYPOGRAPHY_LARGE_TOKENS_DESC, + ), + ) } - private val mediumBuilder by unsafeLazy { - ktFileBuilder.rootObject(TYPOGRAPHY_MEDIUM_TOKENS_NAME, TYPOGRAPHY_MEDIUM_TOKENS_DESC) + private val mediumBuilders by unsafeLazy { + mutableMapOf( + Tenant.Default to ktFileBuilder.rootObject( + TYPOGRAPHY_MEDIUM_TOKENS_NAME, + TYPOGRAPHY_MEDIUM_TOKENS_DESC, + ), + ) } - private val smallBuilder by unsafeLazy { - ktFileBuilder.rootObject(TYPOGRAPHY_SMALL_TOKENS_NAME, TYPOGRAPHY_SMALL_TOKENS_DESC) + private val smallBuilders by unsafeLazy { + mutableMapOf( + Tenant.Default to ktFileBuilder.rootObject( + TYPOGRAPHY_SMALL_TOKENS_NAME, + TYPOGRAPHY_SMALL_TOKENS_DESC, + ), + ) } private val defaultValuesBuilder by unsafeLazy { ktFileBuilder.rootObject(DEFAULTS_NAME, modifiers = PrivateModifier) } private var needDeclareStyle: Boolean = true - private val composeSmallTokenDataCollector = mutableMapOf() - private val composeMediumTokenDataCollector = mutableMapOf() - private val composeLargeTokenDataCollector = mutableMapOf() + private val composeSmallTokenDataCollectors = mutableMapOf>() + private val composeMediumTokenDataCollectors = mutableMapOf>() + private val composeLargeTokenDataCollectors = mutableMapOf>() private val viewTokenDataCollector = mutableMapOf() @@ -100,11 +118,13 @@ internal class TypographyTokenGenerator( private val rFileImport = ClassName(namespace, "R") override fun collectResult() = TypographyTokenResult( - composeTokens = TypographyTokenResult.ComposeTokenData( - small = composeSmallTokenDataCollector, - medium = composeMediumTokenDataCollector, - large = composeLargeTokenDataCollector, - ), + composeTokens = typographyTokenValues.mapValues { + TypographyTokenResult.ComposeTokenData( + composeSmallTokenDataCollectors[it.key] ?: emptyMap(), + composeMediumTokenDataCollectors[it.key] ?: emptyMap(), + composeLargeTokenDataCollectors[it.key] ?: emptyMap(), + ) + }, viewTokens = TypographyTokenResult.ViewTokenData( attrs = viewTokenDataCollector, ), @@ -165,51 +185,83 @@ internal class TypographyTokenGenerator( * @see TokenGenerator.addComposeToken */ override fun addComposeToken(token: TypographyToken): Boolean { - val tokenValue = typographyTokenValues[token.name] + typographyTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for typography token ${token.name}. " + "It should be in android_typography.json.", ) - TypographyTokenValidator.validate(tokenValue, token.name) - val attrName = token.ktName.decapitalize(Locale.getDefault()) - when (token.screenClass) { - ScreenClass.SMALL -> { - smallBuilder.addTypographyToken( - token, - token.description, - tokenValue, - ) - val tokenRef = "$TYPOGRAPHY_SMALL_TOKENS_NAME.${token.ktName}" - composeSmallTokenDataCollector[attrName] = TypographyInfo(tokenRef, token.description) - } + typographyTokenValues.forEach { (tenant, values) -> + val tokenValue = values[token.name] + if (tokenValue != null) { + TypographyTokenValidator.validate(tokenValue, token.name) + val attrName = token.ktName.decapitalized() + val composeSmallTokenDataCollector = composeSmallTokenDataCollectors.getOrPut(tenant) { mutableMapOf() } + val composeMediumTokenDataCollector = composeMediumTokenDataCollectors.getOrPut( + tenant, + ) { mutableMapOf() } + val composeLargeTokenDataCollector = composeLargeTokenDataCollectors.getOrPut(tenant) { mutableMapOf() } + when (token.screenClass) { + ScreenClass.SMALL -> { + val smallBuilder = smallBuilders.getOrPut(tenant) { + ktFileBuilder.rootObject( + "${TYPOGRAPHY_SMALL_TOKENS_NAME}${tenant.name}", + TYPOGRAPHY_SMALL_TOKENS_DESC, + ) + } + smallBuilder.addTypographyToken( + token, + token.description, + tokenValue, + tenant, + ) + val tokenRef = "$TYPOGRAPHY_SMALL_TOKENS_NAME${tenant.name}.${token.ktName}" + composeSmallTokenDataCollector[attrName] = TypographyInfo(tokenRef, token.description) + } - ScreenClass.LARGE -> { - largeBuilder.addTypographyToken( - token, - token.description, - tokenValue, - ) - val tokenRef = "$TYPOGRAPHY_LARGE_TOKENS_NAME.${token.ktName}" - composeLargeTokenDataCollector[attrName] = TypographyInfo(tokenRef, token.description) - } + ScreenClass.LARGE -> { + val largeBuilder = largeBuilders.getOrPut(tenant) { + ktFileBuilder.rootObject( + "${TYPOGRAPHY_LARGE_TOKENS_NAME}${tenant.name}", + TYPOGRAPHY_LARGE_TOKENS_DESC, + ) + } + largeBuilder.addTypographyToken( + token, + token.description, + tokenValue, + tenant, + ) + val tokenRef = "$TYPOGRAPHY_LARGE_TOKENS_NAME${tenant.name}.${token.ktName}" + composeLargeTokenDataCollector[attrName] = TypographyInfo(tokenRef, token.description) + } - else -> { - mediumBuilder.addTypographyToken( - token, - token.description, - tokenValue, - ) - val tokenRef = "$TYPOGRAPHY_MEDIUM_TOKENS_NAME.${token.ktName}" - composeMediumTokenDataCollector[attrName] = TypographyInfo(tokenRef, token.description) + else -> { + val mediumBuilder = mediumBuilders.getOrPut(tenant) { + ktFileBuilder.rootObject( + "${TYPOGRAPHY_MEDIUM_TOKENS_NAME}${tenant.name}", + TYPOGRAPHY_MEDIUM_TOKENS_DESC, + ) + } + mediumBuilder.addTypographyToken( + token, + token.description, + tokenValue, + tenant, + ) + val tokenRef = "$TYPOGRAPHY_MEDIUM_TOKENS_NAME${tenant.name}.${token.ktName}" + composeMediumTokenDataCollector[attrName] = TypographyInfo(tokenRef, token.description) + } + } } } + return true } private fun generateViewTypographyTokens(tokenName: String, screenClass: ScreenClass) { val token = findTypographyTokenByScreenClass(tokenName, screenClass) ?: throw ThemeBuilderException("Token $tokenName not found") - val tokenValue = typographyTokenValues[token.name] + val tokenValue = typographyTokenValues[Tenant.Default]?.get(token.name) ?: throw ThemeBuilderException( "Can't find value for typography token ${token.name}. " + "It should be in android_typography.json.", @@ -366,6 +418,7 @@ internal class TypographyTokenGenerator( token: TypographyToken, description: String, tokenValue: TypographyTokenValue, + tenant: Tenant, ) = with(ktFileBuilder) { val letterSpacing = if (tokenValue.letterSpacing < 0) { "(${tokenValue.letterSpacing}).sp" @@ -376,13 +429,18 @@ internal class TypographyTokenGenerator( val fontSizeInitializer: String val lineHeightInitializer: String if (fromResources) { + val tenantSuffix = if (tenant == Tenant.Default) { + "" + } else { + "_${tenant.name.lowercase()}" + } val textSizeDimen = DimenData( - "${token.name.techToSnakeCase()}_text_size", + "${token.name.techToSnakeCase()}_text_size$tenantSuffix", tokenValue.textSize, DimenData.Type.SP, ) val lineHeightDimen = DimenData( - "${token.name.techToSnakeCase()}_line_height", + "${token.name.techToSnakeCase()}_line_height$tenantSuffix", tokenValue.lineHeight, DimenData.Type.SP, ) @@ -400,7 +458,7 @@ internal class TypographyTokenGenerator( "fontSize = $fontSizeInitializer", "lineHeight = $lineHeightInitializer", "letterSpacing = $letterSpacing", - "fontFamily = FontTokens.${tokenValue.fontFamilyRef.split('.').last()}", + "fontFamily = FontTokens${tenant.name}.${tokenValue.fontFamilyRef.split('.').last()}", "lineHeightStyle = $DEFAULTS_NAME.$DEFAULTS_LINE_STYLE_NAME", "platformStyle = $DEFAULTS_NAME.$DEFAULTS_PLATFORM_STYLE_NAME", ).trimIndent() diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ColorTokenResult.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ColorTokenResult.kt index 34ace209bb..9408d33bf1 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ColorTokenResult.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ColorTokenResult.kt @@ -1,5 +1,6 @@ package com.sdds.plugin.themebuilder.internal.generator.data +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.ColorToken /** @@ -11,7 +12,7 @@ import com.sdds.plugin.themebuilder.internal.token.ColorToken */ internal data class ColorTokenResult( val tokens: List, - val composeTokens: TokenData, + val composeTokens: Map, val viewTokens: TokenData, ) { diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/GradientTokenResult.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/GradientTokenResult.kt index 7446530220..d49a9a0bd3 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/GradientTokenResult.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/GradientTokenResult.kt @@ -1,5 +1,6 @@ package com.sdds.plugin.themebuilder.internal.generator.data +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.GradientToken /** @@ -11,7 +12,7 @@ import com.sdds.plugin.themebuilder.internal.token.GradientToken */ internal data class GradientTokenResult( val tokens: List, - val composeTokens: ComposeTokenData, + val composeTokens: Map, val viewXmlTokens: ViewTokenData, ) { @@ -79,6 +80,7 @@ internal data class GradientTokenResult( val tokenRefs: List, val gradientType: GradientType, val description: String = "", + val tokenObjectName: String? = null, ) /** diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ShadowTokenResult.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ShadowTokenResult.kt index fadc08cf88..dd328057c4 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ShadowTokenResult.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ShadowTokenResult.kt @@ -1,5 +1,7 @@ package com.sdds.plugin.themebuilder.internal.generator.data +import com.sdds.plugin.themebuilder.internal.tenant.Tenant + /** * Данные о токенах тени. * @@ -7,7 +9,7 @@ package com.sdds.plugin.themebuilder.internal.generator.data * @property viewTokens данные о токенах для View */ internal data class ShadowTokenResult( - val composeTokens: List, + val composeTokens: Map>, val viewTokens: List, ) { diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ShapeTokenResult.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ShapeTokenResult.kt index 8fef5edf67..2d5d613e91 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ShapeTokenResult.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/ShapeTokenResult.kt @@ -1,5 +1,7 @@ package com.sdds.plugin.themebuilder.internal.generator.data +import com.sdds.plugin.themebuilder.internal.tenant.Tenant + /** * Данные о токенах формы. * @@ -7,7 +9,7 @@ package com.sdds.plugin.themebuilder.internal.generator.data * @property viewTokens данные о токенах для View */ internal data class ShapeTokenResult( - val composeTokens: List, + val composeTokens: Map>, val viewTokens: List, ) { @@ -22,5 +24,6 @@ internal data class ShapeTokenResult( val attrName: String, val tokenRefName: String, val description: String = "", + val tokenObjectName: String? = null, ) } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/SpacingTokenResult.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/SpacingTokenResult.kt index c27b9b58a2..9fc54c5d94 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/SpacingTokenResult.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/SpacingTokenResult.kt @@ -1,5 +1,7 @@ package com.sdds.plugin.themebuilder.internal.generator.data +import com.sdds.plugin.themebuilder.internal.tenant.Tenant + /** * Данные о токенах отступов. * @@ -7,7 +9,7 @@ package com.sdds.plugin.themebuilder.internal.generator.data * @property viewTokens данные о токенах для View */ internal data class SpacingTokenResult( - val composeTokens: List, + val composeTokens: Map>, val viewTokens: List, ) { @@ -22,5 +24,6 @@ internal data class SpacingTokenResult( val attrName: String, val tokenRefName: String, val description: String? = null, + val tokenObjectName: String? = null, ) } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/TypographyTokenResult.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/TypographyTokenResult.kt index 7555384066..1ee812cbe1 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/TypographyTokenResult.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/data/TypographyTokenResult.kt @@ -1,5 +1,7 @@ package com.sdds.plugin.themebuilder.internal.generator.data +import com.sdds.plugin.themebuilder.internal.tenant.Tenant + /** * Данные о токенах типографики. * @@ -7,7 +9,7 @@ package com.sdds.plugin.themebuilder.internal.generator.data * @property viewTokens данные о токенах для View */ internal data class TypographyTokenResult( - val composeTokens: ComposeTokenData, + val composeTokens: Map, val viewTokens: ViewTokenData, ) { diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeColorAttributeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeColorAttributeGenerator.kt index 3e63448b8d..1d1e0251fd 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeColorAttributeGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeColorAttributeGenerator.kt @@ -3,6 +3,7 @@ package com.sdds.plugin.themebuilder.internal.generator.theme.compose import com.sdds.plugin.themebuilder.internal.PackageResolver import com.sdds.plugin.themebuilder.internal.TargetPackage import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder +import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Companion.TypeString import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Constructor import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Modifier.INFIX import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Modifier.INTERNAL @@ -14,8 +15,12 @@ import com.sdds.plugin.themebuilder.internal.generator.ColorTokenGenerator.Compa import com.sdds.plugin.themebuilder.internal.generator.SimpleBaseGenerator import com.sdds.plugin.themebuilder.internal.generator.data.ColorTokenResult import com.sdds.plugin.themebuilder.internal.generator.data.mergedLightAndDark +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.snakeToCamelCase import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.asTypeName /** @@ -33,7 +38,7 @@ internal class ComposeColorAttributeGenerator( private val packageResolver: PackageResolver, ) : SimpleBaseGenerator { - private var tokenData: ColorTokenResult.TokenData? = null + private var tokenData: Map = emptyMap() private val colorAttributes = mutableSetOf() private val colorKtFileBuilder by unsafeLazy { @@ -47,8 +52,6 @@ internal class ComposeColorAttributeGenerator( } override fun generate() { - tokenData ?: return - addImports() addColorsClass() addUpdateColorsFromFun() @@ -56,16 +59,20 @@ internal class ComposeColorAttributeGenerator( addMutableMapExtension() addLightColorsFun() addDarkColorsFun() + addLightTenants() + addDarkTenants() addColorOverrideScopeClass() + addColorAttrOverrideScopeClass() addObtainStateFun() colorKtFileBuilder.build(outputLocation) } - fun setColorTokenData(data: ColorTokenResult.TokenData) { + fun setColorTokenData(data: Map) { tokenData = data + val defaultTenantData = data[Tenant.Default] ?: return colorAttributes.clear() - colorAttributes.addAll(data.mergedLightAndDark()) + colorAttributes.addAll(defaultTenantData.mergedLightAndDark()) } private fun addColorsClass() { @@ -92,7 +99,7 @@ internal class ComposeColorAttributeGenerator( typeName = KtFileBuilder.TypeColor, isMutable = true, delegate = "colors.obtain(\"$color\")", - description = tokenData?.description(color), + description = tokenData[Tenant.Default]?.description(color), ) } @@ -117,6 +124,29 @@ internal class ComposeColorAttributeGenerator( description = "Возвращает копию [$colorClassName]. " + "Предоставляет возможность переопределять цвета.", ) + + rootColorsClass.appendFun( + name = "copyAttrs", + returnType = getInternalClassType(colorClassName), + params = listOf( + KtFileBuilder.FunParameter( + name = "overrideColors", + type = KtFileBuilder.getLambdaType( + receiver = colorKtFileBuilder.getInternalClassType("ColorAttrOverrideScope"), + ), + defValue = "{}", + ), + ), + body = listOf( + "val colorOverrideScope = ColorAttrOverrideScope()\n", + "overrideColors.invoke(colorOverrideScope)\n", + "val overrideMap = colorOverrideScope.overrideMap\n", + "return $colorClassName(colors.mapValues { colors[overrideMap[it.key]] ?: it.value })", + ), + modifiers = listOf(INTERNAL), + description = "Возвращает копию [$colorClassName]. " + + "Предоставляет возможность переопределять цвета.", + ) } } @@ -159,7 +189,73 @@ internal class ComposeColorAttributeGenerator( ) } + private fun addLightTenants() { + tokenData + .filter { it.key != Tenant.Default } + .forEach { (tenant, data) -> + if (data.light.isNotEmpty()) { + colorKtFileBuilder.appendRootFun( + name = "light${camelThemeName}Colors${tenant.name}", + params = listOf( + KtFileBuilder.FunParameter( + name = "overrideColors", + type = KtFileBuilder.getLambdaType( + receiver = colorKtFileBuilder.getInternalClassType("ColorOverrideScope"), + ), + defValue = "{}", + ), + ), + returnType = colorClassType, + body = listOf( + "return light${camelThemeName}Colors {\n", + data.light.entries.joinToString(separator = "\n") { + "${it.key} overrideBy LightColorTokens${tenant.name}.${it.value.colorRef}" + }, + "\noverrideColors()", + "\n}", + ), + description = "Цвета [$colorClassName] для светлой темы в тенанте ${tenant.name}", + suppressAnnotations = listOf("LongMethod"), + ) + } + } + } + + private fun addDarkTenants() { + tokenData + .filter { it.key != Tenant.Default } + .forEach { (tenant, data) -> + if (data.dark.isNotEmpty()) { + colorKtFileBuilder.appendRootFun( + name = "dark${camelThemeName}Colors${tenant.name}", + params = listOf( + KtFileBuilder.FunParameter( + name = "overrideColors", + type = KtFileBuilder.getLambdaType( + receiver = colorKtFileBuilder.getInternalClassType("ColorOverrideScope"), + ), + defValue = "{}", + ), + ), + returnType = colorClassType, + body = listOf( + "return dark${camelThemeName}Colors {\n", + data.light.entries.joinToString(separator = "\n") { + "${it.key} overrideBy DarkColorTokens${tenant.name}.${it.value.colorRef}" + }, + "\noverrideColors()", + "\n}", + ), + description = "Цвета [$colorClassName] для темной темы в тенанте ${tenant.name}", + suppressAnnotations = listOf("LongMethod"), + ) + } + } + } + private fun addLightColorsFun() { + val defaultTokenData = tokenData[Tenant.Default] + ?: throw ThemeBuilderException("color token data must be presented for default tenant") colorKtFileBuilder.appendRootFun( name = "light${camelThemeName}Colors", params = listOf( @@ -178,11 +274,11 @@ internal class ComposeColorAttributeGenerator( "val overwrite = colorOverrideScope.overrideMap\n", "val initial = mutableMapOf()\n", colorAttributes.joinToString(separator = "\n") { - val defaultValue = if (tokenData?.light?.get(it)?.colorRef != null) { - "LightColorTokens.${tokenData?.light?.get(it)?.colorRef}" + val defaultValue = if (defaultTokenData.light[it]?.colorRef != null) { + "LightColorTokens.${defaultTokenData.light[it]?.colorRef}" } else { "DarkColorTokens.${ - tokenData?.dark?.get(it)?.colorRef ?: throw ThemeBuilderException( + defaultTokenData.dark[it]?.colorRef ?: throw ThemeBuilderException( "Can't find token value for color $it", ) }" @@ -197,6 +293,8 @@ internal class ComposeColorAttributeGenerator( } private fun addDarkColorsFun() { + val defaultTokenData = tokenData[Tenant.Default] + ?: throw ThemeBuilderException("color token data must be presented for default tenant") colorKtFileBuilder.appendRootFun( name = "dark${camelThemeName}Colors", params = listOf( @@ -215,11 +313,11 @@ internal class ComposeColorAttributeGenerator( "val overwrite = colorOverrideScope.overrideMap\n", "val initial = mutableMapOf()\n", colorAttributes.joinToString(separator = "\n") { - val defaultValue = if (tokenData?.dark?.get(it)?.colorRef != null) { - "DarkColorTokens.${tokenData?.dark?.get(it)?.colorRef}" + val defaultValue = if (defaultTokenData.dark[it]?.colorRef != null) { + "DarkColorTokens.${defaultTokenData.dark[it]?.colorRef}" } else { "LightColorTokens.${ - tokenData?.light?.get(it)?.colorRef ?: throw ThemeBuilderException( + defaultTokenData.light[it]?.colorRef ?: throw ThemeBuilderException( "Can't find token value for color $it", ) }" @@ -255,14 +353,14 @@ internal class ComposeColorAttributeGenerator( body = "return _overrideMap.toMap()", ), ) - + val defaultTokenData = tokenData[Tenant.Default] colorAttributes.forEach { color -> rootColorsClass.appendProperty( name = color, typeName = KtFileBuilder.TypeString, isMutable = false, initializer = "\"$color\"", - description = tokenData?.description(color), + description = defaultTokenData?.description(color), ) } @@ -279,6 +377,58 @@ internal class ComposeColorAttributeGenerator( } } + private fun addColorAttrOverrideScopeClass() { + with(colorKtFileBuilder) { + val rootColorsClass = rootClass( + name = "ColorAttrOverrideScope", + description = "Скоуп переопределения цветов по арибутам", + modifiers = listOf(INTERNAL), + ) + val mutableMapType = ClassName( + "kotlin.collections", + "MutableMap", + ).parameterizedBy(TypeString, TypeString) + val mapType = Map::class.asClassName().parameterizedBy(TypeString, TypeString) + + rootColorsClass.appendProperty( + name = "_overrideMap", + typeName = mutableMapType, + initializer = "mutableMapOf()", + modifiers = listOf(PRIVATE), + ) + + rootColorsClass.appendProperty( + name = "overrideMap", + typeName = mapType, + modifiers = listOf(INTERNAL), + propGetter = KtFileBuilder.Getter.Annotated( + body = "return _overrideMap.toMap()", + ), + ) + val defaultTokenData = tokenData[Tenant.Default] + colorAttributes.forEach { color -> + rootColorsClass.appendProperty( + name = color, + typeName = KtFileBuilder.TypeString, + isMutable = false, + initializer = "\"$color\"", + description = defaultTokenData?.description(color), + ) + } + + rootColorsClass.appendFun( + name = "overrideBy", + params = listOf( + KtFileBuilder.FunParameter("color", type = KtFileBuilder.TypeString), + ), + modifiers = listOf(INFIX), + receiver = KtFileBuilder.TypeString, + body = listOf("_overrideMap[this] = color"), + description = "Переопределяет аттрибут цвета.", + ) + } + } + private fun addLocalColorsVal() { colorKtFileBuilder.appendRootVal( name = "Local$colorClassName", @@ -310,22 +460,23 @@ internal class ComposeColorAttributeGenerator( packageName = "androidx.compose.ui.graphics", names = listOf("Color"), ) - val tokenData = tokenData ?: return - if (tokenData.dark.isNotEmpty()) { - addImport( - getInternalClassType( - className = DARK_COLOR_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) - } - if (tokenData.light.isNotEmpty()) { - addImport( - getInternalClassType( - className = LIGHT_COLOR_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) + tokenData.forEach { (tenant, data) -> + if (data.dark.isNotEmpty()) { + addImport( + getInternalClassType( + className = "${DARK_COLOR_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } + if (data.light.isNotEmpty()) { + addImport( + getInternalClassType( + className = "${LIGHT_COLOR_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } } } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeGradientAttributeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeGradientAttributeGenerator.kt index 4d0ac5d31a..bb4831b80e 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeGradientAttributeGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeGradientAttributeGenerator.kt @@ -3,11 +3,13 @@ package com.sdds.plugin.themebuilder.internal.generator.theme.compose import com.sdds.plugin.themebuilder.internal.PackageResolver import com.sdds.plugin.themebuilder.internal.TargetPackage import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder +import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Companion.TypeString import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Companion.nullable import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Constructor import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.FunParameter import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Getter import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Modifier +import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Modifier.INFIX import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder.Modifier.INTERNAL import com.sdds.plugin.themebuilder.internal.exceptions.ThemeBuilderException import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory @@ -16,8 +18,12 @@ import com.sdds.plugin.themebuilder.internal.generator.GradientTokenGenerator.Co import com.sdds.plugin.themebuilder.internal.generator.SimpleBaseGenerator import com.sdds.plugin.themebuilder.internal.generator.data.GradientTokenResult.ComposeTokenData import com.sdds.plugin.themebuilder.internal.generator.data.mergedLightAndDark +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.snakeToCamelCase import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.asClassName /** * Генератор Compose-атрибутов градиента. @@ -34,7 +40,7 @@ internal class ComposeGradientAttributeGenerator( private val packageResolver: PackageResolver, ) : SimpleBaseGenerator { - private var tokenData: ComposeTokenData? = null + private var tokenData: Map = emptyMap() private val gradientAttributes = mutableSetOf() private val gradientKtFileBuilder: KtFileBuilder by unsafeLazy { @@ -47,17 +53,16 @@ internal class ComposeGradientAttributeGenerator( gradientKtFileBuilder.getInternalClassType(gradientClassName) } - fun setGradientTokenData(data: ComposeTokenData) { + fun setGradientTokenData(data: Map) { tokenData = data + val defaultTenantData = data[Tenant.Default] ?: return gradientAttributes.clear() - gradientAttributes.addAll(data.mergedLightAndDark()) + gradientAttributes.addAll(defaultTenantData.mergedLightAndDark()) } override fun generate() { - tokenData ?: return - + if (tokenData.isEmpty()) return createGradientsFile() - gradientKtFileBuilder.build(outputLocation) } @@ -66,8 +71,11 @@ internal class ComposeGradientAttributeGenerator( addGradientsClass() addMutableMapExtension() addLightGradientsFun() + addLightTenants() addDarkGradientsFun() + addDarkTenants() addGradientOverrideScopeClass() + addGradientAttrOverrideScopeClass() addLinearGradientFun() addRadialGradientFun() addSweepGradientFun() @@ -102,22 +110,23 @@ internal class ComposeGradientAttributeGenerator( names = listOf("Gradients"), ) addImport(KtFileBuilder.TypeOffset) - val tokenData = tokenData ?: return - if (tokenData.dark.isNotEmpty()) { - addImport( - getInternalClassType( - className = DARK_GRADIENT_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) - } - if (tokenData.light.isNotEmpty()) { - addImport( - getInternalClassType( - className = LIGHT_GRADIENT_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) + tokenData.forEach { (tenant, data) -> + if (data.dark.isNotEmpty()) { + addImport( + getInternalClassType( + className = "${DARK_GRADIENT_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } + if (data.light.isNotEmpty()) { + addImport( + getInternalClassType( + className = "${LIGHT_GRADIENT_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } } } } @@ -145,7 +154,7 @@ internal class ComposeGradientAttributeGenerator( name = gradient, typeName = KtFileBuilder.TypeListOfShaderBrush, delegate = "gradients", - description = tokenData?.description(gradient).orEmpty(), + description = tokenData[Tenant.Default]?.description(gradient).orEmpty(), ) } @@ -170,6 +179,29 @@ internal class ComposeGradientAttributeGenerator( description = "Возвращает копию [$gradientClassName]. " + "Предоставляет возможность переопределять градиенты.", ) + + rootGradientClass.appendFun( + name = "copyAttrs", + returnType = getInternalClassType(gradientClassName), + params = listOf( + KtFileBuilder.FunParameter( + name = "overrideGradients", + type = KtFileBuilder.getLambdaType( + receiver = gradientKtFileBuilder.getInternalClassType("GradientAttrOverrideScope"), + ), + defValue = "{}", + ), + ), + body = listOf( + "val gradientOverrideScope = GradientAttrOverrideScope()\n", + "overrideGradients.invoke(gradientOverrideScope)\n", + "val overrideMap = gradientOverrideScope.overrideMap\n", + "return $gradientClassName(gradients.mapValues { gradients[overrideMap[it.key]] ?: it.value })", + ), + modifiers = listOf(INTERNAL), + description = "Возвращает копию [$gradientClassName]. " + + "Предоставляет возможность переопределять цвета.", + ) } } @@ -210,6 +242,70 @@ internal class ComposeGradientAttributeGenerator( ) } + private fun addLightTenants() { + tokenData + .filter { it.key != Tenant.Default } + .forEach { (tenant, data) -> + if (data.light.isNotEmpty()) { + gradientKtFileBuilder.appendRootFun( + name = "light${camelThemeName}Gradients${tenant.name}", + params = listOf( + KtFileBuilder.FunParameter( + name = "overrideGradients", + type = KtFileBuilder.getLambdaType( + receiver = gradientKtFileBuilder.getInternalClassType("GradientOverrideScope"), + ), + defValue = "{}", + ), + ), + returnType = gradientClassType, + body = listOf( + "return light${camelThemeName}Gradients {\n", + data.light.entries.joinToString(separator = "\n") { + "${it.key} overrideBy ${lightGradientValue(it.key, tenant)}" + }, + "\noverrideGradients()", + "\n}", + ), + description = "Градиенты [$gradientClassName] для светлой темы в тенанте ${tenant.name}", + suppressAnnotations = listOf("LongMethod"), + ) + } + } + } + + private fun addDarkTenants() { + tokenData + .filter { it.key != Tenant.Default } + .forEach { (tenant, data) -> + if (data.dark.isNotEmpty()) { + gradientKtFileBuilder.appendRootFun( + name = "dark${camelThemeName}Gradients${tenant.name}", + params = listOf( + KtFileBuilder.FunParameter( + name = "overrideGradients", + type = KtFileBuilder.getLambdaType( + receiver = gradientKtFileBuilder.getInternalClassType("GradientOverrideScope"), + ), + defValue = "{}", + ), + ), + returnType = gradientClassType, + body = listOf( + "return dark${camelThemeName}Gradients {\n", + data.light.entries.joinToString(separator = "\n") { + "${it.key} overrideBy ${darkGradientValue(it.key, tenant)}" + }, + "\noverrideGradients()", + "\n}", + ), + description = "Градиенты [$gradientClassName] для темной темы в тенанте ${tenant.name}", + suppressAnnotations = listOf("LongMethod"), + ) + } + } + } + private fun addLightGradientsFun() { gradientKtFileBuilder.appendRootFun( name = "light${camelThemeName}Gradients", @@ -229,7 +325,7 @@ internal class ComposeGradientAttributeGenerator( "val overwrite = gradientOverrideScope.overrideMap\n", "val initial = mutableMapOf>()\n", gradientAttributes.joinToString(separator = "\n") { - val defaultValue = defaultLightGradientValue(it) + val defaultValue = lightGradientValue(it, Tenant.Default) "initial.add(\"$it\", $defaultValue, overwrite)" }, "\nreturn $gradientClassName(initial)", @@ -239,51 +335,36 @@ internal class ComposeGradientAttributeGenerator( ) } - private fun defaultLightGradientValue(attrName: String): String { - val lightLayers = tokenData?.light?.get(attrName) - val darkLayers = tokenData?.dark?.get(attrName) + private fun lightGradientValue(attrName: String, tenant: Tenant): String { + val data = tokenData[tenant] + val lightLayers = data?.light?.get(attrName) + val darkLayers = data?.dark?.get(attrName) - val parameters: List - val objectName: String - - if (lightLayers != null) { - parameters = lightLayers - objectName = "LightGradientTokens" - } else { - parameters = darkLayers - ?: throw ThemeBuilderException("Can't find token value for gradient $attrName") - objectName = "DarkGradientTokens" - } + val parameters: List = lightLayers + ?: darkLayers + ?: throw ThemeBuilderException("Can't find token value for gradient $attrName") return KtFileBuilder.createFunCall( "listOf", - parameters.map { createGradientFabricCall(objectName, it) }, + parameters.map { createGradientFabricCall(it) }, ) } - private fun defaultDarkGradientValue(attrName: String): String { - val lightLayers = tokenData?.light?.get(attrName) - val darkLayers = tokenData?.dark?.get(attrName) - - val parameters: List - val objectName: String + private fun darkGradientValue(attrName: String, tenant: Tenant): String { + val data = tokenData[tenant] + val lightLayers = data?.light?.get(attrName) + val darkLayers = data?.dark?.get(attrName) - if (darkLayers != null) { - parameters = darkLayers - objectName = "DarkGradientTokens" - } else { - parameters = lightLayers - ?: throw ThemeBuilderException("Can't find token value for gradient $attrName") - objectName = "LightGradientTokens" - } + val parameters: List = darkLayers + ?: lightLayers + ?: throw ThemeBuilderException("Can't find token value for gradient $attrName") return KtFileBuilder.createFunCall( "listOf", - parameters.map { createGradientFabricCall(objectName, it) }, + parameters.map { createGradientFabricCall(it) }, ) } private fun createGradientFabricCall( - objectName: String, gradient: ComposeTokenData.Gradient, ): String { val funName = when (gradient.gradientType) { @@ -294,7 +375,7 @@ internal class ComposeGradientAttributeGenerator( } return KtFileBuilder.createFunCall( funName = funName, - parameters = gradient.tokenRefs.map { "$objectName.$it" }, + parameters = gradient.tokenRefs.map { "${gradient.tokenObjectName}.$it" }, ) } @@ -317,7 +398,7 @@ internal class ComposeGradientAttributeGenerator( "val overwrite = gradientOverrideScope.overrideMap\n", "val initial = mutableMapOf>()\n", gradientAttributes.joinToString(separator = "\n") { - val defaultValue = defaultDarkGradientValue(it) + val defaultValue = darkGradientValue(it, Tenant.Default) "initial.add(\"$it\", $defaultValue, overwrite)" }, "\nreturn $gradientClassName(initial)", @@ -354,7 +435,7 @@ internal class ComposeGradientAttributeGenerator( typeName = KtFileBuilder.TypeString, isMutable = false, initializer = "\"$gradient\"", - description = tokenData?.description(gradient), + description = tokenData[Tenant.Default]?.description(gradient), ) } @@ -371,6 +452,55 @@ internal class ComposeGradientAttributeGenerator( } } + private fun addGradientAttrOverrideScopeClass() { + with(gradientKtFileBuilder) { + val rootColorsClass = rootClass( + name = "GradientAttrOverrideScope", + description = "Скоуп переопределения градиентов", + ) + val mutableMapType = ClassName( + "kotlin.collections", + "MutableMap", + ).parameterizedBy(TypeString, TypeString) + val mapType = Map::class.asClassName().parameterizedBy(TypeString, TypeString) + + rootColorsClass.appendProperty( + name = "_overrideMap", + typeName = mutableMapType, + initializer = "mutableMapOf()", + modifiers = listOf(Modifier.PRIVATE), + ) + + rootColorsClass.appendProperty( + name = "overrideMap", + typeName = mapType, + modifiers = listOf(INTERNAL), + propGetter = Getter.Annotated(body = "return _overrideMap.toMap()"), + ) + + gradientAttributes.forEach { gradient -> + rootColorsClass.appendProperty( + name = gradient, + typeName = KtFileBuilder.TypeString, + isMutable = false, + initializer = "\"$gradient\"", + description = tokenData[Tenant.Default]?.description(gradient), + ) + } + + rootColorsClass.appendFun( + name = "overrideBy", + params = listOf( + FunParameter("gradient", type = KtFileBuilder.TypeString), + ), + modifiers = listOf(INFIX), + receiver = KtFileBuilder.TypeString, + body = listOf("_overrideMap[this] = gradient"), + description = "Переопределяет аттрибут градиента.", + ) + } + } + private fun addLinearGradientFun() { gradientKtFileBuilder.appendRootFun( name = "linearGradient", diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeShadowAttributeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeShadowAttributeGenerator.kt index eb8dd1bcc5..292c2f3cfd 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeShadowAttributeGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeShadowAttributeGenerator.kt @@ -11,6 +11,7 @@ import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.ShadowTokenGenerator.Companion.SHADOW_TOKENS_NAME import com.sdds.plugin.themebuilder.internal.generator.SimpleBaseGenerator import com.sdds.plugin.themebuilder.internal.generator.data.ShadowTokenResult +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.decapitalized import com.sdds.plugin.themebuilder.internal.utils.snakeToCamelCase import com.sdds.plugin.themebuilder.internal.utils.techToCamelCase @@ -31,7 +32,7 @@ internal class ComposeShadowAttributeGenerator( private val dimensionsConfig: DimensionsConfig, private val packageResolver: PackageResolver, ) : SimpleBaseGenerator { - private val shadows = mutableListOf() + private val shadows: MutableMap> = mutableMapOf() private val shadowKtFileBuilder by unsafeLazy { ktFileBuilderFactory.create(shadowClassName, TargetPackage.THEME) @@ -43,17 +44,18 @@ internal class ComposeShadowAttributeGenerator( shadowKtFileBuilder.getInternalClassType(shadowClassName) } - fun setShadowTokenData(shadows: List) { + fun setShadowTokenData(shadows: Map>) { this.shadows.clear() - this.shadows.addAll(shadows) + this.shadows.putAll(shadows) } override fun generate() { if (shadows.isEmpty()) return + val defaultShadows = shadows[Tenant.Default] ?: return addImports() addShadowClassFactoryFun() - addShadowClass(shadows) + addShadowClass(defaultShadows) addLocalShadowsVal() shadowKtFileBuilder.build(outputLocation) @@ -107,36 +109,46 @@ internal class ComposeShadowAttributeGenerator( addImport(KtFileBuilder.TypeShadowAppearance) addImport(KtFileBuilder.TypeShadowLayer) addImport(KtFileBuilder.TypeDpOffset) - addImport( - getInternalClassType( - className = SHADOW_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) + shadows.keys.forEach { tenant -> + addImport( + getInternalClassType( + className = "${SHADOW_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } } } - private fun addShadowClassFactoryFun() = with(shadowKtFileBuilder) { - appendRootFun( - name = "default$shadowClassName", - returnType = shadowClassType, - body = listOf( - KtFileBuilder.createConstructorCall( - constructorName = shadowClassName, - initializers = shadows.map { - """ + private fun addShadowClassFactoryFun() { + val defaultShadows = shadows[Tenant.Default] ?: return + shadows + .forEach { (tenant, data) -> + val shadowList = (defaultShadows + data) + .associateBy { it.tokenTechName } + .values + .toList() + shadowKtFileBuilder.appendRootFun( + name = "default$shadowClassName${tenant.name}", + returnType = shadowClassType, + body = listOf( + KtFileBuilder.createConstructorCall( + constructorName = shadowClassName, + initializers = shadowList.map { + """ ${it.tokenTechName.attributeName()} = ${createShadowAppearanceCall(it.layers)} - """.trimIndent() - }.toTypedArray(), - ).let { "return $it" }, - ), - description = "Возвращает [$shadowClassName]", - annotations = listOf( - KtFileBuilder.TypeAnnotationComposable, - KtFileBuilder.TypeAnnotationReadOnlyComposable, - ).takeIf { dimensionsConfig.fromResources }, - suppressAnnotations = listOf("LongMethod"), - ) + """.trimIndent() + }.toTypedArray(), + ).let { "return $it" }, + ), + description = "Возвращает [$shadowClassName]", + annotations = listOf( + KtFileBuilder.TypeAnnotationComposable, + KtFileBuilder.TypeAnnotationReadOnlyComposable, + ).takeIf { dimensionsConfig.fromResources }, + suppressAnnotations = listOf("LongMethod"), + ) + } } private companion object { diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeShapeAttributeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeShapeAttributeGenerator.kt index c59fb489e0..8716516d19 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeShapeAttributeGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeShapeAttributeGenerator.kt @@ -11,6 +11,7 @@ import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.ShapeTokenGenerator.Companion.ROUND_SHAPE_TOKENS_NAME import com.sdds.plugin.themebuilder.internal.generator.SimpleBaseGenerator import com.sdds.plugin.themebuilder.internal.generator.data.ShapeTokenResult +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.snakeToCamelCase import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy @@ -29,7 +30,7 @@ internal class ComposeShapeAttributeGenerator( private val dimensionsConfig: DimensionsConfig, private val packageResolver: PackageResolver, ) : SimpleBaseGenerator { - private val shapes = mutableListOf() + private val shapes = mutableMapOf>() private val shapeKtFileBuilder by unsafeLazy { ktFileBuilderFactory.create(shapeClassName, TargetPackage.THEME) @@ -41,17 +42,18 @@ internal class ComposeShapeAttributeGenerator( shapeKtFileBuilder.getInternalClassType(shapeClassName) } - fun setShapeTokenData(shapes: List) { + fun setShapeTokenData(shapes: Map>) { this.shapes.clear() - this.shapes.addAll(shapes) + this.shapes.putAll(shapes) } override fun generate() { if (shapes.isEmpty()) return + val defaultShapes = shapes[Tenant.Default] ?: return addImports() addShapeClassFactoryFun() - addShapesClass(shapes) + addShapesClass(defaultShapes) addLocalShapesVal() shapeKtFileBuilder.build(outputLocation) @@ -103,32 +105,41 @@ internal class ComposeShapeAttributeGenerator( ), ) addImport(KtFileBuilder.TypeRoundRectShape) - addImport( - getInternalClassType( - className = ROUND_SHAPE_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) + shapes.keys.forEach { tenant -> + addImport( + getInternalClassType( + className = "${ROUND_SHAPE_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } } } private fun addShapeClassFactoryFun() = with(shapeKtFileBuilder) { - appendRootFun( - name = "default$shapeClassName", - returnType = shapeClassType, - body = listOf( - KtFileBuilder.createConstructorCall( - constructorName = shapeClassName, - initializers = shapes.map { - "${it.attrName} = RoundShapeTokens.${it.tokenRefName}" - }.toTypedArray(), - ).let { "return $it" }, - ), - description = "Возвращает [$shapeClassName]", - annotations = listOf( - KtFileBuilder.TypeAnnotationComposable, - KtFileBuilder.TypeAnnotationReadOnlyComposable, - ).takeIf { dimensionsConfig.fromResources }, - ) + val defaultShapes = shapes[Tenant.Default] ?: return + shapes.forEach { (tenant, data) -> + val shapeList = (defaultShapes + data) + .associateBy { it.attrName } + .values + .toList() + appendRootFun( + name = "default$shapeClassName${tenant.name}", + returnType = shapeClassType, + body = listOf( + KtFileBuilder.createConstructorCall( + constructorName = shapeClassName, + initializers = shapeList.map { + "${it.attrName} = ${it.tokenObjectName}.${it.tokenRefName}" + }.toTypedArray(), + ).let { "return $it" }, + ), + description = "Возвращает [$shapeClassName]", + annotations = listOf( + KtFileBuilder.TypeAnnotationComposable, + KtFileBuilder.TypeAnnotationReadOnlyComposable, + ).takeIf { dimensionsConfig.fromResources }, + ) + } } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeSpacingAttributeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeSpacingAttributeGenerator.kt index 1af7689ad4..f1c1268b9e 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeSpacingAttributeGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeSpacingAttributeGenerator.kt @@ -11,6 +11,7 @@ import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.SimpleBaseGenerator import com.sdds.plugin.themebuilder.internal.generator.SpacingTokenGenerator.Companion.SPACING_TOKENS_NAME import com.sdds.plugin.themebuilder.internal.generator.data.SpacingTokenResult +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.snakeToCamelCase import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy @@ -29,7 +30,7 @@ internal class ComposeSpacingAttributeGenerator( private val dimensionsConfig: DimensionsConfig, private val packageResolver: PackageResolver, ) : SimpleBaseGenerator { - private val spacing = mutableListOf() + private val spacing = mutableMapOf>() private val spacingKtFileBuilder by unsafeLazy { ktFileBuilderFactory.create(spacingClassName, TargetPackage.THEME) @@ -41,17 +42,18 @@ internal class ComposeSpacingAttributeGenerator( spacingKtFileBuilder.getInternalClassType(spacingClassName) } - fun setSpacingTokenData(spacing: List) { + fun setSpacingTokenData(spacing: Map>) { this.spacing.clear() - this.spacing.addAll(spacing) + this.spacing.putAll(spacing) } override fun generate() { if (spacing.isEmpty()) return + val defaultSpacing = spacing[Tenant.Default] ?: return addImports() addSpacingClassFactoryFun() - addSpacingClass(spacing) + addSpacingClass(defaultSpacing) addLocalSpacingVal() spacingKtFileBuilder.build(outputLocation) @@ -104,32 +106,41 @@ internal class ComposeSpacingAttributeGenerator( ) addImport(KtFileBuilder.TypeDp) addImport(KtFileBuilder.TypeDpExtension) - addImport( - getInternalClassType( - className = SPACING_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) + spacing.keys.forEach { tenant -> + addImport( + getInternalClassType( + className = "${SPACING_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } } } private fun addSpacingClassFactoryFun() = with(spacingKtFileBuilder) { - appendRootFun( - name = "default$spacingClassName", - returnType = spacingClassType, - body = listOf( - KtFileBuilder.createConstructorCall( - constructorName = spacingClassName, - initializers = spacing.map { - "${it.attrName} = $SPACING_TOKENS_NAME.${it.tokenRefName}" - }.toTypedArray(), - ).let { "return $it" }, - ), - description = "Возвращает [$spacingClassName]", - annotations = listOf( - KtFileBuilder.TypeAnnotationComposable, - KtFileBuilder.TypeAnnotationReadOnlyComposable, - ).takeIf { dimensionsConfig.fromResources }, - ) + val defaultSpacing = spacing[Tenant.Default] ?: return + spacing.forEach { (tenant, data) -> + val spacingList = (defaultSpacing + data) + .associateBy { it.attrName } + .values + .toList() + appendRootFun( + name = "default$spacingClassName${tenant.name}", + returnType = spacingClassType, + body = listOf( + KtFileBuilder.createConstructorCall( + constructorName = spacingClassName, + initializers = spacingList.map { + "${it.attrName} = ${it.tokenObjectName}.${it.tokenRefName}" + }.toTypedArray(), + ).let { "return $it" }, + ), + description = "Возвращает [$spacingClassName]", + annotations = listOf( + KtFileBuilder.TypeAnnotationComposable, + KtFileBuilder.TypeAnnotationReadOnlyComposable, + ).takeIf { dimensionsConfig.fromResources }, + ) + } } } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeSubThemeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeSubThemeGenerator.kt index e2bcab46e8..5ba2afbfc5 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeSubThemeGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeSubThemeGenerator.kt @@ -10,9 +10,9 @@ import com.sdds.plugin.themebuilder.internal.generator.data.GradientTokenResult. import com.sdds.plugin.themebuilder.internal.generator.theme.OverrideToken import com.sdds.plugin.themebuilder.internal.generator.theme.SubTheme import com.sdds.plugin.themebuilder.internal.generator.theme.overriddenBySubTheme +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.ColorToken import com.sdds.plugin.themebuilder.internal.token.GradientToken -import com.sdds.plugin.themebuilder.internal.token.isDark import com.sdds.plugin.themebuilder.internal.utils.decapitalized import com.sdds.plugin.themebuilder.internal.utils.snakeToCamelCase @@ -28,16 +28,16 @@ internal class ComposeSubThemeGenerator( private val colorSubThemes: MutableMap> = mutableMapOf() private val gradientsSubThemes: MutableMap> = mutableMapOf() - private var colorTokenData: ColorTokenResult.TokenData? = null - private var gradientTokenData: ComposeTokenData? = null + private var colorTokenData: Map = emptyMap() + private var gradientTokenData: Map = emptyMap() private val themePackage = packageResolver.getPackage(TargetPackage.THEME) - fun setColorTokens(tokens: List, data: ColorTokenResult.TokenData) { + fun setColorTokens(tokens: List, data: Map) { colorTokenData = data colorSubThemes.putAll(tokens.overriddenBySubTheme()) } - fun setGradientTokens(tokens: List, data: ComposeTokenData) { + fun setGradientTokens(tokens: List, data: Map) { gradientTokenData = data gradientsSubThemes.putAll(tokens.overriddenBySubTheme()) } @@ -52,14 +52,12 @@ internal class ComposeSubThemeGenerator( addImports() val colorAttrs = colorSubThemes[subTheme] ?: emptyList() if (colorAttrs.isNotEmpty()) { - addSubThemeColorsVal(subTheme, true, colorAttrs) - addSubThemeColorsVal(subTheme, false, colorAttrs) + addSubThemeColorsVal(subTheme, colorAttrs) } val gradientAttrs = gradientsSubThemes[subTheme] ?: emptyList() if (gradientAttrs.isNotEmpty()) { - addSubThemeGradientsVal(subTheme, true, gradientAttrs) - addSubThemeGradientsVal(subTheme, false, gradientAttrs) + addSubThemeGradientsVal(subTheme, gradientAttrs) } addSubTheme(subTheme, colorAttrs.isNotEmpty(), gradientAttrs.isNotEmpty()) @@ -70,20 +68,8 @@ internal class ComposeSubThemeGenerator( private fun KtFileBuilder.addImports() { addImport("androidx.compose.foundation", listOf("isSystemInDarkTheme")) - val tokensPackage = packageResolver.getPackage(TargetPackage.TOKENS) - addImport(getInternalClassType("DarkColorTokens", tokensPackage)) - addImport(getInternalClassType("DarkGradientTokens", tokensPackage)) - addImport(getInternalClassType("LightColorTokens", tokensPackage)) - addImport(getInternalClassType("LightGradientTokens", tokensPackage)) - - addImport(getInternalClassType("dark${camelCaseThemeName}Colors", themePackage)) - addImport(getInternalClassType("light${camelCaseThemeName}Colors", themePackage)) - addImport(getInternalClassType("dark${camelCaseThemeName}Gradients", themePackage)) - addImport(getInternalClassType("light${camelCaseThemeName}Gradients", themePackage)) - addImport(getInternalClassType("linearGradient", themePackage)) - addImport(getInternalClassType("radialGradient", themePackage)) - addImport(getInternalClassType("sweepGradient", themePackage)) - addImport(getInternalClassType("singleColor", themePackage)) + addImport(getInternalClassType("Local${camelCaseThemeName}Colors", themePackage)) + addImport(getInternalClassType("Local${camelCaseThemeName}Gradients", themePackage)) } private fun KtFileBuilder.addSubTheme( @@ -113,38 +99,29 @@ internal class ComposeSubThemeGenerator( ) } - private fun getSubThemeColorsValName(isDark: Boolean, subTheme: SubTheme): String { - val prefix = if (isDark) "dark" else "light" - return "$prefix${subTheme.suffix}Colors" + private fun getSubThemeColorsValName(subTheme: SubTheme): String { + return "${subTheme.suffix}ColorsOverride" } - private fun getSubThemeGradientValName(isDark: Boolean, subTheme: SubTheme): String { - val prefix = if (isDark) "dark" else "light" - return "$prefix${subTheme.suffix}Gradients" + private fun getSubThemeGradientsValName(subTheme: SubTheme): String { + return "${subTheme.suffix}GradientsOverride" } private fun KtFileBuilder.addSubThemeGradientsVal( subTheme: SubTheme, - isDark: Boolean, attrs: List, ) { - val tokensClassName = if (isDark) "DarkGradientTokens" else "LightGradientTokens" - val builderPrefix = if (isDark) "dark" else "light" appendRootVal( - name = getSubThemeGradientValName(isDark, subTheme), - typeName = getInternalClassType( - "${camelCaseThemeName}Gradients", - packageResolver.getPackage(TargetPackage.THEME), + name = getSubThemeGradientsValName(subTheme), + typeName = KtFileBuilder.getLambdaType( + receiver = getInternalClassType(className = "GradientAttrOverrideScope", themePackage), ), modifiers = listOf(KtFileBuilder.Modifier.PRIVATE), lazy = true, initializer = buildString { - appendLine("$builderPrefix${camelCaseThemeName}Gradients {") + appendLine("{") attrs.forEach { - if (it.new.isDark == isDark) { - val valueReference = getGradientValueReference(tokensClassName, it.new.ktName, isDark) - appendLine("${it.old.ktName.decapitalized()}.overrideBy($valueReference)") - } + appendLine("${it.old.ktName.decapitalized()}.overrideBy(${it.new.ktName.decapitalized()})") } appendLine("}") }, @@ -153,109 +130,62 @@ internal class ComposeSubThemeGenerator( private fun KtFileBuilder.addSubThemeColorsVal( subTheme: SubTheme, - isDark: Boolean, attrs: List, ) { - val tokensClassName = if (isDark) "DarkColorTokens" else "LightColorTokens" - val builderPrefix = if (isDark) "dark" else "light" appendRootVal( - name = getSubThemeColorsValName(isDark, subTheme), - typeName = getInternalClassType( - "${camelCaseThemeName}Colors", - packageResolver.getPackage(TargetPackage.THEME), + name = getSubThemeColorsValName(subTheme), + typeName = KtFileBuilder.getLambdaType( + receiver = getInternalClassType(className = "ColorAttrOverrideScope", themePackage), ), modifiers = listOf(KtFileBuilder.Modifier.PRIVATE), lazy = true, initializer = buildString { - appendLine("$builderPrefix${camelCaseThemeName}Colors {") + appendLine("{") attrs.forEach { - if (it.new.isDark == isDark) { - val valueReference = getValueReference(it.new.ktName, isDark) - appendLine("${it.old.ktName.decapitalized()}.overrideBy($tokensClassName.$valueReference)") - } + appendLine("${it.old.ktName.decapitalized()}.overrideBy(${it.new.ktName.decapitalized()})") } appendLine("}") }, ) } - private fun getValueReference(attributeName: String, isDark: Boolean): String? { - return if (isDark) { - colorTokenData?.dark?.get(attributeName.decapitalized())?.colorRef - } else { - colorTokenData?.light?.get(attributeName.decapitalized())?.colorRef - } - } - - private fun getGradientValueReference(objectName: String, attributeName: String, isDark: Boolean): String? { - val params = if (isDark) { - gradientTokenData?.dark?.get(attributeName.decapitalized()) - } else { - gradientTokenData?.light?.get(attributeName.decapitalized()) - } - params ?: return null - - return KtFileBuilder.createFunCall( - "listOf", - params.map { createGradientFabricCall(objectName, it) }, - ) - } - - private fun createGradientFabricCall( - objectName: String, - gradient: ComposeTokenData.Gradient, - ): String { - val funName = when (gradient.gradientType) { - ComposeTokenData.GradientType.LINEAR -> "linearGradient" - ComposeTokenData.GradientType.RADIAL -> "radialGradient" - ComposeTokenData.GradientType.SWEEP -> "sweepGradient" - ComposeTokenData.GradientType.BACKGROUND -> "singleColor" - } - return KtFileBuilder.createFunCall( - funName = funName, - parameters = gradient.tokenRefs.map { "$objectName.$it" }, - ) - } - private fun buildDefaultSubThemeFunBody(): String = buildString { - appendLine("val colors = if (isDark) {") - appendLine("dark${camelCaseThemeName}Colors()") - appendLine("} else {") - appendLine("light${camelCaseThemeName}Colors()") - appendLine("}") - appendLine("val gradients = if (isDark) {") - appendLine("dark${camelCaseThemeName}Gradients()") - appendLine("} else {") - appendLine("light${camelCaseThemeName}Gradients()") - appendLine("}") - appendLine("$themeKtClassName(colors = colors, gradients = gradients, content = content)") + appendLine("val currentColors = Local${camelCaseThemeName}Colors.current") + appendLine("val currentGradients = Local${camelCaseThemeName}Gradients.current") + appendLine("$themeKtClassName(colors = currentColors, gradients = currentGradients, content = content)") } private fun buildSubThemeFunBody( subTheme: SubTheme, overrideColors: Boolean, - overrideGradient: Boolean, + overrideGradients: Boolean, ): String { return when (subTheme) { SubTheme.DEFAULT -> buildDefaultSubThemeFunBody() else -> buildString { if (overrideColors) { - appendLine("val colors = if (isDark) {") - appendLine(getSubThemeColorsValName(true, subTheme)) - appendLine("} else {") - appendLine(getSubThemeColorsValName(false, subTheme)) - appendLine("}") + appendLine("val currentColors = Local${camelCaseThemeName}Colors.current") + appendLine( + "val overrideColors = currentColors.copyAttrs(${ + getSubThemeColorsValName( + subTheme, + ) + })", + ) } - if (overrideGradient) { - appendLine("val gradients = if (isDark) {") - appendLine(getSubThemeGradientValName(true, subTheme)) - appendLine("} else {") - appendLine(getSubThemeGradientValName(false, subTheme)) - appendLine("}") + if (overrideGradients) { + appendLine("val currentGradients = Local${camelCaseThemeName}Gradients.current") + appendLine( + "val overrideGradients = currentGradients.copyAttrs(${ + getSubThemeGradientsValName( + subTheme, + ) + })", + ) } appendLine("$themeKtClassName(") - if (overrideColors) appendLine("colors = colors,") - if (overrideGradient) appendLine("gradients = gradients,") + if (overrideColors) appendLine("colors = overrideColors,") + if (overrideGradients) appendLine("gradients = overrideGradients,") appendLine("content = content,") appendLine(")") } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeThemeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeThemeGenerator.kt index 8f6e9a5d5f..f696755c2f 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeThemeGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeThemeGenerator.kt @@ -10,6 +10,7 @@ import com.sdds.plugin.themebuilder.internal.generator.data.ColorTokenResult import com.sdds.plugin.themebuilder.internal.generator.data.TypographyTokenResult import com.sdds.plugin.themebuilder.internal.generator.data.mergedLightAndDark import com.sdds.plugin.themebuilder.internal.generator.data.mergedScreenClasses +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.snakeToCamelCase import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy import com.squareup.kotlinpoet.ClassName @@ -230,14 +231,16 @@ internal class ComposeThemeGenerator( ) } - fun setColorTokenData(colors: ColorTokenResult.TokenData) { - val attrSet = colors.mergedLightAndDark() + fun setColorTokenData(colors: Map) { + val defaultTenantColors = colors[Tenant.Default] ?: return + val attrSet = defaultTenantColors.mergedLightAndDark() findDefaultSelectionColors(attrSet) findDefaultTextStyleColor(attrSet) } - fun setTypographyTokenData(typography: TypographyTokenResult.ComposeTokenData) { - val attrSet = typography.mergedScreenClasses() + fun setTypographyTokenData(typography: Map) { + val defaultTypography = typography[Tenant.Default] ?: return + val attrSet = defaultTypography.mergedScreenClasses() findDefaultTextStyle(attrSet) } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeTypographyAttributeGenerator.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeTypographyAttributeGenerator.kt index 8f2e61cc84..059abd6f53 100644 --- a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeTypographyAttributeGenerator.kt +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/compose/ComposeTypographyAttributeGenerator.kt @@ -14,6 +14,7 @@ import com.sdds.plugin.themebuilder.internal.generator.TypographyTokenGenerator. import com.sdds.plugin.themebuilder.internal.generator.TypographyTokenGenerator.Companion.TYPOGRAPHY_SMALL_TOKENS_NAME import com.sdds.plugin.themebuilder.internal.generator.data.TypographyTokenResult import com.sdds.plugin.themebuilder.internal.generator.data.mergedScreenClasses +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.TypographyToken.ScreenClass import com.sdds.plugin.themebuilder.internal.utils.snakeToCamelCase import com.sdds.plugin.themebuilder.internal.utils.unsafeLazy @@ -38,7 +39,7 @@ internal class ComposeTypographyAttributeGenerator( private val packageResolver: PackageResolver, ) : SimpleBaseGenerator { - private var tokenData: TypographyTokenResult.ComposeTokenData? = null + private var tokenData: Map = emptyMap() private val typographyAttributes = mutableSetOf() private val typographyKtFileBuilder by unsafeLazy { ktFileBuilderFactory.create(typographyClassName, TargetPackage.THEME) @@ -54,7 +55,7 @@ internal class ComposeTypographyAttributeGenerator( } override fun generate() { - tokenData ?: return + if (tokenData.isEmpty()) return createWindowSizeFile() addImports() @@ -69,10 +70,11 @@ internal class ComposeTypographyAttributeGenerator( typographyKtFileBuilder.build(outputLocation) } - fun setTypographyTokenData(data: TypographyTokenResult.ComposeTokenData) { + fun setTypographyTokenData(data: Map) { tokenData = data + val defaultTenantData = data[Tenant.Default] ?: return typographyAttributes.clear() - typographyAttributes.addAll(data.mergedScreenClasses()) + typographyAttributes.addAll(defaultTenantData.mergedScreenClasses()) } private fun createWindowSizeFile() { @@ -95,30 +97,31 @@ internal class ComposeTypographyAttributeGenerator( ), ) addImport(KtFileBuilder.TypeDpExtension) - val tokenData = tokenData ?: return - if (tokenData.small.isNotEmpty()) { - addImport( - getInternalClassType( - className = TYPOGRAPHY_SMALL_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) - } - if (tokenData.medium.isNotEmpty()) { - addImport( - getInternalClassType( - className = TYPOGRAPHY_MEDIUM_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) - } - if (tokenData.large.isNotEmpty()) { - addImport( - getInternalClassType( - className = TYPOGRAPHY_LARGE_TOKENS_NAME, - classPackage = packageResolver.getPackage(TargetPackage.TOKENS), - ), - ) + tokenData.forEach { (tenant, data) -> + if (data.small.isNotEmpty()) { + addImport( + getInternalClassType( + className = "${TYPOGRAPHY_SMALL_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } + if (data.medium.isNotEmpty()) { + addImport( + getInternalClassType( + className = "${TYPOGRAPHY_MEDIUM_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } + if (data.large.isNotEmpty()) { + addImport( + getInternalClassType( + className = "${TYPOGRAPHY_LARGE_TOKENS_NAME}${tenant.name}", + classPackage = packageResolver.getPackage(TargetPackage.TOKENS), + ), + ) + } } } } @@ -138,23 +141,28 @@ internal class ComposeTypographyAttributeGenerator( } private fun addDynamicTypographyFun() { - typographyKtFileBuilder.appendRootFun( - name = "dynamic$typographyClassName", - annotations = listOf(KtFileBuilder.TypeAnnotationComposable), - returnType = typographyClassType, - body = listOf( - """ + tokenData.forEach { (tenant, data) -> + val largeFunCall = "large$typographyClassName${tenant.name}()" + val mediumFunCall = "medium$typographyClassName${tenant.name}()" + val smallFunCall = "small$typographyClassName${tenant.name}()" + typographyKtFileBuilder.appendRootFun( + name = "dynamic$typographyClassName${tenant.name}", + annotations = listOf(KtFileBuilder.TypeAnnotationComposable), + returnType = typographyClassType, + body = listOf( + """ |val widthClass = collectWindowSizeInfoAsState().value.widthClass |return when (widthClass) { - | ${KtFileBuilder.DEFAULT_FILE_INDENT}WindowSizeClass.Expanded -> large$typographyClassName() - | ${KtFileBuilder.DEFAULT_FILE_INDENT}WindowSizeClass.Medium -> medium$typographyClassName() - | ${KtFileBuilder.DEFAULT_FILE_INDENT}WindowSizeClass.Compact -> small$typographyClassName() + | ${KtFileBuilder.DEFAULT_FILE_INDENT}WindowSizeClass.Expanded -> $largeFunCall + | ${KtFileBuilder.DEFAULT_FILE_INDENT}WindowSizeClass.Medium -> $mediumFunCall + | ${KtFileBuilder.DEFAULT_FILE_INDENT}WindowSizeClass.Compact -> $smallFunCall |} - """.trimMargin(), - ), - description = "Возвращает разные [$typographyClassName] в зависимости от ширины окна. " + - "Значение динамически изменяется при изменении ширины окна.", - ) + """.trimMargin(), + ), + description = "Возвращает разные [$typographyClassName] в зависимости от ширины окна. " + + "Значение динамически изменяется при изменении ширины окна.", + ) + } } private fun addBreakPointFun() { @@ -176,6 +184,7 @@ internal class ComposeTypographyAttributeGenerator( private fun addTypographyClass() { with(typographyKtFileBuilder) { + val defaultTokenData = tokenData[Tenant.Default] rootClass( name = typographyClassName, primaryConstructor = Constructor.Primary( @@ -186,7 +195,7 @@ internal class ComposeTypographyAttributeGenerator( type = KtFileBuilder.TypeTextStyle, defValue = "TextStyle.Default", asProperty = true, - description = tokenData?.description(it), + description = defaultTokenData?.description(it), ) }, modifiers = listOf(Modifier.INTERNAL), @@ -199,33 +208,43 @@ internal class ComposeTypographyAttributeGenerator( } private fun addSmallTypographyFun() { - addScreenSpecificTypographyFun( - funName = "small$typographyClassName", - screenClass = ScreenClass.SMALL, - description = "Возвращает [$typographyClassName] для WindowSizeClass.Compact", - ) + tokenData.forEach { entry -> + addScreenSpecificTypographyFun( + funName = "small$typographyClassName${entry.key.name}", + screenClass = ScreenClass.SMALL, + description = "Возвращает [$typographyClassName] для WindowSizeClass.Compact", + tenant = entry.key, + ) + } } private fun addMediumTypographyFun() { - addScreenSpecificTypographyFun( - funName = "medium$typographyClassName", - screenClass = ScreenClass.MEDIUM, - description = "Возвращает [$typographyClassName] для WindowSizeClass.Medium", - ) + tokenData.forEach { entry -> + addScreenSpecificTypographyFun( + funName = "medium$typographyClassName${entry.key.name}", + screenClass = ScreenClass.MEDIUM, + description = "Возвращает [$typographyClassName] для WindowSizeClass.Medium", + tenant = entry.key, + ) + } } private fun addLargeTypographyFun() { - addScreenSpecificTypographyFun( - funName = "large$typographyClassName", - screenClass = ScreenClass.LARGE, - description = "Возвращает [$typographyClassName] для WindowSizeClass.Expanded", - ) + tokenData.forEach { entry -> + addScreenSpecificTypographyFun( + funName = "large$typographyClassName${entry.key.name}", + screenClass = ScreenClass.LARGE, + description = "Возвращает [$typographyClassName] для WindowSizeClass.Expanded", + tenant = entry.key, + ) + } } private fun addScreenSpecificTypographyFun( funName: String, screenClass: ScreenClass, description: String, + tenant: Tenant, ) { with(typographyKtFileBuilder) { appendRootFun( @@ -235,7 +254,7 @@ internal class ComposeTypographyAttributeGenerator( KtFileBuilder.createConstructorCall( constructorName = typographyClassName, initializers = typographyAttributes.map { - "$it = ${findTypographyTokenRef(it, screenClass)}" + "$it = ${findTypographyTokenRef(it, screenClass, tenant)}" }.toTypedArray(), ).let { "return $it" }, ), @@ -248,20 +267,35 @@ internal class ComposeTypographyAttributeGenerator( } } - private fun findTypographyTokenRef(attributeName: String, screenClass: ScreenClass): String? { - val safeTokenData = tokenData ?: return null + @Suppress("CyclomaticComplexMethod") + private fun findTypographyTokenRef( + attributeName: String, + screenClass: ScreenClass, + tenant: Tenant, + ): String? { + val defaultTokenData = tokenData[Tenant.Default] ?: return null + val safeTokenData = tokenData[tenant] ?: defaultTokenData val info = when (screenClass) { ScreenClass.SMALL -> safeTokenData.small[attributeName] ?: safeTokenData.medium[attributeName] ?: safeTokenData.large[attributeName] + ?: defaultTokenData.small[attributeName] + ?: defaultTokenData.medium[attributeName] + ?: defaultTokenData.large[attributeName] ScreenClass.LARGE -> safeTokenData.large[attributeName] ?: safeTokenData.medium[attributeName] ?: safeTokenData.small[attributeName] + ?: defaultTokenData.large[attributeName] + ?: defaultTokenData.medium[attributeName] + ?: defaultTokenData.small[attributeName] else -> safeTokenData.medium[attributeName] ?: safeTokenData.small[attributeName] ?: safeTokenData.large[attributeName] + ?: defaultTokenData.medium[attributeName] + ?: defaultTokenData.small[attributeName] + ?: defaultTokenData.large[attributeName] } return info?.tokenRef } diff --git a/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/tenant/Tenant.kt b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/tenant/Tenant.kt new file mode 100644 index 0000000000..945e8aede9 --- /dev/null +++ b/sdds-core/plugin_theme_builder/src/main/kotlin/com/sdds/plugin/themebuilder/internal/tenant/Tenant.kt @@ -0,0 +1,8 @@ +package com.sdds.plugin.themebuilder.internal.tenant + +internal data class Tenant(val name: String) { + + internal companion object { + val Default: Tenant = Tenant("") + } +} diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeColorAttributeGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeColorAttributeGeneratorTest.kt index f9885b5cbe..aed506e4db 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeColorAttributeGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeColorAttributeGeneratorTest.kt @@ -6,6 +6,7 @@ import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.data.ColorTokenResult import com.sdds.plugin.themebuilder.internal.generator.theme.compose.ComposeColorAttributeGenerator +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.FileProvider import com.sdds.plugin.themebuilder.internal.utils.getResourceAsText import com.squareup.kotlinpoet.PropertySpec @@ -83,14 +84,16 @@ class ComposeColorAttributeGeneratorTest { } private companion object { - val inputData = ColorTokenResult.TokenData( - light = mapOf( - "textPrimary" to ColorTokenResult.TokenData.ColorInfo("TextPrimary"), - "textTertiary" to ColorTokenResult.TokenData.ColorInfo("TextTertiary"), - ), - dark = mapOf( - "textPrimary" to ColorTokenResult.TokenData.ColorInfo("TextPrimary"), - "textTertiary" to ColorTokenResult.TokenData.ColorInfo("TextTertiary"), + val inputData = mapOf( + Tenant.Default to ColorTokenResult.TokenData( + light = mapOf( + "textPrimary" to ColorTokenResult.TokenData.ColorInfo("TextPrimary"), + "textTertiary" to ColorTokenResult.TokenData.ColorInfo("TextTertiary"), + ), + dark = mapOf( + "textPrimary" to ColorTokenResult.TokenData.ColorInfo("TextPrimary"), + "textTertiary" to ColorTokenResult.TokenData.ColorInfo("TextTertiary"), + ), ), ) } diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeGradientAttributeGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeGradientAttributeGeneratorTest.kt index 4f798a742a..4090c5c0dc 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeGradientAttributeGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeGradientAttributeGeneratorTest.kt @@ -7,6 +7,7 @@ import com.sdds.plugin.themebuilder.internal.builder.KtFileFromResourcesBuilder import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.data.GradientTokenResult.ComposeTokenData import com.sdds.plugin.themebuilder.internal.generator.theme.compose.ComposeGradientAttributeGenerator +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.FileProvider import com.sdds.plugin.themebuilder.internal.utils.getResourceAsText import com.squareup.kotlinpoet.PropertySpec @@ -87,41 +88,46 @@ class ComposeGradientAttributeGeneratorTest { } private companion object { - val inputData = ComposeTokenData( - light = mapOf( - "textDefaultAccentGradient" to listOf( - ComposeTokenData.Gradient( - tokenRefs = listOf( - "TextDefaultAccentGradient.colors", - "TextDefaultAccentGradient.positions", - "TextDefaultAccentGradient.angle", + val inputData = mapOf( + Tenant.Default to ComposeTokenData( + light = mapOf( + "textDefaultAccentGradient" to listOf( + ComposeTokenData.Gradient( + tokenRefs = listOf( + "TextDefaultAccentGradient.colors", + "TextDefaultAccentGradient.positions", + "TextDefaultAccentGradient.angle", + ), + gradientType = ComposeTokenData.GradientType.LINEAR, + tokenObjectName = "LightGradientTokens", ), - gradientType = ComposeTokenData.GradientType.LINEAR, ), - ), - "textDefaultGradientJoyActive" to listOf( - ComposeTokenData.Gradient( - tokenRefs = listOf( - "TextDefaultGradientJoyActive.colors", - "TextDefaultGradientJoyActive.positions", - "TextDefaultGradientJoyActive.centerX", - "TextDefaultGradientJoyActive.centerY", + "textDefaultGradientJoyActive" to listOf( + ComposeTokenData.Gradient( + tokenRefs = listOf( + "TextDefaultGradientJoyActive.colors", + "TextDefaultGradientJoyActive.positions", + "TextDefaultGradientJoyActive.centerX", + "TextDefaultGradientJoyActive.centerY", + ), + gradientType = ComposeTokenData.GradientType.SWEEP, + tokenObjectName = "LightGradientTokens", ), - gradientType = ComposeTokenData.GradientType.SWEEP, ), ), - ), - dark = mapOf( - "textDefaultAccentGradient" to listOf( - ComposeTokenData.Gradient( - tokenRefs = listOf( - "TextDefaultAccentGradient.colors", - "TextDefaultAccentGradient.positions", - "TextDefaultAccentGradient.radius", - "TextDefaultAccentGradient.centerX", - "TextDefaultAccentGradient.centerY", + dark = mapOf( + "textDefaultAccentGradient" to listOf( + ComposeTokenData.Gradient( + tokenRefs = listOf( + "TextDefaultAccentGradient.colors", + "TextDefaultAccentGradient.positions", + "TextDefaultAccentGradient.radius", + "TextDefaultAccentGradient.centerX", + "TextDefaultAccentGradient.centerY", + ), + gradientType = ComposeTokenData.GradientType.RADIAL, + tokenObjectName = "DarkGradientTokens", ), - gradientType = ComposeTokenData.GradientType.RADIAL, ), ), ), diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeShapeAttributeGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeShapeAttributeGeneratorTest.kt index af73c70891..770f61b574 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeShapeAttributeGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeShapeAttributeGeneratorTest.kt @@ -7,6 +7,7 @@ import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.data.ShapeTokenResult import com.sdds.plugin.themebuilder.internal.generator.theme.compose.ComposeShapeAttributeGenerator +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.FileProvider import com.sdds.plugin.themebuilder.internal.utils.getResourceAsText import com.squareup.kotlinpoet.PropertySpec @@ -104,9 +105,19 @@ class ComposeShapeAttributeGeneratorTest { } private companion object { - val inputAttrs = listOf( - ShapeTokenResult.TokenData("roundXs", "RoundXs"), - ShapeTokenResult.TokenData("roundXxs", "RoundXxs"), + val inputAttrs = mapOf( + Tenant.Default to listOf( + ShapeTokenResult.TokenData( + attrName = "roundXs", + tokenRefName = "RoundXs", + tokenObjectName = "RoundShapeTokens", + ), + ShapeTokenResult.TokenData( + attrName = "roundXxs", + tokenRefName = "RoundXxs", + tokenObjectName = "RoundShapeTokens", + ), + ), ) } } diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeTypographyAttributeGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeTypographyAttributeGeneratorTest.kt index 3c5e88269a..f61452cdf4 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeTypographyAttributeGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/attributes/generator/ComposeTypographyAttributeGeneratorTest.kt @@ -11,6 +11,7 @@ import com.sdds.plugin.themebuilder.internal.factory.KtFileFromResourcesBuilderF import com.sdds.plugin.themebuilder.internal.generator.data.TypographyTokenResult.ComposeTokenData import com.sdds.plugin.themebuilder.internal.generator.data.TypographyTokenResult.TypographyInfo import com.sdds.plugin.themebuilder.internal.generator.theme.compose.ComposeTypographyAttributeGenerator +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.FileProvider import com.sdds.plugin.themebuilder.internal.utils.getResourceAsText import com.squareup.kotlinpoet.PropertySpec @@ -181,37 +182,43 @@ class ComposeTypographyAttributeGeneratorTest { } private companion object { - val input1 = ComposeTokenData( - small = mapOf( - "displayLNormal" to TypographyInfo("TypographySmallTokens.DisplayLNormal"), - "displayLBold" to TypographyInfo("TypographySmallTokens.DisplayLBold"), - ), - medium = mapOf( - "displayLNormal" to TypographyInfo("TypographyMediumTokens.DisplayLNormal"), - "displayLBold" to TypographyInfo("TypographyMediumTokens.DisplayLBold"), - ), - large = mapOf( - "displayLNormal" to TypographyInfo("TypographyLargeTokens.DisplayLNormal"), - "displayLBold" to TypographyInfo("TypographyLargeTokens.DisplayLBold"), + val input1 = mapOf( + Tenant.Default to ComposeTokenData( + small = mapOf( + "displayLNormal" to TypographyInfo("TypographySmallTokens.DisplayLNormal"), + "displayLBold" to TypographyInfo("TypographySmallTokens.DisplayLBold"), + ), + medium = mapOf( + "displayLNormal" to TypographyInfo("TypographyMediumTokens.DisplayLNormal"), + "displayLBold" to TypographyInfo("TypographyMediumTokens.DisplayLBold"), + ), + large = mapOf( + "displayLNormal" to TypographyInfo("TypographyLargeTokens.DisplayLNormal"), + "displayLBold" to TypographyInfo("TypographyLargeTokens.DisplayLBold"), + ), ), ) - val input2 = ComposeTokenData( - small = mapOf( - "displayLNormal" to TypographyInfo("TypographySmallTokens.DisplayLNormal"), - ), - medium = mapOf( - "displayLBold" to TypographyInfo("TypographyMediumTokens.DisplayLBold"), - ), - large = mapOf( - "displayLNormal" to TypographyInfo("TypographyLargeTokens.DisplayLNormal"), + val input2 = mapOf( + Tenant.Default to ComposeTokenData( + small = mapOf( + "displayLNormal" to TypographyInfo("TypographySmallTokens.DisplayLNormal"), + ), + medium = mapOf( + "displayLBold" to TypographyInfo("TypographyMediumTokens.DisplayLBold"), + ), + large = mapOf( + "displayLNormal" to TypographyInfo("TypographyLargeTokens.DisplayLNormal"), + ), ), ) - val input3 = ComposeTokenData( - small = emptyMap(), - medium = emptyMap(), - large = mapOf( - "displayLNormal" to TypographyInfo("TypographyLargeTokens.DisplayLNormal"), - "displayLBold" to TypographyInfo("TypographyLargeTokens.DisplayLBold"), + val input3 = mapOf( + Tenant.Default to ComposeTokenData( + small = emptyMap(), + medium = emptyMap(), + large = mapOf( + "displayLNormal" to TypographyInfo("TypographyLargeTokens.DisplayLNormal"), + "displayLBold" to TypographyInfo("TypographyLargeTokens.DisplayLBold"), + ), ), ) } diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorTokenGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorTokenGeneratorTest.kt index 20946574e8..7749796471 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorTokenGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ColorTokenGeneratorTest.kt @@ -7,6 +7,7 @@ import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilderFactory import com.sdds.plugin.themebuilder.internal.generator.theme.ThemeGenerator import com.sdds.plugin.themebuilder.internal.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.ColorToken import com.sdds.plugin.themebuilder.internal.utils.FileProvider import com.sdds.plugin.themebuilder.internal.utils.FileProvider.colorsXmlFile @@ -89,8 +90,10 @@ class ColorTokenGeneratorTest { private companion object { val colorTokenValues = mapOf( - "dark.on-light.surface.transparent-accent" to "#FFFFFF1F", - "dark.surface.transparent-accent" to "[general.green.1000]", + Tenant.Default to mapOf( + "dark.on-light.surface.transparent-accent" to "#FFFFFF1F", + "dark.surface.transparent-accent" to "[general.green.1000]", + ), ) val palette = mapOf( "green" to mapOf("1000" to "#EEEEEE1F"), diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/FontTokenGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/FontTokenGeneratorTest.kt index a4648255e8..37922c89b5 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/FontTokenGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/FontTokenGeneratorTest.kt @@ -12,6 +12,7 @@ import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.factory.XmlFontFamilyDocumentBuilderFactory import com.sdds.plugin.themebuilder.internal.fonts.FontsAggregator import com.sdds.plugin.themebuilder.internal.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.FontToken import com.sdds.plugin.themebuilder.internal.token.FontTokenValue import com.sdds.plugin.themebuilder.internal.utils.FileProvider @@ -129,43 +130,45 @@ class FontTokenGeneratorTest { private companion object { val fontTokenValues = mapOf( - "font-family.display" to FontTokenValue( - name = "SB Sans Display", - fonts = listOf( - FontToken.FontVariant( - link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + - "fonts/SBSansDisplay.0.2.0/SBSansDisplay-Regular.otf", - fontWeight = 300, - fontStyle = "normal", - ), - FontToken.FontVariant( - link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + - "fonts/SBSansDisplay.0.2.0/SBSansDisplay-Bold.otf", - fontWeight = 600, - fontStyle = "normal", + Tenant.Default to mapOf( + "font-family.display" to FontTokenValue( + name = "SB Sans Display", + fonts = listOf( + FontToken.FontVariant( + link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + + "fonts/SBSansDisplay.0.2.0/SBSansDisplay-Regular.otf", + fontWeight = 300, + fontStyle = "normal", + ), + FontToken.FontVariant( + link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + + "fonts/SBSansDisplay.0.2.0/SBSansDisplay-Bold.otf", + fontWeight = 600, + fontStyle = "normal", + ), ), ), - ), - "font-family.text" to FontTokenValue( - name = "SB Sans Text", - fonts = listOf( - FontToken.FontVariant( - link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + - "fonts/SBSansText.0.2.0/SBSansText-Regular.otf", - fontWeight = 300, - fontStyle = "normal", - ), - FontToken.FontVariant( - link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + - "fonts/SBSansText.0.2.0/SBSansText-Italic.otf", - fontWeight = 300, - fontStyle = "italic", - ), - FontToken.FontVariant( - link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + - "fonts/SBSansText.0.2.0/SBSansText-Bold.otf", - fontWeight = 600, - fontStyle = "normal", + "font-family.text" to FontTokenValue( + name = "SB Sans Text", + fonts = listOf( + FontToken.FontVariant( + link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + + "fonts/SBSansText.0.2.0/SBSansText-Regular.otf", + fontWeight = 300, + fontStyle = "normal", + ), + FontToken.FontVariant( + link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + + "fonts/SBSansText.0.2.0/SBSansText-Italic.otf", + fontWeight = 300, + fontStyle = "italic", + ), + FontToken.FontVariant( + link = "https://cdn-app.sberdevices.ru/shared-static/0.0.0/" + + "fonts/SBSansText.0.2.0/SBSansText-Bold.otf", + fontWeight = 600, + fontStyle = "normal", + ), ), ), ), diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientTokenGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientTokenGeneratorTest.kt index 628687f9fb..1bee21c46e 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientTokenGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/GradientTokenGeneratorTest.kt @@ -6,6 +6,7 @@ import com.sdds.plugin.themebuilder.internal.builder.KtFileBuilder import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilderFactory import com.sdds.plugin.themebuilder.internal.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.GradientToken import com.sdds.plugin.themebuilder.internal.token.LinearGradientTokenValue import com.sdds.plugin.themebuilder.internal.token.RadialGradientTokenValue @@ -58,7 +59,10 @@ class GradientTokenGeneratorTest { ktFileBuilderFactory = KtFileBuilderFactory(PackageResolver("com.test")), gradientTokenValues = gradientTokenValues, palette = mockk(), - resourceReferenceProvider = ResourceReferenceProvider(resourcePrefix = "thmbldr", "TestTheme"), + resourceReferenceProvider = ResourceReferenceProvider( + resourcePrefix = "thmbldr", + "TestTheme", + ), ) } @@ -100,28 +104,30 @@ class GradientTokenGeneratorTest { private companion object { val gradientTokenValues = mapOf( - "dark.inverse.surface.accent" to listOf( - SweepGradientTokenValue( - colors = listOf("#000", "#fff"), - locations = listOf(0f, 0.7f), - centerX = 0.5f, - centerY = 0.5f, + Tenant.Default to mapOf( + "dark.inverse.surface.accent" to listOf( + SweepGradientTokenValue( + colors = listOf("#000", "#fff"), + locations = listOf(0f, 0.7f), + centerX = 0.5f, + centerY = 0.5f, + ), ), - ), - "light.on-dark.surface.tertiary" to listOf( - LinearGradientTokenValue( - colors = listOf("#000", "#fff"), - locations = listOf(0f, 0.7f), - angle = 90f, + "light.on-dark.surface.tertiary" to listOf( + LinearGradientTokenValue( + colors = listOf("#000", "#fff"), + locations = listOf(0f, 0.7f), + angle = 90f, + ), ), - ), - "light.on-dark.surface.secondary" to listOf( - RadialGradientTokenValue( - colors = listOf("#000", "#fff"), - locations = listOf(0f, 0.7f), - radius = 0.8f, - centerX = 0.5f, - centerY = 0.5f, + "light.on-dark.surface.secondary" to listOf( + RadialGradientTokenValue( + colors = listOf("#000", "#fff"), + locations = listOf(0f, 0.7f), + radius = 0.8f, + centerX = 0.5f, + centerY = 0.5f, + ), ), ), ) diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowTokenGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowTokenGeneratorTest.kt index 28281b6c5a..500f6f0456 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowTokenGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShadowTokenGeneratorTest.kt @@ -7,6 +7,7 @@ import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilderFactory import com.sdds.plugin.themebuilder.internal.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.ShadowToken import com.sdds.plugin.themebuilder.internal.token.ShadowTokenValue import com.sdds.plugin.themebuilder.internal.utils.FileProvider @@ -100,20 +101,22 @@ class ShadowTokenGeneratorTest { private companion object { val shadowTokenValues = mapOf( - "down.hard.l" to listOf( - ShadowTokenValue( - color = "#00000099", - offsetX = 1.0f, - offsetY = 1.0f, - spreadRadius = 1.0f, - blurRadius = 1.0f, - ), - ShadowTokenValue( - color = "[general.black.1000]", - offsetX = 1.0f, - offsetY = 1.0f, - spreadRadius = 1.0f, - blurRadius = 1.0f, + Tenant.Default to mapOf( + "down.hard.l" to listOf( + ShadowTokenValue( + color = "#00000099", + offsetX = 1.0f, + offsetY = 1.0f, + spreadRadius = 1.0f, + blurRadius = 1.0f, + ), + ShadowTokenValue( + color = "[general.black.1000]", + offsetX = 1.0f, + offsetY = 1.0f, + spreadRadius = 1.0f, + blurRadius = 1.0f, + ), ), ), ) diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeTokenGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeTokenGeneratorTest.kt index f4436bf1fd..800c721a16 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeTokenGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/ShapeTokenGeneratorTest.kt @@ -10,6 +10,7 @@ import com.sdds.plugin.themebuilder.internal.dimens.DimensAggregator import com.sdds.plugin.themebuilder.internal.factory.KtFileBuilderFactory import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilderFactory import com.sdds.plugin.themebuilder.internal.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.RoundedShapeTokenValue import com.sdds.plugin.themebuilder.internal.token.ShapeToken import com.sdds.plugin.themebuilder.internal.utils.FileProvider @@ -116,9 +117,11 @@ class ShapeTokenGeneratorTest { private companion object { val shapeTokenValues = mapOf( - "round.xs" to RoundedShapeTokenValue(cornerRadius = 6.0f), - "round.s" to RoundedShapeTokenValue(cornerRadius = 8.0f), - "round.l" to RoundedShapeTokenValue(cornerRadius = 16.0f), + Tenant.Default to mapOf( + "round.xs" to RoundedShapeTokenValue(cornerRadius = 6.0f), + "round.s" to RoundedShapeTokenValue(cornerRadius = 8.0f), + "round.l" to RoundedShapeTokenValue(cornerRadius = 16.0f), + ), ) } } diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/SpacingTokenGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/SpacingTokenGeneratorTest.kt index d2593c02a3..df1169e5b9 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/SpacingTokenGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/SpacingTokenGeneratorTest.kt @@ -7,6 +7,7 @@ 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.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.SpacingToken import com.sdds.plugin.themebuilder.internal.token.SpacingTokenValue import com.sdds.plugin.themebuilder.internal.utils.FileProvider @@ -92,7 +93,10 @@ class SpacingTokenGeneratorTest { ), result.viewTokens.map { it.tokenRefName }, ) - assertEquals(getResourceAsText("spacing-outputs/TestSpacingOutputKt.txt"), outputKt.toString()) + assertEquals( + getResourceAsText("spacing-outputs/TestSpacingOutputKt.txt"), + outputKt.toString(), + ) } @Test @@ -109,14 +113,19 @@ class SpacingTokenGeneratorTest { spacingTokens.forEach { underTest.addToken(it) } underTest.generate() - assertEquals(getResourceAsText("spacing-outputs/TestSpacingFromResourceOutputKt.txt"), outputKt.toString()) + assertEquals( + getResourceAsText("spacing-outputs/TestSpacingFromResourceOutputKt.txt"), + outputKt.toString(), + ) } private companion object { val spacingTokenValues = mapOf( - "spacing.0x" to SpacingTokenValue(0f), - "spacing.1x" to SpacingTokenValue(2f), - "spacing.2x" to SpacingTokenValue(4f), + Tenant.Default to mapOf( + "spacing.0x" to SpacingTokenValue(0f), + "spacing.1x" to SpacingTokenValue(2f), + "spacing.2x" to SpacingTokenValue(4f), + ), ) } } diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyTokenGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyTokenGeneratorTest.kt index 0f447778cf..63fc71c5a0 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyTokenGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/TypographyTokenGeneratorTest.kt @@ -10,6 +10,7 @@ import com.sdds.plugin.themebuilder.internal.factory.XmlResourcesDocumentBuilder import com.sdds.plugin.themebuilder.internal.fonts.FontData import com.sdds.plugin.themebuilder.internal.fonts.FontsAggregator import com.sdds.plugin.themebuilder.internal.serializer.Serializer +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.token.TypographyToken import com.sdds.plugin.themebuilder.internal.token.TypographyTokenValue import com.sdds.plugin.themebuilder.internal.utils.FileProvider @@ -184,53 +185,55 @@ class TypographyTokenGeneratorTest { ) val typographyTokenValues = mapOf( - "screen-l.display.l.normal" to TypographyTokenValue( - fontFamilyRef = "fontFamily.sans", - fontWeight = 300, - fontStyle = "normal", - textSize = 128f, - letterSpacing = 0.02f, - lineHeight = 128f, - ), - "screen-l.display.l.bold" to TypographyTokenValue( - fontFamilyRef = "fontFamily.sans", - fontWeight = 300, - fontStyle = "normal", - textSize = 128f, - letterSpacing = 0.02f, - lineHeight = 128f, - ), - "screen-s.text.l.normal" to TypographyTokenValue( - fontFamilyRef = "fontFamily.sans", - fontWeight = 400, - fontStyle = "normal", - textSize = 124f, - letterSpacing = 0.04f, - lineHeight = 124f, - ), - "screen-m.header.l.normal" to TypographyTokenValue( - fontFamilyRef = "fontFamily.sans", - fontWeight = 400, - fontStyle = "normal", - textSize = 124f, - letterSpacing = 0.04f, - lineHeight = 124f, - ), - "screen-m.display.l.normal" to TypographyTokenValue( - fontFamilyRef = "fontFamily.sans", - fontWeight = 300, - fontStyle = "normal", - textSize = 96f, - letterSpacing = 0.02f, - lineHeight = 96f, - ), - "screen-s.display.l.normal" to TypographyTokenValue( - fontFamilyRef = "fontFamily.sans", - fontWeight = 300, - fontStyle = "normal", - textSize = 72f, - letterSpacing = 0.02f, - lineHeight = 72f, + Tenant.Default to mapOf( + "screen-l.display.l.normal" to TypographyTokenValue( + fontFamilyRef = "fontFamily.sans", + fontWeight = 300, + fontStyle = "normal", + textSize = 128f, + letterSpacing = 0.02f, + lineHeight = 128f, + ), + "screen-l.display.l.bold" to TypographyTokenValue( + fontFamilyRef = "fontFamily.sans", + fontWeight = 300, + fontStyle = "normal", + textSize = 128f, + letterSpacing = 0.02f, + lineHeight = 128f, + ), + "screen-s.text.l.normal" to TypographyTokenValue( + fontFamilyRef = "fontFamily.sans", + fontWeight = 400, + fontStyle = "normal", + textSize = 124f, + letterSpacing = 0.04f, + lineHeight = 124f, + ), + "screen-m.header.l.normal" to TypographyTokenValue( + fontFamilyRef = "fontFamily.sans", + fontWeight = 400, + fontStyle = "normal", + textSize = 124f, + letterSpacing = 0.04f, + lineHeight = 124f, + ), + "screen-m.display.l.normal" to TypographyTokenValue( + fontFamilyRef = "fontFamily.sans", + fontWeight = 300, + fontStyle = "normal", + textSize = 96f, + letterSpacing = 0.02f, + lineHeight = 96f, + ), + "screen-s.display.l.normal" to TypographyTokenValue( + fontFamilyRef = "fontFamily.sans", + fontWeight = 300, + fontStyle = "normal", + textSize = 72f, + letterSpacing = 0.02f, + lineHeight = 72f, + ), ), ) } diff --git a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/ComposeThemeGeneratorTest.kt b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/ComposeThemeGeneratorTest.kt index a506e82b1c..3f6eb74a98 100644 --- a/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/ComposeThemeGeneratorTest.kt +++ b/sdds-core/plugin_theme_builder/src/test/kotlin/com/sdds/plugin/themebuilder/internal/generator/theme/ComposeThemeGeneratorTest.kt @@ -8,6 +8,7 @@ import com.sdds.plugin.themebuilder.internal.generator.data.ColorTokenResult.Tok import com.sdds.plugin.themebuilder.internal.generator.data.TypographyTokenResult import com.sdds.plugin.themebuilder.internal.generator.data.TypographyTokenResult.TypographyInfo import com.sdds.plugin.themebuilder.internal.generator.theme.compose.ComposeThemeGenerator +import com.sdds.plugin.themebuilder.internal.tenant.Tenant import com.sdds.plugin.themebuilder.internal.utils.FileProvider import com.sdds.plugin.themebuilder.internal.utils.getResourceAsText import com.squareup.kotlinpoet.PropertySpec @@ -34,7 +35,8 @@ class ComposeThemeGeneratorTest { TypeSpec, FileProvider, ) - ktFileBuilderFactory = KtFileBuilderFactory(PackageResolver("com.sdds.playground.themebuilder")) + ktFileBuilderFactory = + KtFileBuilderFactory(PackageResolver("com.sdds.playground.themebuilder")) } @After @@ -75,12 +77,14 @@ class ComposeThemeGeneratorTest { themeName = "Test", DefaultThemeTypography.DYNAMIC, ) - underTest.setColorTokenData(TokenData(emptyMap(), emptyMap())) + underTest.setColorTokenData(mapOf(Tenant.Default to TokenData(emptyMap(), emptyMap()))) underTest.setTypographyTokenData( - TypographyTokenResult.ComposeTokenData( - emptyMap(), - emptyMap(), - emptyMap(), + mapOf( + Tenant.Default to TypographyTokenResult.ComposeTokenData( + emptyMap(), + emptyMap(), + emptyMap(), + ), ), ) underTest.generate() @@ -92,24 +96,28 @@ class ComposeThemeGeneratorTest { } private companion object { - val colorAttrsWithDefaultColors = TokenData( - light = mapOf( - "textPrimary" to TokenData.ColorInfo("TextPrimary"), - "textDefaultAccent" to TokenData.ColorInfo("TextDefaultAccent"), - ), - dark = mapOf( - "textPrimary" to TokenData.ColorInfo("TextPrimary"), - "textDefaultAccent" to TokenData.ColorInfo("TextDefaultAccent"), + val colorAttrsWithDefaultColors = mapOf( + Tenant.Default to TokenData( + light = mapOf( + "textPrimary" to TokenData.ColorInfo("TextPrimary"), + "textDefaultAccent" to TokenData.ColorInfo("TextDefaultAccent"), + ), + dark = mapOf( + "textPrimary" to TokenData.ColorInfo("TextPrimary"), + "textDefaultAccent" to TokenData.ColorInfo("TextDefaultAccent"), + ), ), ) - val typographyAttrs = TypographyTokenResult.ComposeTokenData( - small = emptyMap(), - medium = mapOf( - "headerH3Bold" to TypographyInfo("TypographyMediumTokens.HeaderH3Bold"), - "bodyMNormal" to TypographyInfo("TypographyMediumTokens.BodyMNormal"), + val typographyAttrs = mapOf( + Tenant.Default to TypographyTokenResult.ComposeTokenData( + small = emptyMap(), + medium = mapOf( + "headerH3Bold" to TypographyInfo("TypographyMediumTokens.HeaderH3Bold"), + "bodyMNormal" to TypographyInfo("TypographyMediumTokens.BodyMNormal"), + ), + large = emptyMap(), ), - large = emptyMap(), ) } } diff --git a/sdds-core/plugin_theme_builder/src/test/resources/attrs-outputs/ColorsOutputKt.txt b/sdds-core/plugin_theme_builder/src/test/resources/attrs-outputs/ColorsOutputKt.txt index db8669754c..6e506241a5 100644 --- a/sdds-core/plugin_theme_builder/src/test/resources/attrs-outputs/ColorsOutputKt.txt +++ b/sdds-core/plugin_theme_builder/src/test/resources/attrs-outputs/ColorsOutputKt.txt @@ -10,6 +10,7 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.ui.graphics.Color +import com.sdds.playground.themebuilder.theme.ColorAttrOverrideScope import com.sdds.playground.themebuilder.theme.ColorOverrideScope import com.sdds.playground.themebuilder.theme.ThemeColors import com.sdds.playground.themebuilder.tokens.DarkColorTokens @@ -40,6 +41,16 @@ public class ThemeColors( val overrideMap = colorOverrideScope.overrideMap return ThemeColors(colors.mapValues { overrideMap[it.key] ?: it.value }) } + + /** + * Возвращает копию [ThemeColors]. Предоставляет возможность переопределять цвета. + */ + internal fun copyAttrs(overrideColors: ColorAttrOverrideScope.() -> Unit = {}): ThemeColors { + val colorOverrideScope = ColorAttrOverrideScope() + overrideColors.invoke(colorOverrideScope) + val overrideMap = colorOverrideScope.overrideMap + return ThemeColors(colors.mapValues { colors[overrideMap[it.key]] ?: it.value }) + } } /** @@ -63,6 +74,27 @@ public class ColorOverrideScope { } } +/** + * Скоуп переопределения цветов по арибутам + */ +internal class ColorAttrOverrideScope { + private val _overrideMap: MutableMap = mutableMapOf() + + internal val overrideMap: Map + get() = _overrideMap.toMap() + + public val textPrimary: String = "textPrimary" + + public val textTertiary: String = "textTertiary" + + /** + * Переопределяет аттрибут цвета. + */ + public infix fun String.overrideBy(color: String): Unit { + _overrideMap[this] = color + } +} + internal val LocalThemeColors: ProvidableCompositionLocal = staticCompositionLocalOf { lightThemeColors() } diff --git a/sdds-core/plugin_theme_builder/src/test/resources/attrs-outputs/GradientsOutputKt.txt b/sdds-core/plugin_theme_builder/src/test/resources/attrs-outputs/GradientsOutputKt.txt index 5749e12312..fd6ce70f03 100644 --- a/sdds-core/plugin_theme_builder/src/test/resources/attrs-outputs/GradientsOutputKt.txt +++ b/sdds-core/plugin_theme_builder/src/test/resources/attrs-outputs/GradientsOutputKt.txt @@ -12,6 +12,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ShaderBrush import com.sdds.compose.uikit.graphics.Gradients +import com.sdds.playground.themebuilder.theme.GradientAttrOverrideScope import com.sdds.playground.themebuilder.theme.GradientOverrideScope import com.sdds.playground.themebuilder.theme.ThemeGradients import com.sdds.playground.themebuilder.tokens.DarkGradientTokens @@ -45,6 +46,17 @@ public class ThemeGradients( val overrideMap = gradientOverrideScope.overrideMap return ThemeGradients(gradients.mapValues { overrideMap[it.key] ?: it.value }) } + + /** + * Возвращает копию [ThemeGradients]. Предоставляет возможность переопределять цвета. + */ + internal fun copyAttrs(overrideGradients: GradientAttrOverrideScope.() -> Unit = {}): + ThemeGradients { + val gradientOverrideScope = GradientAttrOverrideScope() + overrideGradients.invoke(gradientOverrideScope) + val overrideMap = gradientOverrideScope.overrideMap + return ThemeGradients(gradients.mapValues { gradients[overrideMap[it.key]] ?: it.value }) + } } /** @@ -68,6 +80,27 @@ public class GradientOverrideScope { } } +/** + * Скоуп переопределения градиентов + */ +public class GradientAttrOverrideScope { + private val _overrideMap: MutableMap = mutableMapOf() + + internal val overrideMap: Map + get() = _overrideMap.toMap() + + public val textDefaultAccentGradient: String = "textDefaultAccentGradient" + + public val textDefaultGradientJoyActive: String = "textDefaultGradientJoyActive" + + /** + * Переопределяет аттрибут градиента. + */ + public infix fun String.overrideBy(gradient: String): Unit { + _overrideMap[this] = gradient + } +} + internal val LocalThemeGradients: ProvidableCompositionLocal = staticCompositionLocalOf { lightThemeGradients()