From 6c38a4ae9a46887d83251ba4516ec1417f4a702d Mon Sep 17 00:00:00 2001 From: Shishkin Pavel Date: Tue, 30 Jan 2024 17:18:33 +0100 Subject: [PATCH 01/30] update compiler plugin version to 1.5.8.1-beta01 (#4203) --- .../org/jetbrains/compose/ComposeCompilerCompatibility.kt | 2 +- gradle-plugins/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerCompatibility.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerCompatibility.kt index bf89e2b68f..f5e4a24005 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerCompatibility.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerCompatibility.kt @@ -22,7 +22,7 @@ internal object ComposeCompilerCompatibility { "1.9.20-RC2" to "1.5.3-rc01", "1.9.20" to "1.5.3", "1.9.21" to "1.5.4", - "1.9.22" to "1.5.8-beta01", + "1.9.22" to "1.5.8.1-beta01", "2.0.0-Beta1" to "1.5.4-dev1-kt2.0.0-Beta1", ) diff --git a/gradle-plugins/gradle.properties b/gradle-plugins/gradle.properties index eb6dcae08f..90f339f4d7 100644 --- a/gradle-plugins/gradle.properties +++ b/gradle-plugins/gradle.properties @@ -4,7 +4,7 @@ kotlin.code.style=official # Default version of Compose Libraries used by Gradle plugin compose.version=1.6.0-dev1383 # The latest version of Compose Compiler used by Gradle plugin. Used only in tests/CI. -compose.tests.compiler.version=1.5.8-beta01 +compose.tests.compiler.version=1.5.8.1-beta01 # The latest version of Kotlin compatible with compose.tests.compiler.version. Used only in tests/CI. compose.tests.compiler.compatible.kotlin.version=1.9.22 # The latest version of Kotlin compatible with compose.tests.compiler.version for JS target. Used only on CI. From b4881ffe010673ede358e7e5c9edfdf32c0aecff Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 30 Jan 2024 21:02:36 +0100 Subject: [PATCH 02/30] Fix native xml parser and add ios native tests (#4207) Co-authored-by: Da Risk --- components/resources/library/build.gradle.kts | 4 + .../compose/resources/ComposeResourceTest.kt} | 28 ++- .../src/commonTest/resources/strings.xml | 1 + .../resources/ComposeResourceTest.desktop.kt | 169 ------------------ .../resources/vector/xmldom/DomXmlParser.kt | 4 +- components/test.sh | 6 +- 6 files changed, 21 insertions(+), 191 deletions(-) rename components/resources/library/src/{androidInstrumentedTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.android.kt => commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt} (90%) delete mode 100644 components/resources/library/src/desktopTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.desktop.kt diff --git a/components/resources/library/build.gradle.kts b/components/resources/library/build.gradle.kts index 56c2eaad08..4834fb33e5 100644 --- a/components/resources/library/build.gradle.kts +++ b/components/resources/library/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.compose.ExperimentalComposeLibrary import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl @@ -76,6 +77,9 @@ kotlin { dependencies { implementation(libs.kotlinx.coroutines.test) implementation(kotlin("test")) + implementation(compose.material3) + @OptIn(ExperimentalComposeLibrary::class) + implementation(compose.uiTest) } } val blockingMain by creating { diff --git a/components/resources/library/src/androidInstrumentedTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.android.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt similarity index 90% rename from components/resources/library/src/androidInstrumentedTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.android.kt rename to components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt index 6783007f16..206610ccdb 100644 --- a/components/resources/library/src/androidInstrumentedTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.android.kt +++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt @@ -6,23 +6,14 @@ import androidx.compose.runtime.* import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.runComposeUiTest import kotlinx.coroutines.flow.MutableStateFlow -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith +import kotlin.test.* @OptIn(ExperimentalTestApi::class, ExperimentalResourceApi::class, InternalResourceApi::class) class ComposeResourceTest { - @Before - fun dropCaches() { + init { dropStringsCache() dropImageCache() - } - - @Before - fun configureTestEnvironment() { getResourceEnvironment = ::getTestEnvironment } @@ -111,6 +102,10 @@ class ComposeResourceTest { "Compose Resources App", stringResource(TestStringResource("app_name")) ) + assertEquals( + "Créer une table", + stringResource(TestStringResource("accentuated_characters")) + ) assertEquals( "Hello, test-name! You have 42 new messages.", stringResource(TestStringResource("str_template"), "test-name", 42) @@ -127,12 +122,12 @@ class ComposeResourceTest { @Test fun testLoadStringResource() = runBlockingTest { - kotlin.test.assertEquals("Compose Resources App", getString(TestStringResource("app_name"))) - kotlin.test.assertEquals( + assertEquals("Compose Resources App", getString(TestStringResource("app_name"))) + assertEquals( "Hello, test-name! You have 42 new messages.", getString(TestStringResource("str_template"), "test-name", 42) ) - kotlin.test.assertEquals(listOf("item 1", "item 2", "item 3"), getStringArray(TestStringResource("str_arr"))) + assertEquals(listOf("item 1", "item 2", "item 3"), getStringArray(TestStringResource("str_arr"))) } @Test @@ -143,17 +138,18 @@ class ComposeResourceTest { val error = assertFailsWith { getString(TestStringResource("unknown_id")) } - kotlin.test.assertEquals("String ID=`unknown_id` is not found!", error.message) + assertEquals("String ID=`unknown_id` is not found!", error.message) } @Test fun testReadFileResource() = runBlockingTest { val bytes = readResourceBytes("strings.xml") - kotlin.test.assertEquals( + assertEquals( """ Compose Resources App 😊 Hello world! + Créer une table Hello, %1${'$'}s! You have %2${'$'}d new messages. item 1 diff --git a/components/resources/library/src/commonTest/resources/strings.xml b/components/resources/library/src/commonTest/resources/strings.xml index fd2f9a9bca..2f19269c97 100644 --- a/components/resources/library/src/commonTest/resources/strings.xml +++ b/components/resources/library/src/commonTest/resources/strings.xml @@ -1,6 +1,7 @@ Compose Resources App 😊 Hello world! + Créer une table Hello, %1$s! You have %2$d new messages. item 1 diff --git a/components/resources/library/src/desktopTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.desktop.kt b/components/resources/library/src/desktopTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.desktop.kt deleted file mode 100644 index 6783007f16..0000000000 --- a/components/resources/library/src/desktopTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.desktop.kt +++ /dev/null @@ -1,169 +0,0 @@ -package org.jetbrains.compose.resources - -import androidx.compose.foundation.Image -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.test.ExperimentalTestApi -import androidx.compose.ui.test.runComposeUiTest -import kotlinx.coroutines.flow.MutableStateFlow -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -@OptIn(ExperimentalTestApi::class, ExperimentalResourceApi::class, InternalResourceApi::class) -class ComposeResourceTest { - - @Before - fun dropCaches() { - dropStringsCache() - dropImageCache() - } - - @Before - fun configureTestEnvironment() { - getResourceEnvironment = ::getTestEnvironment - } - - @Test - fun testCountRecompositions() = runComposeUiTest { - runBlockingTest { - val imagePathFlow = MutableStateFlow(DrawableResource("1.png")) - val recompositionsCounter = RecompositionsCounter() - setContent { - CompositionLocalProvider(LocalComposeEnvironment provides TestComposeEnvironment) { - val res by imagePathFlow.collectAsState() - val imgRes = imageResource(res) - recompositionsCounter.content { - Image(bitmap = imgRes, contentDescription = null) - } - } - } - awaitIdle() - imagePathFlow.emit(DrawableResource("2.png")) - awaitIdle() - assertEquals(2, recompositionsCounter.count) - } - } - - @Test - fun testImageResourceCache() = runComposeUiTest { - runBlockingTest { - val testResourceReader = TestResourceReader() - val imagePathFlow = MutableStateFlow(DrawableResource("1.png")) - setContent { - CompositionLocalProvider( - LocalResourceReader provides testResourceReader, - LocalComposeEnvironment provides TestComposeEnvironment - ) { - val res by imagePathFlow.collectAsState() - Image(painterResource(res), null) - } - } - awaitIdle() - imagePathFlow.emit(DrawableResource("2.png")) - awaitIdle() - imagePathFlow.emit(DrawableResource("1.png")) - awaitIdle() - - assertEquals( - expected = listOf("1.png", "2.png"), //no second read of 1.png - actual = testResourceReader.readPaths - ) - } - } - - @Test - fun testStringResourceCache() = runComposeUiTest { - runBlockingTest { - val testResourceReader = TestResourceReader() - val stringIdFlow = MutableStateFlow(TestStringResource("app_name")) - setContent { - CompositionLocalProvider( - LocalResourceReader provides testResourceReader, - LocalComposeEnvironment provides TestComposeEnvironment - ) { - val res by stringIdFlow.collectAsState() - Text(stringResource(res)) - Text(stringArrayResource(TestStringResource("str_arr")).joinToString()) - } - } - awaitIdle() - stringIdFlow.emit(TestStringResource("hello")) - awaitIdle() - stringIdFlow.emit(TestStringResource("app_name")) - awaitIdle() - - assertEquals( - expected = listOf("strings.xml"), //just one string.xml read - actual = testResourceReader.readPaths - ) - } - } - - @Test - fun testReadStringResource() = runComposeUiTest { - runBlockingTest { - setContent { - CompositionLocalProvider(LocalComposeEnvironment provides TestComposeEnvironment) { - assertEquals( - "Compose Resources App", - stringResource(TestStringResource("app_name")) - ) - assertEquals( - "Hello, test-name! You have 42 new messages.", - stringResource(TestStringResource("str_template"), "test-name", 42) - ) - assertEquals( - listOf("item 1", "item 2", "item 3"), - stringArrayResource(TestStringResource("str_arr")) - ) - } - } - awaitIdle() - } - } - - @Test - fun testLoadStringResource() = runBlockingTest { - kotlin.test.assertEquals("Compose Resources App", getString(TestStringResource("app_name"))) - kotlin.test.assertEquals( - "Hello, test-name! You have 42 new messages.", - getString(TestStringResource("str_template"), "test-name", 42) - ) - kotlin.test.assertEquals(listOf("item 1", "item 2", "item 3"), getStringArray(TestStringResource("str_arr"))) - } - - @Test - fun testMissingResource() = runBlockingTest { - assertFailsWith { - readResourceBytes("missing.png") - } - val error = assertFailsWith { - getString(TestStringResource("unknown_id")) - } - kotlin.test.assertEquals("String ID=`unknown_id` is not found!", error.message) - } - - @Test - fun testReadFileResource() = runBlockingTest { - val bytes = readResourceBytes("strings.xml") - kotlin.test.assertEquals( - """ - - Compose Resources App - 😊 Hello world! - Hello, %1${'$'}s! You have %2${'$'}d new messages. - - item 1 - item 2 - item 3 - - - - """.trimIndent(), - bytes.decodeToString() - ) - } -} diff --git a/components/resources/library/src/nativeMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/DomXmlParser.kt b/components/resources/library/src/nativeMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/DomXmlParser.kt index bdc7aeda03..3d2a94a47b 100644 --- a/components/resources/library/src/nativeMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/DomXmlParser.kt +++ b/components/resources/library/src/nativeMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/DomXmlParser.kt @@ -94,7 +94,9 @@ private class DomXmlParser : NSObject(), NSXMLParserDelegateProtocol { } override fun parser(parser: NSXMLParser, foundCharacters: String) { - nodeStack.lastOrNull()?.textContent = foundCharacters + nodeStack.lastOrNull()?.let { node -> + node.textContent = node.textContent.orEmpty() + foundCharacters + } } override fun parser( diff --git a/components/test.sh b/components/test.sh index a16a91fcc0..03985cc26e 100755 --- a/components/test.sh +++ b/components/test.sh @@ -2,10 +2,6 @@ cd "$(dirname "$0")" # Run always in current dir set -euo pipefail # Fail fast -# Unit tests -./gradlew :resources:library:test ./gradlew :resources:library:desktopTest - -# Android integration tests ./gradlew :resources:library:pixel5DebugAndroidTest - +./gradlew :resources:library:iosSimulatorArm64Test From b1e86ade3607d4e9a7e65c93e61e21d9574311b1 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 31 Jan 2024 12:41:38 +0100 Subject: [PATCH 03/30] Generate initializer functions in the Res file to avoid the MethodTooLargeException (#4205) --- .../compose/resources/ResourcesSpec.kt | 155 ++++++++------ .../test/tests/integration/ResourcesTest.kt | 87 +++++++- .../misc/commonResources/expected/Res.kt | 194 ++++++++---------- .../misc/emptyResources/expected/Res.kt | 6 +- .../misc/jvmOnlyResources/expected/Res.kt | 26 +-- 5 files changed, 290 insertions(+), 178 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt index c39ea711d0..9527898c8a 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt @@ -2,6 +2,8 @@ package org.jetbrains.compose.resources import com.squareup.kotlinpoet.* import java.nio.file.Path +import java.util.SortedMap +import java.util.TreeMap import kotlin.io.path.invariantSeparatorsPathString internal enum class ResourceType(val typeName: String) { @@ -26,12 +28,14 @@ internal data class ResourceItem( val path: Path ) -private fun ResourceItem.getClassName(): ClassName = when (type) { +private fun ResourceType.getClassName(): ClassName = when (this) { ResourceType.DRAWABLE -> ClassName("org.jetbrains.compose.resources", "DrawableResource") ResourceType.STRING -> ClassName("org.jetbrains.compose.resources", "StringResource") ResourceType.FONT -> ClassName("org.jetbrains.compose.resources", "FontResource") } +private val resourceItemClass = ClassName("org.jetbrains.compose.resources", "ResourceItem") + private fun CodeBlock.Builder.addQualifiers(resourceItem: ResourceItem): CodeBlock.Builder { val languageQualifier = ClassName("org.jetbrains.compose.resources", "LanguageQualifier") val regionQualifier = ClassName("org.jetbrains.compose.resources", "RegionQualifier") @@ -101,85 +105,118 @@ internal fun getResFileSpec( //type -> id -> items resources: Map>>, packageName: String -): FileSpec = FileSpec.builder(packageName, "Res").apply { - addType(TypeSpec.objectBuilder("Res").apply { - addModifiers(KModifier.INTERNAL) +): FileSpec = + FileSpec.builder(packageName, "Res").apply { addAnnotation( AnnotationSpec.builder(ClassName("kotlin", "OptIn")) .addMember("org.jetbrains.compose.resources.InternalResourceApi::class") - .build() - ) - addAnnotation( - AnnotationSpec.builder(ClassName("org.jetbrains.compose.resources", "ExperimentalResourceApi")) + .addMember("org.jetbrains.compose.resources.ExperimentalResourceApi::class") .build() ) - //readFileBytes - val readResourceBytes = MemberName("org.jetbrains.compose.resources", "readResourceBytes") - addFunction( - FunSpec.builder("readBytes") - .addKdoc(""" + //we need to sort it to generate the same code on different platforms + val sortedResources = sortResources(resources) + + addType(TypeSpec.objectBuilder("Res").apply { + addModifiers(KModifier.INTERNAL) + addAnnotation( + AnnotationSpec.builder( + ClassName("org.jetbrains.compose.resources", "ExperimentalResourceApi") + ).build() + ) + + //readFileBytes + val readResourceBytes = MemberName("org.jetbrains.compose.resources", "readResourceBytes") + addFunction( + FunSpec.builder("readBytes") + .addKdoc( + """ Reads the content of the resource file at the specified path and returns it as a byte array. Example: `val bytes = Res.readBytes("files/key.bin")` @param path The path of the file to read in the compose resource's directory. @return The content of the file as a byte array. - """.trimIndent()) - .addParameter("path", String::class) - .addModifiers(KModifier.SUSPEND) - .returns(ByteArray::class) - .addStatement("return %M(path)", readResourceBytes) //todo: add module ID here - .build() - ) + """.trimIndent() + ) + .addParameter("path", String::class) + .addModifiers(KModifier.SUSPEND) + .returns(ByteArray::class) + .addStatement("return %M(path)", readResourceBytes) //todo: add module ID here + .build() + ) + val types = sortedResources.map { (type, idToResources) -> + getResourceTypeObject(type, idToResources) + } + addTypes(types) + }.build()) - val types = resources.map { (type, idToResources) -> - getResourceTypeObject(type, idToResources) - }.sortedBy { it.name } - addTypes(types) - }.build()) -}.build() + sortedResources + .flatMap { (type, idToResources) -> + idToResources.map { (name, items) -> + getResourceInitializer(name, type, items) + } + } + .forEach { addFunction(it) } + }.build() private fun getResourceTypeObject(type: ResourceType, nameToResources: Map>) = TypeSpec.objectBuilder(type.typeName).apply { - nameToResources.entries - .sortedBy { it.key } - .forEach { (name, items) -> - addResourceProperty(name, items.sortedBy { it.path }) + nameToResources.keys + .forEach { name -> + addProperty( + PropertySpec + .builder(name, type.getClassName()) + .initializer("get_$name()") + .build() + ) } }.build() -private fun TypeSpec.Builder.addResourceProperty(name: String, items: List) { - val resourceItemClass = ClassName("org.jetbrains.compose.resources", "ResourceItem") - - val first = items.first() - val propertyClassName = first.getClassName() - val resourceId = first.let { "${it.type}:${it.name}" } - - val initializer = CodeBlock.builder() - .add("%T(\n", propertyClassName).withIndent { - add("\"$resourceId\",\n") - if (first.type == ResourceType.STRING) { - add("\"${first.name}\",\n") - } - add("setOf(\n").withIndent { - items.forEach { item -> - add("%T(\n", resourceItemClass).withIndent { - add("setOf(").addQualifiers(item).add("),\n") - //file separator should be '/' on all platforms - add("\"${item.path.invariantSeparatorsPathString}\"\n") //todo: add module ID here +private fun getResourceInitializer(name: String, type: ResourceType, items: List): FunSpec { + val propertyTypeName = type.getClassName() + val resourceId = "${type}:${name}" + return FunSpec.builder("get_$name") + .addModifiers(KModifier.PRIVATE) + .returns(propertyTypeName) + .addStatement( + CodeBlock.builder() + .add("return %T(\n", propertyTypeName).withIndent { + add("\"$resourceId\",") + if (type == ResourceType.STRING) add(" \"$name\",") + withIndent { + add("\nsetOf(\n").withIndent { + items.forEach { item -> + add("%T(", resourceItemClass) + add("setOf(").addQualifiers(item).add("), ") + //file separator should be '/' on all platforms + add("\"${item.path.invariantSeparatorsPathString}\"") //todo: add module ID here + add("),\n") + } + } + add(")\n") } - add("),\n") } - } - add(")\n") - } - .add(")") + .add(")") + .build().toString() + ) .build() +} - addProperty( - PropertySpec.builder(name, propertyClassName) - .initializer(initializer) - .build() - ) +private fun sortResources( + resources: Map>> +): TreeMap>> { + val result = TreeMap>>() + resources + .entries + .forEach { (type, items) -> + val typeResult = TreeMap>() + items + .entries + .forEach { (name, resItems) -> + typeResult[name] = resItems.sortedBy { it.path } + } + result[type] = typeResult + } + return result } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt index e9ce9be344..18e08731cb 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt @@ -9,7 +9,7 @@ import kotlin.io.path.Path class ResourcesTest : GradlePluginTestBase() { @Test - fun testGeneratedAccessorsAndCopiedFonts(): Unit = with(testProject("misc/commonResources")) { + fun testGeneratedAccessors(): Unit = with(testProject("misc/commonResources")) { //check generated resource's accessors gradle("generateComposeResClass").checks { assertEqualTextFiles( @@ -137,4 +137,89 @@ class ResourcesTest : GradlePluginTestBase() { } gradle("jar") } + + //https://github.com/JetBrains/compose-multiplatform/issues/4194 + @Test + fun testHugeNumberOfStrings(): Unit = with( + //disable cache for the test because the generateStringFiles task doesn't support it + testProject("misc/commonResources", defaultTestEnvironment.copy(useGradleConfigurationCache = false)) + ) { + file("build.gradle.kts").let { f -> + val originText = f.readText() + f.writeText( + buildString { + appendLine("import java.util.Locale") + append(originText) + appendLine() + append(""" + val template = ""${'"'} + + Compose Resources App + 😊 Hello world! + Lorem ipsum dolor sit amet, + consectetur adipiscing elit. + Donec eget turpis ac sem ultricies consequat. + Hello, %1${'$'}{"$"}s! You have %2${'$'}{"$"}d new messages. + + item 1 + item 2 + item 3 + + [ADDITIONAL_STRINGS] + + ""${'"'}.trimIndent() + + val generateStringFiles = tasks.register("generateStringFiles") { + val numberOfLanguages = 20 + val numberOfStrings = 500 + val langs = Locale.getAvailableLocales() + .map { it.language } + .filter { it.count() == 2 } + .sorted() + .distinct() + .take(numberOfLanguages) + .toList() + + val resourcesFolder = project.file("src/commonMain/composeResources") + + doLast { + // THIS REMOVES THE `values` FOLDER IN `composeResources` + // THIS REMOVES THE `values` FOLDER IN `composeResources` + // Necessary when reducing the number of languages. + resourcesFolder.listFiles()?.filter { it.name.startsWith("values") }?.forEach { + it.deleteRecursively() + } + + langs.forEachIndexed { langIndex, lang -> + val additionalStrings = + (0 until numberOfStrings).joinToString(System.lineSeparator()) { index -> + ""${'"'} + String ${'$'}index in lang ${'$'}lang + ""${'"'}.trimIndent() + } + + val langFile = if (langIndex == 0) { + File(resourcesFolder, "values/strings.xml") + } else { + File(resourcesFolder, "values-${'$'}lang/strings.xml") + } + langFile.parentFile.mkdirs() + langFile.writeText(template.replace("[ADDITIONAL_STRINGS]", additionalStrings)) + } + } + } + + tasks.named("generateComposeResClass") { + dependsOn(generateStringFiles) + } + """.trimIndent()) + } + ) + } + gradle("desktopJar").checks { + check.taskSuccessful(":generateStringFiles") + check.taskSuccessful(":generateComposeResClass") + assert(file("src/commonMain/composeResources/values/strings.xml").readLines().size == 513) + } + } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt index 22e3aa7d47..27a8abb979 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt @@ -1,3 +1,8 @@ +@file:OptIn( + org.jetbrains.compose.resources.InternalResourceApi::class, + org.jetbrains.compose.resources.ExperimentalResourceApi::class, +) + package app.group.resources_test.generated.resources import kotlin.ByteArray @@ -6,14 +11,9 @@ import kotlin.String import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.FontResource -import org.jetbrains.compose.resources.LanguageQualifier -import org.jetbrains.compose.resources.RegionQualifier -import org.jetbrains.compose.resources.ResourceItem import org.jetbrains.compose.resources.StringResource -import org.jetbrains.compose.resources.ThemeQualifier import org.jetbrains.compose.resources.readResourceBytes -@OptIn(org.jetbrains.compose.resources.InternalResourceApi::class) @ExperimentalResourceApi internal object Res { /** @@ -27,115 +27,99 @@ internal object Res { public suspend fun readBytes(path: String): ByteArray = readResourceBytes(path) public object drawable { - public val _3_strange_name: DrawableResource = DrawableResource( - "drawable:_3_strange_name", - setOf( - ResourceItem( - setOf(), - "drawable/3-strange-name.xml" - ), - ) - ) + public val _3_strange_name: DrawableResource = get__3_strange_name() - public val vector: DrawableResource = DrawableResource( - "drawable:vector", - setOf( - ResourceItem( - setOf(LanguageQualifier("au"), RegionQualifier("US"), ), - "drawable-au-rUS/vector.xml" - ), - ResourceItem( - setOf(ThemeQualifier.DARK, LanguageQualifier("ge"), ), - "drawable-dark-ge/vector.xml" - ), - ResourceItem( - setOf(LanguageQualifier("en"), ), - "drawable-en/vector.xml" - ), - ResourceItem( - setOf(), - "drawable/vector.xml" - ), - ) - ) + public val vector: DrawableResource = get_vector() - public val vector_2: DrawableResource = DrawableResource( - "drawable:vector_2", - setOf( - ResourceItem( - setOf(), - "drawable/vector_2.xml" - ), - ) - ) + public val vector_2: DrawableResource = get_vector_2() + } + + public object string { + public val app_name: StringResource = get_app_name() + + public val hello: StringResource = get_hello() + + public val multi_line: StringResource = get_multi_line() + + public val str_arr: StringResource = get_str_arr() + + public val str_template: StringResource = get_str_template() } public object font { - public val emptyfont: FontResource = FontResource( - "font:emptyfont", - setOf( - ResourceItem( - setOf(), - "font/emptyFont.otf" - ), - ) - ) + public val emptyfont: FontResource = get_emptyfont() } +} - public object string { - public val app_name: StringResource = StringResource( - "string:app_name", - "app_name", - setOf( - ResourceItem( - setOf(), - "values/strings.xml" - ), - ) +private fun get__3_strange_name(): DrawableResource = + org.jetbrains.compose.resources.DrawableResource( + "drawable:_3_strange_name", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/3-strange-name.xml"), ) + ) - public val hello: StringResource = StringResource( - "string:hello", - "hello", - setOf( - ResourceItem( - setOf(), - "values/strings.xml" - ), - ) - ) +private fun get_vector(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( + "drawable:vector", + setOf( - public val multi_line: StringResource = StringResource( - "string:multi_line", - "multi_line", - setOf( - ResourceItem( - setOf(), - "values/strings.xml" - ), - ) - ) + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("au"), + org.jetbrains.compose.resources.RegionQualifier("US"), ), "drawable-au-rUS/vector.xml"), - public val str_arr: StringResource = StringResource( - "string:str_arr", - "str_arr", - setOf( - ResourceItem( - setOf(), - "values/strings.xml" - ), - ) - ) + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.ThemeQualifier.DARK, + org.jetbrains.compose.resources.LanguageQualifier("ge"), ), "drawable-dark-ge/vector.xml"), - public val str_template: StringResource = StringResource( - "string:str_template", - "str_template", - setOf( - ResourceItem( - setOf(), - "values/strings.xml" - ), - ) - ) - } -} \ No newline at end of file + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), + ), "drawable-en/vector.xml"), + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml"), + ) +) + +private fun get_vector_2(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( + "drawable:vector_2", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector_2.xml"), + ) +) + +private fun get_app_name(): StringResource = org.jetbrains.compose.resources.StringResource( + "string:app_name", "app_name", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) +) + +private fun get_hello(): StringResource = org.jetbrains.compose.resources.StringResource( + "string:hello", "hello", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) +) + +private fun get_multi_line(): StringResource = org.jetbrains.compose.resources.StringResource( + "string:multi_line", "multi_line", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) +) + +private fun get_str_arr(): StringResource = org.jetbrains.compose.resources.StringResource( + "string:str_arr", "str_arr", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) +) + +private fun get_str_template(): StringResource = org.jetbrains.compose.resources.StringResource( + "string:str_template", "str_template", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) +) + +private fun get_emptyfont(): FontResource = org.jetbrains.compose.resources.FontResource( + "font:emptyfont", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "font/emptyFont.otf"), + ) +) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/Res.kt index 8a954170b1..2a3770e9ae 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/Res.kt @@ -1,3 +1,8 @@ +@file:OptIn( + org.jetbrains.compose.resources.InternalResourceApi::class, + org.jetbrains.compose.resources.ExperimentalResourceApi::class, +) + package app.group.empty_res.generated.resources import kotlin.ByteArray @@ -6,7 +11,6 @@ import kotlin.String import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.readResourceBytes -@OptIn(org.jetbrains.compose.resources.InternalResourceApi::class) @ExperimentalResourceApi internal object Res { /** diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt index fda4811df5..83a5295364 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt @@ -1,3 +1,8 @@ +@file:OptIn( + org.jetbrains.compose.resources.InternalResourceApi::class, + org.jetbrains.compose.resources.ExperimentalResourceApi::class, +) + package me.app.jvmonlyresources.generated.resources import kotlin.ByteArray @@ -5,10 +10,8 @@ import kotlin.OptIn import kotlin.String import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.ExperimentalResourceApi -import org.jetbrains.compose.resources.ResourceItem import org.jetbrains.compose.resources.readResourceBytes -@OptIn(org.jetbrains.compose.resources.InternalResourceApi::class) @ExperimentalResourceApi internal object Res { /** @@ -22,14 +25,13 @@ internal object Res { public suspend fun readBytes(path: String): ByteArray = readResourceBytes(path) public object drawable { - public val vector: DrawableResource = DrawableResource( - "drawable:vector", - setOf( - ResourceItem( - setOf(), - "drawable/vector.xml" - ), - ) - ) + public val vector: DrawableResource = get_vector() } -} \ No newline at end of file +} + +private fun get_vector(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( + "drawable:vector", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml"), + ) +) \ No newline at end of file From d9d4bf34969f03600679597ff58cc48fa5b6329d Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 31 Jan 2024 18:26:34 +0600 Subject: [PATCH 04/30] Add opportunity to use custom prefixes in StyleSheet (#3015) --- .../jetbrains/compose/web/css/StyleSheet.kt | 29 +++++++---- .../src/jsTest/kotlin/css/AnimationTests.kt | 49 +++++++++++++++++++ .../src/jsTest/kotlin/css/StyleSheetTests.kt | 30 ++++++++++++ 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/html/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt b/html/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt index 64f67feb92..3c1bb048c8 100644 --- a/html/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt +++ b/html/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt @@ -20,6 +20,9 @@ class CSSRulesHolderState : CSSRulesHolder { /** * Represents a collection of the css style rules. * StyleSheet needs to be mounted. + * + * @param customPrefix Will be used as prefix with current style. Pass `null` to use default value (classname of realization) + * * @see [Style] * * Example: @@ -38,12 +41,22 @@ class CSSRulesHolderState : CSSRulesHolder { * ``` */ open class StyleSheet( + customPrefix: String?, private val rulesHolder: CSSRulesHolder = CSSRulesHolderState(), - val usePrefix: Boolean = true, ) : StyleSheetBuilder, CSSRulesHolder by rulesHolder { private val boundClasses = mutableMapOf() + protected val prefix: String = customPrefix ?: "${this::class.simpleName}-" + + val usePrefix: Boolean = customPrefix == null + constructor( + rulesHolder: CSSRulesHolder = CSSRulesHolderState(), + usePrefix: Boolean = true + ) : this( + if (usePrefix) null else "", + rulesHolder + ) - protected fun style(cssRule: CSSBuilder.() -> Unit) = CSSHolder(usePrefix, cssRule) + protected fun style(cssRule: CSSBuilder.() -> Unit) = CSSHolder(prefix, cssRule) /** * Example: @@ -69,7 +82,7 @@ open class StyleSheet( * } * ``` */ - protected fun keyframes(cssKeyframes: CSSKeyframesBuilder.() -> Unit) = CSSKeyframesHolder(usePrefix, cssKeyframes) + protected fun keyframes(cssKeyframes: CSSKeyframesBuilder.() -> Unit) = CSSKeyframesHolder(prefix, cssKeyframes) companion object { private var counter = 0 @@ -88,13 +101,12 @@ open class StyleSheet( } } - protected class CSSHolder(private val usePrefix: Boolean, private val cssBuilder: CSSBuilder.() -> Unit) { + protected class CSSHolder(private val prefix: String, private val cssBuilder: CSSBuilder.() -> Unit) { operator fun provideDelegate( sheet: StyleSheet, property: KProperty<*> ): ReadOnlyProperty { - val sheetName = if (usePrefix) "${sheet::class.simpleName}-" else "" - val className = "$sheetName${property.name}" + val className = "$prefix${property.name}" val selector = object : CSSSelector() { override fun asString() = ".${className}" } @@ -110,15 +122,14 @@ open class StyleSheet( * See [keyframes] */ protected class CSSKeyframesHolder( - private val usePrefix: Boolean, + private val prefix: String, private val keyframesBuilder: CSSKeyframesBuilder.() -> Unit ) { operator fun provideDelegate( sheet: StyleSheet, property: KProperty<*> ): ReadOnlyProperty { - val sheetName = if (usePrefix) "${sheet::class.simpleName}-" else "" - val keyframesName = "$sheetName${property.name}" + val keyframesName = "$prefix${property.name}" val rule = buildKeyframes(keyframesName, keyframesBuilder) sheet.add(rule) diff --git a/html/core/src/jsTest/kotlin/css/AnimationTests.kt b/html/core/src/jsTest/kotlin/css/AnimationTests.kt index eae861b3e0..bbdb52b914 100644 --- a/html/core/src/jsTest/kotlin/css/AnimationTests.kt +++ b/html/core/src/jsTest/kotlin/css/AnimationTests.kt @@ -35,6 +35,28 @@ object AnimationsStyleSheet : StyleSheet() { } } +class AnimationsStyleSheetWithCustomPrefix( + customPrefix: String +) : StyleSheet(customPrefix) { + val bounce by keyframes { + from { + property("transform", "translateX(50%)") + } + + to { + property("transform", "translateX(-50%)") + } + } + + val animationClass by style { + animation(bounce) { + duration(2.s) + timingFunction(AnimationTimingFunction.EaseIn) + direction(AnimationDirection.Alternate) + } + } +} + @ExperimentalComposeWebApi class AnimationTests { @Test @@ -76,4 +98,31 @@ class AnimationTests { "Animation class wasn't injected correctly" ) } + + @Test + fun animationClassInjectedWithCustomPrefix() = runTest { + val customPrefix = "CustomPrefix-" + composition { + Style(AnimationsStyleSheetWithCustomPrefix(customPrefix)) + } + + val el = nextChild() as HTMLStyleElement + val cssRules = (el.sheet as? CSSStyleSheet)?.cssRules + val rules = (0 until (cssRules?.length ?: 0)).map { + cssRules?.item(it)?.cssText?.replace("\n", "") ?: "" + } + + // TODO: we need to come up with test that not relying on any kind of formatting + assertEquals( + "@keyframes ${customPrefix}bounce {0% { transform: translateX(50%); }100% { transform: translateX(-50%); }}", + rules[0].replace(" 0%", "0%").replace(" 100%", "100%"), + "Animation keyframes wasn't injected correctly" + ) + + assertEquals( + ".${customPrefix}animationClass { animation: 2s ease-in 0s 1 alternate none running ${customPrefix}bounce; }".trimIndent(), + rules[1], + "Animation class wasn't injected correctly" + ) + } } diff --git a/html/core/src/jsTest/kotlin/css/StyleSheetTests.kt b/html/core/src/jsTest/kotlin/css/StyleSheetTests.kt index ac0278b48e..ed5ccab253 100644 --- a/html/core/src/jsTest/kotlin/css/StyleSheetTests.kt +++ b/html/core/src/jsTest/kotlin/css/StyleSheetTests.kt @@ -42,4 +42,34 @@ class StyleSheetTests { ) } + @Test + fun stylesheetCorrectlyUsingIncomingPrefix() { + val testPrefixParent = "test_prefix_parent-" + val testPrefixChild = "test_prefix_child-" + + val styleSheet = object : StyleSheet(customPrefix = testPrefixParent) { + val someClassName by style { + color(Color.red) + } + } + + val childStyleSheet = object : StyleSheet(customPrefix = testPrefixChild, styleSheet) { + val someClassName by style { + color(Color.green) + } + } + + assertContentEquals( + listOf(".${testPrefixParent}someClassName { color: red;}", ".${testPrefixChild}someClassName { color: green;}"), + styleSheet.serializeRules(), + "styleSheet rules" + ) + + assertContentEquals( + listOf(".${testPrefixParent}someClassName { color: red;}", ".${testPrefixChild}someClassName { color: green;}"), + childStyleSheet.serializeRules(), + "childStyleSheet rules" + ) + } + } \ No newline at end of file From 4a65b1a1d87a2920a611bc27e3eef14d5a87c349 Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Wed, 31 Jan 2024 15:54:03 +0100 Subject: [PATCH 05/30] Refactor compose library tests --- .../compose/resources/TestUtils.blocking.kt | 12 -- .../compose/resources/ComposeResourceTest.kt | 155 +++++++++--------- .../compose/resources/ResourceTest.kt | 4 +- .../jetbrains/compose/resources/TestUtils.kt | 6 - .../compose/resources/TestUtils.js.kt | 7 - .../compose/resources/ResourceReader.macos.kt | 1 + .../compose/resources/TestUtils.wasmJs.kt | 17 -- 7 files changed, 76 insertions(+), 126 deletions(-) delete mode 100644 components/resources/library/src/blockingTest/kotlin/org/jetbrains/compose/resources/TestUtils.blocking.kt delete mode 100644 components/resources/library/src/jsTest/kotlin/org/jetbrains/compose/resources/TestUtils.js.kt delete mode 100644 components/resources/library/src/wasmJsTest/kotlin/org/jetbrains/compose/resources/TestUtils.wasmJs.kt diff --git a/components/resources/library/src/blockingTest/kotlin/org/jetbrains/compose/resources/TestUtils.blocking.kt b/components/resources/library/src/blockingTest/kotlin/org/jetbrains/compose/resources/TestUtils.blocking.kt deleted file mode 100644 index 94a48bf768..0000000000 --- a/components/resources/library/src/blockingTest/kotlin/org/jetbrains/compose/resources/TestUtils.blocking.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.jetbrains.compose.resources - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.runBlocking - - -actual typealias TestReturnType = Unit - -actual fun runBlockingTest(block: suspend CoroutineScope.() -> Unit): TestReturnType { - return runBlocking { block() } -} - diff --git a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt index 206610ccdb..3160db93b2 100644 --- a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt +++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt @@ -5,7 +5,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.runComposeUiTest -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runTest import kotlin.test.* @OptIn(ExperimentalTestApi::class, ExperimentalResourceApi::class, InternalResourceApi::class) @@ -19,109 +19,100 @@ class ComposeResourceTest { @Test fun testCountRecompositions() = runComposeUiTest { - runBlockingTest { - val imagePathFlow = MutableStateFlow(DrawableResource("1.png")) - val recompositionsCounter = RecompositionsCounter() - setContent { - CompositionLocalProvider(LocalComposeEnvironment provides TestComposeEnvironment) { - val res by imagePathFlow.collectAsState() - val imgRes = imageResource(res) - recompositionsCounter.content { - Image(bitmap = imgRes, contentDescription = null) - } + var res by mutableStateOf(DrawableResource("1.png")) + val recompositionsCounter = RecompositionsCounter() + setContent { + CompositionLocalProvider(LocalComposeEnvironment provides TestComposeEnvironment) { + val imgRes = imageResource(res) + recompositionsCounter.content { + Image(bitmap = imgRes, contentDescription = null) } } - awaitIdle() - imagePathFlow.emit(DrawableResource("2.png")) - awaitIdle() - assertEquals(2, recompositionsCounter.count) } + waitForIdle() + res = DrawableResource("2.png") + waitForIdle() + assertEquals(2, recompositionsCounter.count) } @Test fun testImageResourceCache() = runComposeUiTest { - runBlockingTest { - val testResourceReader = TestResourceReader() - val imagePathFlow = MutableStateFlow(DrawableResource("1.png")) - setContent { - CompositionLocalProvider( - LocalResourceReader provides testResourceReader, - LocalComposeEnvironment provides TestComposeEnvironment - ) { - val res by imagePathFlow.collectAsState() - Image(painterResource(res), null) - } + val testResourceReader = TestResourceReader() + var res by mutableStateOf(DrawableResource("1.png")) + setContent { + CompositionLocalProvider( + LocalResourceReader provides testResourceReader, + LocalComposeEnvironment provides TestComposeEnvironment + ) { + Image(painterResource(res), null) } - awaitIdle() - imagePathFlow.emit(DrawableResource("2.png")) - awaitIdle() - imagePathFlow.emit(DrawableResource("1.png")) - awaitIdle() - - assertEquals( - expected = listOf("1.png", "2.png"), //no second read of 1.png - actual = testResourceReader.readPaths - ) } + waitForIdle() + res = DrawableResource("2.png") + waitForIdle() + res = DrawableResource("1.png") + waitForIdle() + + assertEquals( + expected = listOf("1.png", "2.png"), //no second read of 1.png + actual = testResourceReader.readPaths + ) } @Test fun testStringResourceCache() = runComposeUiTest { - runBlockingTest { - val testResourceReader = TestResourceReader() - val stringIdFlow = MutableStateFlow(TestStringResource("app_name")) - setContent { - CompositionLocalProvider( - LocalResourceReader provides testResourceReader, - LocalComposeEnvironment provides TestComposeEnvironment - ) { - val res by stringIdFlow.collectAsState() - Text(stringResource(res)) - Text(stringArrayResource(TestStringResource("str_arr")).joinToString()) - } + val testResourceReader = TestResourceReader() + var res by mutableStateOf(TestStringResource("app_name")) + var str = "" + setContent { + CompositionLocalProvider( + LocalResourceReader provides testResourceReader, + LocalComposeEnvironment provides TestComposeEnvironment + ) { + str = stringResource(res) + Text(str) + Text(stringArrayResource(TestStringResource("str_arr")).joinToString()) } - awaitIdle() - stringIdFlow.emit(TestStringResource("hello")) - awaitIdle() - stringIdFlow.emit(TestStringResource("app_name")) - awaitIdle() - - assertEquals( - expected = listOf("strings.xml"), //just one string.xml read - actual = testResourceReader.readPaths - ) } + waitForIdle() + assertEquals(str, "Compose Resources App") + res = TestStringResource("hello") + waitForIdle() + assertEquals(str, "\uD83D\uDE0A Hello world!") + res = TestStringResource("app_name") + waitForIdle() + assertEquals(str, "Compose Resources App") + + assertEquals( + expected = listOf("strings.xml"), //just one string.xml read + actual = testResourceReader.readPaths + ) } @Test fun testReadStringResource() = runComposeUiTest { - runBlockingTest { - setContent { - CompositionLocalProvider(LocalComposeEnvironment provides TestComposeEnvironment) { - assertEquals( - "Compose Resources App", - stringResource(TestStringResource("app_name")) - ) - assertEquals( - "Créer une table", - stringResource(TestStringResource("accentuated_characters")) - ) - assertEquals( - "Hello, test-name! You have 42 new messages.", - stringResource(TestStringResource("str_template"), "test-name", 42) - ) - assertEquals( - listOf("item 1", "item 2", "item 3"), - stringArrayResource(TestStringResource("str_arr")) - ) - } + var app_name = "" + var accentuated_characters = "" + var str_template = "" + var str_arr = emptyList() + setContent { + CompositionLocalProvider(LocalComposeEnvironment provides TestComposeEnvironment) { + app_name = stringResource(TestStringResource("app_name")) + accentuated_characters = stringResource(TestStringResource("accentuated_characters")) + str_template = stringResource(TestStringResource("str_template"), "test-name", 42) + str_arr = stringArrayResource(TestStringResource("str_arr")) } - awaitIdle() } + waitForIdle() + + assertEquals("Compose Resources App", app_name) + assertEquals("Créer une table", accentuated_characters) + assertEquals("Hello, test-name! You have 42 new messages.", str_template) + assertEquals(listOf("item 1", "item 2", "item 3"), str_arr) } @Test - fun testLoadStringResource() = runBlockingTest { + fun testLoadStringResource() = runTest { assertEquals("Compose Resources App", getString(TestStringResource("app_name"))) assertEquals( "Hello, test-name! You have 42 new messages.", @@ -131,7 +122,7 @@ class ComposeResourceTest { } @Test - fun testMissingResource() = runBlockingTest { + fun testMissingResource() = runTest { assertFailsWith { readResourceBytes("missing.png") } @@ -142,7 +133,7 @@ class ComposeResourceTest { } @Test - fun testReadFileResource() = runBlockingTest { + fun testReadFileResource() = runTest { val bytes = readResourceBytes("strings.xml") assertEquals( """ diff --git a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt index 36fd4230c8..f2a516618a 100644 --- a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt +++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt @@ -13,12 +13,12 @@ import kotlin.test.* @OptIn(ExperimentalResourceApi::class, InternalResourceApi::class) class ResourceTest { @Test - fun testResourceEquals() = runBlockingTest { + fun testResourceEquals() { assertEquals(DrawableResource("a"), DrawableResource("a")) } @Test - fun testResourceNotEquals() = runBlockingTest { + fun testResourceNotEquals() { assertNotEquals(DrawableResource("a"), DrawableResource("b")) } diff --git a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestUtils.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestUtils.kt index cd0658f53d..7675790707 100644 --- a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestUtils.kt +++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestUtils.kt @@ -1,11 +1,5 @@ package org.jetbrains.compose.resources -import kotlinx.coroutines.CoroutineScope - -expect class TestReturnType - -expect fun runBlockingTest(block: suspend CoroutineScope.() -> Unit): TestReturnType - @OptIn(InternalResourceApi::class, ExperimentalResourceApi::class) internal fun TestStringResource(key: String) = StringResource( "STRING:$key", diff --git a/components/resources/library/src/jsTest/kotlin/org/jetbrains/compose/resources/TestUtils.js.kt b/components/resources/library/src/jsTest/kotlin/org/jetbrains/compose/resources/TestUtils.js.kt deleted file mode 100644 index 2d9dfb5498..0000000000 --- a/components/resources/library/src/jsTest/kotlin/org/jetbrains/compose/resources/TestUtils.js.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.jetbrains.compose.resources - -import kotlinx.coroutines.* - -actual typealias TestReturnType = Any -actual fun runBlockingTest(block: suspend CoroutineScope.() -> Unit): TestReturnType = - TODO("Implement if necessary. We focus on k/wasm target for now") \ No newline at end of file diff --git a/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt b/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt index 03d515621c..e226506458 100644 --- a/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt +++ b/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt @@ -13,6 +13,7 @@ actual suspend fun readResourceBytes(path: String): ByteArray { //todo in future bundle resources with app and use all sourceSets (skikoMain, nativeMain) contentsAtPath("$currentDirectoryPath/src/macosMain/resources/$path") ?: contentsAtPath("$currentDirectoryPath/src/commonMain/resources/$path") + ?: contentsAtPath("$currentDirectoryPath/src/commonTest/resources/$path") } ?: throw MissingResourceException(path) return ByteArray(contentsAtPath.length.toInt()).apply { usePinned { diff --git a/components/resources/library/src/wasmJsTest/kotlin/org/jetbrains/compose/resources/TestUtils.wasmJs.kt b/components/resources/library/src/wasmJsTest/kotlin/org/jetbrains/compose/resources/TestUtils.wasmJs.kt deleted file mode 100644 index 0cfd4d8b96..0000000000 --- a/components/resources/library/src/wasmJsTest/kotlin/org/jetbrains/compose/resources/TestUtils.wasmJs.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.jetbrains.compose.resources - -import kotlinx.coroutines.* -import kotlinx.coroutines.test.runTest - -@JsFun("() => ''") -private external fun jsRef(): JsAny - - -actual typealias TestReturnType = Any -/** - * Runs the [block] in a coroutine. - */ -actual fun runBlockingTest(block: suspend CoroutineScope.() -> Unit): TestReturnType = MainScope().promise { - block() - jsRef() -} \ No newline at end of file From 12afbe59a8fdd95248644b2d6d6bb5a30bb176f8 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 31 Jan 2024 16:46:59 +0100 Subject: [PATCH 06/30] add verification-metadata.xml file to verify vlcj artifacts (#4159) Update kotlin and dependencies and add verification-metadata.xml file to verify vlcj artifacts --------- Co-authored-by: Igor Demin --- .../VideoPlayer/library/build.gradle.kts | 2 +- .../compose/videoplayer/VideoPlayer.kt | 1 + experimental/components/build.gradle.kts | 22 +----- experimental/components/gradle.properties | 13 ++-- .../gradle/verification-metadata.xml | 67 +++++++++++++++++++ .../gradle/wrapper/gradle-wrapper.properties | 2 +- experimental/components/gradlew | 0 experimental/components/settings.gradle.kts | 20 ++++++ 8 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 experimental/components/gradle/verification-metadata.xml mode change 100644 => 100755 experimental/components/gradlew diff --git a/experimental/components/VideoPlayer/library/build.gradle.kts b/experimental/components/VideoPlayer/library/build.gradle.kts index 7960e88a3c..902ba94d2a 100644 --- a/experimental/components/VideoPlayer/library/build.gradle.kts +++ b/experimental/components/VideoPlayer/library/build.gradle.kts @@ -18,7 +18,7 @@ kotlin { } named("desktopMain") { dependencies { - implementation("uk.co.caprica:vlcj:4.7.0") + implementation("uk.co.caprica:vlcj:4.8.2") } } } diff --git a/experimental/components/VideoPlayer/library/src/commonMain/kotlin/org/jetbrains/compose/videoplayer/VideoPlayer.kt b/experimental/components/VideoPlayer/library/src/commonMain/kotlin/org/jetbrains/compose/videoplayer/VideoPlayer.kt index d9858f81df..f7094dffc7 100644 --- a/experimental/components/VideoPlayer/library/src/commonMain/kotlin/org/jetbrains/compose/videoplayer/VideoPlayer.kt +++ b/experimental/components/VideoPlayer/library/src/commonMain/kotlin/org/jetbrains/compose/videoplayer/VideoPlayer.kt @@ -31,6 +31,7 @@ fun VideoPlayer( onFinish = onFinish ) +@Composable internal expect fun VideoPlayerImpl( url: String, isResumed: Boolean, diff --git a/experimental/components/build.gradle.kts b/experimental/components/build.gradle.kts index d15b16f54a..7143c3e0e1 100644 --- a/experimental/components/build.gradle.kts +++ b/experimental/components/build.gradle.kts @@ -1,27 +1,11 @@ -buildscript { - val composeVersion = property("compose.version") - - repositories { - google() - mavenCentral() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") - } - - dependencies { - classpath("org.jetbrains.compose:compose-gradle-plugin:$composeVersion") - classpath(kotlin("gradle-plugin", version = "1.5.31")) - } +plugins { + kotlin("multiplatform").apply(false) + id("org.jetbrains.compose").apply(false) } subprojects { version = findProperty("deploy.version") ?: property("compose.version")!! - repositories { - google() - mavenCentral() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") - } - plugins.withId("java") { configureIfExists { sourceCompatibility = JavaVersion.VERSION_11 diff --git a/experimental/components/gradle.properties b/experimental/components/gradle.properties index f153c2b797..b961ea172b 100644 --- a/experimental/components/gradle.properties +++ b/experimental/components/gradle.properties @@ -1,5 +1,8 @@ -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -android.useAndroidX=true -android.enableJetifier=true -kotlin.code.style=official -compose.version=1.0.0 +org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" +org.gradle.caching=true +org.gradle.configuration-cache=true + +compose.version=1.5.11 +kotlin.version=1.9.21 + +kotlin.code.style=official \ No newline at end of file diff --git a/experimental/components/gradle/verification-metadata.xml b/experimental/components/gradle/verification-metadata.xml new file mode 100644 index 0000000000..cea3054305 --- /dev/null +++ b/experimental/components/gradle/verification-metadata.xml @@ -0,0 +1,67 @@ + + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/experimental/components/gradle/wrapper/gradle-wrapper.properties b/experimental/components/gradle/wrapper/gradle-wrapper.properties index 05679dc3c1..a595206642 100644 --- a/experimental/components/gradle/wrapper/gradle-wrapper.properties +++ b/experimental/components/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/experimental/components/gradlew b/experimental/components/gradlew old mode 100644 new mode 100755 diff --git a/experimental/components/settings.gradle.kts b/experimental/components/settings.gradle.kts index cda8fabd32..cab1154844 100644 --- a/experimental/components/settings.gradle.kts +++ b/experimental/components/settings.gradle.kts @@ -1,2 +1,22 @@ +pluginManagement { + repositories { + google() + gradlePluginPortal() + mavenCentral() + } + + plugins { + kotlin("multiplatform").version(extra["kotlin.version"] as String) + id("org.jetbrains.compose").version(extra["compose.version"] as String) + } +} + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } +} + include(":VideoPlayer:library") include(":VideoPlayer:demo") \ No newline at end of file From 18de77e0bdc08adfe4b6c16d92abc84a314f4683 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 31 Jan 2024 17:20:50 +0100 Subject: [PATCH 07/30] Update dependency versions and refactor build configs (#4215) --- .../AnimatedImage/demo/build.gradle.kts | 8 +++--- .../AnimatedImage/library/build.gradle.kts | 8 +++--- components/SplitPane/demo/build.gradle.kts | 10 +++---- components/SplitPane/library/build.gradle.kts | 8 +++--- components/gradle.properties | 5 ++-- components/gradle/libs.versions.toml | 15 ++++++++--- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../demo/androidApp/build.gradle.kts | 8 +++--- .../demo/desktopApp/build.gradle.kts | 8 +++--- .../resources/demo/shared/build.gradle.kts | 25 +++++------------- components/resources/library/build.gradle.kts | 18 +++++-------- .../demo/desktopApp/build.gradle.kts | 8 +++--- .../demo/shared/build.gradle.kts | 26 +++++-------------- .../library/build.gradle.kts | 2 -- 14 files changed, 57 insertions(+), 94 deletions(-) diff --git a/components/AnimatedImage/demo/build.gradle.kts b/components/AnimatedImage/demo/build.gradle.kts index 6c36cc912b..cdbf81018d 100644 --- a/components/AnimatedImage/demo/build.gradle.kts +++ b/components/AnimatedImage/demo/build.gradle.kts @@ -11,11 +11,9 @@ kotlin { optIn("kotlin.RequiresOptIn") } } - val jvmMain by getting { - dependencies { - implementation(compose.desktop.currentOs) - implementation(project(":AnimatedImage:library")) - } + jvmMain.dependencies { + implementation(compose.desktop.currentOs) + implementation(project(":AnimatedImage:library")) } } } diff --git a/components/AnimatedImage/library/build.gradle.kts b/components/AnimatedImage/library/build.gradle.kts index be3889bb81..83f9c283b3 100644 --- a/components/AnimatedImage/library/build.gradle.kts +++ b/components/AnimatedImage/library/build.gradle.kts @@ -12,11 +12,9 @@ kotlin { optIn("kotlin.RequiresOptIn") } } - val commonMain by getting { - dependencies { - api(compose.runtime) - api(compose.foundation) - } + commonMain.dependencies { + api(compose.runtime) + api(compose.foundation) } } } diff --git a/components/SplitPane/demo/build.gradle.kts b/components/SplitPane/demo/build.gradle.kts index 8ef6caaa6b..689e34c821 100644 --- a/components/SplitPane/demo/build.gradle.kts +++ b/components/SplitPane/demo/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.compose.compose - plugins { kotlin("multiplatform") id("org.jetbrains.compose") @@ -14,11 +12,9 @@ kotlin { } } - val jvmMain by getting { - dependencies { - implementation(compose.desktop.currentOs) - implementation(project(":SplitPane:library")) - } + jvmMain.dependencies { + implementation(compose.desktop.currentOs) + implementation(project(":SplitPane:library")) } } } diff --git a/components/SplitPane/library/build.gradle.kts b/components/SplitPane/library/build.gradle.kts index 4b3891c1ff..3f0479e9f4 100644 --- a/components/SplitPane/library/build.gradle.kts +++ b/components/SplitPane/library/build.gradle.kts @@ -14,11 +14,9 @@ kotlin { } } - val commonMain by getting { - dependencies { - api(compose.runtime) - api(compose.foundation) - } + commonMain.dependencies { + api(compose.runtime) + api(compose.foundation) } } } diff --git a/components/gradle.properties b/components/gradle.properties index 1cb2d03b60..bc747d85a8 100644 --- a/components/gradle.properties +++ b/components/gradle.properties @@ -1,13 +1,13 @@ #Gradle org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" org.gradle.caching=true -#org.gradle.configuration-cache=true //stable since kotlin 1.9.20 +org.gradle.configuration-cache=true #Android android.useAndroidX=true #Versions -kotlin.version=1.9.21 +kotlin.version=1.9.22 compose.version=1.6.0-dev1397 agp.version=8.1.2 @@ -23,5 +23,4 @@ kotlin.code.style=official kotlin.js.compiler=ir kotlin.js.webpack.major.version=4 kotlin.native.useEmbeddableCompilerJar=true -kotlin.native.binary.memoryModel=experimental xcodeproj=./resources/demo/iosApp diff --git a/components/gradle/libs.versions.toml b/components/gradle/libs.versions.toml index 8eaf2cf157..39edcda408 100644 --- a/components/gradle/libs.versions.toml +++ b/components/gradle/libs.versions.toml @@ -1,7 +1,16 @@ [versions] -kotlinx-coroutines = "1.8.0-RC" +kotlinx-coroutines = "1.8.0-RC2" +androidx-appcompat = "1.6.1" +androidx-activity-compose = "1.8.2" +androidx-test = "1.5.0" +androidx-compose = "1.6.0" [libraries] kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } -kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } -kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } \ No newline at end of file +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" } +androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test" } +androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test", version.ref = "androidx-compose" } +androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-compose" } +androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-compose" } \ No newline at end of file diff --git a/components/gradle/wrapper/gradle-wrapper.properties b/components/gradle/wrapper/gradle-wrapper.properties index e411586a54..a595206642 100644 --- a/components/gradle/wrapper/gradle-wrapper.properties +++ b/components/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/components/resources/demo/androidApp/build.gradle.kts b/components/resources/demo/androidApp/build.gradle.kts index 122f5e1123..bf0d6c00a0 100644 --- a/components/resources/demo/androidApp/build.gradle.kts +++ b/components/resources/demo/androidApp/build.gradle.kts @@ -19,10 +19,10 @@ android { targetCompatibility = JavaVersion.VERSION_11 } dependencies { + implementation(compose.ui) + implementation(compose.foundation) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.activity.compose) implementation(project(":resources:demo:shared")) - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("androidx.activity:activity-compose:1.8.0") - implementation("androidx.compose.foundation:foundation:1.5.3") - implementation("androidx.compose.ui:ui:1.5.3") } } diff --git a/components/resources/demo/desktopApp/build.gradle.kts b/components/resources/demo/desktopApp/build.gradle.kts index 279ae99206..a5e5ecd7f3 100644 --- a/components/resources/demo/desktopApp/build.gradle.kts +++ b/components/resources/demo/desktopApp/build.gradle.kts @@ -6,11 +6,9 @@ plugins { kotlin { jvm() sourceSets { - val jvmMain by getting { - dependencies { - implementation(compose.desktop.currentOs) - implementation(project(":resources:demo:shared")) - } + jvmMain.dependencies { + implementation(compose.desktop.currentOs) + implementation(project(":resources:demo:shared")) } } } diff --git a/components/resources/demo/shared/build.gradle.kts b/components/resources/demo/shared/build.gradle.kts index af97e41f33..fd6510bd45 100644 --- a/components/resources/demo/shared/build.gradle.kts +++ b/components/resources/demo/shared/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl plugins { @@ -8,8 +7,6 @@ plugins { } kotlin { - @OptIn(ExperimentalKotlinGradlePluginApi::class) - targetHierarchy.default() androidTarget { compilations.all { kotlinOptions { @@ -59,17 +56,14 @@ kotlin { optIn("org.jetbrains.compose.resources.ExperimentalResourceApi") } } - val commonMain by getting { - dependencies { - implementation(compose.runtime) - implementation(compose.material3) - implementation(project(":resources:library")) - } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.material3) + implementation(project(":resources:library")) } - val desktopMain by getting { - dependencies { - implementation(compose.desktop.common) - } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.desktop.common) } } } @@ -89,8 +83,3 @@ android { compose.experimental { web.application {} } - -// TODO: remove this block after we update on a newer kotlin. Currently there is an error: `error:0308010C:digital envelope routines::unsupported` -rootProject.plugins.withType { - rootProject.the().nodeVersion = "16.0.0" -} \ No newline at end of file diff --git a/components/resources/library/build.gradle.kts b/components/resources/library/build.gradle.kts index 4834fb33e5..1e563934c5 100644 --- a/components/resources/library/build.gradle.kts +++ b/components/resources/library/build.gradle.kts @@ -12,8 +12,6 @@ plugins { val composeVersion = extra["compose.version"] as String kotlin { - @OptIn(ExperimentalKotlinGradlePluginApi::class) - targetHierarchy.default() jvm("desktop") androidTarget { publishLibraryVariants("release") @@ -48,6 +46,7 @@ kotlin { macosX64() macosArm64() + applyDefaultHierarchyTemplate() sourceSets { all { languageSettings { @@ -75,8 +74,8 @@ kotlin { } val commonTest by getting { dependencies { - implementation(libs.kotlinx.coroutines.test) implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) implementation(compose.material3) @OptIn(ExperimentalComposeLibrary::class) implementation(compose.uiTest) @@ -96,9 +95,6 @@ kotlin { } val jvmAndAndroidMain by creating { dependsOn(blockingMain) - dependencies { - implementation(compose.material3) - } } val jvmAndAndroidTest by creating { dependsOn(blockingTest) @@ -112,8 +108,6 @@ kotlin { dependsOn(jvmAndAndroidTest) dependencies { implementation(compose.desktop.currentOs) - implementation(compose.desktop.uiTestJUnit4) - implementation(libs.kotlinx.coroutines.swing) } } val androidMain by getting { @@ -122,10 +116,10 @@ kotlin { val androidInstrumentedTest by getting { dependsOn(jvmAndAndroidTest) dependencies { - implementation("androidx.test:core:1.5.0") - implementation("androidx.compose.ui:ui-test-manifest:1.5.4") - implementation("androidx.compose.ui:ui-test:1.5.4") - implementation("androidx.compose.ui:ui-test-junit4:1.5.4") + implementation(libs.androidx.test.core) + implementation(libs.androidx.compose.ui.test) + implementation(libs.androidx.compose.ui.test.manifest) + implementation(libs.androidx.compose.ui.test.junit4) } } val androidUnitTest by getting { diff --git a/components/ui-tooling-preview/demo/desktopApp/build.gradle.kts b/components/ui-tooling-preview/demo/desktopApp/build.gradle.kts index b810e75f0f..1685f83da6 100644 --- a/components/ui-tooling-preview/demo/desktopApp/build.gradle.kts +++ b/components/ui-tooling-preview/demo/desktopApp/build.gradle.kts @@ -6,11 +6,9 @@ plugins { kotlin { jvm() sourceSets { - val jvmMain by getting { - dependencies { - implementation(compose.desktop.currentOs) - implementation(project(":ui-tooling-preview:demo:shared")) - } + jvmMain.dependencies { + implementation(compose.desktop.currentOs) + implementation(project(":ui-tooling-preview:demo:shared")) } } } diff --git a/components/ui-tooling-preview/demo/shared/build.gradle.kts b/components/ui-tooling-preview/demo/shared/build.gradle.kts index f5846f7907..754457c4b0 100644 --- a/components/ui-tooling-preview/demo/shared/build.gradle.kts +++ b/components/ui-tooling-preview/demo/shared/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi - plugins { kotlin("multiplatform") id("com.android.library") @@ -7,8 +5,6 @@ plugins { } kotlin { - @OptIn(ExperimentalKotlinGradlePluginApi::class) - targetHierarchy.default() androidTarget { compilations.all { kotlinOptions { @@ -48,17 +44,14 @@ kotlin { } sourceSets { - val commonMain by getting { - dependencies { - implementation(compose.runtime) - implementation(compose.material3) - implementation(project(":ui-tooling-preview:library")) - } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.material3) + implementation(project(":ui-tooling-preview:library")) } - val desktopMain by getting { - dependencies { - implementation(compose.desktop.common) - } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.desktop.common) } } } @@ -78,8 +71,3 @@ android { compose.experimental { web.application {} } - -// TODO: remove this block after we update on a newer kotlin. Currently there is an error: `error:0308010C:digital envelope routines::unsupported` -rootProject.plugins.withType { - rootProject.the().nodeVersion = "16.0.0" -} \ No newline at end of file diff --git a/components/ui-tooling-preview/library/build.gradle.kts b/components/ui-tooling-preview/library/build.gradle.kts index 3c4d4822b4..e7a7ac4612 100644 --- a/components/ui-tooling-preview/library/build.gradle.kts +++ b/components/ui-tooling-preview/library/build.gradle.kts @@ -9,8 +9,6 @@ plugins { val composeVersion = extra["compose.version"] as String kotlin { - @OptIn(ExperimentalKotlinGradlePluginApi::class) - targetHierarchy.default() jvm("desktop") androidTarget { publishLibraryVariants("release") From 3c7260ea51157d423b3799bd339b682ffabdce06 Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Wed, 31 Jan 2024 17:46:44 +0100 Subject: [PATCH 08/30] Add a ttf font to the resources demo app --- .../font/Workbench-Regular.ttf | Bin 0 -> 21980 bytes .../compose/resources/demo/shared/FontRes.kt | 38 ++++++++++++++---- .../resources/demo/shared/StringRes.kt | 30 ++++---------- 3 files changed, 38 insertions(+), 30 deletions(-) create mode 100644 components/resources/demo/shared/src/commonMain/composeResources/font/Workbench-Regular.ttf diff --git a/components/resources/demo/shared/src/commonMain/composeResources/font/Workbench-Regular.ttf b/components/resources/demo/shared/src/commonMain/composeResources/font/Workbench-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3714a82c5f616ed9ea0014a5e389320b6f52526b GIT binary patch literal 21980 zcmcIs34C2;l|T38<-IJ;+O%1lyrj?|U|R~5P33iCv#bS~>I@Ltw5ha7Y*Hxdz(W=h z5fKFjoMAv1tB4FRF33;iIijEr2%=)ytUo77Da&X`PpK zHRisC@8rYY_|*@@KLb(ZjgM0eMxxhg7RLXi*>Z*p_J_}Lza%rBfh-yy8y|}`N9N$R zT#3>0vZs5GKfmTZf1;AO2zYGlAohuVcg@vLVJ*d)<$405^bbofld}f-Jb+?rRxCS> zOu(RS;RNVE0g1RjJzAE6t>+S+kh*l>ga8tjF-XHL}__48H<-D zaRS5%DCwV5R-LwnvP4~T)HhavKk^aFlw@$6!Wo}eP&+LoZ{-QA&xWny0C3|wUQf^F zVO(Qqcx{Z{M+u<8$7T4g#_WUX0IYAqJ&!&?9EBL&K)2xYGjuaPuct5I^V9Ts+}Cw6 zYM>;&hu#Zm$5Wb?(Q@2NLT)YObkck}jE=!vqAK}1hI%3CNAyRp(>uqz!h6trK9-Cf z7aNG(8vAzah1g$8T1$>E87%oq$rB~JcYrj*wr>?PXW!-SySL>ds`=9#x z^{eX#>#wcn$+unKaop=4byXHMK@8|RW&|cNPr2X9X?d^B9 z|ET?qj*5=?9fx+D)3KprsN)MA-|Tp%PwQ?;q%Qx~PSr>;)jl)5|haO%m_uT#HI z{cmSY=YgHab)MPT*Liv8EiOHHlOYC&eK_XCI1d>w6AWA zyb_;o>yllc$LITZ zb?r*M{OHS%NR1*?L!wjY7qkZj>pESFqpQ4Y-EnA)(H-<%`WN~cy-1JJ_u$Xh=^lET zZl|x%ZS-aOBK;%%ntn;Yg4N##8kN|Ks!{9a&|GRn#hZuv*#W<}@*P6&hW179@i1CS zN72zh=s0=+Xg^5L)7R)_RQUJPYC4V1pmXR0bT0i3ok!=>1=K?q(t6rV{WL&>w3UWv zJ6%d2rH=vO`{{A|SK3Lp(m&Hr=t=r1Jw;E@59lTOceG$*)J%?I-GyRrc zz|Qd{?3SNL$M7Bc9NkDa(Osy}aqL9p*a6D0XH+4wb%^6^YNKXqp;qj)UEsw5bRfNp z76842=txBSa5@6Hqj+COxx&U`Z#T&zsF8<5q*okO%KsG=$rI!*j3~YcJ2%gcy|f^c^wyH!21CB z{5a70I^^FEUk-$I>tX3Du&@_6{1lOU7o;BoEB^!NJViebL}3Gbc$%IOi#`VwAAm=% z!>1!HJ{;FR#P=9@c^r1NSLwZ8i5K@uy@Z$a%Di%Ty$P0YhFAUY<4$030%CL`*maUc z>LWnq6p8C|@c3f*y$$%B21L#PM%#hXrHJFl5zV`R!-K%=4WP&I|CBJ`G9YjTBDvO( ze2pFfs{aID&PJ3R&ooMHUWHfb&GM?i*|+FV^k=WytMO{RIF z3C#E;Vx2&wj|XQT0`AL&!RLW<@5Ao>7=!d(B>p z*Xp%-^SpMi!%M|aJ7@Lk#Il9!dWL!vt8_S7htqU8U59gYSSv%>K!5MT_5A}|%7!l) zkb{chzOB7_t$f44wykn8xvhWG;>F94v2lrwhx52(@li+U`Le~a6=x;3Z0g@O+?yEc zT{qCbKE7(()&ZHdxJvPIq~Ca21W0 z<2W0?#g09yk6&fSF{+MVY{w;Zbo_mG9H*{$n;n-@3))GQlc27WU3OeXO(jp*ae3f) z7A-8f-L6-8U9s(UTun=3%k8+CQn4;QMgrEyYVFvgiWql_uo*d6>;2h|OK6Grm>tJy z7M?hCPAM%!rPbpEu3u)yNnAg}j!|{I6YRJ=XTMhg`AhA3CFIBLcoypG8+KfUO8PB3 zuEzB{?YIWl`|P+DeEgst*P#L*Y{&JuZWtm%YEiw_&ITM2a&Z{db^y7(5ji`6%F>JT z9$f83=5sacK{jv3`MJ2e6_wyZTkw zx_TjF5VD7Gv_4Nt;aSC{ZH7&Q_}U~MMQw7@!n5+Foeg`oiI0WlMDMQvLh3=jtX1M? zKW1E-=Uwn#{>n92vk8dy0sW{a!F%~DIdZ&qokcRbI(mOOX7>vIYh(>u%bxclD*4jG zac1j=f%p)1kVW_!f`qMDJ&3DA=mv&xycr|2XCv;Pj{Rdb>VF@u521GQZqkprDR6NB zd>h98!TjmP6`i4;Fzb{fDa5AVn3}?T-gSBP$&j0Z{6U=e<0vP2H6*Q*t1RJA-1`7V z?lLxs_7wV+h4P&WSQggntm#2Kd$5iW--1G1D?zsj(GmM6gm#BW*^4=bwDYFNH7|G zxW>^=p=XNXy3t~{4bfbPRRgGHi$ntREsXXek+emm^SnWk&P;kYn5#S@f%Fy zDDfLr<|+hZ#0<4PXtQZhnn~C@aUU8(5knnTWL-VBeF{v8aDGe~ zFd-92Q(I18I zL8c2nuv6^keOCRTkRK!F2keUAHaWeVnI>2C?S>tU5ByMl79X~QuRHu8J&IqX|3pGb z+|^&`F&tGN>*4#5EBbssMD)Tx(aZj`{sMi5?XF{xuNp04tZ^STshCDE)jVa5Noe$u zWe+FQ8ALf^Tz5T9kzRO4Bxl6-BHi$mG`P5AG(J|rIR1<>`@-=U^?5MkD1~cG;0Xtu zPzxgQ*0_VY%4k0d=7}*I9lUv@3`fUlndRcHqx5fx|46u_of3v=)sz>1672TDX=B=+ z2{|3f-snD?=NoHI;wjLNmUR|DO`>VJW7T@f-ShYjRt31Xb-Orr4a@y>Br*;kjfmS_ zGy?;*G}l3UY3ZO@ddS}Wlk15ET%Q;h#4)Td>XF7_ig-b?_B)bK1vR+(j4C8tLj5bi zR>n#qT2Nxv(9bIRE)Vcz%RY^`dS_3%*h!2d=}?bTr0ZjJqIsM&p#rWE+DK*F9L! zK_2OtziU_>8r3X7cf{P}>|;Hyd*H5!uVa>E19we)zxZBGrq?lzg4hN26sI>MdcjD| z8Q8({bAGBmoEOMKvCHI3HahvCJtM1_xi zSm^Dsw1}tJp-kEaU zk?qgAde^XGQTQV%JcbUte8sHLXXSZ zjpzw(i{P0xi?hbJtY(DZ01MS`l9iEsFSL?%iBGy>3c9#DW;HwPE%8*XWeJmKWFhNa zWSAddV1S9!uqDbs|7jj9LA`Tt3BU>A4v)q-jRa!s7#Of5(61Kb3W=QPPkToFcO>a> zjYI#(d&kr{0&b`ziyI>Evet5B6s~a&Wz<8T%^BmIvKbw@d7P0E&-%(ihk!gIaJInm zP-pApLyhFLk6=hXXPw=5h+<4~b#~b&JxE?ee0xpq1X(TeCf?;?sdnc1wcF>wJ~bt^ z^K(cl@;`a|McStPO zr$)(XoCi8i?dB2-WDj(igp3H`Q8*JtyWlX!6@_uoS~*Q=l7|`vscd-1t98ltZgA5h z={T9oWN{3}t(cvt+HD3 z{harde4RQfmzELL%(B?9riL2oG5_qLbWC@t(GN{Niu&eDXk!^5QfuCqu zlB))I=TNX3BR@hK_WgdBY@3VEwWx&>+C}oiG9psvCF*^iR=zhd!+LNYT}7t z=S#Fce3BB~la9PO&ZaCv zBlM{BW*Ch{mji_)Uq{zAj(>NG+?1KnZ8Mb}$YPlZ+-zpG%>>r?C!><3+V=rjJGT|! zdPp{8$xPori3P0SXH~aD%9FronkDg`Q#@8lWI3YE>-0AT#7{+t-&v@HH2A>&XRsH$ z4tn2mp_k=i6`m**3!u)@@I;}W@o$6dN4?OaIAL!UFQ8wd!>-$)ou7=^ess&w%{;`ZoTp_SUuW$2x>(KY91=1U zwuLa{8qN}2EysPS!2C24UWpl^gR2>@U`ZJrovo4DjTvE`6I578j!0mgs!RRz_c~Yo zebzDJf|p3x;h~Dj`&6}3ku?6vI|lJsxPkx4MEt-gGN9Am7Vx`hjb~W9AOKdmmAt~N zj4}GK(!Fo6PHr?U_kmm38^X%qew$WA4JYiw0l7e2`Y4S4^8|7__g*7`suq?Xjg;$o z*gmmS@Yc5#$`a^TOP$BXG7b{(^D5}mhym}N-rl%$i5@erd*Unl9%6iNB7u%0!d4%t ztZ{Bn^G3n-&VtB#*W2cN$gw{&0SXyG0rSOz0uo?(#=I|xf-s9~u1Vl7V4u|vJ1r^_ z|NaBNBN1G&Q|Z16UNCmdf*595{Yk?{nWtlz!&8{KBNnJkPNwZKhzQqe%vft$BRtU< zf$_QAcG#w+8jM;saL)-~p6Rf8)@A2-l$NSt9f*^xJF-O$lG$^=R`CixCuhCI#^$-- zB$X~vu#`PuBR_X(J#x|XYq`q`_%C@4$&5p81vCiyZbgWL(4jSN3Q?F0#`-=h@29!8 zi~GN^VTkj&+DL*A&LcQ6gXHH5F3+a;z6KdBkwDj}njF$rzxgMv7AcF2Y8>-fEoSIN?U z`5EmxMda(AH2-9EL~0xQJ00=K`r)bhVWY*$)nCk4zx*u8m|s~P%`eC?|7`UA%Le_M zq*dr)%__B$troN3w?mby0!xw|9&y5Tt+<%k>{lF_!FM

+(*-7>wbQQR9AD&~sJUVwM8HH1_U|N_6d{cMFc0y|wXU2H)6+T+B4?&WBt_Any*)cMCq~xH-OX z8~^|282;~^wfNs1Dv2&Re96)y7R;aDm1vk-(^c2lSjQjL?FY5vubYeRJvz5#_1t;$ z=JH2m?#=>pujje^5uWQc=g*!0NLwn^#vhTn1?-*we>|5zB6FR+o#I<3`!?S@mM6dc f Date: Fri, 2 Feb 2024 16:25:51 +0100 Subject: [PATCH 09/30] Improve handling of special characters in string resources (#4220) Introduced a function to process and replace certain escaped symbols like '\n', '\t', and '\uXXXX' in the strings extracted from compose string resources. --- .../composeResources/values/strings.xml | 13 +++--- .../compose/resources/StringResources.kt | 40 +++++++++++++++++-- .../compose/resources/ResourceTest.kt | 7 ++++ 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/components/resources/demo/shared/src/commonMain/composeResources/values/strings.xml b/components/resources/demo/shared/src/commonMain/composeResources/values/strings.xml index bdfed65b61..2fa6bad2bc 100644 --- a/components/resources/demo/shared/src/commonMain/composeResources/values/strings.xml +++ b/components/resources/demo/shared/src/commonMain/composeResources/values/strings.xml @@ -1,13 +1,12 @@ Compose Resources App 😊 Hello world! - Lorem ipsum dolor sit amet, - consectetur adipiscing elit. - Donec eget turpis ac sem ultricies consequat. - Hello, %1$s! You have %2$d new messages. + Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit. +Donec eget turpis ac sem ultricies consequat. + Hello, %1$s!\nYou have %2$d new messages. - item 1 - item 2 - item 3 + item \u2605 + item \u2318 + item \u00BD diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt index 1dcebfa830..da3d53eb6c 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt @@ -20,7 +20,7 @@ private val SimpleStringFormatRegex = Regex("""%(\d)\$[ds]""") @ExperimentalResourceApi @Immutable class StringResource -@InternalResourceApi constructor(id: String, val key: String, items: Set): Resource(id, items) +@InternalResourceApi constructor(id: String, val key: String, items: Set) : Resource(id, items) private sealed interface StringItem { data class Value(val text: String) : StringItem @@ -53,11 +53,13 @@ private suspend fun getParsedStrings( private suspend fun parseStringXml(path: String, resourceReader: ResourceReader): Map { val nodes = resourceReader.read(path).toXmlElement().childNodes val strings = nodes.getElementsWithName("string").associate { element -> - element.getAttribute("name") to StringItem.Value(element.textContent.orEmpty()) + val rawString = element.textContent.orEmpty() + element.getAttribute("name") to StringItem.Value(handleSpecialCharacters(rawString)) } val arrays = nodes.getElementsWithName("string-array").associate { arrayElement -> val items = arrayElement.childNodes.getElementsWithName("item").map { element -> - element.textContent.orEmpty() + val rawString = element.textContent.orEmpty() + handleSpecialCharacters(rawString) } arrayElement.getAttribute("name") to StringItem.Array(items) } @@ -203,4 +205,34 @@ private suspend fun loadStringArray( private fun NodeList.getElementsWithName(name: String): List = List(length) { item(it) } .filterIsInstance() - .filter { it.localName == name } \ No newline at end of file + .filter { it.localName == name } + +//https://developer.android.com/guide/topics/resources/string-resource#escaping_quotes +/** + * Replaces + * + * '\n' -> new line + * + * '\t' -> tab + * + * '\uXXXX' -> unicode symbol + * + * '\\' -> '\' + * + * @param string The input string to handle. + * @return The string with special characters replaced according to the logic. + */ +internal fun handleSpecialCharacters(string: String): String { + val unicodeNewLineTabRegex = Regex("""\\u[a-fA-F\d]{4}|\\n|\\t""") + val doubleSlashRegex = Regex("""\\\\""") + val doubleSlashIndexes = doubleSlashRegex.findAll(string).map { it.range.first } + val handledString = unicodeNewLineTabRegex.replace(string) { matchResult -> + if (doubleSlashIndexes.contains(matchResult.range.first - 1)) matchResult.value + else when (matchResult.value) { + "\\n" -> "\n" + "\\t" -> "\t" + else -> matchResult.value.substring(2).toInt(16).toChar().toString() + } + }.replace("""\\""", """\""") + return handledString +} \ No newline at end of file diff --git a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt index f2a516618a..0ea746a0b2 100644 --- a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt +++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt @@ -98,6 +98,13 @@ class ResourceTest { }.message.let { msg -> assertEquals("Resource with ID='ImageResource:test3' has more than one file: en1, en2", msg) } + } + @Test + fun testEscapedSymbols() { + assertEquals( + "abc \n \\n \t \\t \u1234 \ua45f \\u1234 \\ \\u355g", + handleSpecialCharacters("""abc \n \\n \t \\t \u1234 \ua45f \\u1234 \\ \u355g""") + ) } } From d6f3861286bb04c8a51597f3a84a09dc720a708f Mon Sep 17 00:00:00 2001 From: antonindrawan Date: Mon, 5 Feb 2024 17:55:03 +0100 Subject: [PATCH 10/30] build(kn-performance): use compose 1.5.10 to fix the build (#4173) Error: Compose Multiplatform 1.5.1 doesn't support Kotlin 1.9.20. --- benchmarks/kn-performance/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/kn-performance/gradle.properties b/benchmarks/kn-performance/gradle.properties index 60d9eb1336..17ba8bd122 100644 --- a/benchmarks/kn-performance/gradle.properties +++ b/benchmarks/kn-performance/gradle.properties @@ -1,4 +1,4 @@ -compose.version=1.5.1 +compose.version=1.5.10 kotlin.version=1.9.20 org.gradle.jvmargs=-Xmx3g kotlin.native.useEmbeddableCompilerJar=true From 322f2e99065e8765ac6f7a3f1913f17b8b43e9c6 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Tue, 6 Feb 2024 11:07:36 +0100 Subject: [PATCH 11/30] Update CHANGELOG.md (1.6.0-beta02) (#4232) --- CHANGELOG.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 859b921dad..81ae5f499f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,75 @@ +# 1.6.0-beta02 (February 2024) + +## Highlights +- [Basic accessibility support](https://github.com/JetBrains/compose-multiplatform-core/pull/1025) _iOS_ +- [Popups/Dialogs can now be displayed outside a ViewController over native components on iOS by default](https://github.com/JetBrains/compose-multiplatform-core/pull/1031) _iOS_ +- [Allow selecting `Text` in `SelectionContainer` by double and triple tap](https://github.com/JetBrains/compose-multiplatform-core/pull/984) _iOS_ +- [Add support for text decoration line styles via PlatformTextStyle](https://github.com/JetBrains/compose-multiplatform-core/pull/999) _iOS, desktop, web_ +- Bugfixes in the resource library (see below for the details) + +## iOS/desktop/web + +### Fixes +- [Fix "`TextLayoutResult#getLineForVerticalPosition` returns wrong value + slow performance"](https://github.com/JetBrains/compose-multiplatform-core/pull/1012) +- [Run all effects before sending synthetic events](https://github.com/JetBrains/compose-multiplatform-core/pull/1034) +- _(prerelease bug)_ [Fix the pointer icon in `SelectionContainer`](https://github.com/JetBrains/compose-multiplatform-core/pull/1014) + +## iOS + +### Features +- [Adjust overscroll effect params to match iOS animations](https://github.com/JetBrains/compose-multiplatform-core/pull/1010/files) +- [Add ability to change opacity for compose view](https://github.com/JetBrains/compose-multiplatform-core/pull/1022) + +### Fixes +- [Fix UIKitView z-order](https://github.com/JetBrains/compose-multiplatform-core/pull/965) +- [Fix missing case for loading `SystemFont` on iOS](https://github.com/JetBrains/compose-multiplatform-core/pull/1013) +- [Fix selection container crash](https://github.com/JetBrains/compose-multiplatform-core/pull/1016) +- [Fix `WindowInfo.containerSize` without `platformLayers` flag](https://github.com/JetBrains/compose-multiplatform-core/pull/1028) +- _(prerelease bug)_ [Fix "textfield with visual transformation crashes after single tap"](https://github.com/JetBrains/compose-multiplatform-core/pull/1045) +- _(prerelease bug)_ [Fix selection handles crossed](https://github.com/JetBrains/compose-multiplatform-core/pull/1017) +- _(prerelease bug)_ [Fix CMPViewControllerMisuse error](https://github.com/JetBrains/compose-multiplatform-core/pull/1027) +- _(prerelease bug)_ [Fix selection handles with platformLayers=true](https://github.com/JetBrains/compose-multiplatform-core/pull/1023) +- _(prerelease bug)_ [Fix interaction handling for interop views](https://github.com/JetBrains/compose-multiplatform-core/pull/1032) + +## Desktop + +### Fixes +- [Add Arial and Consolas as backup fonts on Linux and mention font name when one fails to load](https://github.com/JetBrains/compose-multiplatform-core/pull/994) + +### Breaking changes and deprecated API +- [Remove deprecated APIs in `TooltipArea` and `PointerEvent`](https://github.com/JetBrains/compose-multiplatform-core/pull/1029) + +## HTML library +### Features +- [Add opportunity to use custom prefixes in `StyleSheet`](https://github.com/JetBrains/compose-multiplatform/pull/3015) + +## Gradle Plugin + +### Features +- [Add `ui-tooling-preview` alias](https://github.com/JetBrains/compose-multiplatform/pull/4190) + +## Resource library + +### Fixes +- _(prerelease bug)_ [Configure Android resources after AGP is applied and ignore hidden files in resources](https://github.com/JetBrains/compose-multiplatform/commit/3040ea85bbc81cb6d1e22d6928646509ee8b601f) +- _(prerelease bug)_ [Generate Res class if there is no common composeResource dir](https://github.com/JetBrains/compose-multiplatform/pull/4176) +- _(prerelease bug)_ [Support Res class generation in JVM only compose projects](https://github.com/JetBrains/compose-multiplatform/pull/4183) +- _(prerelease bug)_ [Support Compose resources for iOS tests](https://github.com/JetBrains/compose-multiplatform/pull/4185) +- _(prerelease bug)_ [Fix sub-module gradle properties for res class generation](https://github.com/JetBrains/compose-multiplatform/commit/ee26bf8beea595dce67fbe880aa86a8363d428ae) +- _(prerelease bug)_ [Fix Native xml parser](https://github.com/JetBrains/compose-multiplatform/pull/4207) +- _(prerelease bug)_ [Generate initializer functions in the Res file to avoid the `MethodTooLargeException`](https://github.com/JetBrains/compose-multiplatform/pull/4205) +- _(prerelease bug)_ [Improve handling of special characters in string resources](https://github.com/JetBrains/compose-multiplatform/pull/4220) +- _(prerelease bug)_ [Add a `ttf` font to the resources demo app](https://github.com/JetBrains/compose-multiplatform/commit/3c7260ea51157d423b3799bd339b682ffabdce06) + +## Dependencies +This version of Compose Multiplatform is based on the next Jetpack Compose libraries: +- [Compiler 1.5.8](https://developer.android.com/jetpack/androidx/releases/compose-compiler#1.5.8) +- [Runtime 1.6.0](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.6.0) +- [UI 1.6.0](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.6.0) +- [Foundation 1.6.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.6.0) +- [Material 1.6.0](https://developer.android.com/jetpack/androidx/releases/compose-material#1.6.0) +- [Material3 1.2.0-rc01](https://developer.android.com/jetpack/androidx/releases/compose-material3#1.2.0-rc01) + # 1.5.12 ## Common @@ -19,7 +91,7 @@ This version of Compose Multiplatform is based on the next Jetpack Compose libra * [Material 1.5.4](https://developer.android.com/jetpack/androidx/releases/compose-material#1.5.4) * [Material3 1.1.2](https://developer.android.com/jetpack/androidx/releases/compose-material3#1.1.2) -# 1.6.0-beta01 (February 2024) +# 1.6.0-beta01 (January 2024) ## Highlights From 84e52986dc99539c0acf2263eca794d402490aa7 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 6 Feb 2024 11:16:53 +0100 Subject: [PATCH 12/30] Relocate a bundled KotlinPoet to the internal package --- gradle-plugins/compose/build.gradle.kts | 2 +- .../test/tests/integration/ResourcesTest.kt | 6 +++++ .../bundledKotlinPoet/app/build.gradle.kts | 25 +++++++++++++++++++ .../app/src/commonMain/kotlin/App.kt | 9 +++++++ .../misc/bundledKotlinPoet/build.gradle.kts | 4 +++ .../misc/bundledKotlinPoet/gradle.properties | 1 + .../bundledKotlinPoet/settings.gradle.kts | 24 ++++++++++++++++++ gradle-plugins/gradle/libs.versions.toml | 2 +- 8 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/app/build.gradle.kts create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/app/src/commonMain/kotlin/App.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/build.gradle.kts create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/gradle.properties create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/settings.gradle.kts diff --git a/gradle-plugins/compose/build.gradle.kts b/gradle-plugins/compose/build.gradle.kts index b0865b2d8c..e89fdd48f2 100644 --- a/gradle-plugins/compose/build.gradle.kts +++ b/gradle-plugins/compose/build.gradle.kts @@ -72,7 +72,7 @@ dependencies { embedded(project(":jdk-version-probe")) } -val packagesToRelocate = listOf("de.undercouch") +val packagesToRelocate = listOf("de.undercouch", "com.squareup.kotlinpoet") val shadow = tasks.named("shadowJar") { for (packageToRelocate in packagesToRelocate) { diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt index 18e08731cb..9a6a5d3e44 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt @@ -222,4 +222,10 @@ class ResourcesTest : GradlePluginTestBase() { assert(file("src/commonMain/composeResources/values/strings.xml").readLines().size == 513) } } + + //https://github.com/gmazzo/gradle-buildconfig-plugin/issues/131 + @Test + fun testBundledKotlinPoet(): Unit = with(testProject("misc/bundledKotlinPoet")) { + gradle("generateBuildConfig") + } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/app/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/app/build.gradle.kts new file mode 100644 index 0000000000..5e0fd3e9fa --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/app/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") + id("com.github.gmazzo.buildconfig") +} + +group = "app.group" + +kotlin { + jvm() + + sourceSets { + commonMain { + dependencies { + implementation(compose.runtime) + implementation(compose.material) + implementation(compose.components.resources) + } + } + } +} + +buildConfig { + buildConfigField(String::class.java, "str", "") +} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/app/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/app/src/commonMain/kotlin/App.kt new file mode 100644 index 0000000000..e831ab588a --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/app/src/commonMain/kotlin/App.kt @@ -0,0 +1,9 @@ +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import app.group.empty_res.generated.resources.Res + +@Composable +fun App() { + val res = Res + Text("text") +} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/build.gradle.kts new file mode 100644 index 0000000000..53f913fce7 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + kotlin("multiplatform").apply(false) + id("org.jetbrains.compose").apply(false) +} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/gradle.properties b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/gradle.properties new file mode 100644 index 0000000000..c13202c0d5 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx8096M \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/settings.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/settings.gradle.kts new file mode 100644 index 0000000000..af0c57f19b --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/bundledKotlinPoet/settings.gradle.kts @@ -0,0 +1,24 @@ +rootProject.name = "bundled_kp" +include(":app") +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + google() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + } + plugins { + id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER") + id("org.jetbrains.compose").version("COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER") + id("com.github.gmazzo.buildconfig").version("5.3.5") + } +} +dependencyResolutionManagement { + repositories { + mavenLocal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + mavenCentral() + gradlePluginPortal() + google() + } +} \ No newline at end of file diff --git a/gradle-plugins/gradle/libs.versions.toml b/gradle-plugins/gradle/libs.versions.toml index 1b70767c49..5a2fc3056a 100644 --- a/gradle-plugins/gradle/libs.versions.toml +++ b/gradle-plugins/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "1.9.0" gradle-download-plugin = "5.5.0" -kotlin-poet = "1.14.2" +kotlin-poet = "1.16.0" plugin-android = "7.3.0" shadow-jar = "8.1.1" publish-plugin = "1.2.1" From a6b8b50423e13f712b3997183d6c808aec75255f Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 6 Feb 2024 11:28:54 +0100 Subject: [PATCH 13/30] Add a type name to the resource initializers (#4240) to avoid "Overload resolution ambiguity" for different resources with a same name https://github.com/JetBrains/compose-multiplatform/issues/4237 --- .../compose/resources/ResourcesSpec.kt | 7 +- .../misc/commonResources/expected/Res.kt | 84 ++++++++++--------- .../misc/jvmOnlyResources/expected/Res.kt | 15 ++-- 3 files changed, 57 insertions(+), 49 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt index 9527898c8a..195f3691ea 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt @@ -160,6 +160,9 @@ internal fun getResFileSpec( .forEach { addFunction(it) } }.build() +private fun getterName(resourceType: ResourceType, resourceName: String): String = + "get_${resourceType.typeName}_$resourceName" + private fun getResourceTypeObject(type: ResourceType, nameToResources: Map>) = TypeSpec.objectBuilder(type.typeName).apply { nameToResources.keys @@ -167,7 +170,7 @@ private fun getResourceTypeObject(type: ResourceType, nameToResources: Map): FunSpec { val propertyTypeName = type.getClassName() val resourceId = "${type}:${name}" - return FunSpec.builder("get_$name") + return FunSpec.builder(getterName(type, name)) .addModifiers(KModifier.PRIVATE) .returns(propertyTypeName) .addStatement( diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt index 27a8abb979..b173c6bc2a 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt @@ -27,31 +27,31 @@ internal object Res { public suspend fun readBytes(path: String): ByteArray = readResourceBytes(path) public object drawable { - public val _3_strange_name: DrawableResource = get__3_strange_name() + public val _3_strange_name: DrawableResource = get_drawable__3_strange_name() - public val vector: DrawableResource = get_vector() + public val vector: DrawableResource = get_drawable_vector() - public val vector_2: DrawableResource = get_vector_2() + public val vector_2: DrawableResource = get_drawable_vector_2() } public object string { - public val app_name: StringResource = get_app_name() + public val app_name: StringResource = get_string_app_name() - public val hello: StringResource = get_hello() + public val hello: StringResource = get_string_hello() - public val multi_line: StringResource = get_multi_line() + public val multi_line: StringResource = get_string_multi_line() - public val str_arr: StringResource = get_str_arr() + public val str_arr: StringResource = get_string_str_arr() - public val str_template: StringResource = get_str_template() + public val str_template: StringResource = get_string_str_template() } public object font { - public val emptyfont: FontResource = get_emptyfont() + public val emptyfont: FontResource = get_font_emptyfont() } } -private fun get__3_strange_name(): DrawableResource = +private fun get_drawable__3_strange_name(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:_3_strange_name", setOf( @@ -59,65 +59,69 @@ private fun get__3_strange_name(): DrawableResource = ) ) -private fun get_vector(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( - "drawable:vector", - setOf( +private fun get_drawable_vector(): DrawableResource = + org.jetbrains.compose.resources.DrawableResource( + "drawable:vector", + setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("au"), - org.jetbrains.compose.resources.RegionQualifier("US"), ), "drawable-au-rUS/vector.xml"), + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("au"), + org.jetbrains.compose.resources.RegionQualifier("US"), ), "drawable-au-rUS/vector.xml"), - org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.ThemeQualifier.DARK, - org.jetbrains.compose.resources.LanguageQualifier("ge"), ), "drawable-dark-ge/vector.xml"), + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.ThemeQualifier.DARK, + org.jetbrains.compose.resources.LanguageQualifier("ge"), ), "drawable-dark-ge/vector.xml"), - org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), - ), "drawable-en/vector.xml"), - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml"), + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), + ), "drawable-en/vector.xml"), + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml"), + ) ) -) -private fun get_vector_2(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( - "drawable:vector_2", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector_2.xml"), +private fun get_drawable_vector_2(): DrawableResource = + org.jetbrains.compose.resources.DrawableResource( + "drawable:vector_2", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector_2.xml"), + ) ) -) -private fun get_app_name(): StringResource = org.jetbrains.compose.resources.StringResource( +private fun get_string_app_name(): StringResource = org.jetbrains.compose.resources.StringResource( "string:app_name", "app_name", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), ) ) -private fun get_hello(): StringResource = org.jetbrains.compose.resources.StringResource( +private fun get_string_hello(): StringResource = org.jetbrains.compose.resources.StringResource( "string:hello", "hello", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), ) ) -private fun get_multi_line(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:multi_line", "multi_line", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), +private fun get_string_multi_line(): StringResource = + org.jetbrains.compose.resources.StringResource( + "string:multi_line", "multi_line", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) ) -) -private fun get_str_arr(): StringResource = org.jetbrains.compose.resources.StringResource( +private fun get_string_str_arr(): StringResource = org.jetbrains.compose.resources.StringResource( "string:str_arr", "str_arr", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), ) ) -private fun get_str_template(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:str_template", "str_template", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), +private fun get_string_str_template(): StringResource = + org.jetbrains.compose.resources.StringResource( + "string:str_template", "str_template", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) ) -) -private fun get_emptyfont(): FontResource = org.jetbrains.compose.resources.FontResource( +private fun get_font_emptyfont(): FontResource = org.jetbrains.compose.resources.FontResource( "font:emptyfont", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), "font/emptyFont.otf"), diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt index 83a5295364..ae5da9444e 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt @@ -25,13 +25,14 @@ internal object Res { public suspend fun readBytes(path: String): ByteArray = readResourceBytes(path) public object drawable { - public val vector: DrawableResource = get_vector() + public val vector: DrawableResource = get_drawable_vector() } } -private fun get_vector(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( - "drawable:vector", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml"), - ) -) \ No newline at end of file +private fun get_drawable_vector(): DrawableResource = + org.jetbrains.compose.resources.DrawableResource( + "drawable:vector", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml"), + ) + ) \ No newline at end of file From ffea196aa8eeb2ec6be6d59bcf96b57916159ce2 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Tue, 6 Feb 2024 18:18:48 +0100 Subject: [PATCH 14/30] Use coroutines 1.7.3 for desktop and native. But use 1.8.0-RC2 for web targets (#4244) --- components/gradle/libs.versions.toml | 2 +- components/resources/library/build.gradle.kts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/components/gradle/libs.versions.toml b/components/gradle/libs.versions.toml index 39edcda408..0a6d16ca83 100644 --- a/components/gradle/libs.versions.toml +++ b/components/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlinx-coroutines = "1.8.0-RC2" +kotlinx-coroutines = "1.7.3" androidx-appcompat = "1.6.1" androidx-activity-compose = "1.8.2" androidx-test = "1.5.0" diff --git a/components/resources/library/build.gradle.kts b/components/resources/library/build.gradle.kts index 1e563934c5..222274e5f8 100644 --- a/components/resources/library/build.gradle.kts +++ b/components/resources/library/build.gradle.kts @@ -204,3 +204,16 @@ afterEvaluate { if (name == "compileWebMainKotlinMetadata") enabled = false } } + +// TODO: remove this (https://youtrack.jetbrains.com/issue/COMPOSE-939) +configurations.all { + val isWeb = name.startsWith("wasmJs") || name.startsWith("js") + if (isWeb) { + resolutionStrategy.eachDependency { + if (requested.group.startsWith("org.jetbrains.kotlinx") && + requested.name.startsWith("kotlinx-coroutines-")) { + useVersion("1.8.0-RC2") + } + } + } +} \ No newline at end of file From b8be217dadf5fcc04cde0a7c72cbe38b9a455167 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 7 Feb 2024 09:14:37 +0100 Subject: [PATCH 15/30] Configure explicit dependency on the copyFonts task to fix a build (#4247) --- .../resources/AndroidTargetConfiguration.kt | 5 ++++- .../test/tests/integration/ResourcesTest.kt | 2 +- .../misc/commonResources/build.gradle.kts | 14 ++++++++++++-- .../misc/commonResources/settings.gradle.kts | 2 +- .../src/androidMain/AndroidManifest.xml | 4 ++++ 5 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/androidMain/AndroidManifest.xml diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt index f5e5b4f981..91c37a0390 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt @@ -2,7 +2,7 @@ package org.jetbrains.compose.resources import com.android.build.api.variant.AndroidComponentsExtension import com.android.build.gradle.BaseExtension -import com.android.build.gradle.tasks.MergeSourceSetFolders +import com.android.build.gradle.internal.tasks.AndroidVariantTask import org.gradle.api.Project import org.gradle.api.provider.Provider import org.gradle.api.tasks.Copy @@ -39,4 +39,7 @@ internal fun Project.configureAndroidResources( } ) } + //fixme: it seems like a problem in AGP, so dirty hack now + //https://github.com/JetBrains/compose-multiplatform/issues/4085 + tasks.matching { it is AndroidVariantTask }.configureEach { it.dependsOn(copyFonts) } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt index 9a6a5d3e44..73851f43ab 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt @@ -112,7 +112,7 @@ class ResourcesTest : GradlePluginTestBase() { @Test fun testCopyFontsInAndroidApp(): Unit = with(testProject("misc/commonResources")) { - gradle("assembleDebug").checks { + gradle("build").checks { check.taskSuccessful(":copyFontsToAndroidAssets") } } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts index d650b32dc6..a72fb6423f 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts @@ -1,6 +1,6 @@ plugins { kotlin("multiplatform") - id("com.android.library") + id("com.android.application") id("org.jetbrains.compose") } @@ -29,10 +29,20 @@ kotlin { } android { - compileSdk = 31 + compileSdk = 34 namespace = "org.jetbrains.compose.resources.test" + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { + applicationId = "org.example.project" minSdk = 21 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/settings.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/settings.gradle.kts index 0a0287df97..09cf8bcfc6 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/settings.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/settings.gradle.kts @@ -7,7 +7,7 @@ pluginManagement { maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } plugins { - id("com.android.library").version("AGP_VERSION_PLACEHOLDER") + id("com.android.application").version("AGP_VERSION_PLACEHOLDER") id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER") id("org.jetbrains.compose").version("COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER") } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/androidMain/AndroidManifest.xml b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000000..96aa10f056 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/androidMain/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + From d6b8681b22b24c8ac2f79db7199e09bf8873a577 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 7 Feb 2024 14:58:51 +0100 Subject: [PATCH 16/30] Don't make resource IDs lowercased (#4253) --- .../compose/resources/demo/shared/FontRes.kt | 2 +- .../compose/resources/GenerateResClassTask.kt | 3 +- .../compose/resources/ResourcesGenerator.kt | 2 +- .../misc/commonResources/expected/Res.kt | 45 +++++++++++++++++-- .../drawable/camelCaseName.xml | 36 +++++++++++++++ .../composeResources/values/strings.xml | 3 ++ .../src/commonMain/kotlin/App.kt | 2 +- 7 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/drawable/camelCaseName.xml diff --git a/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FontRes.kt b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FontRes.kt index c2a3e4551a..49d39b7a19 100644 --- a/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FontRes.kt +++ b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FontRes.kt @@ -42,7 +42,7 @@ fun FontRes(paddingValues: PaddingValues) { } Text( modifier = Modifier.padding(16.dp), - fontFamily = FontFamily(Font(Res.font.workbench_regular)), + fontFamily = FontFamily(Font(Res.font.Workbench_Regular)), style = MaterialTheme.typography.headlineLarge, text = "brown fox jumps over the lazy dog" ) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt index 130e2a9d71..b3d19ef68f 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt @@ -106,6 +106,5 @@ abstract class GenerateResClassTask : DefaultTask() { } internal fun String.asUnderscoredIdentifier(): String = - lowercase() - .replace('-', '_') + replace('-', '_') .let { if (it.isNotEmpty() && it.first().isDigit()) "_$it" else it } \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt index b84193f8d4..b0cb6ae8a8 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt @@ -43,7 +43,7 @@ private fun Project.configureResourceGenerator(commonComposeResourcesDir: File, val commonComposeResources = provider { commonComposeResourcesDir } val packageName = provider { buildString { - val group = project.group.toString().asUnderscoredIdentifier() + val group = project.group.toString().lowercase().asUnderscoredIdentifier() append(group) if (group.isNotEmpty()) append(".") append(project.name.lowercase()) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt index b173c6bc2a..7b3a1c23b0 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt @@ -29,14 +29,22 @@ internal object Res { public object drawable { public val _3_strange_name: DrawableResource = get_drawable__3_strange_name() + public val camelCaseName: DrawableResource = get_drawable_camelCaseName() + public val vector: DrawableResource = get_drawable_vector() public val vector_2: DrawableResource = get_drawable_vector_2() } public object string { + public val PascalCase: StringResource = get_string_PascalCase() + + public val _1_kebab_case: StringResource = get_string__1_kebab_case() + public val app_name: StringResource = get_string_app_name() + public val camelCase: StringResource = get_string_camelCase() + public val hello: StringResource = get_string_hello() public val multi_line: StringResource = get_string_multi_line() @@ -47,7 +55,7 @@ internal object Res { } public object font { - public val emptyfont: FontResource = get_font_emptyfont() + public val emptyFont: FontResource = get_font_emptyFont() } } @@ -59,6 +67,14 @@ private fun get_drawable__3_strange_name(): DrawableResource = ) ) +private fun get_drawable_camelCaseName(): DrawableResource = + org.jetbrains.compose.resources.DrawableResource( + "drawable:camelCaseName", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/camelCaseName.xml"), + ) + ) + private fun get_drawable_vector(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:vector", @@ -84,6 +100,22 @@ private fun get_drawable_vector_2(): DrawableResource = ) ) +private fun get_string_PascalCase(): StringResource = + org.jetbrains.compose.resources.StringResource( + "string:PascalCase", "PascalCase", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) + ) + +private fun get_string__1_kebab_case(): StringResource = + org.jetbrains.compose.resources.StringResource( + "string:_1_kebab_case", "_1_kebab_case", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) + ) + private fun get_string_app_name(): StringResource = org.jetbrains.compose.resources.StringResource( "string:app_name", "app_name", setOf( @@ -91,6 +123,13 @@ private fun get_string_app_name(): StringResource = org.jetbrains.compose.resour ) ) +private fun get_string_camelCase(): StringResource = org.jetbrains.compose.resources.StringResource( + "string:camelCase", "camelCase", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.xml"), + ) +) + private fun get_string_hello(): StringResource = org.jetbrains.compose.resources.StringResource( "string:hello", "hello", setOf( @@ -121,8 +160,8 @@ private fun get_string_str_template(): StringResource = ) ) -private fun get_font_emptyfont(): FontResource = org.jetbrains.compose.resources.FontResource( - "font:emptyfont", +private fun get_font_emptyFont(): FontResource = org.jetbrains.compose.resources.FontResource( + "font:emptyFont", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), "font/emptyFont.otf"), ) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/drawable/camelCaseName.xml b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/drawable/camelCaseName.xml new file mode 100644 index 0000000000..d7bf7955f4 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/drawable/camelCaseName.xml @@ -0,0 +1,36 @@ + + + + + + + + diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/values/strings.xml b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/values/strings.xml index bdfed65b61..6fa9a0966b 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/values/strings.xml +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/values/strings.xml @@ -10,4 +10,7 @@ item 2 item 3 + PascalCase + 1-kebab-case + camelCase diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/kotlin/App.kt index 376fe108ba..0f463fd122 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/kotlin/App.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/kotlin/App.kt @@ -18,6 +18,6 @@ fun App() { contentDescription = null ) Text(stringResource(Res.string.app_name)) - val font = FontFamily(Font(Res.font.emptyfont)) + val font = FontFamily(Font(Res.font.emptyFont)) } } From 8ee7531c424421657842a24a5c365db53ba19e18 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 7 Feb 2024 16:13:30 +0100 Subject: [PATCH 17/30] Clean code-gen directory if there was deleted a dependency on the res library (#4257) fixes https://github.com/JetBrains/compose-multiplatform/issues/4242 --- .../compose/resources/GenerateResClassTask.kt | 52 +++++++++++-------- .../compose/resources/ResourcesGenerator.kt | 6 +-- .../test/tests/integration/ResourcesTest.kt | 50 ++++++++++++++++++ .../compose/test/utils/assertUtils.kt | 4 ++ .../misc/commonResources/build.gradle.kts | 1 - 5 files changed, 87 insertions(+), 26 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt index b3d19ef68f..c30cb2583d 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt @@ -12,10 +12,13 @@ import kotlin.io.path.relativeTo /** * This task should be FAST and SAFE! Because it is being run during IDE import. */ -abstract class GenerateResClassTask : DefaultTask() { +internal abstract class GenerateResClassTask : DefaultTask() { @get:Input abstract val packageName: Property + @get:Input + abstract val shouldGenerateResClass: Property + @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val resDir: Property @@ -26,32 +29,37 @@ abstract class GenerateResClassTask : DefaultTask() { @TaskAction fun generate() { try { - val rootResDir = resDir.get() - logger.info("Generate resources for $rootResDir") + val kotlinDir = codeDir.get().asFile + logger.info("Clean directory $kotlinDir") + kotlinDir.deleteRecursively() + kotlinDir.mkdirs() - //get first level dirs - val dirs = rootResDir.listNotHiddenFiles() + if (shouldGenerateResClass.get()) { + val rootResDir = resDir.get() + logger.info("Generate resources for $rootResDir") - dirs.forEach { f -> - if (!f.isDirectory) { - error("${f.name} is not directory! Raw files should be placed in '${rootResDir.name}/files' directory.") - } - } + //get first level dirs + val dirs = rootResDir.listNotHiddenFiles() - //type -> id -> resource item - val resources: Map>> = dirs - .flatMap { dir -> - dir.listNotHiddenFiles() - .mapNotNull { it.fileToResourceItems(rootResDir.toPath()) } - .flatten() + dirs.forEach { f -> + if (!f.isDirectory) { + error("${f.name} is not directory! Raw files should be placed in '${rootResDir.name}/files' directory.") + } } - .groupBy { it.type } - .mapValues { (_, items) -> items.groupBy { it.name } } - val kotlinDir = codeDir.get().asFile - kotlinDir.deleteRecursively() - kotlinDir.mkdirs() - getResFileSpec(resources, packageName.get()).writeTo(kotlinDir) + //type -> id -> resource item + val resources: Map>> = dirs + .flatMap { dir -> + dir.listNotHiddenFiles() + .mapNotNull { it.fileToResourceItems(rootResDir.toPath()) } + .flatten() + } + .groupBy { it.type } + .mapValues { (_, items) -> items.groupBy { it.name } } + getResFileSpec(resources, packageName.get()).writeTo(kotlinDir) + } else { + logger.info("Generation Res class is disabled") + } } catch (e: Exception) { //message must contain two ':' symbols to be parsed by IDE UI! logger.error("e: GenerateResClassTask was failed:", e) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt index b0cb6ae8a8..9378ac67f9 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt @@ -54,7 +54,7 @@ private fun Project.configureResourceGenerator(commonComposeResourcesDir: File, fun buildDir(path: String) = layout.dir(layout.buildDirectory.map { File(it.asFile, path) }) //lazy check a dependency on the Resources library - val shouldGenerateResourceAccessors: Provider = provider { + val shouldGenerateResClass: Provider = provider { if (ComposeProperties.alwaysGenerateResourceAccessors(project).get()) { true } else { @@ -72,9 +72,9 @@ private fun Project.configureResourceGenerator(commonComposeResourcesDir: File, GenerateResClassTask::class.java ) { it.packageName.set(packageName) + it.shouldGenerateResClass.set(shouldGenerateResClass) it.resDir.set(commonComposeResources) it.codeDir.set(buildDir("$RES_GEN_DIR/kotlin")) - it.onlyIf { shouldGenerateResourceAccessors.get() } } //register generated source set @@ -93,7 +93,7 @@ private fun Project.configureResourceGenerator(commonComposeResourcesDir: File, configureAndroidResources( commonComposeResources, buildDir("$RES_GEN_DIR/androidFonts").map { it.asFile }, - shouldGenerateResourceAccessors + shouldGenerateResClass ) } } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt index 73851f43ab..aef995819d 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt @@ -117,6 +117,56 @@ class ResourcesTest : GradlePluginTestBase() { } } + @Test + fun testUpToDateChecks(): Unit = with(testProject("misc/commonResources")) { + gradle("prepareKotlinIdeaImport").checks { + check.taskSuccessful(":generateComposeResClass") + assert(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + } + gradle("prepareKotlinIdeaImport").checks { + check.taskUpToDate(":generateComposeResClass") + } + + modifyText("build.gradle.kts") { str -> + str.replace( + "implementation(compose.components.resources)", + "//implementation(compose.components.resources)" + ) + } + gradle("prepareKotlinIdeaImport").checks { + check.taskSuccessful(":generateComposeResClass") + assert(!file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + } + + gradle("prepareKotlinIdeaImport", "-Pcompose.resources.always.generate.accessors=true").checks { + check.taskSuccessful(":generateComposeResClass") + assert(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + } + + modifyText("build.gradle.kts") { str -> + str.replace( + "//implementation(compose.components.resources)", + "implementation(compose.components.resources)" + ) + } + gradle("prepareKotlinIdeaImport").checks { + check.taskUpToDate(":generateComposeResClass") + assert(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + } + + modifyText("build.gradle.kts") { str -> + str.replace( + "group = \"app.group\"", + "group = \"io.company\"" + ) + } + gradle("prepareKotlinIdeaImport").checks { + check.taskSuccessful(":generateComposeResClass") + assert(!file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + assert(file("build/generated/compose/resourceGenerator/kotlin/io/company/resources_test/generated/resources/Res.kt").exists()) + } + } + @Test fun testEmptyResClass(): Unit = with(testProject("misc/emptyResources")) { gradle("generateComposeResClass").checks { diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt index 6be9978bb0..1503c8f083 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt @@ -57,6 +57,10 @@ internal class BuildResultChecks(private val result: BuildResult) { taskOutcome(task, TaskOutcome.FAILED) } + fun taskUpToDate(task: String) { + taskOutcome(task, TaskOutcome.UP_TO_DATE) + } + fun taskFromCache(task: String) { taskOutcome(task, TaskOutcome.FROM_CACHE) } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts index a72fb6423f..392b9a9efc 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts @@ -21,7 +21,6 @@ kotlin { dependencies { implementation(compose.runtime) implementation(compose.material) - @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) implementation(compose.components.resources) } } From 7241003df8bc657129488c58d13808faf55b2fd7 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Wed, 7 Feb 2024 17:11:30 +0100 Subject: [PATCH 18/30] Update CHANGELOG.md (#4249) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81ae5f499f..edc57fad61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,7 +95,7 @@ This version of Compose Multiplatform is based on the next Jetpack Compose libra ## Highlights -- Resource library improvements ([demo project](https://github.com/JetBrains/compose-multiplatform/tree/c31c761e09212eaa13014f4d0d2a6516511f859a/gradle-plugins/compose/src/test/test-projects/misc/commonResources)) +- Resource library improvements ([an example project](https://github.com/JetBrains/compose-multiplatform/tree/8ee7531c424421657842a24a5c365db53ba19e18/components/resources/demo)) - [Compile-time checking of resources through a generated `Res` class](https://github.com/JetBrains/compose-multiplatform/pull/3961) - [Introduce top level `composeResources` dir with `drawable`, `font`, `files`, `values/strings.xml` support](https://github.com/JetBrains/compose-multiplatform/pull/4127) - [Support for various screen densities, multiple languages and regions, and light and dark themes](https://github.com/JetBrains/compose-multiplatform/pull/4018) From 2b12d57008c9a12326e664fa5bfc5b0b414b25d0 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 8 Feb 2024 12:48:14 +0100 Subject: [PATCH 19/30] Configure android variants the correct way instead of the hack with 'dependsOn' (#4264) --- .../resources/AndroidTargetConfiguration.kt | 43 +++++++++++-------- .../test/tests/integration/ResourcesTest.kt | 3 +- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt index 91c37a0390..d251a82b54 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt @@ -2,12 +2,12 @@ package org.jetbrains.compose.resources import com.android.build.api.variant.AndroidComponentsExtension import com.android.build.gradle.BaseExtension -import com.android.build.gradle.internal.tasks.AndroidVariantTask import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Provider -import org.gradle.api.tasks.Copy -import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.* import org.jetbrains.compose.internal.utils.registerTask +import org.jetbrains.compose.internal.utils.uppercaseFirstChar import java.io.File internal fun Project.configureAndroidResources( @@ -22,24 +22,31 @@ internal fun Project.configureAndroidResources( androidMainSourceSet.resources.srcDir(commonResourcesDir) androidMainSourceSet.assets.srcDir(androidFontsDir) - val copyFonts = registerTask("copyFontsToAndroidAssets") { - includeEmptyDirs = false - from(commonResourcesDir) - include("**/font*/*") - into(androidFontsDir) - onlyIf { onlyIfProvider.get() } - } androidComponents.onVariants { variant -> + val copyFonts = registerTask( + "copy${variant.name.uppercaseFirstChar()}FontsToAndroidAssets" + ) { + includeEmptyDirs = false + from(commonResourcesDir) + include("**/font*/*") + onlyIf { onlyIfProvider.get() } + } variant.sources?.assets?.addGeneratedSourceDirectory( taskProvider = copyFonts, - wiredWith = { - objects.directoryProperty().fileProvider( - copyFonts.map { t -> t.destinationDir } - ) - } + wiredWith = CopyAndroidAssetsTask::outputDirectory ) } - //fixme: it seems like a problem in AGP, so dirty hack now - //https://github.com/JetBrains/compose-multiplatform/issues/4085 - tasks.matching { it is AndroidVariantTask }.configureEach { it.dependsOn(copyFonts) } +} + +//https://github.com/JetBrains/compose-multiplatform/issues/4085 +private abstract class CopyAndroidAssetsTask : Copy() { + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + override fun getDestinationDir(): File = + outputDirectory.get().asFile + + override fun setDestinationDir(destination: File) { + outputDirectory.set(destination) + } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt index aef995819d..619cf7fba0 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt @@ -113,7 +113,8 @@ class ResourcesTest : GradlePluginTestBase() { @Test fun testCopyFontsInAndroidApp(): Unit = with(testProject("misc/commonResources")) { gradle("build").checks { - check.taskSuccessful(":copyFontsToAndroidAssets") + check.taskSuccessful(":copyDebugFontsToAndroidAssets") + check.taskSuccessful(":copyReleaseFontsToAndroidAssets") } } From a8e9486b9e69045bd29aef9a04764913a852257e Mon Sep 17 00:00:00 2001 From: Ahmed Hosny Date: Thu, 8 Feb 2024 15:41:56 +0200 Subject: [PATCH 20/30] Support Rtl in SplitPane (#4265) This is related to issue: https://github.com/JetBrains/compose-multiplatform/issues/4258 changes - DesktopSplitPane.kt: placable.place() -> placable.placeRelative - SplitePaneDSL.kt: change the delta direction to follow the layout direction ```kotlin @Composable override fun Modifier.markAsHandle(): Modifier = this.run { val layoutDirection = LocalLayoutDirection.current pointerInput(containerScope.splitPaneState) { detectDragGestures { change, _ -> change.consume() containerScope.splitPaneState.dispatchRawMovement( if (containerScope.isHorizontal) if (layoutDirection == LayoutDirection.Ltr) change.position.x else -change.position.x else change.position.y ) } } ``` the problem with .onPointerEvent() Modifier, or onDrag also, is whenever the layout direction is Ltr or Rtl: moving to right always produce positive change, [expected negative if dir =Rtl] moving to left always produce negative change, [expected positive if dir =Rtl] the calculation of postion will fail if layoutDir is Rtl, because positionPercentage will be out of range ```kotlin fun dispatchRawMovement(delta: Float) { val movableArea = maxPosition - minPosition if (movableArea > 0) { positionPercentage = ((movableArea * positionPercentage) + delta).coerceIn(0f, movableArea) / movableArea } } ``` --- .../compose/splitpane/SplitPaneDSL.kt | 20 +++++++++++++------ .../compose/splitpane/DesktopSplitPane.kt | 14 ++++++------- .../compose/splitpane/DesktopSplitter.kt | 19 ++++++++++++------ 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/components/SplitPane/library/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneDSL.kt b/components/SplitPane/library/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneDSL.kt index 0a730931d4..4c53343152 100644 --- a/components/SplitPane/library/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneDSL.kt +++ b/components/SplitPane/library/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneDSL.kt @@ -4,8 +4,11 @@ import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.composed import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp /** Receiver scope which is used by [HorizontalSplitPane] and [VerticalSplitPane] */ @@ -83,12 +86,17 @@ interface SplitterScope { internal class HandleScopeImpl( private val containerScope: SplitPaneScopeImpl ) : HandleScope { - override fun Modifier.markAsHandle(): Modifier = this.pointerInput(containerScope.splitPaneState) { - detectDragGestures { change, _ -> - change.consume() - containerScope.splitPaneState.dispatchRawMovement( - if (containerScope.isHorizontal) change.position.x else change.position.y - ) + override fun Modifier.markAsHandle(): Modifier = composed { + val layoutDirection = LocalLayoutDirection.current + pointerInput(containerScope.splitPaneState) { + detectDragGestures { change, _ -> + change.consume() + containerScope.splitPaneState.dispatchRawMovement( + if (containerScope.isHorizontal) + if (layoutDirection == LayoutDirection.Ltr) change.position.x else -change.position.x + else change.position.y + ) + } } } } diff --git a/components/SplitPane/library/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitPane.kt b/components/SplitPane/library/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitPane.kt index cf978e2ae8..a8c1f00d22 100644 --- a/components/SplitPane/library/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitPane.kt +++ b/components/SplitPane/library/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitPane.kt @@ -121,18 +121,18 @@ internal actual fun SplitPane( } layout(constraints.maxWidth, constraints.maxHeight) { - firstPlaceable.place(0, 0) + firstPlaceable.placeRelative(0, 0) if (isHorizontal) { - secondPlaceable.place(secondPlaceablePosition, 0) - splitterPlaceable.place(position, 0) + secondPlaceable.placeRelative(secondPlaceablePosition, 0) + splitterPlaceable.placeRelative(position, 0) if (moveEnabled) { - handlePlaceable.place(handlePosition, 0) + handlePlaceable.placeRelative(handlePosition, 0) } } else { - secondPlaceable.place(0, secondPlaceablePosition) - splitterPlaceable.place(0, position) + secondPlaceable.placeRelative(0, secondPlaceablePosition) + splitterPlaceable.placeRelative(0, position) if (moveEnabled) { - handlePlaceable.place(0, handlePosition) + handlePlaceable.placeRelative(0, handlePosition) } } } diff --git a/components/SplitPane/library/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitter.kt b/components/SplitPane/library/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitter.kt index 15e5c6cccb..92ad6749e8 100644 --- a/components/SplitPane/library/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitter.kt +++ b/components/SplitPane/library/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitter.kt @@ -6,6 +6,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.* +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import java.awt.Cursor @@ -20,12 +22,17 @@ private fun DesktopHandle( splitPaneState: SplitPaneState ) = Box( Modifier - .pointerInput(splitPaneState) { - detectDragGestures { change, _ -> - change.consumeAllChanges() - splitPaneState.dispatchRawMovement( - if (isHorizontal) change.position.x else change.position.y - ) + .run { + val layoutDirection = LocalLayoutDirection.current + pointerInput(splitPaneState) { + detectDragGestures { change, _ -> + change.consume() + splitPaneState.dispatchRawMovement( + if (isHorizontal) + if (layoutDirection == LayoutDirection.Ltr) change.position.x else -change.position.x + else change.position.y + ) + } } } .cursorForHorizontalResize(isHorizontal) From e680f9ffa349193c7c2c1f664aec7b0166c818da Mon Sep 17 00:00:00 2001 From: Shishkin Pavel Date: Thu, 8 Feb 2024 17:05:50 +0100 Subject: [PATCH 21/30] update compiler version to 1.5.8.1-beta02 (#4269) fixed: https://github.com/JetBrains/compose-multiplatform/issues/3318 https://github.com/JetBrains/compose-multiplatform/issues/3643 https://github.com/JetBrains/compose-multiplatform/issues/4055 --- .../org/jetbrains/compose/ComposeCompilerCompatibility.kt | 2 +- gradle-plugins/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerCompatibility.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerCompatibility.kt index f5e4a24005..176fba80e5 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerCompatibility.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerCompatibility.kt @@ -22,7 +22,7 @@ internal object ComposeCompilerCompatibility { "1.9.20-RC2" to "1.5.3-rc01", "1.9.20" to "1.5.3", "1.9.21" to "1.5.4", - "1.9.22" to "1.5.8.1-beta01", + "1.9.22" to "1.5.8.1-beta02", "2.0.0-Beta1" to "1.5.4-dev1-kt2.0.0-Beta1", ) diff --git a/gradle-plugins/gradle.properties b/gradle-plugins/gradle.properties index 90f339f4d7..6ffac9c3b1 100644 --- a/gradle-plugins/gradle.properties +++ b/gradle-plugins/gradle.properties @@ -4,7 +4,7 @@ kotlin.code.style=official # Default version of Compose Libraries used by Gradle plugin compose.version=1.6.0-dev1383 # The latest version of Compose Compiler used by Gradle plugin. Used only in tests/CI. -compose.tests.compiler.version=1.5.8.1-beta01 +compose.tests.compiler.version=1.5.8.1-beta02 # The latest version of Kotlin compatible with compose.tests.compiler.version. Used only in tests/CI. compose.tests.compiler.compatible.kotlin.version=1.9.22 # The latest version of Kotlin compatible with compose.tests.compiler.version for JS target. Used only on CI. From c4bc761b0dc2a4a3ae3d379203d2d54b1f58c044 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Sat, 10 Feb 2024 13:13:03 +0100 Subject: [PATCH 22/30] Gradle Plugin: Force kotlinx-coroutines version 1.8.0-RC2 for web targets in user projects (#4278) Removed such the version substituion from the resources library. The versions will be subtituted now by gradle plugin. --- components/resources/library/build.gradle.kts | 13 ------------- .../org/jetbrains/compose/ComposePlugin.kt | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/components/resources/library/build.gradle.kts b/components/resources/library/build.gradle.kts index 222274e5f8..1e563934c5 100644 --- a/components/resources/library/build.gradle.kts +++ b/components/resources/library/build.gradle.kts @@ -204,16 +204,3 @@ afterEvaluate { if (name == "compileWebMainKotlinMetadata") enabled = false } } - -// TODO: remove this (https://youtrack.jetbrains.com/issue/COMPOSE-939) -configurations.all { - val isWeb = name.startsWith("wasmJs") || name.startsWith("js") - if (isWeb) { - resolutionStrategy.eachDependency { - if (requested.group.startsWith("org.jetbrains.kotlinx") && - requested.name.startsWith("kotlinx-coroutines-")) { - useVersion("1.8.0-RC2") - } - } - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt index f542374d67..a7c4b4acd3 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt @@ -87,6 +87,23 @@ abstract class ComposePlugin : Plugin { disableSignatureClashCheck(project) } + + // TODO: remove this (https://youtrack.jetbrains.com/issue/COMPOSE-939) + // we substitute the coroutines version for web targets in user projects, + // so they don't need to do that manually + project.configurations.all { + val isWeb = it.name.startsWith("wasmJs") || it.name.startsWith("js") + if (isWeb) { + it.resolutionStrategy.eachDependency { + if (it.requested.group.startsWith("org.jetbrains.kotlinx") && + it.requested.name.startsWith("kotlinx-coroutines-")) { + if (it.requested.version?.startsWith("1.8") != true) { + it.useVersion("1.8.0-RC2") + } + } + } + } + } } private fun disableSignatureClashCheck(project: Project) { From a0601c3024a82cdfb7386282c55726f6ce0bcf81 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 12 Feb 2024 14:39:49 +0100 Subject: [PATCH 23/30] Register all hierarchical compose resources in android compilation (#4274) --- .../composeResources/files/platform-text.txt | 1 + .../compose/resources/demo/shared/FileRes.kt | 49 ++++-- .../composeResources/files/platform-text.txt | 1 + .../composeResources/files/platform-text.txt | 1 + .../composeResources/files/platform-text.txt | 1 + .../composeResources/files/platform-text.txt | 1 + .../composeResources/files/platform-text.txt | 1 + gradle-plugins/compose/build.gradle.kts | 1 + .../resources/AndroidTargetConfiguration.kt | 52 ------ .../compose/resources/ResourcesGenerator.kt | 112 +++++++++++-- .../test/tests/integration/ResourcesTest.kt | 158 ++++++++++++++---- .../misc/commonResources/build.gradle.kts | 2 +- .../misc/commonResources/gradle.properties | 3 +- 13 files changed, 267 insertions(+), 116 deletions(-) create mode 100644 components/resources/demo/shared/src/androidMain/composeResources/files/platform-text.txt create mode 100644 components/resources/demo/shared/src/desktopMain/composeResources/files/platform-text.txt create mode 100644 components/resources/demo/shared/src/iosMain/composeResources/files/platform-text.txt create mode 100644 components/resources/demo/shared/src/jsMain/composeResources/files/platform-text.txt create mode 100644 components/resources/demo/shared/src/macosMain/composeResources/files/platform-text.txt create mode 100644 components/resources/demo/shared/src/wasmJsMain/composeResources/files/platform-text.txt delete mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt diff --git a/components/resources/demo/shared/src/androidMain/composeResources/files/platform-text.txt b/components/resources/demo/shared/src/androidMain/composeResources/files/platform-text.txt new file mode 100644 index 0000000000..30cd8eb8bc --- /dev/null +++ b/components/resources/demo/shared/src/androidMain/composeResources/files/platform-text.txt @@ -0,0 +1 @@ +Android platform \ No newline at end of file diff --git a/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt index 57f1c65b59..efe4b68737 100644 --- a/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt +++ b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt @@ -1,22 +1,11 @@ package org.jetbrains.compose.resources.demo.shared -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedCard -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import components.resources.demo.shared.generated.resources.Res @@ -59,5 +48,37 @@ fun FileRes(paddingValues: PaddingValues) { Text(bytes.decodeToString()) """.trimIndent() ) + Text( + modifier = Modifier.padding(16.dp), + text = "File: 'files/platform-text.txt'", + style = MaterialTheme.typography.titleLarge + ) + OutlinedCard( + modifier = Modifier.padding(horizontal = 16.dp), + shape = RoundedCornerShape(4.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer) + ) { + var bytes by remember { mutableStateOf(ByteArray(0)) } + LaunchedEffect(Unit) { + bytes = Res.readBytes("files/platform-text.txt") + } + Text( + modifier = Modifier.padding(8.dp), + text = bytes.decodeToString(), + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + Text( + modifier = Modifier.padding(16.dp), + text = """ + var bytes by remember { + mutableStateOf(ByteArray(0)) + } + LaunchedEffect(Unit) { + bytes = Res.readFileBytes("files/platform-text.txt") + } + Text(bytes.decodeToString()) + """.trimIndent() + ) } } \ No newline at end of file diff --git a/components/resources/demo/shared/src/desktopMain/composeResources/files/platform-text.txt b/components/resources/demo/shared/src/desktopMain/composeResources/files/platform-text.txt new file mode 100644 index 0000000000..e4be1a9d0b --- /dev/null +++ b/components/resources/demo/shared/src/desktopMain/composeResources/files/platform-text.txt @@ -0,0 +1 @@ +Desktop platform \ No newline at end of file diff --git a/components/resources/demo/shared/src/iosMain/composeResources/files/platform-text.txt b/components/resources/demo/shared/src/iosMain/composeResources/files/platform-text.txt new file mode 100644 index 0000000000..05dccd1499 --- /dev/null +++ b/components/resources/demo/shared/src/iosMain/composeResources/files/platform-text.txt @@ -0,0 +1 @@ +iOS platform \ No newline at end of file diff --git a/components/resources/demo/shared/src/jsMain/composeResources/files/platform-text.txt b/components/resources/demo/shared/src/jsMain/composeResources/files/platform-text.txt new file mode 100644 index 0000000000..531c43b14a --- /dev/null +++ b/components/resources/demo/shared/src/jsMain/composeResources/files/platform-text.txt @@ -0,0 +1 @@ +JS platform \ No newline at end of file diff --git a/components/resources/demo/shared/src/macosMain/composeResources/files/platform-text.txt b/components/resources/demo/shared/src/macosMain/composeResources/files/platform-text.txt new file mode 100644 index 0000000000..c0ab602c16 --- /dev/null +++ b/components/resources/demo/shared/src/macosMain/composeResources/files/platform-text.txt @@ -0,0 +1 @@ +macOS platform \ No newline at end of file diff --git a/components/resources/demo/shared/src/wasmJsMain/composeResources/files/platform-text.txt b/components/resources/demo/shared/src/wasmJsMain/composeResources/files/platform-text.txt new file mode 100644 index 0000000000..100bd90dfa --- /dev/null +++ b/components/resources/demo/shared/src/wasmJsMain/composeResources/files/platform-text.txt @@ -0,0 +1 @@ +WasmJS platform \ No newline at end of file diff --git a/gradle-plugins/compose/build.gradle.kts b/gradle-plugins/compose/build.gradle.kts index e89fdd48f2..85c9df2114 100644 --- a/gradle-plugins/compose/build.gradle.kts +++ b/gradle-plugins/compose/build.gradle.kts @@ -63,6 +63,7 @@ dependencies { compileOnly(libs.plugin.android) compileOnly(libs.plugin.android.api) + testImplementation(kotlin("test")) testImplementation(gradleTestKit()) testImplementation(kotlin("gradle-plugin-api")) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt deleted file mode 100644 index d251a82b54..0000000000 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.jetbrains.compose.resources - -import com.android.build.api.variant.AndroidComponentsExtension -import com.android.build.gradle.BaseExtension -import org.gradle.api.Project -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* -import org.jetbrains.compose.internal.utils.registerTask -import org.jetbrains.compose.internal.utils.uppercaseFirstChar -import java.io.File - -internal fun Project.configureAndroidResources( - commonResourcesDir: Provider, - androidFontsDir: Provider, - onlyIfProvider: Provider -) { - val androidExtension = project.extensions.findByName("android") as? BaseExtension ?: return - val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return - - val androidMainSourceSet = androidExtension.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME) - androidMainSourceSet.resources.srcDir(commonResourcesDir) - androidMainSourceSet.assets.srcDir(androidFontsDir) - - androidComponents.onVariants { variant -> - val copyFonts = registerTask( - "copy${variant.name.uppercaseFirstChar()}FontsToAndroidAssets" - ) { - includeEmptyDirs = false - from(commonResourcesDir) - include("**/font*/*") - onlyIf { onlyIfProvider.get() } - } - variant.sources?.assets?.addGeneratedSourceDirectory( - taskProvider = copyFonts, - wiredWith = CopyAndroidAssetsTask::outputDirectory - ) - } -} - -//https://github.com/JetBrains/compose-multiplatform/issues/4085 -private abstract class CopyAndroidAssetsTask : Copy() { - @get:OutputDirectory - abstract val outputDirectory: DirectoryProperty - - override fun getDestinationDir(): File = - outputDirectory.get().asFile - - override fun setDestinationDir(destination: File) { - outputDirectory.set(destination) - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt index 9378ac67f9..801c8af054 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt @@ -1,18 +1,35 @@ package org.jetbrains.compose.resources +import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.internal.tasks.ProcessJavaResTask +import org.gradle.api.DefaultTask import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.provider.Property import org.gradle.api.provider.Provider -import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.* import org.jetbrains.compose.ComposePlugin import org.jetbrains.compose.desktop.application.internal.ComposeProperties import org.jetbrains.compose.internal.KOTLIN_JVM_PLUGIN_ID import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID +import org.jetbrains.compose.internal.utils.* +import org.jetbrains.compose.internal.utils.dependsOn +import org.jetbrains.compose.internal.utils.registerTask +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget +import org.jetbrains.kotlin.gradle.plugin.sources.android.androidSourceSetInfoOrNull +import org.jetbrains.kotlin.gradle.utils.ObservableSet import java.io.File +import javax.inject.Inject internal const val COMPOSE_RESOURCES_DIR = "composeResources" -private const val RES_GEN_DIR = "generated/compose/resourceGenerator" +internal const val RES_GEN_DIR = "generated/compose/resourceGenerator" private val androidPluginIds = listOf( "com.android.application", "com.android.library" @@ -20,27 +37,77 @@ private val androidPluginIds = listOf( internal fun Project.configureComposeResources() { plugins.withId(KOTLIN_MPP_PLUGIN_ID) { - configureComposeResources(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + val kotlinExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java) + configureComposeResources(kotlinExtension, KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + + //when applied AGP then configure android resources + androidPluginIds.forEach { pluginId -> + plugins.withId(pluginId) { + val androidExtension = project.extensions.getByType(BaseExtension::class.java) + configureAndroidComposeResources(kotlinExtension, androidExtension) + } + } } plugins.withId(KOTLIN_JVM_PLUGIN_ID) { - configureComposeResources(SourceSet.MAIN_SOURCE_SET_NAME) + val kotlinExtension = project.extensions.getByType(KotlinProjectExtension::class.java) + configureComposeResources(kotlinExtension, SourceSet.MAIN_SOURCE_SET_NAME) } } -private fun Project.configureComposeResources(commonSourceSetName: String) { - val kotlinExtension = project.extensions.getByType(KotlinProjectExtension::class.java) +private fun Project.configureComposeResources(kotlinExtension: KotlinProjectExtension, commonSourceSetName: String) { kotlinExtension.sourceSets.all { sourceSet -> val sourceSetName = sourceSet.name val composeResourcesPath = project.projectDir.resolve("src/$sourceSetName/$COMPOSE_RESOURCES_DIR") + + //To compose resources will be packed to a final artefact we need to mark them as resources + //sourceSet.resources works for all targets except ANDROID! sourceSet.resources.srcDirs(composeResourcesPath) + if (sourceSetName == commonSourceSetName) { configureResourceGenerator(composeResourcesPath, sourceSet) } } } +@OptIn(ExperimentalKotlinGradlePluginApi::class) +private fun Project.configureAndroidComposeResources( + kotlinExtension: KotlinMultiplatformExtension, + androidExtension: BaseExtension +) { + //mark all composeResources as Android resources + kotlinExtension.targets.matching { it is KotlinAndroidTarget }.all { androidTarget -> + androidTarget.compilations.all { compilation: KotlinCompilation<*> -> + compilation.defaultSourceSet.androidSourceSetInfoOrNull?.let { kotlinAndroidSourceSet -> + androidExtension.sourceSets + .matching { it.name == kotlinAndroidSourceSet.androidSourceSetName } + .all { androidSourceSet -> + (compilation.allKotlinSourceSets as ObservableSet).forAll { kotlinSourceSet -> + androidSourceSet.resources.srcDir( + projectDir.resolve("src/${kotlinSourceSet.name}/$COMPOSE_RESOURCES_DIR") + ) + } + } + } + } + } + + //copy fonts from the compose resources dir to android assets + val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return + val commonResourcesDir = projectDir.resolve("src/${KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME}/$COMPOSE_RESOURCES_DIR") + androidComponents.onVariants { variant -> + val copyFonts = registerTask( + "copy${variant.name.uppercaseFirstChar()}FontsToAndroidAssets" + ) { + from.set(commonResourcesDir) + } + variant.sources?.assets?.addGeneratedSourceDirectory( + taskProvider = copyFonts, + wiredWith = CopyAndroidFontsToAssetsTask::outputDirectory + ) + } +} + private fun Project.configureResourceGenerator(commonComposeResourcesDir: File, commonSourceSet: KotlinSourceSet) { - val commonComposeResources = provider { commonComposeResourcesDir } val packageName = provider { buildString { val group = project.group.toString().lowercase().asUnderscoredIdentifier() @@ -73,7 +140,7 @@ private fun Project.configureResourceGenerator(commonComposeResourcesDir: File, ) { it.packageName.set(packageName) it.shouldGenerateResClass.set(shouldGenerateResClass) - it.resDir.set(commonComposeResources) + it.resDir.set(commonComposeResourcesDir) it.codeDir.set(buildDir("$RES_GEN_DIR/kotlin")) } @@ -86,15 +153,26 @@ private fun Project.configureResourceGenerator(commonComposeResourcesDir: File, it.dependsOn(genTask) } } +} - //when applied AGP then configure android resources - androidPluginIds.forEach { pluginId -> - plugins.withId(pluginId) { - configureAndroidResources( - commonComposeResources, - buildDir("$RES_GEN_DIR/androidFonts").map { it.asFile }, - shouldGenerateResClass - ) +//Copy task doesn't work with 'variant.sources?.assets?.addGeneratedSourceDirectory' API +internal abstract class CopyAndroidFontsToAssetsTask : DefaultTask() { + @get:Inject + abstract val fileSystem: FileSystemOperations + + @get:Input + abstract val from: Property + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @TaskAction + fun action() { + fileSystem.copy { + it.includeEmptyDirs = false + it.from(from) + it.include("**/font*/*") + it.into(outputDirectory) } } -} +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt index 619cf7fba0..534216f3ee 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt @@ -1,11 +1,10 @@ package org.jetbrains.compose.test.tests.integration -import org.jetbrains.compose.test.utils.GradlePluginTestBase -import org.jetbrains.compose.test.utils.assertEqualTextFiles -import org.jetbrains.compose.test.utils.assertNotEqualTextFiles -import org.jetbrains.compose.test.utils.checks +import org.jetbrains.compose.test.utils.* import org.junit.jupiter.api.Test -import kotlin.io.path.Path +import java.io.File +import java.util.zip.ZipFile +import kotlin.test.* class ResourcesTest : GradlePluginTestBase() { @Test @@ -33,72 +32,88 @@ class ResourcesTest : GradlePluginTestBase() { file("src/commonMain/composeResources/drawable-ren") ) gradle("generateComposeResClass").checks { - check.logContains(""" + check.logContains( + """ contains unknown qualifier: 'ren'. - """.trimIndent()) + """.trimIndent() + ) } file("src/commonMain/composeResources/drawable-ren").renameTo( file("src/commonMain/composeResources/drawable-rUS-en") ) gradle("generateComposeResClass").checks { - check.logContains(""" + check.logContains( + """ Region qualifier must be declared after language: 'en-rUS'. - """.trimIndent()) + """.trimIndent() + ) } file("src/commonMain/composeResources/drawable-rUS-en").renameTo( file("src/commonMain/composeResources/drawable-rUS") ) gradle("generateComposeResClass").checks { - check.logContains(""" + check.logContains( + """ Region qualifier must be used only with language. - """.trimIndent()) + """.trimIndent() + ) } file("src/commonMain/composeResources/drawable-rUS").renameTo( file("src/commonMain/composeResources/drawable-en-fr") ) gradle("generateComposeResClass").checks { - check.logContains(""" + check.logContains( + """ contains repetitive qualifiers: 'en' and 'fr'. - """.trimIndent()) + """.trimIndent() + ) } file("src/commonMain/composeResources/drawable-en-fr").renameTo( file("src/commonMain/composeResources/image") ) gradle("generateComposeResClass").checks { - check.logContains(""" + check.logContains( + """ Unknown resource type: 'image' - """.trimIndent()) + """.trimIndent() + ) } file("src/commonMain/composeResources/image").renameTo( file("src/commonMain/composeResources/files-de") ) gradle("generateComposeResClass").checks { - check.logContains(""" + check.logContains( + """ The 'files' directory doesn't support qualifiers: 'files-de'. - """.trimIndent()) + """.trimIndent() + ) } file("src/commonMain/composeResources/files-de").renameTo( file("src/commonMain/composeResources/strings") ) gradle("generateComposeResClass").checks { - check.logContains(""" + check.logContains( + """ Unknown resource type: 'strings'. - """.trimIndent()) + """.trimIndent() + ) } file("src/commonMain/composeResources/strings").renameTo( file("src/commonMain/composeResources/string-us") ) gradle("generateComposeResClass").checks { - check.logContains(""" + check.logContains( + """ Forbidden directory name 'string-us'! String resources should be declared in 'values/strings.xml'. - """.trimIndent()) + """.trimIndent() + ) } //restore defaults @@ -111,10 +126,91 @@ class ResourcesTest : GradlePluginTestBase() { } @Test - fun testCopyFontsInAndroidApp(): Unit = with(testProject("misc/commonResources")) { + fun testFinalArtefacts(): Unit = with(testProject("misc/commonResources")) { + //https://developer.android.com/build/build-variants?utm_source=android-studio#product-flavors + file("build.gradle.kts").appendText(""" + + kotlin { + js { + browser { + testTask(Action { + enabled = false + }) + } + binaries.executable() + } + } + + android { + flavorDimensions += "version" + productFlavors { + create("demo") + create("full") + } + } + """.trimIndent()) + file("src/androidDemoDebug/composeResources/files/platform.txt").writeNewFile("android demo-debug") + file("src/androidDemoRelease/composeResources/files/platform.txt").writeNewFile("android demo-release") + file("src/androidFullDebug/composeResources/files/platform.txt").writeNewFile("android full-debug") + file("src/androidFullRelease/composeResources/files/platform.txt").writeNewFile("android full-release") + file("src/desktopMain/composeResources/files/platform.txt").writeNewFile("desktop") + file("src/jsMain/composeResources/files/platform.txt").writeNewFile("js") + + val commonResourcesDir = file("src/commonMain/composeResources") + val commonResourcesFiles = commonResourcesDir.walkTopDown() + .filter { !it.isDirectory && !it.isHidden } + .map { it.relativeTo(commonResourcesDir).invariantSeparatorsPath } + gradle("build").checks { - check.taskSuccessful(":copyDebugFontsToAndroidAssets") - check.taskSuccessful(":copyReleaseFontsToAndroidAssets") + check.taskSuccessful(":copyDemoDebugFontsToAndroidAssets") + check.taskSuccessful(":copyDemoReleaseFontsToAndroidAssets") + check.taskSuccessful(":copyFullDebugFontsToAndroidAssets") + check.taskSuccessful(":copyFullReleaseFontsToAndroidAssets") + + checkAndroidApk("demo", "debug", commonResourcesFiles) + checkAndroidApk("demo", "release", commonResourcesFiles) + checkAndroidApk("full", "debug", commonResourcesFiles) + checkAndroidApk("full", "release", commonResourcesFiles) + + val desktopJar = file("build/libs/resources_test-desktop.jar") + assertTrue(desktopJar.exists()) + ZipFile(desktopJar).use { zip -> + commonResourcesFiles.forEach { res -> + assertNotNull(zip.getEntry(res)) + } + val platformTxt = zip.getEntry("files/platform.txt") + assertNotNull(platformTxt) + val text = zip.getInputStream(platformTxt).readBytes().decodeToString() + assertEquals("desktop", text) + } + + val jsBuildDir = file("build/dist/js/productionExecutable") + commonResourcesFiles.forEach { res -> + assertTrue(jsBuildDir.resolve(res).exists()) + } + assertEquals("js", jsBuildDir.resolve("files/platform.txt").readText()) + } + } + + private fun File.writeNewFile(text: String) { + parentFile.mkdirs() + createNewFile() + writeText(text) + } + + private fun TestProject.checkAndroidApk(flavor: String, type: String, commonResourcesFiles: Sequence) { + val apk = file("build/outputs/apk/$flavor/$type/resources_test-$flavor-$type.apk") + assertTrue(apk.exists()) + ZipFile(apk).use { zip -> + commonResourcesFiles.forEach { res -> + assertNotNull(zip.getEntry(res)) + //todo fix duplicate fonts + } + assertNotNull(zip.getEntry("assets/font/emptyFont.otf")) + val platformTxt = zip.getEntry("files/platform.txt") + assertNotNull(platformTxt) + val text = zip.getInputStream(platformTxt).readBytes().decodeToString() + assertEquals("android $flavor-$type", text) } } @@ -122,7 +218,7 @@ class ResourcesTest : GradlePluginTestBase() { fun testUpToDateChecks(): Unit = with(testProject("misc/commonResources")) { gradle("prepareKotlinIdeaImport").checks { check.taskSuccessful(":generateComposeResClass") - assert(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + assertTrue(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) } gradle("prepareKotlinIdeaImport").checks { check.taskUpToDate(":generateComposeResClass") @@ -136,12 +232,12 @@ class ResourcesTest : GradlePluginTestBase() { } gradle("prepareKotlinIdeaImport").checks { check.taskSuccessful(":generateComposeResClass") - assert(!file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + assertFalse(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) } gradle("prepareKotlinIdeaImport", "-Pcompose.resources.always.generate.accessors=true").checks { check.taskSuccessful(":generateComposeResClass") - assert(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + assertTrue(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) } modifyText("build.gradle.kts") { str -> @@ -152,7 +248,7 @@ class ResourcesTest : GradlePluginTestBase() { } gradle("prepareKotlinIdeaImport").checks { check.taskUpToDate(":generateComposeResClass") - assert(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + assertTrue(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) } modifyText("build.gradle.kts") { str -> @@ -163,8 +259,8 @@ class ResourcesTest : GradlePluginTestBase() { } gradle("prepareKotlinIdeaImport").checks { check.taskSuccessful(":generateComposeResClass") - assert(!file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) - assert(file("build/generated/compose/resourceGenerator/kotlin/io/company/resources_test/generated/resources/Res.kt").exists()) + assertFalse(file("build/generated/compose/resourceGenerator/kotlin/app/group/resources_test/generated/resources/Res.kt").exists()) + assertTrue(file("build/generated/compose/resourceGenerator/kotlin/io/company/resources_test/generated/resources/Res.kt").exists()) } } @@ -270,7 +366,7 @@ class ResourcesTest : GradlePluginTestBase() { gradle("desktopJar").checks { check.taskSuccessful(":generateStringFiles") check.taskSuccessful(":generateComposeResClass") - assert(file("src/commonMain/composeResources/values/strings.xml").readLines().size == 513) + assertEquals(513, file("src/commonMain/composeResources/values/strings.xml").readLines().size) } } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts index 392b9a9efc..b26aa2a750 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts @@ -40,7 +40,7 @@ android { } buildTypes { getByName("release") { - isMinifyEnabled = false + initWith(getByName("debug")) } } compileOptions { diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/gradle.properties b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/gradle.properties index 6f0ca8b216..f4d7109663 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/gradle.properties +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/gradle.properties @@ -1,2 +1,3 @@ org.gradle.jvmargs=-Xmx8096M -android.useAndroidX=true \ No newline at end of file +android.useAndroidX=true +org.jetbrains.compose.experimental.jscanvas.enabled=true \ No newline at end of file From 689f63112a49aa8dbf8f04d9498d658b1a06798a Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Mon, 12 Feb 2024 16:29:40 +0100 Subject: [PATCH 24/30] Gradle Plugin: Simplify coroutines version check (#4283) --- .../main/kotlin/org/jetbrains/compose/ComposePlugin.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt index a7c4b4acd3..e689e31c99 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt @@ -19,8 +19,8 @@ import org.jetbrains.compose.desktop.DesktopExtension import org.jetbrains.compose.desktop.application.internal.configureDesktop import org.jetbrains.compose.desktop.preview.internal.initializePreview import org.jetbrains.compose.experimental.dsl.ExperimentalExtension -import org.jetbrains.compose.experimental.internal.configureExperimentalTargetsFlagsCheck import org.jetbrains.compose.experimental.internal.configureExperimental +import org.jetbrains.compose.experimental.internal.configureExperimentalTargetsFlagsCheck import org.jetbrains.compose.experimental.internal.configureNativeCompilerCaching import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID import org.jetbrains.compose.internal.mppExt @@ -31,11 +31,11 @@ import org.jetbrains.compose.internal.utils.currentTarget import org.jetbrains.compose.resources.configureComposeResources import org.jetbrains.compose.resources.ios.configureSyncTask import org.jetbrains.compose.web.WebExtension -import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler -import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion import org.jetbrains.kotlin.gradle.dsl.KotlinCompile import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile +import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion internal val composeVersion get() = ComposeBuildConfig.composeVersion @@ -97,7 +97,7 @@ abstract class ComposePlugin : Plugin { it.resolutionStrategy.eachDependency { if (it.requested.group.startsWith("org.jetbrains.kotlinx") && it.requested.name.startsWith("kotlinx-coroutines-")) { - if (it.requested.version?.startsWith("1.8") != true) { + if (it.requested.version?.startsWith("1.7") == true) { it.useVersion("1.8.0-RC2") } } From fbf5dbe20fbd34c11cd48ef64ac93e050b8c3388 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 12 Feb 2024 17:57:12 +0100 Subject: [PATCH 25/30] Fix fonts duplication in android app. (#4284) --- .../resources/ResourceReader.android.kt | 18 +++++++ .../resources/ResourceReader.desktop.kt} | 0 .../compose/resources/ResourcesGenerator.kt | 48 +++++++++++++++---- .../test/tests/integration/ResourcesTest.kt | 8 +++- 4 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.android.kt rename components/resources/library/src/{jvmAndAndroidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.jvmAndAndroid.kt => desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt} (100%) diff --git a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.android.kt b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.android.kt new file mode 100644 index 0000000000..b04857b424 --- /dev/null +++ b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.android.kt @@ -0,0 +1,18 @@ +package org.jetbrains.compose.resources + +import java.io.File + +private object AndroidResourceReader + +@OptIn(ExperimentalResourceApi::class) +@InternalResourceApi +actual suspend fun readResourceBytes(path: String): ByteArray { + val classLoader = Thread.currentThread().contextClassLoader ?: AndroidResourceReader.javaClass.classLoader + val resource = classLoader.getResourceAsStream(path) ?: run { + //try to find a font in the android assets + if (File(path).parentFile?.name.orEmpty() == "font") { + classLoader.getResourceAsStream("assets/$path") + } else null + } ?: throw MissingResourceException(path) + return resource.readBytes() +} \ No newline at end of file diff --git a/components/resources/library/src/jvmAndAndroidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.jvmAndAndroid.kt b/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt similarity index 100% rename from components/resources/library/src/jvmAndAndroidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.jvmAndAndroid.kt rename to components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt index 801c8af054..1d1c8081fa 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt @@ -15,8 +15,6 @@ import org.jetbrains.compose.desktop.application.internal.ComposeProperties import org.jetbrains.compose.internal.KOTLIN_JVM_PLUGIN_ID import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID import org.jetbrains.compose.internal.utils.* -import org.jetbrains.compose.internal.utils.dependsOn -import org.jetbrains.compose.internal.utils.registerTask import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension @@ -74,17 +72,31 @@ private fun Project.configureAndroidComposeResources( kotlinExtension: KotlinMultiplatformExtension, androidExtension: BaseExtension ) { + val commonResourcesDir = projectDir.resolve("src/${KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME}/$COMPOSE_RESOURCES_DIR") + + //Copy common compose resources except fonts to android resources + val copyCommonAndroidComposeResources = registerTask( + "copyCommonAndroidComposeResources" + ) { + from.set(commonResourcesDir) + outputDirectory.set(layout.buildDirectory.dir("$RES_GEN_DIR/commonAndroidComposeResources")) + } + tasks.configureEachWithType { dependsOn(copyCommonAndroidComposeResources) } + //mark all composeResources as Android resources - kotlinExtension.targets.matching { it is KotlinAndroidTarget }.all { androidTarget -> + kotlinExtension.targets.withType(KotlinAndroidTarget::class.java).all { androidTarget -> androidTarget.compilations.all { compilation: KotlinCompilation<*> -> compilation.defaultSourceSet.androidSourceSetInfoOrNull?.let { kotlinAndroidSourceSet -> androidExtension.sourceSets .matching { it.name == kotlinAndroidSourceSet.androidSourceSetName } .all { androidSourceSet -> - (compilation.allKotlinSourceSets as ObservableSet).forAll { kotlinSourceSet -> - androidSourceSet.resources.srcDir( - projectDir.resolve("src/${kotlinSourceSet.name}/$COMPOSE_RESOURCES_DIR") - ) + androidSourceSet.resources.srcDir(copyCommonAndroidComposeResources.flatMap { it.outputDirectory.asFile }) + (compilation.allKotlinSourceSets as? ObservableSet)?.forAll { kotlinSourceSet -> + if (kotlinSourceSet.name != KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) { + androidSourceSet.resources.srcDir( + projectDir.resolve("src/${kotlinSourceSet.name}/$COMPOSE_RESOURCES_DIR") + ) + } } } } @@ -93,7 +105,6 @@ private fun Project.configureAndroidComposeResources( //copy fonts from the compose resources dir to android assets val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return - val commonResourcesDir = projectDir.resolve("src/${KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME}/$COMPOSE_RESOURCES_DIR") androidComponents.onVariants { variant -> val copyFonts = registerTask( "copy${variant.name.uppercaseFirstChar()}FontsToAndroidAssets" @@ -155,6 +166,27 @@ private fun Project.configureResourceGenerator(commonComposeResourcesDir: File, } } +internal abstract class CopyCommonAndroidComposeResources : DefaultTask() { + @get:Inject + abstract val fileSystem: FileSystemOperations + + @get:InputFiles + abstract val from: Property + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @TaskAction + fun action() { + fileSystem.copy { + it.includeEmptyDirs = false + it.from(from) + it.exclude("**/font*/*") + it.into(outputDirectory) + } + } +} + //Copy task doesn't work with 'variant.sources?.assets?.addGeneratedSourceDirectory' API internal abstract class CopyAndroidFontsToAssetsTask : DefaultTask() { @get:Inject diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt index 534216f3ee..8958aafacc 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt @@ -203,8 +203,12 @@ class ResourcesTest : GradlePluginTestBase() { assertTrue(apk.exists()) ZipFile(apk).use { zip -> commonResourcesFiles.forEach { res -> - assertNotNull(zip.getEntry(res)) - //todo fix duplicate fonts + if (res == "font/emptyFont.otf") { + //android fonts should be only in assets + assertNull(zip.getEntry(res)) + } else { + assertNotNull(zip.getEntry(res)) + } } assertNotNull(zip.getEntry("assets/font/emptyFont.otf")) val platformTxt = zip.getEntry("files/platform.txt") From cf04f46d49b82d82b2b3185b68b234166677c69e Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Mon, 12 Feb 2024 11:15:44 +0100 Subject: [PATCH 26/30] Add test sign key in test project. --- .../misc/commonResources/build.gradle.kts | 14 +++++++++++++- .../misc/commonResources/key/debug.keystore | Bin 0 -> 2778 bytes 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/key/debug.keystore diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts index b26aa2a750..de773643c8 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts @@ -38,9 +38,21 @@ android { versionCode = 1 versionName = "1.0" } + signingConfigs { + create("testkey") { + storeFile = project.file("key/debug.keystore") + storePassword = "android" + keyAlias = "androiddebugkey" + keyPassword = "android" + } + } buildTypes { getByName("release") { - initWith(getByName("debug")) + isMinifyEnabled = false + signingConfig = signingConfigs.getByName("testkey") + } + getByName("debug") { + signingConfig = signingConfigs.getByName("testkey") } } compileOptions { diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/key/debug.keystore b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/key/debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..640ea305b8c3ccb4c9800e83cb26bec154885e78 GIT binary patch literal 2778 zcma)8S5y-U5=}xv2^|3eB?hF|00~_QD2Vi4l-`0OiWDhIG19vj5u}AC-4L1-K@ikX z6fg)Rk!nz+gH#{NW6$pKo&DPVm~&_DojafR3<3)s2LTxnSZE9*i%h&>{2n`y35dr+ zZ-cQ=|6}?Z0t@N>F9|{fVvb;RrTH=6_!Vu|NO_VMZ3WctfB!0~ja+ z=5mYrvWO|&+z)f`5V>t}qzlG^N0LJ?cGpz2+$pxcGBS!VFH1C*0e*RKROJshglfLJ z=MKR|etgKPp$p84bpeD;$8Be$tCEPP=(lU_9eO3EML=7>nWhHdS*Gg)%^hn6Qcn|k z@a=AA5A}#k>Qr#+)Jm@kfi0s5ZTCFgm@Yr*`5`Dh!6IhxeYHVC(dY9F^rcq~ZSE5i zNiD54lT#tz-P6}yRc_#wiOmeD2DS-)Z=6o5eBQ0PtavV=^}%QXM@a;Ya`%QcUQmh5 z9cON;2<-0LU7lACDN*cul2doCZa$x@l2NeU4&EPdU*AY^=L~dB$5!)z0o_||^pLYZ zy>80IpELn-kkHHG{zCBFC@$aeKVfa4l$Nq+gz?DnjI}&1gs&I4T-)57mmEbNl!sid z%Uvy%QwXH#3mYj%0Rpne_B~;z%yOH>Z zIS6#BfTU%rUDSIkd_sLc3?D1g5Nq{({55SNC46N^Ql`8Et^Co;hw>?=u<)|s0pv6p z+C5DDUCV(>QcKptu8XvVBUuaYRe*!Muf@8z+#h+l+v3Q(R?zqH0O(wsiOAUm`Ct2rn>pxeph?Z(N-EJrGS|{ z8_2{(Fo}@)Vc4XWE<`;u0+&*-VzeQ5OEqxF4k1mRZ4W*ZczIW(^YZroTRuhU@6D6k zfq*`xgpILXeE6XY*yn&>#<+ZIniBds-rftGabEHVHjT*Hus>cI4(H#`9MBkM%O!kK@5rIv7cb*FU4P z$<{}bK63oYwZp?D_m2v7+c_>hDobOLK4IDLwzPLG%@l@?LdK4L6H~c2H){3t*S2x; z+3C^M-<2L)Hq+rhIC6V4Y3!9(3KjMaSy5+-imLOP7hef!!YZcs^Oa9XEZCI(N`u+h z`aY?j5y!GZ#rBeVq!+GAvHwcR;9A5fd6wj~tuSA&8I+@H2E6)Y=~0%{5cVJ@v)Qvk zA2-n&dDCQ#n_d&Bv2&v#*H1r~JsYXF!{IJxa;fiRl5{r-*}F6_d5LLGOH=x-1B3FK zJo6#57KuS+DYo>bxWld3pi>=Q=GFK2MQy>{c0z!}`Ou9=?af-jS{G6Z1eS1B#3%a% za%7i@k`+S!Ph7I_D#2kefIHwOzzYxvxCOX&j9vh5fCnH1fC2a&ci{+;f7bbwc)>h2 z*ZjTVN@@r-HI%A~vZ|5_5`hI5|5;*U#ACr($5c892snm>e-z;V3ho}On6&3%FXzv> zLu7RY>J9+0gvb9+aAW#nmZOV@gNtg?mpfe`)EF$7YtHPVP)?_6)g9X+Q&^Hw&#iN0 z0k6atqj=xiT6{F69oCw~wAUv1wn*63fL(4_RVggbnqPO!-_}In;me+!Zy!4YQWh(g zhIoWu`z^Ssc?h21Dj8jdiX|r za(ZRf%wCUAZhUKygi6~fw8YR^C5nZV%9A`i9hv&+ku|&}lLrM++7|IPa%@ZUVO9DB z5$|<}CmMa%mosJKM84MT`=m=Rf4Yvq=4IR^m%eYoc>t^pI%+!Ksu~}OAGA};UZjN2 z@=Y}+LY*<=JX$QZ^f!eJeDPe=q~5Xtjt<%$SjwJd&rP_C+3sUX#68Mt|EWMYq9K*( z$^9r$jDK3foQQ?)r>=yRx7^Q5zzkLh}9d0IiPnP zB+a37sdCnfcbZZDv=IM$f&p-`-QlRyS>|+>Fy;fCR(=zojYh=1%uW75Y)68I{pLbj zljQl~nVHp?rKYI6V^OtPoOwWJxV*1_Nq>{6Y?l0@L!n~eifOy|Vdumli~7?0?8(W) zeW$BZAfY=X(q@dPibbgLE_O1cu^TrLe-P-!@K9Rg4N0zmGsJ_XE@(Y>;`QyU2V*s> zGrauK%WhA%=Yd;C&%T{ulbS)HRIZw3Jo>rZ1?=(*2kf9LN1}AXrFGD!bmt1IE9`4b z$WQEM#aX-NzyDHY%i3%+XoSgo{e;2f-LG1x4J(kJGgQT0TGz+B`v&t34~J?l=(NOn zr64HmrJ|u4EiZi18Ofpq{>H_8#N%(aTHVq;UuPn}PSvWp#UKf1murXmU*z6KSIA|A zsRSba>gO~B>TkdhNDJ46T)m*CaL@dL!Pg-6(;Mt%pFem@By%NJb__jPj^pc5(I2jJ z<58s(DT6|h8KINmHD0X+vyZ&zHp17CN@5gkep1fq(tC$ap8kEO?^1onHU_R4`?H-0 zS~xR%wSih)Dya6WF`sx9kcx(>GJQ5)8~7e-Hm4|1PhRcMMueO~4K^&uWCq*4XI}dC z%$Z>J)bYBsV!EvDxZ!B!c|LJlceDx}It>p}jEqm`%q6}uB%pd2ieHJxlnU>0RPL-8 zImXJ?>e2!a0!pkU{QNfxl!u_j4+I^C$wo4MpFJjLd@9&=_Ujb&^?8{=k)C!`{P<1R zpL&M0$BswB#eQ)>tId}de)g0_Mw;RUah!m1Qzit8FhwXK*#G={ARq()h9`|TH292C zYj8_PP`NiU`4^hME>Q;Fbta(N&x)-@>VbK`ox;$K9)5S0aO4i#FnMOvCfx%4S0w!n Dv33Me literal 0 HcmV?d00001 From 903a9bb8aa4cfd5576a7b13488be33cb02183d2a Mon Sep 17 00:00:00 2001 From: "dima.avdeev" Date: Mon, 12 Feb 2024 21:21:38 +0400 Subject: [PATCH 27/30] Added WASM to components.uiToolingPreview library (#4286) Added Wasm target to gradle publication --- .../ui-tooling-preview/library/build.gradle.kts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/components/ui-tooling-preview/library/build.gradle.kts b/components/ui-tooling-preview/library/build.gradle.kts index e7a7ac4612..964b992ea0 100644 --- a/components/ui-tooling-preview/library/build.gradle.kts +++ b/components/ui-tooling-preview/library/build.gradle.kts @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl plugins { kotlin("multiplatform") @@ -23,9 +24,11 @@ kotlin { iosSimulatorArm64() js { browser { - testTask(Action { - enabled = false - }) + } + } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser { } } macosX64() @@ -51,3 +54,10 @@ configureMavenPublication( name = "Experimental Compose Multiplatform tooling library API. This library provides the API required to declare " + "@Preview composables in user apps." ) + +afterEvaluate { + // TODO(o.k.): remove this after we refactor jsAndWasmMain source set in skiko to get rid of broken "common" js-interop + tasks.configureEach { + if (name == "compileWebMainKotlinMetadata") enabled = false + } +} From 793b5e9a898d4f5cfe5847fba1f5ba852c4d8653 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Mon, 12 Feb 2024 22:13:01 +0100 Subject: [PATCH 28/30] Fix Gradle MPP tests (android.useAndroidX) (#4287) It fails with: ``` Configuration `:jvmRuntimeClasspath` contains AndroidX dependencies, but the `android.useAndroidX` property is not enabled, which may cause runtime issues. :jvmRuntimeClasspath -> org.jetbrains.compose.desktop:desktop-jvm-linux-x64:0.0.0-dev1418 -> org.jetbrains.compose.desktop:desktop:0.0.0-dev1418 -> org.jetbrains.compose.desktop:desktop-jvm:0.0.0-dev1418 -> org.jetbrains.compose.material:material:0.0.0-dev1418 -> org.jetbrains.compose.material:material-desktop:0.0.0-dev1418 -> org.jetbrains.compose.annotation-internal:annotation:0.0.0-dev1418 -> androidx.annotation:annotation:1.7.1 :jvmRuntimeClasspath -> org.jetbrains.compose.desktop:desktop-jvm-linux-x64:0.0.0-dev1418 -> org.jetbrains.compose.desktop:desktop:0.0.0-dev1418 -> org.jetbrains.compose.desktop:desktop-jvm:0.0.0-dev1418 -> org.jetbrains.compose.material:material:0.0.0-dev1418 -> org.jetbrains.compose.material:material-desktop:0.0.0-dev1418 -> org.jetbrains.compose.annotation-internal:annotation:0.0.0-dev1418 -> androidx.annotation:annotation:1.7.1 -> androidx.annotation:annotation-jvm:1.7.1 :jvmRuntimeClasspath -> org.jetbrains.compose.desktop:desktop-jvm-linux-x64:0.0.0-dev1418 -> org.jetbrains.compose.desktop:desktop:0.0.0-dev1418 -> org.jetbrains.compose.desktop:desktop-jvm:0.0.0-dev1418 -> org.jetbrains.compose.foundation:foundation:0.0.0-dev1418 -> org.jetbrains.compose.foundation:foundation-desktop:0.0.0-dev1418 -> org.jetbrains.compose.collection-internal:collection:0.0.0-dev1418 -> androidx.collection:collection:1.4.0 :jvmRuntimeClasspath -> org.jetbrains.compose.desktop:desktop-jvm-linux-x64:0.0.0-dev1418 -> org.jetbrains.compose.desktop:desktop:0.0.0-dev1418 -> org.jetbrains.compose.desktop:desktop-jvm:0.0.0-dev1418 -> org.jetbrains.compose.foundation:foundation:0.0.0-dev1418 -> org.jetbrains.compose.foundation:foundation-desktop:0.0.0-dev1418 -> org.jetbrains.compose.collection-internal:collection:0.0.0-dev1418 -> androidx.collection:collection:1.4.0 -> androidx.collection:collection-jvm:1.4.0 ``` (https://teamcity.jetbrains.com/buildConfiguration/JetBrainsPublicProjects_Compose_Publish_2_All_2/4484004?hideTestsFromDependencies=false&hideProblemsFromDependencies=false&expandBuildTestsSection=true&expandBuildChangesSection=true&expandBuildDeploymentsSection=false&expandBuildProblemsSection=true) after we added annotation/collection to desktop target dependencies. It seems that Android plugin checks all JVM classpathes, including pure JVM (desktop) ones. And fails, if the project doesn't have this property. Adding this property, as the Android fails without it as well. --- .../src/test/test-projects/application/mpp/gradle.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 gradle-plugins/compose/src/test/test-projects/application/mpp/gradle.properties diff --git a/gradle-plugins/compose/src/test/test-projects/application/mpp/gradle.properties b/gradle-plugins/compose/src/test/test-projects/application/mpp/gradle.properties new file mode 100644 index 0000000000..2d8d1e4dd1 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/mpp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true \ No newline at end of file From d99cf154e89e42549da61da8731ffd6d77cead08 Mon Sep 17 00:00:00 2001 From: Shishkin Pavel Date: Tue, 13 Feb 2024 17:15:28 +0100 Subject: [PATCH 29/30] Add compose compiler test cases for fixed issues (#4268) fixes https://github.com/JetBrains/compose-multiplatform/issues/3318 https://github.com/JetBrains/compose-multiplatform/issues/3643 https://github.com/JetBrains/compose-multiplatform/issues/4055 --- .../lib/src/commonMain/kotlin/Dependencies.kt | 34 +++++++++++++ .../kotlin/CollectionOfComposablesTests.kt | 51 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/lib/src/commonMain/kotlin/Dependencies.kt b/compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/lib/src/commonMain/kotlin/Dependencies.kt index e83fc89398..7511423a5a 100644 --- a/compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/lib/src/commonMain/kotlin/Dependencies.kt +++ b/compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/lib/src/commonMain/kotlin/Dependencies.kt @@ -1,4 +1,6 @@ import androidx.compose.runtime.Composable +import com.example.common.TextLeafNode +import kotlin.jvm.JvmInline interface ComposableContent { @@ -14,3 +16,35 @@ interface CollectionOfComposable { fun iterator(): Iterator<@Composable () -> Unit> } + +interface DefaultComposableContent { + @Composable + @Suppress("ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE") + fun ComposableContent( + any: String = "any" // default value is required to reproduce + ) { + TextLeafNode("DefaultComposableContent - $any") + } +} + +abstract class AbstractGreeter { + @Composable + protected abstract fun Greeting() + + @Composable + fun Hi() { + Greeting() + } +} + +class Greeter(val target: String) : AbstractGreeter() { + @Composable + override fun Greeting() { + TextLeafNode("Hello, $target!") + } +} + +@JvmInline +value class ValClass(val key: Int) { + constructor(a: Int, b: Int) : this(a + b) +} diff --git a/compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonTest/kotlin/CollectionOfComposablesTests.kt b/compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonTest/kotlin/CollectionOfComposablesTests.kt index 41b470cabc..1e1159751a 100644 --- a/compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonTest/kotlin/CollectionOfComposablesTests.kt +++ b/compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonTest/kotlin/CollectionOfComposablesTests.kt @@ -1,3 +1,4 @@ +import androidx.compose.runtime.Composable import com.example.common.TextContainerNode import com.example.common.TextLeafNode import com.example.common.composeText @@ -37,4 +38,54 @@ class CollectionOfComposablesTests { actual = root.dump() ) } + + /** Default args for overridden composable produces corrupted function definitions + * https://github.com/JetBrains/compose-multiplatform/issues/3318 + */ + @Test + fun testDefaultArgsForOverridden() = runTest { + class Impl : DefaultComposableContent + + val root = composeText { + Impl().ComposableContent() + } + + assertEquals( + expected = "root:{DefaultComposableContent - any}", + actual = root.dump() + ) + } + + /** Override a protected @Composable method leads to Compilation Failed on iOS target + * https://github.com/JetBrains/compose-multiplatform/issues/4055 + */ + @Test + fun testOverrideProtected() = runTest { + val root = composeText { + Greeter("Bob").Hi() + } + + assertEquals( + expected = "root:{Hello, Bob!}", + actual = root.dump() + ) + } + + /** Default params for value type defined in separate module may result in compilation failure on iOS + * https://github.com/JetBrains/compose-multiplatform/issues/3643 + */ + @Test + fun testDefaultParamValueClass() = runTest { + @Composable + fun test(qualifiers: ValClass = ValClass(123)): String = "${qualifiers.key}" + + val root = composeText { TextLeafNode(test()) } + + assertEquals( + expected = "root:{123}", + actual = root.dump() + ) + } } + + From 8fc27508bb6032363e4ada1b2b64c15968a5ae06 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Wed, 14 Feb 2024 10:47:07 +0100 Subject: [PATCH 30/30] 1.6.0-rc01 CHANGELOG (#4292) --- CHANGELOG.md | 93 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edc57fad61..5917f3355a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,68 @@ +# 1.6.0-rc01 (February 2024) + +_Changes since 1.6.0-beta02_ + +## iOS/desktop/web + +### Fixes +- [Fix "DropdownMenu performs onDismissRequest twice"](https://github.com/JetBrains/compose-multiplatform-core/pull/1057) +- [Use a large rectangle for the picture bounds in RenderNodeLayer.drawLayer to prevent clipping](https://github.com/JetBrains/compose-multiplatform-core/pull/1090) +- [Fix closing scene during scroll animation](https://github.com/JetBrains/compose-multiplatform-core/pull/1096) +- [Fix "Using `painterResource(:DrawableResource)` outside desktop Window can produce `UnsatisfiedLinkError`"](https://github.com/JetBrains/skiko/pull/866) +- [Fix "Rect::makeLTRB expected l <= r" crashes](https://github.com/JetBrains/skiko/pull/867) +- _(prerelease fix)_ [Commonize BasicTooltipBox](https://github.com/JetBrains/compose-multiplatform-core/pull/1092) +- _(prerelease fix)_ [Fix sharing composition locals with new platform layers](https://github.com/JetBrains/compose-multiplatform-core/pull/1086) + +## iOS + +### Fixes +- [Fixed unexpected fling animation over scrolling content](https://github.com/JetBrains/compose-multiplatform-core/pull/1039) +- _(prerelease fix)_ [Fix "Wrong scroll behaviour of LazyColumn inside HorizontalPager"](https://github.com/JetBrains/compose-multiplatform-core/pull/1097) +- _(prerelease fix)_ [Fix scene size after sending the app to background or changing orientation with open modal](https://github.com/JetBrains/compose-multiplatform-core/pull/1093) + +## Desktop + +### Fixes +- [Protect against `MouseInfo.getPointerInfo()` returning null in `WindowDraggableArea`](https://github.com/JetBrains/compose-multiplatform-core/pull/1049) +- [Support Rtl in `SplitPane`](https://github.com/JetBrains/compose-multiplatform/pull/4265) +- [Fix a native crash on `makeGL`](https://github.com/JetBrains/skiko/pull/869) +- _(prerelease fix)_ [Fix "Skiko RenderException" when creating `ComposePanel`](https://github.com/JetBrains/skiko/pull/858) + +## Web + +### Fixes +- [Add a `SystemThemeObserver` implementation for wasmJs](https://github.com/JetBrains/compose-multiplatform-core/pull/998) +- [Fix keyboard events with meta key on wasm/js targets](https://github.com/JetBrains/compose-multiplatform-core/pull/1088) +- [Added WASM to `components.uiToolingPreview` library](https://github.com/JetBrains/compose-multiplatform/pull/4286) +- [Fix "The cursor is invisible in compose web"](https://github.com/JetBrains/skiko/pull/846) + +## Gradle Plugin + +### Fixes +- _(prerelease fix)_ [Relocate a bundled `KotlinPoet` to the internal package](https://github.com/JetBrains/compose-multiplatform/pull/4239) + +## Resource library + +### Fixes +- _(prerelease fix)_ [Add a type name to the resource initializers](https://github.com/JetBrains/compose-multiplatform/pull/4240) +- _(prerelease fix)_ [Don't make resource IDs lowercased](https://github.com/JetBrains/compose-multiplatform/pull/4253) +- _(prerelease fix)_ [Clean code-gen directory if there was deleted a dependency on the res library](https://github.com/JetBrains/compose-multiplatform/pull/4257) +- _(prerelease fix)_ [Register all hierarchical compose resources in android compilation](https://github.com/JetBrains/compose-multiplatform/pull/4274) +- _(prerelease fix)_ [Fix fonts duplication in android app](https://github.com/JetBrains/compose-multiplatform/pull/4284) + +## Dependencies +This version of Compose Multiplatform is based on the next Jetpack Compose libraries: +- [Compiler 1.5.8](https://developer.android.com/jetpack/androidx/releases/compose-compiler#1.5.8) +- [Runtime 1.6.1](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.6.1) +- [UI 1.6.1](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.6.1) +- [Foundation 1.6.1](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.6.1) +- [Material 1.6.1](https://developer.android.com/jetpack/androidx/releases/compose-material#1.6.1) +- [Material3 1.2.0](https://developer.android.com/jetpack/androidx/releases/compose-material3#1.2.0) + # 1.6.0-beta02 (February 2024) +_Changes since 1.6.0-beta01_ + ## Highlights - [Basic accessibility support](https://github.com/JetBrains/compose-multiplatform-core/pull/1025) _iOS_ - [Popups/Dialogs can now be displayed outside a ViewController over native components on iOS by default](https://github.com/JetBrains/compose-multiplatform-core/pull/1031) _iOS_ @@ -25,11 +88,11 @@ - [Fix missing case for loading `SystemFont` on iOS](https://github.com/JetBrains/compose-multiplatform-core/pull/1013) - [Fix selection container crash](https://github.com/JetBrains/compose-multiplatform-core/pull/1016) - [Fix `WindowInfo.containerSize` without `platformLayers` flag](https://github.com/JetBrains/compose-multiplatform-core/pull/1028) -- _(prerelease bug)_ [Fix "textfield with visual transformation crashes after single tap"](https://github.com/JetBrains/compose-multiplatform-core/pull/1045) -- _(prerelease bug)_ [Fix selection handles crossed](https://github.com/JetBrains/compose-multiplatform-core/pull/1017) -- _(prerelease bug)_ [Fix CMPViewControllerMisuse error](https://github.com/JetBrains/compose-multiplatform-core/pull/1027) -- _(prerelease bug)_ [Fix selection handles with platformLayers=true](https://github.com/JetBrains/compose-multiplatform-core/pull/1023) -- _(prerelease bug)_ [Fix interaction handling for interop views](https://github.com/JetBrains/compose-multiplatform-core/pull/1032) +- _(prerelease fix)_ [Fix "textfield with visual transformation crashes after single tap"](https://github.com/JetBrains/compose-multiplatform-core/pull/1045) +- _(prerelease fix)_ [Fix selection handles crossed](https://github.com/JetBrains/compose-multiplatform-core/pull/1017) +- _(prerelease fix)_ [Fix CMPViewControllerMisuse error](https://github.com/JetBrains/compose-multiplatform-core/pull/1027) +- _(prerelease fix)_ [Fix selection handles with platformLayers=true](https://github.com/JetBrains/compose-multiplatform-core/pull/1023) +- _(prerelease fix)_ [Fix interaction handling for interop views](https://github.com/JetBrains/compose-multiplatform-core/pull/1032) ## Desktop @@ -51,15 +114,15 @@ ## Resource library ### Fixes -- _(prerelease bug)_ [Configure Android resources after AGP is applied and ignore hidden files in resources](https://github.com/JetBrains/compose-multiplatform/commit/3040ea85bbc81cb6d1e22d6928646509ee8b601f) -- _(prerelease bug)_ [Generate Res class if there is no common composeResource dir](https://github.com/JetBrains/compose-multiplatform/pull/4176) -- _(prerelease bug)_ [Support Res class generation in JVM only compose projects](https://github.com/JetBrains/compose-multiplatform/pull/4183) -- _(prerelease bug)_ [Support Compose resources for iOS tests](https://github.com/JetBrains/compose-multiplatform/pull/4185) -- _(prerelease bug)_ [Fix sub-module gradle properties for res class generation](https://github.com/JetBrains/compose-multiplatform/commit/ee26bf8beea595dce67fbe880aa86a8363d428ae) -- _(prerelease bug)_ [Fix Native xml parser](https://github.com/JetBrains/compose-multiplatform/pull/4207) -- _(prerelease bug)_ [Generate initializer functions in the Res file to avoid the `MethodTooLargeException`](https://github.com/JetBrains/compose-multiplatform/pull/4205) -- _(prerelease bug)_ [Improve handling of special characters in string resources](https://github.com/JetBrains/compose-multiplatform/pull/4220) -- _(prerelease bug)_ [Add a `ttf` font to the resources demo app](https://github.com/JetBrains/compose-multiplatform/commit/3c7260ea51157d423b3799bd339b682ffabdce06) +- _(prerelease fix)_ [Configure Android resources after AGP is applied and ignore hidden files in resources](https://github.com/JetBrains/compose-multiplatform/commit/3040ea85bbc81cb6d1e22d6928646509ee8b601f) +- _(prerelease fix)_ [Generate Res class if there is no common composeResource dir](https://github.com/JetBrains/compose-multiplatform/pull/4176) +- _(prerelease fix)_ [Support Res class generation in JVM only compose projects](https://github.com/JetBrains/compose-multiplatform/pull/4183) +- _(prerelease fix)_ [Support Compose resources for iOS tests](https://github.com/JetBrains/compose-multiplatform/pull/4185) +- _(prerelease fix)_ [Fix sub-module gradle properties for res class generation](https://github.com/JetBrains/compose-multiplatform/commit/ee26bf8beea595dce67fbe880aa86a8363d428ae) +- _(prerelease fix)_ [Fix Native xml parser](https://github.com/JetBrains/compose-multiplatform/pull/4207) +- _(prerelease fix)_ [Generate initializer functions in the Res file to avoid the `MethodTooLargeException`](https://github.com/JetBrains/compose-multiplatform/pull/4205) +- _(prerelease fix)_ [Improve handling of special characters in string resources](https://github.com/JetBrains/compose-multiplatform/pull/4220) +- _(prerelease fix)_ [Add a `ttf` font to the resources demo app](https://github.com/JetBrains/compose-multiplatform/commit/3c7260ea51157d423b3799bd339b682ffabdce06) ## Dependencies This version of Compose Multiplatform is based on the next Jetpack Compose libraries: @@ -100,7 +163,7 @@ This version of Compose Multiplatform is based on the next Jetpack Compose libra - [Introduce top level `composeResources` dir with `drawable`, `font`, `files`, `values/strings.xml` support](https://github.com/JetBrains/compose-multiplatform/pull/4127) - [Support for various screen densities, multiple languages and regions, and light and dark themes](https://github.com/JetBrains/compose-multiplatform/pull/4018) - [Experimental support is available for tests in common code](https://github.com/JetBrains/compose-multiplatform-core/pull/978) -- [Compose for Web (Wasm) artifacts are available in Maven Central](https://github.com/JetBrains/compose-multiplatform-core/pull/914) +- [Compose for Web (Wasm) artifacts are available in Maven Central](https://github.com/JetBrains/compose-multiplatform-core/pull/914). **Warning**: Kotlin 1.9.21 has [an issue](https://github.com/JetBrains/compose-multiplatform/issues/4230) with web target. Use Kotlin 1.9.22. - iOS. Native-like caret behaviour by long/single taps in textfields([1](https://github.com/JetBrains/compose-multiplatform-core/pull/913), [2](https://github.com/JetBrains/compose-multiplatform-core/pull/858)) - [Support `LineHeightStyle.Trim`](https://github.com/JetBrains/compose-multiplatform-core/pull/897) - [Desktop. Proper clipping of `SwingPanel` interop](https://github.com/JetBrains/compose-multiplatform-core/pull/915) _(under an experimental flag, see the link)_