From 0485f03f424d556ac8f23187b837c16eede2b6a2 Mon Sep 17 00:00:00 2001 From: Philip Dukhov Date: Mon, 27 May 2024 18:50:55 +0700 Subject: [PATCH 01/47] [Android] update Font with new resource value (#4864) Fixes #4863 Before this update, when a new `resource` value was passed to `org.jetbrains.compose.resources.Font` composable, it kept the original value. Test sample code. `Res.font` here is autogenerated from `commonMain/composeResources/font/` folder content. ```kt var flag by remember { mutableStateOf(false) } Column { Text( "hey", fontFamily = FontFamily(Font(if (flag) Res.font.HelveticaNeueMedium else Res.font.COMICSANS, FontWeight.Normal)) ) Switch(checked = flag, onCheckedChange = { flag = it }) } ``` ## Release Notes ### Fixes - Resources - Fix a cached font if the resource acessor was changed --- .../org/jetbrains/compose/resources/FontResources.android.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt index c85d34c1e8..009419a020 100644 --- a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt +++ b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt @@ -8,6 +8,6 @@ import androidx.compose.ui.text.font.* @Composable actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font { val environment = LocalComposeEnvironment.current.rememberEnvironment() - val path = remember(environment) { resource.getResourceItemByEnvironment(environment).path } + val path = remember(environment, resource) { resource.getResourceItemByEnvironment(environment).path } return Font(path, LocalContext.current.assets, weight, style) } \ No newline at end of file From 7131b5b9a665e8b1d72aabc9476947650145c11e Mon Sep 17 00:00:00 2001 From: issamux Date: Mon, 27 May 2024 10:04:23 -0400 Subject: [PATCH 02/47] [Android/ImageViewer] Add missing FileProvider in android manifest (#4842) Add missing FileProvider in android manifest Fixes https://github.com/JetBrains/compose-multiplatform/issues/4841 --- .../imageviewer/shared/src/androidMain/AndroidManifest.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/imageviewer/shared/src/androidMain/AndroidManifest.xml b/examples/imageviewer/shared/src/androidMain/AndroidManifest.xml index 019e56b1ce..3d3da33adc 100755 --- a/examples/imageviewer/shared/src/androidMain/AndroidManifest.xml +++ b/examples/imageviewer/shared/src/androidMain/AndroidManifest.xml @@ -10,6 +10,11 @@ android:name="example.imageviewer.ImageViewerFileProvider" android:authorities="example.imageviewer.fileprovider" android:exported="false" - android:grantUriPermissions="true"> + android:grantUriPermissions="true"> + + + \ No newline at end of file From c4e36401d94d073633347dce43ce5b769ad6d249 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 28 May 2024 11:09:54 +0200 Subject: [PATCH 03/47] [gradle] Configure Compose Compiler if kotlin-android or kotlin-js plugins are applied. (#4879) Fix Compose Compiler configuration for Kotlin < 2.0 when kotlin-android or kotlin-js gradle plugins are applied. Fixes https://github.com/JetBrains/compose-multiplatform/issues/4831 ## Release Notes ### Fixes - Gradle Plugin - Fix Compose Compiler configuration for Kotlin < 2.0 when kotlin-android or kotlin-js gradle plugins are applied. --- .../ComposeCompilerKotlinSupportPlugin.kt | 17 ++++++++++++----- .../org/jetbrains/compose/internal/constants.kt | 1 + .../integration/KotlinCompatibilityTest.kt | 4 +--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt index 8fb8434b95..4bc74bd0d8 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt @@ -8,6 +8,8 @@ package org.jetbrains.compose import org.gradle.api.Project import org.gradle.api.provider.Provider import org.jetbrains.compose.internal.ComposeCompilerArtifactProvider +import org.jetbrains.compose.internal.KOTLIN_ANDROID_PLUGIN_ID +import org.jetbrains.compose.internal.KOTLIN_JS_PLUGIN_ID import org.jetbrains.compose.internal.KOTLIN_JVM_PLUGIN_ID import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID import org.jetbrains.compose.internal.Version @@ -27,11 +29,16 @@ import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile internal fun Project.configureComposeCompilerPlugin() { - plugins.withId(KOTLIN_MPP_PLUGIN_ID) { plugin -> - configureComposeCompilerPlugin(plugin as KotlinBasePlugin) - } - plugins.withId(KOTLIN_JVM_PLUGIN_ID) { plugin -> - configureComposeCompilerPlugin(plugin as KotlinBasePlugin) + //only one of them can be applied to the project + listOf( + KOTLIN_MPP_PLUGIN_ID, + KOTLIN_JVM_PLUGIN_ID, + KOTLIN_ANDROID_PLUGIN_ID, + KOTLIN_JS_PLUGIN_ID + ).forEach { pluginId -> + plugins.withId(pluginId) { plugin -> + configureComposeCompilerPlugin(plugin as KotlinBasePlugin) + } } } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/constants.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/constants.kt index 50acdecdcf..e71dcc3563 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/constants.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/constants.kt @@ -7,6 +7,7 @@ package org.jetbrains.compose.internal internal const val KOTLIN_MPP_PLUGIN_ID = "org.jetbrains.kotlin.multiplatform" internal const val KOTLIN_JVM_PLUGIN_ID = "org.jetbrains.kotlin.jvm" +internal const val KOTLIN_ANDROID_PLUGIN_ID = "org.jetbrains.kotlin.android" internal const val KOTLIN_JS_PLUGIN_ID = "org.jetbrains.kotlin.js" internal const val COMPOSE_PLUGIN_ID = "org.jetbrains.compose" diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt index a790c56bd8..a1987fd9ec 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt @@ -51,11 +51,9 @@ class KotlinCompatibilityTest : GradlePluginTestBase() { @Test fun testNewCompilerPluginError() { - // TODO replace by this after Kotlin 2.0 release - // testEnvironment = defaultTestEnvironment.copy(kotlinVersion = "2.0") val testProject = testProject( TestProjects.mpp, - testEnvironment = defaultTestEnvironment.copy(kotlinVersion = newCompilerIsAvailableVersion) + testEnvironment = defaultTestEnvironment.copy(kotlinVersion = "2.0.0") ) testProject.gradleFailure("tasks").checks { check.logContains(newComposeCompilerError) From e45e03aa1c0c753e760c1863ac6c5c0d0806d8f5 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 28 May 2024 18:37:09 +0200 Subject: [PATCH 04/47] [resources] Add binary compatibility validator and API dump to the resources library (#4888) Add binary compatibility validator and API dump to the resources library. --- components/build.gradle.kts | 1 + .../resources/library/api/android/library.api | 108 ++++++++++++++++++ .../resources/library/api/desktop/library.api | 108 ++++++++++++++++++ .../resources/library/api/library.klib.api | 108 ++++++++++++++++++ components/resources/library/build.gradle.kts | 8 ++ components/settings.gradle.kts | 1 + 6 files changed, 334 insertions(+) create mode 100644 components/resources/library/api/android/library.api create mode 100644 components/resources/library/api/desktop/library.api create mode 100644 components/resources/library/api/library.klib.api diff --git a/components/build.gradle.kts b/components/build.gradle.kts index d09b33a6dc..00461713c7 100644 --- a/components/build.gradle.kts +++ b/components/build.gradle.kts @@ -1,6 +1,7 @@ plugins { kotlin("multiplatform") apply false id("com.android.library") apply false + id("org.jetbrains.kotlinx.binary-compatibility-validator") apply false } subprojects { diff --git a/components/resources/library/api/android/library.api b/components/resources/library/api/android/library.api new file mode 100644 index 0000000000..9c01aca1e2 --- /dev/null +++ b/components/resources/library/api/android/library.api @@ -0,0 +1,108 @@ +public final class org/jetbrains/compose/resources/DensityQualifier$Companion { + public final fun selectByDensity (F)Lorg/jetbrains/compose/resources/DensityQualifier; + public final fun selectByValue (I)Lorg/jetbrains/compose/resources/DensityQualifier; +} + +public final class org/jetbrains/compose/resources/DrawableResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I +} + +public abstract interface annotation class org/jetbrains/compose/resources/ExperimentalResourceApi : java/lang/annotation/Annotation { +} + +public final class org/jetbrains/compose/resources/FontResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I +} + +public final class org/jetbrains/compose/resources/FontResourcesKt { + public static final fun getFontResourceBytes (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/FontResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class org/jetbrains/compose/resources/FontResources_androidKt { + public static final fun Font-DnXFreY (Lorg/jetbrains/compose/resources/FontResource;Landroidx/compose/ui/text/font/FontWeight;ILandroidx/compose/runtime/Composer;II)Landroidx/compose/ui/text/font/Font; +} + +public final class org/jetbrains/compose/resources/ImageResourcesKt { + public static final fun getDrawableResourceBytes (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/DrawableResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun imageResource (Lorg/jetbrains/compose/resources/DrawableResource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/ImageBitmap; + public static final fun painterResource (Lorg/jetbrains/compose/resources/DrawableResource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter; + public static final fun vectorResource (Lorg/jetbrains/compose/resources/DrawableResource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/vector/ImageVector; +} + +public abstract interface annotation class org/jetbrains/compose/resources/InternalResourceApi : java/lang/annotation/Annotation { +} + +public final class org/jetbrains/compose/resources/MissingResourceException : java/lang/Exception { + public static final field $stable I + public fun (Ljava/lang/String;)V +} + +public final class org/jetbrains/compose/resources/PluralStringResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I + public final fun getKey ()Ljava/lang/String; +} + +public final class org/jetbrains/compose/resources/PluralStringResourcesKt { + public static final fun getPluralString (Lorg/jetbrains/compose/resources/PluralStringResource;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getPluralString (Lorg/jetbrains/compose/resources/PluralStringResource;I[Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getPluralString (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/PluralStringResource;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getPluralString (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/PluralStringResource;I[Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun pluralStringResource (Lorg/jetbrains/compose/resources/PluralStringResource;ILandroidx/compose/runtime/Composer;I)Ljava/lang/String; + public static final fun pluralStringResource (Lorg/jetbrains/compose/resources/PluralStringResource;I[Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/String; +} + +public abstract interface class org/jetbrains/compose/resources/Qualifier { +} + +public abstract class org/jetbrains/compose/resources/Resource { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Ljava/util/Set;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I +} + +public final class org/jetbrains/compose/resources/ResourceEnvironment { + public static final field $stable I + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I +} + +public final class org/jetbrains/compose/resources/ResourceEnvironmentKt { + public static final fun getSystemResourceEnvironment ()Lorg/jetbrains/compose/resources/ResourceEnvironment; + public static final fun rememberResourceEnvironment (Landroidx/compose/runtime/Composer;I)Lorg/jetbrains/compose/resources/ResourceEnvironment; +} + +public final class org/jetbrains/compose/resources/StringArrayResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I + public final fun getKey ()Ljava/lang/String; +} + +public final class org/jetbrains/compose/resources/StringArrayResourcesKt { + public static final fun getStringArray (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/StringArrayResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getStringArray (Lorg/jetbrains/compose/resources/StringArrayResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun stringArrayResource (Lorg/jetbrains/compose/resources/StringArrayResource;Landroidx/compose/runtime/Composer;I)Ljava/util/List; +} + +public final class org/jetbrains/compose/resources/StringResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I + public final fun getKey ()Ljava/lang/String; +} + +public final class org/jetbrains/compose/resources/StringResourcesKt { + public static final fun getString (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/StringResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getString (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/StringResource;[Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getString (Lorg/jetbrains/compose/resources/StringResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getString (Lorg/jetbrains/compose/resources/StringResource;[Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun stringResource (Lorg/jetbrains/compose/resources/StringResource;Landroidx/compose/runtime/Composer;I)Ljava/lang/String; + public static final fun stringResource (Lorg/jetbrains/compose/resources/StringResource;[Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/String; +} + +public final class org/jetbrains/compose/resources/ThemeQualifier$Companion { + public final fun selectByValue (Z)Lorg/jetbrains/compose/resources/ThemeQualifier; +} + +public final class org/jetbrains/compose/resources/vector/xmldom/MalformedXMLException : java/lang/Exception { + public static final field $stable I + public fun (Ljava/lang/String;)V +} + diff --git a/components/resources/library/api/desktop/library.api b/components/resources/library/api/desktop/library.api new file mode 100644 index 0000000000..fd7b4771ff --- /dev/null +++ b/components/resources/library/api/desktop/library.api @@ -0,0 +1,108 @@ +public final class org/jetbrains/compose/resources/DensityQualifier$Companion { + public final fun selectByDensity (F)Lorg/jetbrains/compose/resources/DensityQualifier; + public final fun selectByValue (I)Lorg/jetbrains/compose/resources/DensityQualifier; +} + +public final class org/jetbrains/compose/resources/DrawableResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I +} + +public abstract interface annotation class org/jetbrains/compose/resources/ExperimentalResourceApi : java/lang/annotation/Annotation { +} + +public final class org/jetbrains/compose/resources/FontResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I +} + +public final class org/jetbrains/compose/resources/FontResourcesKt { + public static final fun getFontResourceBytes (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/FontResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class org/jetbrains/compose/resources/FontResources_skikoKt { + public static final fun Font-DnXFreY (Lorg/jetbrains/compose/resources/FontResource;Landroidx/compose/ui/text/font/FontWeight;ILandroidx/compose/runtime/Composer;II)Landroidx/compose/ui/text/font/Font; +} + +public final class org/jetbrains/compose/resources/ImageResourcesKt { + public static final fun getDrawableResourceBytes (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/DrawableResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun imageResource (Lorg/jetbrains/compose/resources/DrawableResource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/ImageBitmap; + public static final fun painterResource (Lorg/jetbrains/compose/resources/DrawableResource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter; + public static final fun vectorResource (Lorg/jetbrains/compose/resources/DrawableResource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/vector/ImageVector; +} + +public abstract interface annotation class org/jetbrains/compose/resources/InternalResourceApi : java/lang/annotation/Annotation { +} + +public final class org/jetbrains/compose/resources/MissingResourceException : java/lang/Exception { + public static final field $stable I + public fun (Ljava/lang/String;)V +} + +public final class org/jetbrains/compose/resources/PluralStringResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I + public final fun getKey ()Ljava/lang/String; +} + +public final class org/jetbrains/compose/resources/PluralStringResourcesKt { + public static final fun getPluralString (Lorg/jetbrains/compose/resources/PluralStringResource;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getPluralString (Lorg/jetbrains/compose/resources/PluralStringResource;I[Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getPluralString (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/PluralStringResource;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getPluralString (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/PluralStringResource;I[Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun pluralStringResource (Lorg/jetbrains/compose/resources/PluralStringResource;ILandroidx/compose/runtime/Composer;I)Ljava/lang/String; + public static final fun pluralStringResource (Lorg/jetbrains/compose/resources/PluralStringResource;I[Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/String; +} + +public abstract interface class org/jetbrains/compose/resources/Qualifier { +} + +public abstract class org/jetbrains/compose/resources/Resource { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Ljava/util/Set;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I +} + +public final class org/jetbrains/compose/resources/ResourceEnvironment { + public static final field $stable I + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I +} + +public final class org/jetbrains/compose/resources/ResourceEnvironmentKt { + public static final fun getSystemResourceEnvironment ()Lorg/jetbrains/compose/resources/ResourceEnvironment; + public static final fun rememberResourceEnvironment (Landroidx/compose/runtime/Composer;I)Lorg/jetbrains/compose/resources/ResourceEnvironment; +} + +public final class org/jetbrains/compose/resources/StringArrayResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I + public final fun getKey ()Ljava/lang/String; +} + +public final class org/jetbrains/compose/resources/StringArrayResourcesKt { + public static final fun getStringArray (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/StringArrayResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getStringArray (Lorg/jetbrains/compose/resources/StringArrayResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun stringArrayResource (Lorg/jetbrains/compose/resources/StringArrayResource;Landroidx/compose/runtime/Composer;I)Ljava/util/List; +} + +public final class org/jetbrains/compose/resources/StringResource : org/jetbrains/compose/resources/Resource { + public static final field $stable I + public final fun getKey ()Ljava/lang/String; +} + +public final class org/jetbrains/compose/resources/StringResourcesKt { + public static final fun getString (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/StringResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getString (Lorg/jetbrains/compose/resources/ResourceEnvironment;Lorg/jetbrains/compose/resources/StringResource;[Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getString (Lorg/jetbrains/compose/resources/StringResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getString (Lorg/jetbrains/compose/resources/StringResource;[Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun stringResource (Lorg/jetbrains/compose/resources/StringResource;Landroidx/compose/runtime/Composer;I)Ljava/lang/String; + public static final fun stringResource (Lorg/jetbrains/compose/resources/StringResource;[Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/String; +} + +public final class org/jetbrains/compose/resources/ThemeQualifier$Companion { + public final fun selectByValue (Z)Lorg/jetbrains/compose/resources/ThemeQualifier; +} + +public final class org/jetbrains/compose/resources/vector/xmldom/MalformedXMLException : java/lang/Exception { + public static final field $stable I + public fun (Ljava/lang/String;)V +} + diff --git a/components/resources/library/api/library.klib.api b/components/resources/library/api/library.klib.api new file mode 100644 index 0000000000..a9c8d66c89 --- /dev/null +++ b/components/resources/library/api/library.klib.api @@ -0,0 +1,108 @@ +// Klib ABI Dump +// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, macosArm64, macosX64, wasmJs] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +abstract interface org.jetbrains.compose.resources/Qualifier // org.jetbrains.compose.resources/Qualifier|null[0] +final class org.jetbrains.compose.resources.vector.xmldom/MalformedXMLException : kotlin/Exception { // org.jetbrains.compose.resources.vector.xmldom/MalformedXMLException|null[0] + constructor (kotlin/String?) // org.jetbrains.compose.resources.vector.xmldom/MalformedXMLException.|(kotlin.String?){}[0] +} +final class org.jetbrains.compose.resources/DrawableResource : org.jetbrains.compose.resources/Resource // org.jetbrains.compose.resources/DrawableResource|null[0] +final class org.jetbrains.compose.resources/FontResource : org.jetbrains.compose.resources/Resource // org.jetbrains.compose.resources/FontResource|null[0] +final class org.jetbrains.compose.resources/MissingResourceException : kotlin/Exception { // org.jetbrains.compose.resources/MissingResourceException|null[0] + constructor (kotlin/String) // org.jetbrains.compose.resources/MissingResourceException.|(kotlin.String){}[0] +} +final class org.jetbrains.compose.resources/PluralStringResource : org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/PluralStringResource|null[0] + final val key // org.jetbrains.compose.resources/PluralStringResource.key|{}key[0] + final fun (): kotlin/String // org.jetbrains.compose.resources/PluralStringResource.key.|(){}[0] +} +final class org.jetbrains.compose.resources/ResourceEnvironment { // org.jetbrains.compose.resources/ResourceEnvironment|null[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.jetbrains.compose.resources/ResourceEnvironment.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.jetbrains.compose.resources/ResourceEnvironment.hashCode|hashCode(){}[0] +} +final class org.jetbrains.compose.resources/StringArrayResource : org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/StringArrayResource|null[0] + final val key // org.jetbrains.compose.resources/StringArrayResource.key|{}key[0] + final fun (): kotlin/String // org.jetbrains.compose.resources/StringArrayResource.key.|(){}[0] +} +final class org.jetbrains.compose.resources/StringResource : org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/StringResource|null[0] + final val key // org.jetbrains.compose.resources/StringResource.key|{}key[0] + final fun (): kotlin/String // org.jetbrains.compose.resources/StringResource.key.|(){}[0] +} +final const val org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule$stableprop // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule$stableprop|#static{}org_jetbrains_compose_resources_plural_PluralRule$stableprop[0] +final const val org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRuleList$stableprop // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRuleList$stableprop|#static{}org_jetbrains_compose_resources_plural_PluralRuleList$stableprop[0] +final const val org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRuleParseException$stableprop // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRuleParseException$stableprop|#static{}org_jetbrains_compose_resources_plural_PluralRuleParseException$stableprop[0] +final const val org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_And$stableprop // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_And$stableprop|#static{}org_jetbrains_compose_resources_plural_PluralRule_Condition_And$stableprop[0] +final const val org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Or$stableprop // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Or$stableprop|#static{}org_jetbrains_compose_resources_plural_PluralRule_Condition_Or$stableprop[0] +final const val org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Relation$stableprop // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Relation$stableprop|#static{}org_jetbrains_compose_resources_plural_PluralRule_Condition_Relation$stableprop[0] +final const val org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_MalformedXMLException$stableprop // org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_MalformedXMLException$stableprop|#static{}org_jetbrains_compose_resources_vector_xmldom_MalformedXMLException$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_AsyncCache$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_AsyncCache$stableprop|#static{}org_jetbrains_compose_resources_AsyncCache$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_DrawCache$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_DrawCache$stableprop|#static{}org_jetbrains_compose_resources_DrawCache$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_DrawableResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_DrawableResource$stableprop|#static{}org_jetbrains_compose_resources_DrawableResource$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_FontResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_FontResource$stableprop|#static{}org_jetbrains_compose_resources_FontResource$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Bitmap$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Bitmap$stableprop|#static{}org_jetbrains_compose_resources_ImageCache_Bitmap$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Svg$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Svg$stableprop|#static{}org_jetbrains_compose_resources_ImageCache_Svg$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Vector$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Vector$stableprop|#static{}org_jetbrains_compose_resources_ImageCache_Vector$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_LanguageQualifier$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_LanguageQualifier$stableprop|#static{}org_jetbrains_compose_resources_LanguageQualifier$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_MissingResourceException$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_MissingResourceException$stableprop|#static{}org_jetbrains_compose_resources_MissingResourceException$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_PluralStringResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_PluralStringResource$stableprop|#static{}org_jetbrains_compose_resources_PluralStringResource$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_RegionQualifier$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_RegionQualifier$stableprop|#static{}org_jetbrains_compose_resources_RegionQualifier$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_Resource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_Resource$stableprop|#static{}org_jetbrains_compose_resources_Resource$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceEnvironment$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceEnvironment$stableprop|#static{}org_jetbrains_compose_resources_ResourceEnvironment$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceItem$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceItem$stableprop|#static{}org_jetbrains_compose_resources_ResourceItem$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringArrayResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringArrayResource$stableprop|#static{}org_jetbrains_compose_resources_StringArrayResource$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Array$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Array$stableprop|#static{}org_jetbrains_compose_resources_StringItem_Array$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Plurals$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Plurals$stableprop|#static{}org_jetbrains_compose_resources_StringItem_Plurals$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Value$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Value$stableprop|#static{}org_jetbrains_compose_resources_StringItem_Value$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringResource$stableprop|#static{}org_jetbrains_compose_resources_StringResource$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_SvgElement$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_SvgElement$stableprop|#static{}org_jetbrains_compose_resources_SvgElement$stableprop[0] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_SvgPainter$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_SvgPainter$stableprop|#static{}org_jetbrains_compose_resources_SvgPainter$stableprop[0] +final fun org.jetbrains.compose.resources/Font(org.jetbrains.compose.resources/FontResource, androidx.compose.ui.text.font/FontWeight?, androidx.compose.ui.text.font/FontStyle, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int): androidx.compose.ui.text.font/Font // org.jetbrains.compose.resources/Font|Font(org.jetbrains.compose.resources.FontResource;androidx.compose.ui.text.font.FontWeight?;androidx.compose.ui.text.font.FontStyle;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0] +final fun org.jetbrains.compose.resources/getSystemResourceEnvironment(): org.jetbrains.compose.resources/ResourceEnvironment // org.jetbrains.compose.resources/getSystemResourceEnvironment|getSystemResourceEnvironment(){}[0] +final fun org.jetbrains.compose.resources/imageResource(org.jetbrains.compose.resources/DrawableResource, androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui.graphics/ImageBitmap // org.jetbrains.compose.resources/imageResource|imageResource(org.jetbrains.compose.resources.DrawableResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final fun org.jetbrains.compose.resources/painterResource(org.jetbrains.compose.resources/DrawableResource, androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui.graphics.painter/Painter // org.jetbrains.compose.resources/painterResource|painterResource(org.jetbrains.compose.resources.DrawableResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final fun org.jetbrains.compose.resources/pluralStringResource(org.jetbrains.compose.resources/PluralStringResource, kotlin/Int, androidx.compose.runtime/Composer?, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/pluralStringResource|pluralStringResource(org.jetbrains.compose.resources.PluralStringResource;kotlin.Int;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final fun org.jetbrains.compose.resources/pluralStringResource(org.jetbrains.compose.resources/PluralStringResource, kotlin/Int, kotlin/Array..., androidx.compose.runtime/Composer?, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/pluralStringResource|pluralStringResource(org.jetbrains.compose.resources.PluralStringResource;kotlin.Int;kotlin.Array...;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final fun org.jetbrains.compose.resources/rememberResourceEnvironment(androidx.compose.runtime/Composer?, kotlin/Int): org.jetbrains.compose.resources/ResourceEnvironment // org.jetbrains.compose.resources/rememberResourceEnvironment|rememberResourceEnvironment(androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final fun org.jetbrains.compose.resources/stringArrayResource(org.jetbrains.compose.resources/StringArrayResource, androidx.compose.runtime/Composer?, kotlin/Int): kotlin.collections/List // org.jetbrains.compose.resources/stringArrayResource|stringArrayResource(org.jetbrains.compose.resources.StringArrayResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final fun org.jetbrains.compose.resources/stringResource(org.jetbrains.compose.resources/StringResource, androidx.compose.runtime/Composer?, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/stringResource|stringResource(org.jetbrains.compose.resources.StringResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final fun org.jetbrains.compose.resources/stringResource(org.jetbrains.compose.resources/StringResource, kotlin/Array..., androidx.compose.runtime/Composer?, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/stringResource|stringResource(org.jetbrains.compose.resources.StringResource;kotlin.Array...;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final fun org.jetbrains.compose.resources/vectorResource(org.jetbrains.compose.resources/DrawableResource, androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui.graphics.vector/ImageVector // org.jetbrains.compose.resources/vectorResource|vectorResource(org.jetbrains.compose.resources.DrawableResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final suspend fun org.jetbrains.compose.resources/getDrawableResourceBytes(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/DrawableResource): kotlin/ByteArray // org.jetbrains.compose.resources/getDrawableResourceBytes|getDrawableResourceBytes(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.DrawableResource){}[0] +final suspend fun org.jetbrains.compose.resources/getFontResourceBytes(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/FontResource): kotlin/ByteArray // org.jetbrains.compose.resources/getFontResourceBytes|getFontResourceBytes(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.FontResource){}[0] +final suspend fun org.jetbrains.compose.resources/getPluralString(org.jetbrains.compose.resources/PluralStringResource, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/getPluralString|getPluralString(org.jetbrains.compose.resources.PluralStringResource;kotlin.Int){}[0] +final suspend fun org.jetbrains.compose.resources/getPluralString(org.jetbrains.compose.resources/PluralStringResource, kotlin/Int, kotlin/Array...): kotlin/String // org.jetbrains.compose.resources/getPluralString|getPluralString(org.jetbrains.compose.resources.PluralStringResource;kotlin.Int;kotlin.Array...){}[0] +final suspend fun org.jetbrains.compose.resources/getPluralString(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/PluralStringResource, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/getPluralString|getPluralString(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.PluralStringResource;kotlin.Int){}[0] +final suspend fun org.jetbrains.compose.resources/getPluralString(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/PluralStringResource, kotlin/Int, kotlin/Array...): kotlin/String // org.jetbrains.compose.resources/getPluralString|getPluralString(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.PluralStringResource;kotlin.Int;kotlin.Array...){}[0] +final suspend fun org.jetbrains.compose.resources/getString(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/StringResource): kotlin/String // org.jetbrains.compose.resources/getString|getString(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.StringResource){}[0] +final suspend fun org.jetbrains.compose.resources/getString(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/StringResource, kotlin/Array...): kotlin/String // org.jetbrains.compose.resources/getString|getString(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.StringResource;kotlin.Array...){}[0] +final suspend fun org.jetbrains.compose.resources/getString(org.jetbrains.compose.resources/StringResource): kotlin/String // org.jetbrains.compose.resources/getString|getString(org.jetbrains.compose.resources.StringResource){}[0] +final suspend fun org.jetbrains.compose.resources/getString(org.jetbrains.compose.resources/StringResource, kotlin/Array...): kotlin/String // org.jetbrains.compose.resources/getString|getString(org.jetbrains.compose.resources.StringResource;kotlin.Array...){}[0] +final suspend fun org.jetbrains.compose.resources/getStringArray(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/StringArrayResource): kotlin.collections/List // org.jetbrains.compose.resources/getStringArray|getStringArray(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.StringArrayResource){}[0] +final suspend fun org.jetbrains.compose.resources/getStringArray(org.jetbrains.compose.resources/StringArrayResource): kotlin.collections/List // org.jetbrains.compose.resources/getStringArray|getStringArray(org.jetbrains.compose.resources.StringArrayResource){}[0] +open annotation class org.jetbrains.compose.resources/ExperimentalResourceApi : kotlin/Annotation { // org.jetbrains.compose.resources/ExperimentalResourceApi|null[0] + constructor () // org.jetbrains.compose.resources/ExperimentalResourceApi.|(){}[0] +} +open annotation class org.jetbrains.compose.resources/InternalResourceApi : kotlin/Annotation { // org.jetbrains.compose.resources/InternalResourceApi|null[0] + constructor () // org.jetbrains.compose.resources/InternalResourceApi.|(){}[0] +} +sealed class org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/Resource|null[0] + open fun equals(kotlin/Any?): kotlin/Boolean // org.jetbrains.compose.resources/Resource.equals|equals(kotlin.Any?){}[0] + open fun hashCode(): kotlin/Int // org.jetbrains.compose.resources/Resource.hashCode|hashCode(){}[0] +} +// Targets: [js, wasmJs] +final const val org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_ElementImpl$stableprop // org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_ElementImpl$stableprop|#static{}org_jetbrains_compose_resources_vector_xmldom_ElementImpl$stableprop[0] +// Targets: [js, wasmJs] +final const val org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_NodeImpl$stableprop // org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_NodeImpl$stableprop|#static{}org_jetbrains_compose_resources_vector_xmldom_NodeImpl$stableprop[0] +// Targets: [js, wasmJs] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_Intl_Locale$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_Intl_Locale$stableprop|#static{}org_jetbrains_compose_resources_Intl_Locale$stableprop[0] +// Targets: [js, wasmJs] +final const val org.jetbrains.compose.resources/org_jetbrains_compose_resources_WebResourcesConfiguration$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_WebResourcesConfiguration$stableprop|#static{}org_jetbrains_compose_resources_WebResourcesConfiguration$stableprop[0] +// Targets: [js, wasmJs] +final fun org.jetbrains.compose.resources/configureWebResources(kotlin/Function1) // org.jetbrains.compose.resources/configureWebResources|configureWebResources(kotlin.Function1){}[0] +// Targets: [js, wasmJs] +final object org.jetbrains.compose.resources/WebResourcesConfiguration { // org.jetbrains.compose.resources/WebResourcesConfiguration|null[0] + final fun resourcePathMapping(kotlin/Function1) // org.jetbrains.compose.resources/WebResourcesConfiguration.resourcePathMapping|resourcePathMapping(kotlin.Function1){}[0] +} diff --git a/components/resources/library/build.gradle.kts b/components/resources/library/build.gradle.kts index b23ba687eb..17a6d7489a 100644 --- a/components/resources/library/build.gradle.kts +++ b/components/resources/library/build.gradle.kts @@ -1,3 +1,4 @@ +import kotlinx.validation.ExperimentalBCVApi import org.jetbrains.compose.ExperimentalComposeLibrary import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl @@ -6,6 +7,7 @@ plugins { id("org.jetbrains.compose") id("maven-publish") id("com.android.library") + id("org.jetbrains.kotlinx.binary-compatibility-validator") } val composeVersion = extra["compose.version"] as String @@ -194,6 +196,12 @@ configureMavenPublication( name = "Resources for Compose JB" ) +apiValidation { + @OptIn(ExperimentalBCVApi::class) + klib { enabled = true } + nonPublicMarkers.add("org.jetbrains.compose.resources.InternalResourceApi") +} + // adding it here to make sure skiko is unpacked and available in web tests compose.experimental { web.application {} diff --git a/components/settings.gradle.kts b/components/settings.gradle.kts index 2e5b391e1d..060807a189 100644 --- a/components/settings.gradle.kts +++ b/components/settings.gradle.kts @@ -14,6 +14,7 @@ pluginManagement { kotlin("multiplatform").version(extra["kotlin.version"] as String) id("org.jetbrains.compose").version(extra["compose.version"] as String) id("com.android.library").version(extra["agp.version"] as String) + id("org.jetbrains.kotlinx.binary-compatibility-validator").version("0.15.0-Beta.2") } } From 6604addbacaf3522960b45eb86bcbe6fe5ecd8dc Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Wed, 29 May 2024 10:35:17 +0200 Subject: [PATCH 05/47] ImageViewer: workaround k/wasm configuration cache issue KT-68614 (#4891) Added a workaround in ImageViewer for K/Wasm configuration cache issue https://youtrack.jetbrains.com/issue/KT-68614/Wasm.-KotlinWebpack-cannot-serialize-Gradle-script-object-references --- examples/imageviewer/webApp/build.gradle.kts | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/imageviewer/webApp/build.gradle.kts b/examples/imageviewer/webApp/build.gradle.kts index 4d3f358ddc..52f28985ef 100644 --- a/examples/imageviewer/webApp/build.gradle.kts +++ b/examples/imageviewer/webApp/build.gradle.kts @@ -26,16 +26,17 @@ kotlin { wasmJs { moduleName = "imageviewer" browser { - commonWebpackConfig { - devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { - static = (static ?: mutableListOf()).apply { - // Serve sources to debug inside browser - add(rootDirPath) - add(rootDirPath + "/shared/") - add(rootDirPath + "/webApp/") - } - } - } + // TODO: uncomment when https://youtrack.jetbrains.com/issue/KT-68614 is fixed (it doesn't work with configuration cache) +// commonWebpackConfig { +// devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { +// static = (static ?: mutableListOf()).apply { +// // Serve sources to debug inside browser +// add(rootDirPath) +// add(rootDirPath + "/shared/") +// add(rootDirPath + "/webApp/") +// } +// } +// } } binaries.executable() } From c519a69d4d953c1dd81052ab7bd64fd5d739a61c Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 29 May 2024 17:55:45 +0200 Subject: [PATCH 06/47] [resources] Delete Thread.currentThread().contextClassLoader on JVM targets (#4895) The class loader retrieval method has been modified in both `ResourceReader.android.kt` and `ResourceReader.desktop.kt` files. The return statement has been changed to prioritize java class classLoader and provides a clearer error message when it can't be found. Fixes https://github.com/JetBrains/compose-multiplatform/issues/4887 Fixes https://github.com/JetBrains/compose-multiplatform/issues/4742 ## Release Notes ### Fixes - Resources - Delete contextClassLoader usage on JVM targets --- .../org/jetbrains/compose/resources/ResourceReader.android.kt | 2 +- .../org/jetbrains/compose/resources/ResourceReader.desktop.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 5ec66d7f02..7c1ce6fdbf 100644 --- 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 @@ -46,6 +46,6 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou } private fun getClassLoader(): ClassLoader { - return Thread.currentThread().contextClassLoader ?: this.javaClass.classLoader!! + return this.javaClass.classLoader ?: error("Cannot find class loader") } } \ No newline at end of file diff --git a/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt b/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt index 25d4ef44c1..3f9097e520 100644 --- a/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt +++ b/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt @@ -30,6 +30,6 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou } private fun getClassLoader(): ClassLoader { - return Thread.currentThread().contextClassLoader ?: this.javaClass.classLoader!! + return this.javaClass.classLoader ?: error("Cannot find class loader") } } \ No newline at end of file From 2305ea77eeb1b5cc7cd6208a8ae3f52de10819b2 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 30 May 2024 14:42:01 +0200 Subject: [PATCH 07/47] [resources] Use cached value synchronously on web. (#4893) The change speeds resources web rendering up by the reading a cached value instantly by request (it was being dispatched to the end of the UI queue in `LaunchedEffect`) ## Release Notes ### Features - Resources - Speed resources web rendering up by the reading a cached value instantly --- .../compose/resources/ResourceState.web.kt | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt b/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt index 8317c96d2a..3ef99c9a73 100644 --- a/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt +++ b/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt @@ -1,10 +1,12 @@ package org.jetbrains.compose.resources import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.launch @Composable internal actual fun rememberResourceState( @@ -13,11 +15,14 @@ internal actual fun rememberResourceState( block: suspend (ResourceEnvironment) -> T ): State { val environment = LocalComposeEnvironment.current.rememberEnvironment() - val state = remember(key1) { mutableStateOf(getDefault()) } - LaunchedEffect(key1) { - state.value = block(environment) + val scope = rememberCoroutineScope() + return remember(key1) { + val mutableState = mutableStateOf(getDefault()) + scope.launch(start = CoroutineStart.UNDISPATCHED) { + mutableState.value = block(environment) + } + mutableState } - return state } @Composable @@ -28,11 +33,14 @@ internal actual fun rememberResourceState( block: suspend (ResourceEnvironment) -> T ): State { val environment = LocalComposeEnvironment.current.rememberEnvironment() - val state = remember(key1, key2) { mutableStateOf(getDefault()) } - LaunchedEffect(key1, key2) { - state.value = block(environment) + val scope = rememberCoroutineScope() + return remember(key1, key2) { + val mutableState = mutableStateOf(getDefault()) + scope.launch(start = CoroutineStart.UNDISPATCHED) { + mutableState.value = block(environment) + } + mutableState } - return state } @Composable @@ -44,9 +52,12 @@ internal actual fun rememberResourceState( block: suspend (ResourceEnvironment) -> T ): State { val environment = LocalComposeEnvironment.current.rememberEnvironment() - val state = remember(key1, key2, key3) { mutableStateOf(getDefault()) } - LaunchedEffect(key1, key2, key3) { - state.value = block(environment) + val scope = rememberCoroutineScope() + return remember(key1, key2, key3) { + val mutableState = mutableStateOf(getDefault()) + scope.launch(start = CoroutineStart.UNDISPATCHED) { + mutableState.value = block(environment) + } + mutableState } - return state } \ No newline at end of file From bf47d0b9e815dedea5dda366f2a89e132ee248fe Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 30 May 2024 15:22:18 +0200 Subject: [PATCH 08/47] [resources] Use regular classes for resource qualifiers. (#4892) To avoid data classes in the public API. --- .../jetbrains/compose/resources/Qualifier.kt | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Qualifier.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Qualifier.kt index 150f144bc3..a3c314b8c9 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Qualifier.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Qualifier.kt @@ -3,14 +3,48 @@ package org.jetbrains.compose.resources interface Qualifier @InternalResourceApi -data class LanguageQualifier( +class LanguageQualifier( val language: String -) : Qualifier +) : Qualifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as LanguageQualifier + + return language == other.language + } + + override fun hashCode(): Int { + return language.hashCode() + } + + override fun toString(): String { + return "LanguageQualifier(language='$language')" + } +} @InternalResourceApi -data class RegionQualifier( +class RegionQualifier( val region: String -) : Qualifier +) : Qualifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as RegionQualifier + + return region == other.region + } + + override fun hashCode(): Int { + return region.hashCode() + } + + override fun toString(): String { + return "RegionQualifier(region='$region')" + } +} @InternalResourceApi enum class ThemeQualifier : Qualifier { From 1bc3d1a6349a654fdb57e58f20f446726eb3b0ee Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 31 May 2024 10:11:50 +0200 Subject: [PATCH 09/47] [gradle] Create an empty resource dir with "podspec" task instead "podInstall" (#4900) By mistake the generation resources directory was linked to "podInstall" task instead "podspec". Fixes https://github.com/JetBrains/compose-multiplatform/issues/4720 ## Testing - create a new Compose App project with an iOS integration via Cocoapods - add some multiplatform resources - clean all caches and build dirs - call "pod install" - check that first run of the iOS app works fine ## Release Notes ### Fixes - Resources - Create an empty resource dir with "podspec" task instead "podInstall" --- .../jetbrains/compose/resources/IosResources.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt index bdb114d4ac..1a05e615e8 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt @@ -1,10 +1,13 @@ package org.jetbrains.compose.resources +import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.file.Directory import org.gradle.api.plugins.ExtensionAware +import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.TaskAction import org.jetbrains.compose.desktop.application.internal.ComposeProperties import org.jetbrains.compose.internal.utils.* import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension @@ -83,13 +86,16 @@ internal fun Project.configureSyncIosComposeResources( framework { podFramework -> val syncDir = podFramework.getFinalResourcesDir().get().asFile.relativeTo(projectDir) val specAttr = "['${syncDir.path}']" - extraSpecAttributes["resources"] = specAttr - project.tasks.named("podInstall").configure { + val specAttributes = extraSpecAttributes + val buildFile = project.buildFile + val projectPath = project.path + specAttributes["resources"] = specAttr + project.tasks.named("podspec").configure { it.doFirst { - if (extraSpecAttributes["resources"] != specAttr) error( + if (specAttributes["resources"] != specAttr) error( """ |Kotlin.cocoapods.extraSpecAttributes["resources"] is not compatible with Compose Multiplatform's resources management for iOS. - | * Recommended action: remove extraSpecAttributes["resources"] from '${project.buildFile}' and run '${project.path}:podInstall' once; + | * Recommended action: remove extraSpecAttributes["resources"] from '$buildFile' and run '$projectPath:podInstall' once; | * Alternative action: turn off Compose Multiplatform's resources management for iOS by adding '${ComposeProperties.SYNC_RESOURCES_PROPERTY}=false' to your gradle.properties; """.trimMargin() ) From 9a513c55e4c46709977caa8a955fb583dc418358 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 31 May 2024 14:12:13 +0200 Subject: [PATCH 10/47] [gradle] Fix resource accessor name escaping. (#4901) Fixes https://github.com/JetBrains/compose-multiplatform/issues/4548 ## Testing Add compose resources with name such as "package", "is" or "item_$xxx" and check that app compiles and works fine. Accessors should be properly escaped ## Release Notes ### Fixes - Resources - Fix resource accessors escaping. Now it is possible to use resources with names: "package", "is", "item_$xxx" etc --- .../resources/GeneratedResClassSpec.kt | 8 ++--- .../test/tests/integration/ResourcesTest.kt | 4 +-- .../my/lib/res/Drawable0.commonMain.kt | 13 +++++++ .../my/lib/res/String0.commonMain.kt | 19 ++++++++-- .../resources/Drawable0.commonMain.kt | 13 +++++++ .../generated/resources/String0.commonMain.kt | 19 ++++++++-- .../composeResources/drawable/is.xml | 36 +++++++++++++++++++ .../composeResources/values/strings.xml | 1 + 8 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/drawable/is.xml diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GeneratedResClassSpec.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GeneratedResClassSpec.kt index 1ecdd84f35..df8add1101 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GeneratedResClassSpec.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GeneratedResClassSpec.kt @@ -240,7 +240,7 @@ private fun getChunkFileSpec( typeObject.addModifiers(KModifier.PRIVATE) val properties = idToResources.keys.map { resName -> PropertySpec.builder(resName, type.getClassName()) - .delegate("\nlazy·{ init_$resName() }") + .delegate("\nlazy·{ %N() }", "init_$resName") .build() } typeObject.addProperties(properties) @@ -250,7 +250,7 @@ private fun getChunkFileSpec( idToResources.forEach { (resName, items) -> val accessor = PropertySpec.builder(resName, type.getClassName(), resModifier) .receiver(ClassName(packageName, "Res", type.accessorName)) - .getter(FunSpec.getterBuilder().addStatement("return $chunkClassName.$resName").build()) + .getter(FunSpec.getterBuilder().addStatement("return $chunkClassName.%N", resName).build()) .build() chunkFile.addProperty(accessor) @@ -260,8 +260,8 @@ private fun getChunkFileSpec( .addStatement( CodeBlock.builder() .add("return %T(\n", type.getClassName()).withIndent { - add("\"${type}:${resName}\",") - if (type.requiresKeyName()) add(" \"$resName\",") + add("%S,", "$type:$resName") + if (type.requiresKeyName()) add(" %S,", resName) withIndent { add("\nsetOf(\n").withIndent { items.forEach { item -> 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 3a3da032cf..9b5c8b22ae 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 @@ -227,9 +227,7 @@ class ResourcesTest : GradlePluginTestBase() { @Test fun testMultiModuleResources() { - val environment = defaultTestEnvironment.copy( - kotlinVersion = "2.0.0-RC2" - ) + val environment = defaultTestEnvironment.copy(kotlinVersion = "2.0.0") with( testProject("misc/kmpResourcePublication", environment) ) { diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt index 408e508f37..d85c4af1ad 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt @@ -12,6 +12,9 @@ private object CommonMainDrawable0 { public val camelCaseName: DrawableResource by lazy { init_camelCaseName() } + public val `is`: DrawableResource by + lazy { init_is() } + public val vector: DrawableResource by lazy { init_vector() } @@ -41,6 +44,16 @@ private fun init_camelCaseName(): DrawableResource = ) ) +public val Res.drawable.`is`: DrawableResource + get() = CommonMainDrawable0.`is` + +private fun init_is(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( + "drawable:is", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/is.xml", -1, -1), + ) +) + public val Res.drawable.vector: DrawableResource get() = CommonMainDrawable0.vector diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt index d3a70bb1f2..e7ddb90a51 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt @@ -21,6 +21,9 @@ private object CommonMainString0 { public val hello: StringResource by lazy { init_hello() } + public val `info_using_release_$x`: StringResource by + lazy { `init_info_using_release_$x`() } + public val multi_line: StringResource by lazy { init_multi_line() } @@ -83,13 +86,25 @@ private fun init_hello(): StringResource = org.jetbrains.compose.resources.Strin ) ) +public val Res.string.`info_using_release_$x`: StringResource + get() = CommonMainString0.`info_using_release_$x` + +private fun `init_info_using_release_$x`(): StringResource = + org.jetbrains.compose.resources.StringResource( + "string:info_using_release_${'$'}x", "info_using_release_${'$'}x", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 320, + 57), + ) + ) + public val Res.string.multi_line: StringResource get() = CommonMainString0.multi_line private fun init_multi_line(): StringResource = org.jetbrains.compose.resources.StringResource( "string:multi_line", "multi_line", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 320, + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 378, 178), ) ) @@ -100,7 +115,7 @@ public val Res.string.str_template: StringResource private fun init_str_template(): StringResource = org.jetbrains.compose.resources.StringResource( "string:str_template", "str_template", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 499, + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 557, 76), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt index 5e7a25ac0e..0853a96b34 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt @@ -12,6 +12,9 @@ private object CommonMainDrawable0 { public val camelCaseName: DrawableResource by lazy { init_camelCaseName() } + public val `is`: DrawableResource by + lazy { init_is() } + public val vector: DrawableResource by lazy { init_vector() } @@ -41,6 +44,16 @@ private fun init_camelCaseName(): DrawableResource = ) ) +internal val Res.drawable.`is`: DrawableResource + get() = CommonMainDrawable0.`is` + +private fun init_is(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( + "drawable:is", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/is.xml", -1, -1), + ) +) + internal val Res.drawable.vector: DrawableResource get() = CommonMainDrawable0.vector diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt index c57944b003..570cccd780 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt @@ -21,6 +21,9 @@ private object CommonMainString0 { public val hello: StringResource by lazy { init_hello() } + public val `info_using_release_$x`: StringResource by + lazy { `init_info_using_release_$x`() } + public val multi_line: StringResource by lazy { init_multi_line() } @@ -83,13 +86,25 @@ private fun init_hello(): StringResource = org.jetbrains.compose.resources.Strin ) ) +internal val Res.string.`info_using_release_$x`: StringResource + get() = CommonMainString0.`info_using_release_$x` + +private fun `init_info_using_release_$x`(): StringResource = + org.jetbrains.compose.resources.StringResource( + "string:info_using_release_${'$'}x", "info_using_release_${'$'}x", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 320, + 57), + ) + ) + internal val Res.string.multi_line: StringResource get() = CommonMainString0.multi_line private fun init_multi_line(): StringResource = org.jetbrains.compose.resources.StringResource( "string:multi_line", "multi_line", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 320, + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 378, 178), ) ) @@ -100,7 +115,7 @@ internal val Res.string.str_template: StringResource private fun init_str_template(): StringResource = org.jetbrains.compose.resources.StringResource( "string:str_template", "str_template", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 499, + org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 557, 76), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/drawable/is.xml b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/drawable/is.xml new file mode 100644 index 0000000000..d7bf7955f4 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/composeResources/drawable/is.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 2afea688cf..06e9e90654 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 @@ -8,6 +8,7 @@ PascalCase 1-kebab-case camelCase + info_using_release_$x %d zero From 9b453a916496066875105d5855e64f02f82dbabf Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Mon, 3 Jun 2024 16:44:05 +0200 Subject: [PATCH 11/47] Fix eager tryGetSkikoRuntimeIfNeeded (#4918) Fixes #4886 ## Testing - Built the gradle plugin to mavenLocal - used it in the reproducer of #4886, - the issue is gonve This should be tested by QA ## Release Notes ### Fixes - Gradle Plugin - Make sure tryGetSkikoRuntimeIfNeeded is executed only during the task execution --- .../compose/desktop/preview/internal/configurePreview.kt | 2 +- .../preview/tasks/AbstractConfigureDesktopPreviewTask.kt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/internal/configurePreview.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/internal/configurePreview.kt index 83f06a9805..80977d917d 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/internal/configurePreview.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/internal/configurePreview.kt @@ -34,7 +34,7 @@ private fun registerConfigurePreviewTask( ) { previewTask -> runtimeFiles.configureUsageBy(previewTask) { (runtimeJars, _) -> previewClasspath = runtimeJars - skikoRuntime = tryGetSkikoRuntimeIfNeeded() + skikoRuntime.set(project.provider { tryGetSkikoRuntimeIfNeeded() }) } } } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/tasks/AbstractConfigureDesktopPreviewTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/tasks/AbstractConfigureDesktopPreviewTask.kt index 02bab85264..a61e5f732d 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/tasks/AbstractConfigureDesktopPreviewTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/tasks/AbstractConfigureDesktopPreviewTask.kt @@ -19,7 +19,7 @@ abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask( internal lateinit var previewClasspath: FileCollection @get:InputFiles - internal lateinit var skikoRuntime: FileCollection + internal abstract val skikoRuntime: Property @get:Internal internal val javaHome: Property = objects.notNullProperty().apply { @@ -58,10 +58,12 @@ abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask( javaExecutable = javaExecutable(javaHome.get()), hostClasspath = hostClasspath.files.asSequence().pathString() ) + + val skikoRuntimeFiles = skikoRuntime.get() val previewClasspathString = (previewClasspath.files.asSequence() + uiTooling.files.asSequence() + - skikoRuntime.files.asSequence() + skikoRuntimeFiles.files.asSequence() ).pathString() val gradleLogger = logger From fc90219ad63799fc4cd08ceb57b428948a223b21 Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Mon, 3 Jun 2024 18:27:02 +0200 Subject: [PATCH 12/47] Add 1.6.11 to changelog (#4905) --- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a818e8f7a1..bb42d0c04a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,50 @@ +# 1.6.11 (June 2024) + +_Changes since 1.6.10_ + +## Fixes + +### Multiple Platforms + +- [Fix endless re-layout when layout is invalidated by measure, which includes measuring `TextField(singleLine=true)` with `IntrinsicSize`](https://github.com/JetBrains/compose-multiplatform-core/pull/1355) +- [Fix container size for `Dialog` centering inside `ImageComposeScene`](https://github.com/JetBrains/compose-multiplatform-core/pull/1375) + +### iOS + +- [Fix crash on iOS 12 caused by usage unavailable `UIMenuController` API](https://github.com/JetBrains/compose-multiplatform-core/pull/1361) + +### Desktop + +- [Fix `DropdownMenu`/`Popup` positioning when a window is moved to a screen with a different density](https://github.com/JetBrains/compose-multiplatform-core/pull/1333) +- [Fix possible scrolling without animation on some mouse models](https://github.com/JetBrains/compose-multiplatform-core/pull/1326) + +### Web + +- [Fixed crash when `DatePicker` text field receives illegal input](https://github.com/JetBrains/compose-multiplatform-core/pull/1368) + +### Resources + +- [Fix a cached font if the resource acessor was changed](https://github.com/JetBrains/compose-multiplatform/pull/4864) + +### Gradle Plugin + +- [Fix Compose Compiler configuration for Kotlin < 2.0 when kotlin-android or kotlin-js gradle plugins are applied](https://github.com/JetBrains/compose-multiplatform/pull/4879) + +## Dependencies + +- Gradle Plugin `org.jetbrains.compose`, version `1.6.11`. Based on Jetpack Compose libraries: + - [Compiler 1.5.14](https://developer.android.com/jetpack/androidx/releases/compose-compiler#1.5.14) + - [Runtime 1.6.7](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.6.7) + - [UI 1.6.7](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.6.7) + - [Foundation 1.6.7](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.6.7) + - [Material 1.6.7](https://developer.android.com/jetpack/androidx/releases/compose-material#1.6.7) + - [Material3 1.2.1](https://developer.android.com/jetpack/androidx/releases/compose-material3#1.2.1) + +- Lifecycle libraries `org.jetbrains.androidx.lifecycle:lifecycle-*:2.8.0`. Based on [Jetpack Lifecycle 2.8.0](https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.0) +- Navigation libraries `org.jetbrains.androidx.navigation:navigation-*:2.7.0-alpha07`. Based on [Jetpack Navigation 2.7.7](https://developer.android.com/jetpack/androidx/releases/navigation#2.7.7) + +___ + # 1.6.10 (May 2024) _Changes since 1.6.2_ From 8432577f5d8a373981af436d242d832c7e6f5637 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 7 Jun 2024 15:43:25 +0200 Subject: [PATCH 13/47] [resources] Read exactly requested count of bytes from InputStream on jvm platforms. (#4943) In some cases the skip and read methods may handle less bytes then expected. The PR fixes it by proper API on the JVM and manual check on the Android. Fixes https://github.com/JetBrains/compose-multiplatform/issues/4938 ## Testing I manually checked it on the project from the issue. ## Release Notes ### Fixes - Resources - Read exactly requested count of bytes from InputStream on jvm platforms. --- .../resources/ResourceReader.android.kt | 24 +++++++++++++++++-- .../resources/ResourceReader.desktop.kt | 4 ++-- 2 files changed, 24 insertions(+), 4 deletions(-) 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 index 7c1ce6fdbf..972cb39205 100644 --- 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 @@ -13,12 +13,32 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou val resource = getResourceAsStream(path) val result = ByteArray(size.toInt()) resource.use { input -> - input.skip(offset) - input.read(result, 0, size.toInt()) + input.skipBytes(offset) + input.readBytes(result, 0, size.toInt()) } return result } + //skipNBytes requires API 34 + private fun InputStream.skipBytes(offset: Long) { + var skippedBytes = 0L + while (skippedBytes < offset) { + val count = skip(offset - skippedBytes) + if (count == 0L) break + skippedBytes += count + } + } + + //readNBytes requires API 34 + private fun InputStream.readBytes(byteArray: ByteArray, offset: Int, size: Int) { + var readBytes = 0 + while (readBytes < size) { + val count = read(byteArray, offset + readBytes, size - readBytes) + if (count <= 0) break + readBytes += count + } + } + override fun getUri(path: String): String { val classLoader = getClassLoader() val resource = classLoader.getResource(path) ?: run { diff --git a/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt b/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt index 3f9097e520..da70a3899c 100644 --- a/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt +++ b/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt @@ -12,8 +12,8 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou val resource = getResourceAsStream(path) val result = ByteArray(size.toInt()) resource.use { input -> - input.skip(offset) - input.read(result, 0, size.toInt()) + input.skipNBytes(offset) + input.readNBytes(result, 0, size.toInt()) } return result } From a73ed61ea5119b5289cf3d89df63b50324800c78 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 13 Jun 2024 12:33:24 +0200 Subject: [PATCH 14/47] Delete outdated build services (#4959) We had two build services: 1) to check unsupported compose compiler plugins were applied 2) to check a native cache kind configuration Both of them are outdated and we may get rid of them. Fixes https://github.com/JetBrains/compose-multiplatform/issues/4815 ## Release Notes ### Fixes - Gradle Plugin - Delete outdated build services --- .../ComposeCompilerKotlinSupportPlugin.kt | 38 ------ .../org/jetbrains/compose/ComposePlugin.kt | 10 -- .../configureNativeCompilerCaching.kt | 127 ------------------ ...bstractComposeMultiplatformBuildService.kt | 81 ----------- .../BuildEventsListenerRegistryProvider.kt | 19 --- .../ConfigurationProblemReporterService.kt | 69 ---------- .../service/GradlePropertySnapshotService.kt | 52 ------- .../internal/utils/KGPPropertyProvider.kt | 36 ----- .../tests/integration/GradlePluginTest.kt | 110 +-------------- .../UnsupportedCompilerPluginWarningTest.kt | 52 ------- 10 files changed, 6 insertions(+), 588 deletions(-) delete mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt delete mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt delete mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/BuildEventsListenerRegistryProvider.kt delete mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt delete mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/GradlePropertySnapshotService.kt delete mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyProvider.kt delete mode 100644 gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt index 4bc74bd0d8..37c87a3a0b 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt @@ -14,8 +14,6 @@ import org.jetbrains.compose.internal.KOTLIN_JVM_PLUGIN_ID import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID import org.jetbrains.compose.internal.Version import org.jetbrains.compose.internal.ideaIsInSyncProvider -import org.jetbrains.compose.internal.mppExtOrNull -import org.jetbrains.compose.internal.service.ConfigurationProblemReporterService import org.jetbrains.compose.internal.webExt import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation @@ -26,7 +24,6 @@ import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact import org.jetbrains.kotlin.gradle.plugin.SubpluginOption import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile internal fun Project.configureComposeCompilerPlugin() { //only one of them can be applied to the project @@ -84,33 +81,9 @@ class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin { } applicableForPlatformTypes = composeExt.platformTypes - - collectUnsupportedCompilerPluginUsages(target) } } - private fun collectUnsupportedCompilerPluginUsages(project: Project) { - fun Project.hasNonJvmTargets(): Boolean { - val nonJvmTargets = setOf(KotlinPlatformType.native, KotlinPlatformType.js, KotlinPlatformType.wasm) - return mppExtOrNull?.targets?.any { - it.platformType in nonJvmTargets - } ?: false - } - - fun SubpluginArtifact.isNonJBComposeCompiler(): Boolean { - return !groupId.startsWith("org.jetbrains.compose.compiler") - } - - ConfigurationProblemReporterService.registerUnsupportedPluginProvider( - project, - project.provider { - composeCompilerArtifactProvider.compilerArtifact.takeIf { - project.hasNonJvmTargets() && it.isNonJBComposeCompiler() - } - } - ) - } - override fun getCompilerPluginId(): String = "androidx.compose.compiler.plugins.kotlin" @@ -153,14 +126,3 @@ class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin { private fun options(vararg options: Pair): List = options.map { SubpluginOption(it.first, it.second) } } - -private const val COMPOSE_COMPILER_COMPATIBILITY_LINK = - "https://github.com/JetBrains/compose-jb/blob/master/VERSIONING.md#using-compose-multiplatform-compiler" - -internal fun createWarningAboutNonCompatibleCompiler(currentCompilerPluginGroupId: String): String { - return """ -WARNING: Usage of the Custom Compose Compiler plugin ('$currentCompilerPluginGroupId') -with non-JVM targets (Kotlin/Native, Kotlin/JS, Kotlin/WASM) is not supported. -For more information, please visit: $COMPOSE_COMPILER_COMPATIBILITY_LINK -""".trimMargin() -} 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 086c529207..41e9afd7ef 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 @@ -21,8 +21,6 @@ import org.jetbrains.compose.desktop.preview.internal.initializePreview import org.jetbrains.compose.experimental.dsl.ExperimentalExtension import org.jetbrains.compose.experimental.internal.* import org.jetbrains.compose.internal.* -import org.jetbrains.compose.internal.service.ConfigurationProblemReporterService -import org.jetbrains.compose.internal.service.GradlePropertySnapshotService import org.jetbrains.compose.internal.utils.currentTarget import org.jetbrains.compose.resources.ResourcesExtension import org.jetbrains.compose.resources.configureComposeResources @@ -35,15 +33,8 @@ import org.jetbrains.kotlin.gradle.plugin.* internal val composeVersion get() = ComposeBuildConfig.composeVersion -private fun initBuildServices(project: Project) { - ConfigurationProblemReporterService.init(project) - GradlePropertySnapshotService.init(project) -} - abstract class ComposePlugin : Plugin { override fun apply(project: Project) { - initBuildServices(project) - val composeExtension = project.extensions.create("compose", ComposeExtension::class.java, project) val desktopExtension = composeExtension.extensions.create("desktop", DesktopExtension::class.java) val androidExtension = composeExtension.extensions.create("android", AndroidExtension::class.java) @@ -60,7 +51,6 @@ abstract class ComposePlugin : Plugin { composeExtension.extensions.create("web", WebExtension::class.java) project.configureComposeCompilerPlugin() - project.configureNativeCompilerCaching() project.configureComposeResources(resourcesExtension) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt deleted file mode 100644 index 1ecde81ccd..0000000000 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.experimental.internal - -import org.gradle.api.Project -import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID -import org.jetbrains.compose.internal.mppExt -import org.jetbrains.compose.internal.utils.KGPPropertyProvider -import org.jetbrains.compose.internal.utils.configureEachWithType -import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget -import org.jetbrains.kotlin.konan.target.KonanTarget -import org.jetbrains.kotlin.konan.target.presetName - -private const val PROJECT_CACHE_KIND_PROPERTY_NAME = "kotlin.native.cacheKind" -private const val COMPOSE_NATIVE_MANAGE_CACHE_KIND = "compose.kotlin.native.manageCacheKind" -private const val NONE_VALUE = "none" - -// Compose runtime supports these k/native targets: -// https://github.com/JetBrains/compose-multiplatform-core/blob/jb-main/compose/runtime/runtime/build.gradle#L75 -private val SUPPORTED_NATIVE_TARGETS = setOf( - // ios - KonanTarget.IOS_X64, - KonanTarget.IOS_ARM64, - KonanTarget.IOS_SIMULATOR_ARM64, - // macos - KonanTarget.MACOS_X64, - KonanTarget.MACOS_ARM64, - // tvos - KonanTarget.TVOS_X64, - KonanTarget.TVOS_ARM64, - KonanTarget.TVOS_SIMULATOR_ARM64, - // watchOS - KonanTarget.WATCHOS_ARM64, - KonanTarget.WATCHOS_ARM32, - KonanTarget.WATCHOS_X64, - KonanTarget.WATCHOS_SIMULATOR_ARM64, - // mingw - KonanTarget.MINGW_X64, - // linux - KonanTarget.LINUX_X64, -) - -internal val SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES = - SUPPORTED_NATIVE_TARGETS.map { it.targetCacheKindPropertyName } + - PROJECT_CACHE_KIND_PROPERTY_NAME - -internal fun Project.configureNativeCompilerCaching() { - if (findProperty(COMPOSE_NATIVE_MANAGE_CACHE_KIND) == "false") return - - plugins.withId(KOTLIN_MPP_PLUGIN_ID) { - val kotlinPluginVersion = kotlinVersionNumbers(project.getKotlinPluginVersion()) - mppExt.targets.configureEachWithType { - if (konanTarget in SUPPORTED_NATIVE_TARGETS) { - checkExplicitCacheKind() - if (kotlinPluginVersion < KotlinVersion(1, 9, 20)) { - // Pre-1.9.20 Kotlin compiler caches have known compatibility issues - // See KT-57329, KT-61270 - disableKotlinNativeCache() - } - } - } - } -} - -private fun KotlinNativeTarget.checkExplicitCacheKind() { - // To determine cache kind KGP checks kotlin.native.cacheKind. first, then kotlin.native.cacheKind - // For each property it tries to read Project.property, then checks local.properties - // See https://github.com/JetBrains/kotlin/blob/d4d30dcfcf1afb083f09279c6f1ba05031efeabb/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt#L416 - val cacheKindProperties = listOf(targetCacheKindPropertyName, PROJECT_CACHE_KIND_PROPERTY_NAME) - val propertyProviders = listOf( - KGPPropertyProvider.GradleProperties(project), - KGPPropertyProvider.LocalProperties(project) - ) - - for (cacheKindProperty in cacheKindProperties) { - for (provider in propertyProviders) { - val value = provider.valueOrNull(cacheKindProperty) - if (value != null) { - error(explicitCacheKindErrorMessage(cacheKindProperty, value, provider)) - } - } - } -} - -private fun explicitCacheKindErrorMessage( - cacheKindProperty: String, - value: String, - provider: KGPPropertyProvider -) = """ - |Error: '$cacheKindProperty' is explicitly set to '$value'. - |This option significantly slows the Kotlin/Native compiler. - |Compose Multiplatform Gradle plugin manages this property automatically based on a Kotlin compiler version being used. - | * Recommended action: remove explicit '$cacheKindProperty=$value' from ${provider.location}. - | * Alternative action: disable cache kind management by adding '$COMPOSE_NATIVE_MANAGE_CACHE_KIND=false' to your 'gradle.properties'. -""".trimMargin() - - -private val KotlinNativeTarget.targetCacheKindPropertyName: String - get() = konanTarget.targetCacheKindPropertyName - -private val KonanTarget.targetCacheKindPropertyName: String - get() = "$PROJECT_CACHE_KIND_PROPERTY_NAME.${presetName}" - -private fun KotlinNativeTarget.disableKotlinNativeCache() { - val existingValue = project.findProperty(targetCacheKindPropertyName)?.toString() - if (NONE_VALUE.equals(existingValue, ignoreCase = true)) return - - if (targetCacheKindPropertyName in project.properties) { - project.setProperty(targetCacheKindPropertyName, NONE_VALUE) - } else { - project.extensions.extraProperties.set(targetCacheKindPropertyName, NONE_VALUE) - } -} - -internal fun kotlinVersionNumbers(version: String): KotlinVersion { - val m = Regex("(\\d+)\\.(\\d+)\\.(\\d+)").find(version) ?: error("Kotlin version has unexpected format: '$version'") - val (_, majorPart, minorPart, patchPart) = m.groupValues - return KotlinVersion( - major = majorPart.toIntOrNull() ?: error("Could not parse major part '$majorPart' of Kotlin plugin version: '$version'"), - minor = minorPart.toIntOrNull() ?: error("Could not parse minor part '$minorPart' of Kotlin plugin version: '$version'"), - patch = patchPart.toIntOrNull() ?: error("Could not parse patch part '$patchPart' of Kotlin plugin version: '$version'"), - ) -} diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt deleted file mode 100644 index 8a987034be..0000000000 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.internal.service - -import org.gradle.api.Project -import org.gradle.api.services.BuildService -import org.gradle.api.services.BuildServiceParameters -import org.gradle.api.services.BuildServiceRegistration -import org.gradle.tooling.events.FinishEvent -import org.gradle.tooling.events.OperationCompletionListener - -// The service implements OperationCompletionListener just so Gradle would materialize the service even if the service is not used by any task or transformation - abstract class AbstractComposeMultiplatformBuildService

: BuildService

, OperationCompletionListener, AutoCloseable { - override fun onFinish(event: FinishEvent) {} - override fun close() {} -} - -internal inline fun > serviceName(instance: Service? = null): String = - fqName(instance) - -internal inline fun , reified Params : BuildServiceParameters> registerServiceIfAbsent( - project: Project, - crossinline initParams: Params.() -> Unit = {} -): BuildServiceRegistration { - if (findRegistration(project) == null) { - val newService = project.gradle.sharedServices.registerIfAbsent(fqName(), Service::class.java) { - it.parameters.initParams() - } - // Workaround to materialize a service even if it is not bound to a task - BuildEventsListenerRegistryProvider.getInstance(project).onTaskCompletion(newService) - } - - return getExistingServiceRegistration(project) -} - -internal inline fun , reified Params : BuildServiceParameters> getExistingServiceRegistration( - project: Project -): BuildServiceRegistration { - val registration = findRegistration(project) - ?: error("Service '${serviceName()}' was not initialized") - return registration.verified(project) -} - -private inline fun , reified Params : BuildServiceParameters> BuildServiceRegistration<*, *>.verified( - project: Project -): BuildServiceRegistration { - val parameters = parameters - // We are checking the type of parameters instead of the type of service - // to avoid materializing the service. - // After a service instance is created all changes made to its parameters won't be visible to - // that particular service instance. - // This is undesirable in some cases. For example, when reporting configuration problems, - // we want to collect all configuration issues from all projects first, then report issues all at once - // in execution phase. - if (parameters !is Params) { - // Compose Gradle plugin was probably loaded more than once - // See https://github.com/JetBrains/compose-multiplatform/issues/3459 - if (fqName(parameters) == fqName()) { - val rootScript = project.rootProject.buildFile - error(""" - Compose Multiplatform Gradle plugin has been loaded in multiple classloaders. - To avoid classloading issues, declare Compose Gradle Plugin in root build file $rootScript. - """.trimIndent()) - } else { - error("Shared build service '${serviceName()}' parameters have unexpected type: ${fqName(parameters)}") - } - } - - @Suppress("UNCHECKED_CAST") - return this as BuildServiceRegistration -} - -private inline fun , reified P : BuildServiceParameters> findRegistration( - project: Project -): BuildServiceRegistration<*, *>? = - project.gradle.sharedServices.registrations.findByName(fqName()) - -private inline fun fqName(instance: T? = null) = T::class.java.canonicalName diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/BuildEventsListenerRegistryProvider.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/BuildEventsListenerRegistryProvider.kt deleted file mode 100644 index 8473334131..0000000000 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/BuildEventsListenerRegistryProvider.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.internal.service - -import org.gradle.api.Project -import org.gradle.build.event.BuildEventsListenerRegistry -import javax.inject.Inject - -// a hack to get BuildEventsListenerRegistry conveniently, which can only be injected by Gradle -@Suppress("UnstableApiUsage") -internal abstract class BuildEventsListenerRegistryProvider @Inject constructor(val registry: BuildEventsListenerRegistry) { - companion object { - fun getInstance(project: Project): BuildEventsListenerRegistry = - project.objects.newInstance(BuildEventsListenerRegistryProvider::class.java).registry - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt deleted file mode 100644 index e61528f6cc..0000000000 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.internal.service - -import org.gradle.api.Project -import org.gradle.api.logging.Logging -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Provider -import org.gradle.api.provider.SetProperty -import org.gradle.api.services.BuildServiceParameters -import org.jetbrains.compose.createWarningAboutNonCompatibleCompiler -import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact - -abstract class ConfigurationProblemReporterService : AbstractComposeMultiplatformBuildService() { - interface Parameters : BuildServiceParameters { - val unsupportedPluginWarningProviders: ListProperty> - val warnings: SetProperty - } - - private val log = Logging.getLogger(this.javaClass) - - override fun close() { - warnAboutUnsupportedCompilerPlugin() - logWarnings() - } - - private fun warnAboutUnsupportedCompilerPlugin() { - for (warningProvider in parameters.unsupportedPluginWarningProviders.get()) { - val warning = warningProvider.orNull - if (warning != null) { - log.warn(warning) - } - } - } - - private fun logWarnings() { - for (warning in parameters.warnings.get()) { - log.warn(warning) - } - } - companion object { - fun init(project: Project) { - registerServiceIfAbsent(project) { - // WORKAROUND! Call getter at least once, because of Issue: https://github.com/gradle/gradle/issues/27099 - warnings - } - } - - private inline fun configureParameters(project: Project, fn: Parameters.() -> Unit) { - getExistingServiceRegistration(project) - .parameters.fn() - } - - fun reportWarning(project: Project, message: String) { - configureParameters(project) { warnings.add(message) } - } - - fun registerUnsupportedPluginProvider(project: Project, unsupportedPlugin: Provider) { - configureParameters(project) { - unsupportedPluginWarningProviders.add(unsupportedPlugin.map { unsupportedCompiler -> - unsupportedCompiler?.groupId?.let { createWarningAboutNonCompatibleCompiler(it) } - }) - } - } - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/GradlePropertySnapshotService.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/GradlePropertySnapshotService.kt deleted file mode 100644 index 32e7cfa559..0000000000 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/GradlePropertySnapshotService.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.internal.service - -import org.gradle.api.Project -import org.gradle.api.provider.MapProperty -import org.gradle.api.services.BuildServiceParameters -import org.jetbrains.compose.experimental.internal.SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES -import org.jetbrains.compose.internal.utils.loadProperties -import org.jetbrains.compose.internal.utils.localPropertiesFile - -internal abstract class GradlePropertySnapshotService : AbstractComposeMultiplatformBuildService() { - interface Parameters : BuildServiceParameters { - val gradlePropertiesCacheKindSnapshot: MapProperty - val localPropertiesCacheKindSnapshot: MapProperty - } - - internal val gradleProperties: Map = parameters.gradlePropertiesCacheKindSnapshot.get() - internal val localProperties: Map = parameters.localPropertiesCacheKindSnapshot.get() - - companion object { - fun init(project: Project) { - registerServiceIfAbsent(project) { - // WORKAROUND! Call getter at least once, because of Issue: https://github.com/gradle/gradle/issues/27099 - gradlePropertiesCacheKindSnapshot - localPropertiesCacheKindSnapshot - initParams(project) - } - } - - fun getInstance(project: Project): GradlePropertySnapshotService = - getExistingServiceRegistration(project).service.get() - - private fun Parameters.initParams(project: Project) { - // we want to record original properties (explicitly set by a user) - // before we possibly change them in configureNativeCompilerCaching.kt - val rootProject = project.rootProject - val localProperties = loadProperties(rootProject.localPropertiesFile) - for (cacheKindProperty in SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES) { - rootProject.findProperty(cacheKindProperty)?.toString()?.let { value -> - gradlePropertiesCacheKindSnapshot.put(cacheKindProperty, value) - } - localProperties[cacheKindProperty]?.toString()?.let { value -> - localPropertiesCacheKindSnapshot.put(cacheKindProperty, value) - } - } - } - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyProvider.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyProvider.kt deleted file mode 100644 index d0431a41fc..0000000000 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyProvider.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.internal.utils - -import org.gradle.api.Project -import org.jetbrains.compose.internal.service.GradlePropertySnapshotService - -/** - * Reads Kotlin Gradle plugin properties. - * - * Kotlin Gradle plugin supports reading property from two sources: - * 1. Gradle properties. Normally located in gradle.properties file, - * but can also be provided via command-line, /gradle.properties - * or can be set via Gradle API. - * 2. local.properties file. local.properties file is not supported by Gradle out-of-the-box. - * Nevertheless, it became a widespread convention. - */ -internal abstract class KGPPropertyProvider { - abstract fun valueOrNull(propertyName: String): String? - abstract val location: String - - class GradleProperties(private val project: Project) : KGPPropertyProvider() { - override fun valueOrNull(propertyName: String): String? = - GradlePropertySnapshotService.getInstance(project).gradleProperties[propertyName] - override val location: String = "gradle.properties" - } - - class LocalProperties(private val project: Project) : KGPPropertyProvider() { - override fun valueOrNull(propertyName: String): String? = - GradlePropertySnapshotService.getInstance(project).localProperties[propertyName] - override val location: String = "local.properties" - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt index 959135780e..2315dc9119 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt @@ -9,121 +9,23 @@ import org.gradle.util.GradleVersion import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewLogger import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.RemoteConnection import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.receiveConfigFromGradle -import org.jetbrains.compose.experimental.internal.kotlinVersionNumbers -import org.jetbrains.compose.internal.utils.Arch -import org.jetbrains.compose.internal.utils.OS -import org.jetbrains.compose.internal.utils.currentArch -import org.jetbrains.compose.internal.utils.currentOS -import org.jetbrains.compose.test.utils.* +import org.jetbrains.compose.test.utils.GradlePluginTestBase +import org.jetbrains.compose.test.utils.TestProjects +import org.jetbrains.compose.test.utils.TestProperties +import org.jetbrains.compose.test.utils.checks import org.junit.jupiter.api.Assumptions - +import org.junit.jupiter.api.Test import java.net.ServerSocket import java.net.Socket import java.net.SocketTimeoutException import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.MethodSource -import java.io.File class GradlePluginTest : GradlePluginTestBase() { - - @Test - fun nativeCacheKind() { - Assumptions.assumeTrue(currentOS == OS.MacOS) - val task = if (currentArch == Arch.X64) { - ":subproject:linkDebugFrameworkIosX64" - } else { - ":subproject:linkDebugFrameworkIosArm64" - } - // Note: we used to test with kotlin version 1.9.0 and 1.9.10 too, - // but since we now use Compose core libs (1.6.0-dev-1340 and newer) built using kotlin 1.9.21, - // the compiler crashed (older k/native doesn't support libs built using newer k/native): - // e: kotlin.NotImplementedError: Generation of stubs for class org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterPublicSymbolImpl is not supported yet - - if (kotlinVersionNumbers(defaultTestEnvironment.kotlinVersion) >= KotlinVersion(1, 9, 20)) { - testWorkDir.deleteRecursively() - testWorkDir.mkdirs() - val project = TestProject( - TestProjects.nativeCacheKind, - defaultTestEnvironment.copy(useGradleConfigurationCache = false) - ) - with(project) { - gradle(task, "--info").checks { - check.taskSuccessful(task) - check.logContains("-Xauto-cache-from=") - } - } - } - } - - @Test - fun nativeCacheKindError() { - Assumptions.assumeTrue(currentOS == OS.MacOS) - fun withNativeCacheKindErrorProject(kotlinVersion: String, fn: TestProject.() -> Unit) { - with(testProject( - TestProjects.nativeCacheKindError, - defaultTestEnvironment.copy(kotlinVersion = kotlinVersion) - )) { - fn() - testWorkDir.deleteRecursively() - testWorkDir.mkdirs() - } - } - - fun testKotlinVersion(kotlinVersion: String) { - val args = arrayOf("help") - val commonPartOfWarning = "Compose Multiplatform Gradle plugin manages this property automatically" - withNativeCacheKindErrorProject(kotlinVersion = kotlinVersion) { - gradle(*args).checks { - check.logDoesntContain("Error: 'kotlin.native.cacheKind") - check.logDoesntContain(commonPartOfWarning) - } - } - withNativeCacheKindErrorProject(kotlinVersion = kotlinVersion) { - gradleFailure(*args, "-Pkotlin.native.cacheKind=none").checks { - check.logContains("Error: 'kotlin.native.cacheKind' is explicitly set to 'none'") - check.logContains(commonPartOfWarning) - } - - gradleFailure(*args, "-Pkotlin.native.cacheKind=none").checks { - check.logContains("Error: 'kotlin.native.cacheKind' is explicitly set to 'none'") - check.logContains(commonPartOfWarning) - } - } - withNativeCacheKindErrorProject(kotlinVersion = kotlinVersion) { - gradleFailure(*args, "-Pkotlin.native.cacheKind=static").checks { - check.logContains("Error: 'kotlin.native.cacheKind' is explicitly set to 'static'") - check.logContains(commonPartOfWarning) - } - } - withNativeCacheKindErrorProject(kotlinVersion = kotlinVersion) { - gradleFailure(*args, "-Pkotlin.native.cacheKind.iosX64=none").checks { - check.logContains("Error: 'kotlin.native.cacheKind.iosX64' is explicitly set to 'none'") - check.logContains(commonPartOfWarning) - } - } - withNativeCacheKindErrorProject(kotlinVersion = kotlinVersion) { - gradleFailure(*args, "-Pkotlin.native.cacheKind.iosX64=static").checks { - check.logContains("Error: 'kotlin.native.cacheKind.iosX64' is explicitly set to 'static'") - check.logContains(commonPartOfWarning) - } - } - } - - testKotlinVersion("1.9.21") - } - @Test fun skikoWasm() = with( - testProject( - TestProjects.skikoWasm, - // configuration cache is disabled as a temporary workaround for KT-58057 - // todo: enable once KT-58057 is fixed - testEnvironment = defaultTestEnvironment.copy(useGradleConfigurationCache = false) - ) + testProject(TestProjects.skikoWasm) ) { fun jsCanvasEnabled(value: Boolean) { modifyGradleProperties { put("org.jetbrains.compose.experimental.jscanvas.enabled", value.toString()) } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt deleted file mode 100644 index 36ee19a06a..0000000000 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.jetbrains.compose.test.tests.integration - -import org.jetbrains.compose.createWarningAboutNonCompatibleCompiler -import org.jetbrains.compose.test.utils.GradlePluginTestBase -import org.jetbrains.compose.test.utils.TestProjects -import org.jetbrains.compose.test.utils.checks -import org.junit.jupiter.api.Test - -class UnsupportedCompilerPluginWarningTest : GradlePluginTestBase() { - - private val androidxComposeCompilerGroupId = "androidx.compose.compiler" - private val androidxComposeCompilerPlugin = "$androidxComposeCompilerGroupId:compiler:1.4.8" - - private fun testCustomCompilerUnsupportedPlatformsWarning( - platforms: String, - warningIsExpected: Boolean - ) { - testProject( - TestProjects.customCompilerUnsupportedPlatformsWarning, defaultTestEnvironment.copy( - kotlinVersion = "1.8.22", - composeCompilerPlugin = "\"$androidxComposeCompilerPlugin\"", - ) - ).apply { - // repeat twice to check that configuration cache hit does not affect the result - repeat(2) { - gradle("-Pplatforms=$platforms").checks { - val warning = createWarningAboutNonCompatibleCompiler(androidxComposeCompilerGroupId) - if (warningIsExpected) { - check.logContainsOnce(warning) - } else { - check.logDoesntContain(warning) - } - } - } - } - } - - @Test - fun testJs() { - testCustomCompilerUnsupportedPlatformsWarning("js", warningIsExpected = true) - } - - @Test - fun testIos() { - testCustomCompilerUnsupportedPlatformsWarning("ios", warningIsExpected = true) - } - - @Test - fun testJvm() { - testCustomCompilerUnsupportedPlatformsWarning("jvm", warningIsExpected = false) - } -} \ No newline at end of file From f8cf966ef465f72a6c613de45608e4432c9b4f63 Mon Sep 17 00:00:00 2001 From: rmaksim Date: Thu, 13 Jun 2024 14:48:15 +0300 Subject: [PATCH 15/47] Update README.md - fix typo (#4960) --- tutorials/HTML/Building_UI/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/HTML/Building_UI/README.md b/tutorials/HTML/Building_UI/README.md index 1670b1779d..2de5c7a2b5 100644 --- a/tutorials/HTML/Building_UI/README.md +++ b/tutorials/HTML/Building_UI/README.md @@ -1,4 +1,4 @@ -# Building the UI with with the Compose HTML library +# Building the UI with the Compose HTML library In this tutorial we will look at several examples that use the Composable HTML/CSS DSL to describe the user interface for your web application. From a1b88db890ca77e9212c3d2c495b675ce871e7fa Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Thu, 13 Jun 2024 14:51:57 +0300 Subject: [PATCH 16/47] Fix `DesktopImageStorage` to store images by id, rather than by the `PictureData.Camera` itself (#4963) --- .../kotlin/example/imageviewer/Dependencies.kt | 1 + .../example/imageviewer/DesktopImageStorage.kt | 18 ++++++++---------- .../imageviewer/view/ImageViewer.desktop.kt | 2 +- .../imageviewer/storage/IosImageStorage.ios.kt | 4 +--- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/Dependencies.kt b/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/Dependencies.kt index 6117af6fcc..0a70b52de3 100644 --- a/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/Dependencies.kt +++ b/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/Dependencies.kt @@ -39,6 +39,7 @@ abstract class Dependencies { } override fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage) { + pictures.add(0, picture) imageStorage.saveImage(picture, image) } diff --git a/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/DesktopImageStorage.kt b/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/DesktopImageStorage.kt index f25b0593aa..ee26308365 100644 --- a/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/DesktopImageStorage.kt +++ b/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/DesktopImageStorage.kt @@ -1,6 +1,5 @@ package example.imageviewer -import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.toAwtImage import androidx.compose.ui.graphics.toComposeImageBitmap @@ -13,25 +12,24 @@ private const val maxStorableImageSizePx = 2000 private const val storableThumbnailSizePx = 200 class DesktopImageStorage( - private val pictures: SnapshotStateList, private val ioScope: CoroutineScope ) : ImageStorage { - private val largeImages = mutableMapOf() - private val thumbnails = mutableMapOf() + private val largeImages = mutableMapOf() + private val thumbnails = mutableMapOf() override fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage) { if (image.imageBitmap.width == 0 || image.imageBitmap.height == 0) { return } ioScope.launch { - largeImages[picture] = image.imageBitmap.fitInto(maxStorableImageSizePx) - thumbnails[picture] = image.imageBitmap.fitInto(storableThumbnailSizePx) - pictures.add(0, picture) + largeImages[picture.id] = image.imageBitmap.fitInto(maxStorableImageSizePx) + thumbnails[picture.id] = image.imageBitmap.fitInto(storableThumbnailSizePx) } } override fun delete(picture: PictureData.Camera) { - // For now, on Desktop pictures saving in memory. We don't need additional delete logic. + largeImages.remove(picture.id) + thumbnails.remove(picture.id) } override fun rewrite(picture: PictureData.Camera) { @@ -39,11 +37,11 @@ class DesktopImageStorage( } override suspend fun getThumbnail(picture: PictureData.Camera): ImageBitmap { - return thumbnails[picture]!! + return thumbnails[picture.id]!! } override suspend fun getImage(picture: PictureData.Camera): ImageBitmap { - return largeImages[picture]!! + return largeImages[picture.id]!! } } diff --git a/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt b/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt index ccf93f9aef..ff7800d460 100755 --- a/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt +++ b/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt @@ -104,7 +104,7 @@ private fun getDependencies( toastState.value = ToastState.Shown(text) } } - override val imageStorage: DesktopImageStorage = DesktopImageStorage(pictures, ioScope) + override val imageStorage: DesktopImageStorage = DesktopImageStorage(ioScope) override val sharePicture: SharePicture = object : SharePicture { override fun share(context: PlatformContext, picture: PictureData) { // On Desktop share feature not supported diff --git a/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt b/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt index 59b0d7c1be..b84d46d13c 100644 --- a/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt +++ b/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import platform.CoreGraphics.CGRectMake @@ -36,7 +35,7 @@ private const val storableThumbnailSizePx = 180 private const val jpegCompressionQuality = 60 class IosImageStorage( - private val pictures: SnapshotStateList, + pictures: SnapshotStateList, private val ioScope: CoroutineScope ) : ImageStorage { @@ -77,7 +76,6 @@ class IosImageStorage( picture.jpgFile.writeJpeg(fitInto(maxStorableImageSizePx)) picture.thumbnailJpgFile.writeJpeg(fitInto(storableThumbnailSizePx)) } - pictures.add(0, picture) picture.jsonFile.writeText(picture.toJson()) } } From 6d4e0da9315fd9510498f7a368beeeb119b6d9d2 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Mon, 17 Jun 2024 14:30:48 +0200 Subject: [PATCH 17/47] Examples. JetSnack. Migrate to Compose 1.7 (#4989) `rememberRipple` was deprecated: https://teamcity.jetbrains.com/buildConfiguration/JetBrainsPublicProjects_Compose_Task4ValidateExamples/4656662?hideTestsFromDependencies=false&hideProblemsFromDependencies=false&expandBuildDeploymentsSection=false&expandBuildProblemsSection=true ``` e: file:///home/teamcity/agent/work/b302b5c06ec67883/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Button.kt:94:52 '@Deprecated(...) @Composable() fun rememberRipple(bounded: Boolean = ..., radius: Dp = ..., color: Color = ...): Indication' is deprecated. rememberRipple has been deprecated - it returns an old Indication implementation that is not compatible with the new Indication APIs that provide notable performance improvements. Instead, use the new ripple APIs provided by design system libraries, such as material and material3. If you are implementing your own design system library, use createRippleNode to create your own custom ripple implementation that queries your own theme values. For a migration guide and background information, please visit developer.android.com. ``` --- .../kotlin/com/example/jetsnack/ui/components/Button.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Button.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Button.kt index dd1889e39e..73fa507eea 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Button.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Button.kt @@ -86,11 +86,14 @@ fun JetsnackButton( value = MaterialTheme.typography.button ) { Row( + @Suppress("DEPRECATION_ERROR") Modifier .defaultMinSize( minWidth = ButtonDefaults.MinWidth, minHeight = ButtonDefaults.MinHeight ) + // TODO This should be replaced by non-deprecated alternative after the original example migrates to Jetpack Compose 1.7: + // https://github.com/android/compose-samples/blob/3bc6b7d7c74571ea74776ec5b15518b40de4d31b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt#L95 .indication(interactionSource, rememberRipple()) .padding(contentPadding), horizontalArrangement = Arrangement.Center, From 67da8d12f9b18f6aefe68a449cb2ed8c3eb8646f Mon Sep 17 00:00:00 2001 From: vdshb <58123143+vdshb@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:20:27 +0300 Subject: [PATCH 18/47] Improve SplitPaneState (#3974) Improve `SplitPane` programmatic configuration through changes in `SplitPaneState`. Add programmatic `SplitPane` programmatic configuration changes to demo. --------- Co-authored-by: vdshb --- .../jetbrains/compose/splitpane/demo/Main.kt | 114 ++++++++++++------ .../compose/splitpane/SplitPaneState.kt | 7 +- 2 files changed, 84 insertions(+), 37 deletions(-) diff --git a/components/SplitPane/demo/src/jvmMain/kotlin/org/jetbrains/compose/splitpane/demo/Main.kt b/components/SplitPane/demo/src/jvmMain/kotlin/org/jetbrains/compose/splitpane/demo/Main.kt index 226bb5c343..dd14048434 100644 --- a/components/SplitPane/demo/src/jvmMain/kotlin/org/jetbrains/compose/splitpane/demo/Main.kt +++ b/components/SplitPane/demo/src/jvmMain/kotlin/org/jetbrains/compose/splitpane/demo/Main.kt @@ -1,17 +1,34 @@ package org.jetbrains.compose.splitpane.demo import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.Button import androidx.compose.material.MaterialTheme -import androidx.compose.runtime.* +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.window.singleWindowApplication -import org.jetbrains.compose.splitpane.* +import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi +import org.jetbrains.compose.splitpane.HorizontalSplitPane +import org.jetbrains.compose.splitpane.VerticalSplitPane +import org.jetbrains.compose.splitpane.rememberSplitPaneState import java.awt.Cursor private fun Modifier.cursorForHorizontalResize(): Modifier = @@ -24,40 +41,69 @@ fun main() = singleWindowApplication( MaterialTheme { val splitterState = rememberSplitPaneState() val hSplitterState = rememberSplitPaneState() - HorizontalSplitPane( - splitPaneState = splitterState - ) { - first(20.dp) { - Box(Modifier.background(Color.Red).fillMaxSize()) - } - second(50.dp) { - VerticalSplitPane(splitPaneState = hSplitterState) { - first(50.dp) { - Box(Modifier.background(Color.Blue).fillMaxSize()) - } - second(20.dp) { - Box(Modifier.background(Color.Green).fillMaxSize()) - } + var delta by remember { mutableStateOf("20") } + var percentage by remember { mutableStateOf("0.20") } + Row { + Column(verticalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(10.dp).width(180.dp)) { + Text("Action panel", fontWeight = FontWeight.Bold) + Button(onClick = { splitterState.moveEnabled = !splitterState.moveEnabled }) { + Text(if (splitterState.moveEnabled) "Freeze V" else "Unfreeze V") + } + Button(onClick = { hSplitterState.moveEnabled = !hSplitterState.moveEnabled }) { + Text(if (hSplitterState.moveEnabled) "Freeze H" else "Unfreeze H") + } + + OutlinedTextField(value = delta, onValueChange = { delta = it }, label = { Text("Delta") }) + Button(onClick = { delta.toFloatOrNull()?.let { splitterState.dispatchRawMovement(it) } }) { + Text("Add delta V") + } + Button(onClick = { delta.toFloatOrNull()?.let { hSplitterState.dispatchRawMovement(it) } }) { + Text("Add delta H") + } + + OutlinedTextField(value = percentage, onValueChange = { percentage = it }, label = { Text("Fraction") }) + Button(onClick = { percentage.toFloatOrNull()?.let { splitterState.positionPercentage = it } }) { + Text("Set fraction V") + } + Button(onClick = { percentage.toFloatOrNull()?.let { hSplitterState.positionPercentage = it } }) { + Text("Set fraction H") } } - splitter { - visiblePart { - Box( - Modifier - .width(1.dp) - .fillMaxHeight() - .background(MaterialTheme.colors.background) - ) + HorizontalSplitPane( + splitPaneState = splitterState + ) { + first(20.dp) { + Box(Modifier.background(Color.Red).fillMaxSize()) } - handle { - Box( - Modifier - .markAsHandle() - .cursorForHorizontalResize() - .background(SolidColor(Color.Gray), alpha = 0.50f) - .width(9.dp) - .fillMaxHeight() - ) + second(50.dp) { + VerticalSplitPane(splitPaneState = hSplitterState) { + first(50.dp) { + Box(Modifier.background(Color.Blue).fillMaxSize()) + } + second(20.dp) { + Box(Modifier.background(Color.Green).fillMaxSize()) + } + } + } + splitter { + visiblePart { + Box( + Modifier + .width(1.dp) + .fillMaxHeight() + .background(MaterialTheme.colors.background) + ) + } + handle { + Box( + Modifier + .markAsHandle() + .cursorForHorizontalResize() + .background(SolidColor(Color.Gray), alpha = 0.50f) + .width(9.dp) + .fillMaxHeight() + ) + } } } } diff --git a/components/SplitPane/library/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneState.kt b/components/SplitPane/library/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneState.kt index 6d9d8815c3..0ea874f708 100644 --- a/components/SplitPane/library/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneState.kt +++ b/components/SplitPane/library/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneState.kt @@ -11,10 +11,11 @@ class SplitPaneState( ) { var moveEnabled by mutableStateOf(moveEnabled) - internal set - var positionPercentage by mutableStateOf(initialPositionPercentage) - internal set + private var _positionPercentage by mutableStateOf(initialPositionPercentage) + var positionPercentage: Float + get() = _positionPercentage + set(value) { _positionPercentage = value.coerceIn(0f, 1f) } internal var minPosition: Float = 0f From 53bf4dffa6b9b8e37b8cf8d7deb8b7b28e70116d Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Thu, 20 Jun 2024 12:09:34 +0200 Subject: [PATCH 19/47] Make sure the web app distribution doesn't contain a duplicate skiko.wasm (#4958) https://youtrack.jetbrains.com/issue/CMP-1114 **Testing:** - changed an existing test to test where added a new check for the content of the app distribution --- .../web/internal/configureWebApplication.kt | 48 ++++++++----------- .../tests/integration/GradlePluginTest.kt | 29 +++++++++-- .../test-projects/misc/skikoWasm/build.gradle | 8 ++-- 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/web/internal/configureWebApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/web/internal/configureWebApplication.kt index 2c163824fc..79b68b8227 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/web/internal/configureWebApplication.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/web/internal/configureWebApplication.kt @@ -13,12 +13,11 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier import org.gradle.api.provider.Provider import org.jetbrains.compose.ComposeBuildConfig import org.jetbrains.compose.ComposeExtension -import org.jetbrains.compose.web.tasks.UnpackSkikoWasmRuntimeTask -import org.jetbrains.compose.internal.utils.* +import org.jetbrains.compose.internal.utils.detachedComposeDependency import org.jetbrains.compose.internal.utils.registerTask -import org.jetbrains.compose.internal.utils.uppercaseFirstChar import org.jetbrains.compose.web.WebExtension -import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget +import org.jetbrains.compose.web.tasks.UnpackSkikoWasmRuntimeTask +import org.jetbrains.kotlin.gradle.tasks.IncrementalSyncTask internal fun Project.configureWeb( composeExt: ComposeExtension, @@ -48,11 +47,12 @@ internal fun Project.configureWeb( } // configure only if there is k/wasm or k/js target: - webExt.targetsToConfigure(project) - .configureWebApplication(project, shouldRunUnpackSkiko) + if (webExt.targetsToConfigure(project).isNotEmpty()) { + configureWebApplication(project, shouldRunUnpackSkiko) + } } -internal fun Collection.configureWebApplication( +internal fun configureWebApplication( project: Project, shouldRunUnpackSkiko: Provider ) { @@ -63,28 +63,22 @@ internal fun Collection.configureWebApplication( skikoJsWasmRuntimeConfiguration.defaultDependencies { it.addLater(skikoJsWasmRuntimeDependency) } - forEach { - val mainCompilation = it.compilations.getByName("main") - val testCompilation = it.compilations.getByName("test") - val unpackedRuntimeDir = project.layout.buildDirectory.dir("compose/skiko-wasm/${it.targetName}") - val taskName = "unpackSkikoWasmRuntime${it.targetName.uppercaseFirstChar()}" - mainCompilation.defaultSourceSet.resources.srcDir(unpackedRuntimeDir) - testCompilation.defaultSourceSet.resources.srcDir(unpackedRuntimeDir) - - val unpackRuntime = project.registerTask(taskName) { - onlyIf { - shouldRunUnpackSkiko.get() - } - skikoRuntimeFiles = skikoJsWasmRuntimeConfiguration - outputDir.set(unpackedRuntimeDir) - } - project.tasks.named(mainCompilation.processResourcesTaskName).configure { processResourcesTask -> - processResourcesTask.dependsOn(unpackRuntime) - } - project.tasks.named(testCompilation.processResourcesTaskName).configure { processResourcesTask -> - processResourcesTask.dependsOn(unpackRuntime) + val unpackedRuntimeDir = project.layout.buildDirectory.dir("compose/skiko-wasm") + val taskName = "unpackSkikoWasmRuntime" + + val unpackRuntime = project.registerTask(taskName) { + onlyIf { + shouldRunUnpackSkiko.get() } + + skikoRuntimeFiles = skikoJsWasmRuntimeConfiguration + outputDir.set(unpackedRuntimeDir) + } + + project.tasks.withType(IncrementalSyncTask::class.java) { + it.dependsOn(unpackRuntime) + it.from.from(unpackedRuntimeDir) } } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt index 2315dc9119..866e46c945 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt @@ -5,13 +5,13 @@ package org.jetbrains.compose.test.tests.integration +import org.gradle.internal.impldep.junit.framework.TestCase.assertEquals +import org.gradle.internal.impldep.junit.framework.TestCase.assertTrue import org.gradle.util.GradleVersion import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewLogger import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.RemoteConnection import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.receiveConfigFromGradle -import org.jetbrains.compose.test.utils.GradlePluginTestBase -import org.jetbrains.compose.test.utils.TestProjects -import org.jetbrains.compose.test.utils.TestProperties +import org.jetbrains.compose.test.utils.* import org.jetbrains.compose.test.utils.checks import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.Test @@ -21,11 +21,16 @@ import java.net.SocketTimeoutException import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread +import kotlin.test.assertFalse class GradlePluginTest : GradlePluginTestBase() { @Test fun skikoWasm() = with( - testProject(TestProjects.skikoWasm) + testProject( + TestProjects.skikoWasm, + // TODO: enable the configuration cache after moving all test projects to kotlin 2.0 or newer + defaultTestEnvironment.copy(useGradleConfigurationCache = false) + ) ) { fun jsCanvasEnabled(value: Boolean) { modifyGradleProperties { put("org.jetbrains.compose.experimental.jscanvas.enabled", value.toString()) } @@ -39,8 +44,22 @@ class GradlePluginTest : GradlePluginTestBase() { jsCanvasEnabled(true) gradle(":build").checks { - check.taskSuccessful(":unpackSkikoWasmRuntimeJs") + check.taskSuccessful(":unpackSkikoWasmRuntime") check.taskSuccessful(":compileKotlinJs") + check.taskSuccessful(":compileKotlinWasmJs") + check.taskSuccessful(":wasmJsBrowserDistribution") + + file("./build/dist/wasmJs/productionExecutable").apply { + checkExists() + assertTrue(isDirectory) + val distributionFiles = listFiles()!!.map { it.name }.toList() + assertFalse( + distributionFiles.contains("skiko.wasm"), + "skiko.wasm is probably a duplicate" + ) + // one file is the app wasm file and another one is skiko wasm file with a mangled name + assertEquals(2, distributionFiles.filter { it.endsWith(".wasm") }.size) + } } } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle index c6583d3c0a..dc861c0809 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle @@ -10,6 +10,10 @@ kotlin { browser() binaries.executable() } + wasm { + browser() + binaries.executable() + } sourceSets { commonMain { dependencies { @@ -22,10 +26,6 @@ kotlin { } } -compose.experimental { - web.application {} -} - // test for https://github.com/JetBrains/compose-multiplatform/issues/3169 afterEvaluate { afterEvaluate { From 5c141b52133d6b57d803122906e815bec8f4f898 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 20 Jun 2024 13:57:10 +0200 Subject: [PATCH 20/47] [resources] Update resource density-based lookup to be equal with the android logic (#4969) In general, Android prefers scaling down a larger original image to scaling up a smaller original image: https://developer.android.com/guide/topics/resources/providing-resources#BestMatch Fixes https://github.com/JetBrains/compose-multiplatform/issues/4368 ## Release Notes ### Highlights - Resources - If there is no resource with suitable density, use resource with the most suitable density, otherwise use default (similar to the Android logic) --- .../compose/resources/ResourceEnvironment.kt | 51 ++++++++++++++++++- .../compose/resources/ComposeResourceTest.kt | 38 ++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceEnvironment.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceEnvironment.kt index 20ac17ca09..e658ad3e11 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceEnvironment.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceEnvironment.kt @@ -97,7 +97,7 @@ internal fun Resource.getResourceItemByEnvironment(environment: ResourceEnvironm .also { if (it.size == 1) return it.first() } .filterBy(environment.theme) .also { if (it.size == 1) return it.first() } - .filterBy(environment.density) + .filterByDensity(environment.density) .also { if (it.size == 1) return it.first() } .let { items -> if (items.isEmpty()) { @@ -125,12 +125,59 @@ private fun List.filterBy(qualifier: Qualifier): List.filterByDensity(density: DensityQualifier): List { + val items = this + var withQualifier = emptyList() + + // filter with the same or better density + val exactAndHigherQualifiers = DensityQualifier.entries + .filter { it.dpi >= density.dpi } + .sortedBy { it.dpi } + + for (qualifier in exactAndHigherQualifiers) { + withQualifier = items.filter { item -> item.qualifiers.any { it == qualifier } } + if (withQualifier.isNotEmpty()) break + } + if (withQualifier.isNotEmpty()) return withQualifier + + // filter with low density + val lowQualifiers = DensityQualifier.entries + .minus(DensityQualifier.LDPI) + .filter { it.dpi < density.dpi } + .sortedByDescending { it.dpi } + for (qualifier in lowQualifiers) { + withQualifier = items.filter { item -> item.qualifiers.any { it == qualifier } } + if (withQualifier.isNotEmpty()) break + } + if (withQualifier.isNotEmpty()) return withQualifier + + //items with no DensityQualifier (default) + // The system assumes that default resources (those from a directory without configuration qualifiers) + // are designed for the baseline pixel density (mdpi) and resizes those bitmaps + // to the appropriate size for the current pixel density. + // https://developer.android.com/training/multiscreen/screendensities#DensityConsiderations + val withNoDensity = items.filter { item -> + item.qualifiers.none { it is DensityQualifier } + } + if (withNoDensity.isNotEmpty()) return withNoDensity + + //items with LDPI density + return items.filter { item -> + item.qualifiers.any { it == DensityQualifier.LDPI } + } +} + // we need to filter by language and region together because there is slightly different logic: // 1) if there is the exact match language+region then use it // 2) if there is the language WITHOUT region match then use it // 3) in other cases use items WITHOUT language and region qualifiers at all // issue: https://github.com/JetBrains/compose-multiplatform/issues/4571 -private fun List.filterByLocale(language: LanguageQualifier, region: RegionQualifier): List { +private fun List.filterByLocale( + language: LanguageQualifier, + region: RegionQualifier +): List { val withLanguage = filter { item -> item.qualifiers.any { it == language } } 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 712e8a8cbf..cdd1287ea6 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 @@ -59,6 +59,44 @@ class ComposeResourceTest { ) } + @Test + fun testImageResourceDensity() = runComposeUiTest { + val testResourceReader = TestResourceReader() + val imgRes = DrawableResource( + "test_id", setOf( + ResourceItem(setOf(DensityQualifier.XXXHDPI), "2.png", -1, -1), + ResourceItem(setOf(DensityQualifier.MDPI), "1.png", -1, -1), + ) + ) + val mdpiEnvironment = object : ComposeEnvironment { + @Composable + override fun rememberEnvironment() = ResourceEnvironment( + language = LanguageQualifier("en"), + region = RegionQualifier("US"), + theme = ThemeQualifier.LIGHT, + density = DensityQualifier.MDPI + ) + } + + var environment by mutableStateOf(TestComposeEnvironment) + setContent { + CompositionLocalProvider( + LocalResourceReader provides testResourceReader, + LocalComposeEnvironment provides environment + ) { + Image(painterResource(imgRes), null) + } + } + waitForIdle() + environment = mdpiEnvironment + waitForIdle() + + assertEquals( + expected = listOf("2.png", "1.png"), //XXXHDPI - fist, MDPI - next + actual = testResourceReader.readPaths + ) + } + @Test fun testStringResourceCache() = runComposeUiTest { val testResourceReader = TestResourceReader() From 8fc3dd2f7565c9a5d13c2481f86e176c36ce7438 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 20 Jun 2024 14:30:24 +0200 Subject: [PATCH 21/47] Pack all resources to assets on the android target. (#4965) The PR changes the android resources packaging. Now all resources are packed to the android assets (not only fonts). It unblocks usage android URIs to the resources in a WebView or other external resource consumers. Additionally the PR fixes Android Studio Compose Previews work with multiplatform resources: ![](https://private-user-images.githubusercontent.com/3532155/341182790-ef26b667-ad0d-4efd-b7f9-23cff92ab49d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTg4Nzg0MTgsIm5iZiI6MTcxODg3ODExOCwicGF0aCI6Ii8zNTMyMTU1LzM0MTE4Mjc5MC1lZjI2YjY2Ny1hZDBkLTRlZmQtYjdmOS0yM2NmZjkyYWI0OWQucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDYyMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA2MjBUMTAwODM4WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OTY1MzdhMTAxMjNmZDRhMDA4ZjdjODBjYzg3M2MyNDg0ZTA5OWFkZGZkZjk1ZDUwOWFkZDk3MmQ2YjIzNzJiYiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.xgUAr_2--ZHo6txhdAANRbe8ju2SQ5EACvK96gaGJnY) For a backward compatibility the resources library tries to read resources in java resources if assets were not found. Fixes https://github.com/JetBrains/compose-multiplatform/issues/4877 Fixes https://github.com/JetBrains/compose-multiplatform/issues/4503 Fixes https://github.com/JetBrains/compose-multiplatform/issues/4932 Fixes https://github.com/JetBrains/compose-multiplatform/issues/4476 ## Release Notes ### Features - Resources - Android Studio Preview works with Compose Multiplatform resources now - Compose Multiplatform resources are stored in the android assets now. This fixes such cases as a rendering resource files in WebViews or Media Players --- components/build.gradle.kts | 2 +- components/gradle.properties | 3 +- components/gradle/libs.versions.toml | 4 +- .../resources/demo/shared/build.gradle.kts | 10 ++ .../resources/demo/shared/main.android.kt | 18 ++++ .../resources/library/api/android/library.api | 4 + components/resources/library/build.gradle.kts | 8 +- .../src/androidMain/AndroidManifest.xml | 13 +++ .../resources/AndroidContextProvider.kt | 81 ++++++++++++++++ .../resources/FontResources.android.kt | 3 +- .../resources/ResourceReader.android.kt | 71 +++++++++----- .../compose/resources/ImageResources.kt | 6 +- .../resources/PluralStringResources.kt | 4 +- .../compose/resources/ResourceReader.kt | 6 ++ .../compose/resources/StringArrayResources.kt | 2 +- .../compose/resources/StringResources.kt | 4 +- .../compose/resources/ComposeResourceTest.kt | 2 +- .../compose/resources/FontResources.skiko.kt | 2 +- .../compose/resources/ResourceReader.skiko.kt | 7 ++ components/settings.gradle.kts | 4 +- .../library/build.gradle.kts | 2 - .../compose/resources/AndroidResources.kt | 96 ++++++++++--------- .../compose/resources/ComposeResources.kt | 34 +++---- .../compose/resources/KmpResources.kt | 32 +++---- .../test/tests/integration/ResourcesTest.kt | 74 +++----------- 25 files changed, 305 insertions(+), 187 deletions(-) create mode 100644 components/resources/library/src/androidMain/AndroidManifest.xml create mode 100644 components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/AndroidContextProvider.kt create mode 100644 components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ResourceReader.skiko.kt diff --git a/components/build.gradle.kts b/components/build.gradle.kts index 00461713c7..8bba7d8509 100644 --- a/components/build.gradle.kts +++ b/components/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } subprojects { - version = findProperty("deploy.version") ?: property("compose.version")!! + version = findProperty("deploy.version")!! plugins.withId("java") { configureIfExists { diff --git a/components/gradle.properties b/components/gradle.properties index 8604e6cf0e..a16ea9956e 100644 --- a/components/gradle.properties +++ b/components/gradle.properties @@ -8,12 +8,11 @@ android.useAndroidX=true #Versions kotlin.version=1.9.23 -compose.version=1.6.10-beta02 agp.version=8.2.2 +deploy.version=0.1.0-SNAPSHOT #Compose org.jetbrains.compose.experimental.jscanvas.enabled=true -org.jetbrains.compose.experimental.wasm.enabled=true org.jetbrains.compose.experimental.macos.enabled=true compose.desktop.verbose=true compose.useMavenLocal=false diff --git a/components/gradle/libs.versions.toml b/components/gradle/libs.versions.toml index 643996da53..6ecd0569a6 100644 --- a/components/gradle/libs.versions.toml +++ b/components/gradle/libs.versions.toml @@ -13,4 +13,6 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver 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 +androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-compose" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "androidx-compose" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "androidx-compose" } \ No newline at end of file diff --git a/components/resources/demo/shared/build.gradle.kts b/components/resources/demo/shared/build.gradle.kts index cc87a66ef0..5dc1580b80 100644 --- a/components/resources/demo/shared/build.gradle.kts +++ b/components/resources/demo/shared/build.gradle.kts @@ -52,6 +52,10 @@ kotlin { desktopMain.dependencies { implementation(compose.desktop.common) } + androidMain.dependencies { + implementation(libs.androidx.ui.tooling) + implementation(libs.androidx.ui.tooling.preview) + } val nonAndroidMain by creating { dependsOn(commonMain.get()) @@ -73,6 +77,12 @@ android { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.11" + } } compose.experimental { diff --git a/components/resources/demo/shared/src/androidMain/kotlin/org/jetbrains/compose/resources/demo/shared/main.android.kt b/components/resources/demo/shared/src/androidMain/kotlin/org/jetbrains/compose/resources/demo/shared/main.android.kt index 37f0bf45d7..e61279fa17 100644 --- a/components/resources/demo/shared/src/androidMain/kotlin/org/jetbrains/compose/resources/demo/shared/main.android.kt +++ b/components/resources/demo/shared/src/androidMain/kotlin/org/jetbrains/compose/resources/demo/shared/main.android.kt @@ -5,9 +5,27 @@ package org.jetbrains.compose.resources.demo.shared +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.PreviewContextConfigurationEffect @Composable fun MainView() { UseResources() } + +@Preview(showBackground = true) +@Composable +fun ImagesResPreview() { + ImagesRes(PaddingValues()) +} + +@OptIn(ExperimentalResourceApi::class) +@Preview(showBackground = true) +@Composable +fun FileResPreview() { + PreviewContextConfigurationEffect() + FileRes(PaddingValues()) +} diff --git a/components/resources/library/api/android/library.api b/components/resources/library/api/android/library.api index 9c01aca1e2..0acbd5b930 100644 --- a/components/resources/library/api/android/library.api +++ b/components/resources/library/api/android/library.api @@ -1,3 +1,7 @@ +public final class org/jetbrains/compose/resources/AndroidContextProviderKt { + public static final fun PreviewContextConfigurationEffect (Landroidx/compose/runtime/Composer;I)V +} + public final class org/jetbrains/compose/resources/DensityQualifier$Companion { public final fun selectByDensity (F)Lorg/jetbrains/compose/resources/DensityQualifier; public final fun selectByValue (I)Lorg/jetbrains/compose/resources/DensityQualifier; diff --git a/components/resources/library/build.gradle.kts b/components/resources/library/build.gradle.kts index 17a6d7489a..c53dcba642 100644 --- a/components/resources/library/build.gradle.kts +++ b/components/resources/library/build.gradle.kts @@ -10,8 +10,6 @@ plugins { id("org.jetbrains.kotlinx.binary-compatibility-validator") } -val composeVersion = extra["compose.version"] as String - kotlin { jvm("desktop") androidTarget { @@ -187,6 +185,7 @@ android { assets.srcDir("src/androidInstrumentedTest/assets") } named("test") { resources.srcDir(commonTestResources) } + named("main") { manifest.srcFile("src/androidMain/AndroidManifest.xml") } } } @@ -202,11 +201,6 @@ apiValidation { nonPublicMarkers.add("org.jetbrains.compose.resources.InternalResourceApi") } -// adding it here to make sure skiko is unpacked and available in web tests -compose.experimental { - web.application {} -} - //utility task to generate CLDRPluralRuleLists.kt file by 'CLDRPluralRules/plurals.xml' tasks.register("generatePluralRuleLists") { val projectDir = project.layout.projectDirectory diff --git a/components/resources/library/src/androidMain/AndroidManifest.xml b/components/resources/library/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000000..ed54901fe4 --- /dev/null +++ b/components/resources/library/src/androidMain/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/AndroidContextProvider.kt b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/AndroidContextProvider.kt new file mode 100644 index 0000000000..df9cc46c4c --- /dev/null +++ b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/AndroidContextProvider.kt @@ -0,0 +1,81 @@ +package org.jetbrains.compose.resources + +import android.annotation.SuppressLint +import android.content.ContentProvider +import android.content.ContentValues +import android.content.Context +import android.content.pm.ProviderInfo +import android.database.Cursor +import android.net.Uri +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode + +internal val androidContext get() = AndroidContextProvider.ANDROID_CONTEXT + +/** + * The function configures the android context + * to be used for non-composable resource read functions + * + * e.g. `Res.readBytes(...)` + * + * Example usage: + * ``` + * @Preview + * @Composable + * fun MyPreviewComponent() { + * PreviewContextConfigurationEffect() + * //... + * } + * ``` + */ +@ExperimentalResourceApi +@Composable +fun PreviewContextConfigurationEffect() { + if (LocalInspectionMode.current) { + AndroidContextProvider.ANDROID_CONTEXT = LocalContext.current + } +} + +//https://andretietz.com/2017/09/06/autoinitialise-android-library/ +internal class AndroidContextProvider : ContentProvider() { + companion object { + @SuppressLint("StaticFieldLeak") + var ANDROID_CONTEXT: Context? = null + } + + override fun onCreate(): Boolean { + ANDROID_CONTEXT = context + return true + } + + override fun attachInfo(context: Context, info: ProviderInfo?) { + if (info == null) { + throw NullPointerException("AndroidContextProvider ProviderInfo cannot be null.") + } + // So if the authorities equal the library internal ones, the developer forgot to set his applicationId + if ("org.jetbrains.compose.components.resources.resources.AndroidContextProvider" == info.authority) { + throw IllegalStateException("Incorrect provider authority in manifest. Most likely due to a " + + "missing applicationId variable your application\'s build.gradle.") + } + + super.attachInfo(context, info) + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor? = null + override fun getType(uri: Uri): String? = null + override fun insert(uri: Uri, values: ContentValues?): Uri? = null + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0 + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): Int = 0 +} \ No newline at end of file diff --git a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt index 009419a020..64f87e5440 100644 --- a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt +++ b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt @@ -9,5 +9,6 @@ import androidx.compose.ui.text.font.* actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font { val environment = LocalComposeEnvironment.current.rememberEnvironment() val path = remember(environment, resource) { resource.getResourceItemByEnvironment(environment).path } - return Font(path, LocalContext.current.assets, weight, style) + val assets = LocalContext.current.assets + return Font(path, assets, weight, style) } \ No newline at end of file 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 index 972cb39205..be92557588 100644 --- 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 @@ -1,9 +1,21 @@ package org.jetbrains.compose.resources -import java.io.File +import android.content.res.AssetManager +import android.net.Uri +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ProvidableCompositionLocal +import java.io.FileNotFoundException import java.io.InputStream internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader { + private val assets: AssetManager by lazy { + val context = androidContext ?: error( + "Android context is not initialized. " + + "If it happens in the Preview mode then call PreviewContextConfigurationEffect() function." + ) + context.assets + } + override suspend fun read(path: String): ByteArray { val resource = getResourceAsStream(path) return resource.readBytes() @@ -33,39 +45,52 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou private fun InputStream.readBytes(byteArray: ByteArray, offset: Int, size: Int) { var readBytes = 0 while (readBytes < size) { - val count = read(byteArray, offset + readBytes, size - readBytes) + val count = read(byteArray, offset + readBytes, size - readBytes) if (count <= 0) break readBytes += count } } override fun getUri(path: String): String { - val classLoader = getClassLoader() - val resource = classLoader.getResource(path) ?: run { - //try to find a font in the android assets - if (File(path).isFontResource()) { - classLoader.getResource("assets/$path") - } else null - } ?: throw MissingResourceException(path) - return resource.toURI().toString() + val uri = if (assets.hasFile(path)) { + Uri.parse("file:///android_asset/$path") + } else { + val classLoader = getClassLoader() + val resource = classLoader.getResource(path) ?: throw MissingResourceException(path) + resource.toURI() + } + return uri.toString() } private fun getResourceAsStream(path: String): InputStream { - val classLoader = getClassLoader() - val resource = classLoader.getResourceAsStream(path) ?: run { - //try to find a font in the android assets - if (File(path).isFontResource()) { - classLoader.getResourceAsStream("assets/$path") - } else null - } ?: throw MissingResourceException(path) - return resource - } - - private fun File.isFontResource(): Boolean { - return this.parentFile?.name.orEmpty().startsWith("font") + return try { + assets.open(path) + } catch (e: FileNotFoundException) { + val classLoader = getClassLoader() + classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path) + } } private fun getClassLoader(): ClassLoader { return this.javaClass.classLoader ?: error("Cannot find class loader") } -} \ No newline at end of file + + private fun AssetManager.hasFile(path: String): Boolean { + var inputStream: InputStream? = null + val result = try { + inputStream = open(path) + true + } catch (e: FileNotFoundException) { + false + } finally { + inputStream?.close() + } + return result + } +} + +internal actual val ProvidableCompositionLocal.currentOrPreview: ResourceReader + @Composable get() { + PreviewContextConfigurationEffect() + return current + } diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt index 96f653d87e..30892cf43f 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt @@ -55,7 +55,7 @@ private val emptyImageBitmap: ImageBitmap by lazy { ImageBitmap(1, 1) } */ @Composable fun imageResource(resource: DrawableResource): ImageBitmap { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview val imageBitmap by rememberResourceState(resource, resourceReader, { emptyImageBitmap }) { env -> val path = resource.getResourceItemByEnvironment(env).path val cached = loadImage(path, resourceReader) { @@ -78,7 +78,7 @@ private val emptyImageVector: ImageVector by lazy { */ @Composable fun vectorResource(resource: DrawableResource): ImageVector { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview val density = LocalDensity.current val imageVector by rememberResourceState(resource, resourceReader, density, { emptyImageVector }) { env -> val path = resource.getResourceItemByEnvironment(env).path @@ -98,7 +98,7 @@ private val emptySvgPainter: Painter by lazy { BitmapPainter(emptyImageBitmap) } @Composable private fun svgPainter(resource: DrawableResource): Painter { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview val density = LocalDensity.current val svgPainter by rememberResourceState(resource, resourceReader, density, { emptySvgPainter }) { env -> val path = resource.getResourceItemByEnvironment(env).path diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/PluralStringResources.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/PluralStringResources.kt index 27be9337f5..fa12b8c11b 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/PluralStringResources.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/PluralStringResources.kt @@ -26,7 +26,7 @@ class PluralStringResource */ @Composable fun pluralStringResource(resource: PluralStringResource, quantity: Int): String { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview val pluralStr by rememberResourceState(resource, quantity, { "" }) { env -> loadPluralString(resource, quantity, resourceReader, env) } @@ -93,7 +93,7 @@ private suspend fun loadPluralString( */ @Composable fun pluralStringResource(resource: PluralStringResource, quantity: Int, vararg formatArgs: Any): String { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview val args = formatArgs.map { it.toString() } val pluralStr by rememberResourceState(resource, quantity, args, { "" }) { env -> loadPluralString(resource, quantity, args, resourceReader, env) diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt index 7c2979e586..9c7bf1b4d5 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt @@ -1,5 +1,7 @@ package org.jetbrains.compose.resources +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.staticCompositionLocalOf class MissingResourceException(path: String) : Exception("Missing resource with path: $path") @@ -34,3 +36,7 @@ internal val DefaultResourceReader = getPlatformResourceReader() //ResourceReader provider will be overridden for tests internal val LocalResourceReader = staticCompositionLocalOf { DefaultResourceReader } + +//For an android preview we need to initialize the resource reader with the local context +internal expect val ProvidableCompositionLocal.currentOrPreview: ResourceReader + @Composable get diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringArrayResources.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringArrayResources.kt index e89aa5c600..2077db5531 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringArrayResources.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringArrayResources.kt @@ -30,7 +30,7 @@ class StringArrayResource */ @Composable fun stringArrayResource(resource: StringArrayResource): List { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview val array by rememberResourceState(resource, { emptyList() }) { env -> loadStringArray(resource, resourceReader, env) } 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 3cce39b722..6f44a23b89 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 @@ -23,7 +23,7 @@ class StringResource */ @Composable fun stringResource(resource: StringResource): String { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview val str by rememberResourceState(resource, { "" }) { env -> loadString(resource, resourceReader, env) } @@ -75,7 +75,7 @@ private suspend fun loadString( */ @Composable fun stringResource(resource: StringResource, vararg formatArgs: Any): String { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview val args = formatArgs.map { it.toString() } val str by rememberResourceState(resource, args, { "" }) { env -> loadString(resource, args, resourceReader, env) 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 cdd1287ea6..bc7c7ddb85 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 @@ -331,7 +331,7 @@ class ComposeResourceTest { var uri2 = "" setContent { CompositionLocalProvider(LocalComposeEnvironment provides TestComposeEnvironment) { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview uri1 = resourceReader.getUri("1.png") uri2 = resourceReader.getUri("2.png") } diff --git a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt index 5390361c63..c5e9e1682e 100644 --- a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt +++ b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt @@ -31,7 +31,7 @@ private val defaultEmptyFont by lazy { Font("org.jetbrains.compose.emptyFont", B @Composable actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font { - val resourceReader = LocalResourceReader.current + val resourceReader = LocalResourceReader.currentOrPreview val fontFile by rememberResourceState(resource, weight, style, { defaultEmptyFont }) { env -> val path = resource.getResourceItemByEnvironment(env).path val fontBytes = resourceReader.read(path) diff --git a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ResourceReader.skiko.kt b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ResourceReader.skiko.kt new file mode 100644 index 0000000000..17d29d49ef --- /dev/null +++ b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ResourceReader.skiko.kt @@ -0,0 +1,7 @@ +package org.jetbrains.compose.resources + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ProvidableCompositionLocal + +internal actual val ProvidableCompositionLocal.currentOrPreview: ResourceReader + @Composable get() = current \ No newline at end of file diff --git a/components/settings.gradle.kts b/components/settings.gradle.kts index 060807a189..d7283e752d 100644 --- a/components/settings.gradle.kts +++ b/components/settings.gradle.kts @@ -12,10 +12,12 @@ pluginManagement { plugins { kotlin("jvm").version(extra["kotlin.version"] as String) kotlin("multiplatform").version(extra["kotlin.version"] as String) - id("org.jetbrains.compose").version(extra["compose.version"] as String) + id("org.jetbrains.compose") //version is not required because the plugin is included to the build id("com.android.library").version(extra["agp.version"] as String) id("org.jetbrains.kotlinx.binary-compatibility-validator").version("0.15.0-Beta.2") } + + includeBuild("../gradle-plugins") } dependencyResolutionManagement { diff --git a/components/ui-tooling-preview/library/build.gradle.kts b/components/ui-tooling-preview/library/build.gradle.kts index 964b992ea0..1007eadc3b 100644 --- a/components/ui-tooling-preview/library/build.gradle.kts +++ b/components/ui-tooling-preview/library/build.gradle.kts @@ -7,8 +7,6 @@ plugins { id("com.android.library") } -val composeVersion = extra["compose.version"] as String - kotlin { jvm("desktop") androidTarget { diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt index f1d9f2855b..8e98561fb9 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt @@ -1,7 +1,6 @@ package org.jetbrains.compose.resources import com.android.build.api.variant.AndroidComponentsExtension -import com.android.build.gradle.BaseExtension import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask import com.android.build.gradle.internal.lint.LintModelWriterTask import org.gradle.api.DefaultTask @@ -10,49 +9,23 @@ import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileCollection import org.gradle.api.file.FileSystemOperations import org.gradle.api.provider.Property -import org.gradle.api.tasks.* +import org.gradle.api.tasks.IgnoreEmptyDirectories +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction import org.jetbrains.compose.internal.utils.registerTask import org.jetbrains.compose.internal.utils.uppercaseFirstChar -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation -import org.jetbrains.kotlin.gradle.plugin.sources.android.androidSourceSetInfoOrNull import org.jetbrains.kotlin.gradle.utils.ObservableSet import javax.inject.Inject -@OptIn(ExperimentalKotlinGradlePluginApi::class) internal fun Project.configureAndroidComposeResources( - kotlinExtension: KotlinMultiplatformExtension, - androidExtension: BaseExtension + kotlinExtension: KotlinMultiplatformExtension ) { - // 1) get the Kotlin Android Target Compilation -> [A] - // 2) get default source set name for the 'A' - // 3) find the associated Android SourceSet in the AndroidExtension -> [B] - // 4) get all source sets in the 'A' and add its resources to the 'B' - kotlinExtension.targets.withType(KotlinAndroidTarget::class.java).all { androidTarget -> - androidTarget.compilations.all { compilation: KotlinJvmAndroidCompilation -> - compilation.defaultSourceSet.androidSourceSetInfoOrNull?.let { kotlinAndroidSourceSet -> - androidExtension.sourceSets - .matching { it.name == kotlinAndroidSourceSet.androidSourceSetName } - .all { androidSourceSet -> - (compilation.allKotlinSourceSets as? ObservableSet)?.forAll { kotlinSourceSet -> - val preparedComposeResources = getPreparedComposeResourcesDir(kotlinSourceSet) - androidSourceSet.resources.srcDirs(preparedComposeResources) - - //fix for AGP < 8.0 - //usually 'androidSourceSet.resources.srcDir(preparedCommonResources)' should be enough - compilation.androidVariant.processJavaResourcesProvider.configure { - it.dependsOn(preparedComposeResources) - } - } - } - } - } - } - - //copy fonts from the compose resources dir to android assets + //copy all compose resources to android assets val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return androidComponents.onVariants { variant -> val variantResources = project.files() @@ -60,7 +33,7 @@ internal fun Project.configureAndroidComposeResources( kotlinExtension.targets.withType(KotlinAndroidTarget::class.java).all { androidTarget -> androidTarget.compilations.all { compilation: KotlinJvmAndroidCompilation -> if (compilation.androidVariant.name == variant.name) { - project.logger.info("Configure fonts for variant ${variant.name}") + project.logger.info("Configure resources for variant ${variant.name}") (compilation.allKotlinSourceSets as? ObservableSet)?.forAll { kotlinSourceSet -> val preparedComposeResources = getPreparedComposeResourcesDir(kotlinSourceSet) variantResources.from(preparedComposeResources) @@ -69,22 +42,32 @@ internal fun Project.configureAndroidComposeResources( } } - val copyFonts = registerTask( - "copy${variant.name.uppercaseFirstChar()}FontsToAndroidAssets" + val copyResources = registerTask( + "copy${variant.name.uppercaseFirstChar()}ResourcesToAndroidAssets" ) { from.set(variantResources) } - variant.sources?.assets?.addGeneratedSourceDirectory( - taskProvider = copyFonts, - wiredWith = CopyAndroidFontsToAssetsTask::outputDirectory - ) - //exclude a duplication of fonts in apks - variant.packaging.resources.excludes.add("**/font*/*") + variant.sources.assets?.apply { + addGeneratedSourceDirectory( + taskProvider = copyResources, + wiredWith = CopyResourcesToAndroidAssetsTask::outputDirectory + ) + + // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work + addStaticSourceDirectory(copyResources.flatMap { it.outputDirectory.asFile }.get().path) + + // addGeneratedSourceDirectory doesn't run the copyResources task during AS Compose Preview build + tasks.configureEach { task -> + if (task.name == "compile${variant.name.uppercaseFirstChar()}Sources") { + task.dependsOn(copyResources) + } + } + } } } //Copy task doesn't work with 'variant.sources?.assets?.addGeneratedSourceDirectory' API -internal abstract class CopyAndroidFontsToAssetsTask : DefaultTask() { +internal abstract class CopyResourcesToAndroidAssetsTask : DefaultTask() { @get:Inject abstract val fileSystem: FileSystemOperations @@ -100,7 +83,6 @@ internal abstract class CopyAndroidFontsToAssetsTask : DefaultTask() { fileSystem.copy { it.includeEmptyDirs = false it.from(from) - it.include("**/font*/*") it.into(outputDirectory) } } @@ -121,5 +103,31 @@ internal fun Project.fixAndroidLintTaskDependencies() { it is AndroidLintAnalysisTask || it is LintModelWriterTask }.configureEach { it.mustRunAfter(tasks.withType(GenerateResourceAccessorsTask::class.java)) + it.mustRunAfter(tasks.withType(CopyResourcesToAndroidAssetsTask::class.java)) + } +} + +internal fun Project.configureAndroidAssetsForPreview() { + val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return + androidComponents.onVariants { variant -> + variant.sources.assets?.apply { + val kgpCopyAssetsTaskName = "${variant.name}AssetsCopyForAGP" + + // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work + tasks.all { task -> + if (task.name == kgpCopyAssetsTaskName) { + task.outputs.files.forEach { file -> + addStaticSourceDirectory(file.path) + } + } + } + + // addGeneratedSourceDirectory doesn't run the copyResources task during AS Compose Preview build + tasks.configureEach { task -> + if (task.name == "compile${variant.name.uppercaseFirstChar()}Sources") { + task.dependsOn(kgpCopyAssetsTaskName) + } + } + } } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt index 4b67866036..7b7ba44529 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt @@ -1,24 +1,17 @@ package org.jetbrains.compose.resources -import com.android.build.gradle.BaseExtension -import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask -import com.android.build.gradle.internal.lint.LintModelWriterTask import org.gradle.api.Project import org.gradle.api.provider.Provider import org.gradle.api.tasks.SourceSet -import org.gradle.api.tasks.TaskProvider import org.gradle.util.GradleVersion -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.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin -import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.extraProperties -import java.io.File internal const val COMPOSE_RESOURCES_DIR = "composeResources" internal const val RES_GEN_DIR = "generated/compose/resourceGenerator" @@ -46,7 +39,10 @@ private fun Project.onKgpApplied(config: Provider, kgp: Kotl if (kmpResourcesAreAvailable) { configureKmpResources(kotlinExtension, extraProperties.get(KMP_RES_EXT)!!, config) - onAgpApplied { fixAndroidLintTaskDependencies() } + onAgpApplied { + configureAndroidAssetsForPreview() + fixAndroidLintTaskDependencies() + } } else { if (!disableMultimoduleResources) { if (!hasKmpResources) logger.info( @@ -66,8 +62,8 @@ private fun Project.onKgpApplied(config: Provider, kgp: Kotl val commonMain = KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME configureComposeResources(kotlinExtension, commonMain, config) - onAgpApplied { androidExtension -> - configureAndroidComposeResources(kotlinExtension, androidExtension) + onAgpApplied { + configureAndroidComposeResources(kotlinExtension) fixAndroidLintTaskDependencies() } } @@ -75,11 +71,10 @@ private fun Project.onKgpApplied(config: Provider, kgp: Kotl configureSyncIosComposeResources(kotlinExtension) } -private fun Project.onAgpApplied(block: (androidExtension: BaseExtension) -> Unit) { +private fun Project.onAgpApplied(block: () -> Unit) { androidPluginIds.forEach { pluginId -> plugins.withId(pluginId) { - val androidExtension = project.extensions.getByType(BaseExtension::class.java) - block(androidExtension) + block() } } } @@ -90,8 +85,6 @@ private fun Project.onKotlinJvmApplied(config: Provider) { configureComposeResources(kotlinExtension, main, config) } -// sourceSet.resources.srcDirs doesn't work for Android targets. -// Android resources should be configured separately private fun Project.configureComposeResources( kotlinExtension: KotlinProjectExtension, resClassSourceSetName: String, @@ -100,7 +93,16 @@ private fun Project.configureComposeResources( logger.info("Configure compose resources") configureComposeResourcesGeneration(kotlinExtension, resClassSourceSetName, config, false) + // mark prepared resources as sourceSet.resources + // 1) it automatically packs the resources to JVM jars + // 2) it configures the webpack to use the resources + // 3) for native targets we will use source set resources to pack them into the final app. see IosResources.kt + // 4) for the android it DOESN'T pack resources! we copy resources to assets in AndroidResources.kt kotlinExtension.sourceSets.all { sourceSet -> - sourceSet.resources.srcDirs(getPreparedComposeResourcesDir(sourceSet)) + // the HACK is here because KGP copy androidMain java resources to Android target + // if the resources were registered in the androidMain source set before the target declaration + afterEvaluate { + sourceSet.resources.srcDirs(getPreparedComposeResourcesDir(sourceSet)) + } } } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/KmpResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/KmpResources.kt index 3a945db560..5ace0c22a9 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/KmpResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/KmpResources.kt @@ -32,28 +32,26 @@ internal fun Project.configureKmpResources( logger.info("Configure resources publication for '${target.targetName}' target") val packedResourceDir = config.getModuleResourcesDir(project) - kmpResources.publishResourcesAsKotlinComponent( - target, - { sourceSet -> - KotlinTargetResourcesPublication.ResourceRoot( - getPreparedComposeResourcesDir(sourceSet), - emptyList(), - //for android target exclude fonts - if (target is KotlinAndroidTarget) listOf("**/font*/*") else emptyList() - ) - }, - packedResourceDir - ) - - if (target is KotlinAndroidTarget) { - //for android target publish fonts in assets - logger.info("Configure fonts relocation for '${target.targetName}' target") + if (target !is KotlinAndroidTarget) { + kmpResources.publishResourcesAsKotlinComponent( + target, + { sourceSet -> + KotlinTargetResourcesPublication.ResourceRoot( + getPreparedComposeResourcesDir(sourceSet), + emptyList(), + emptyList() + ) + }, + packedResourceDir + ) + } else { + //for android target publish resources in assets kmpResources.publishInAndroidAssets( target, { sourceSet -> KotlinTargetResourcesPublication.ResourceRoot( getPreparedComposeResourcesDir(sourceSet), - listOf("**/font*/*"), + emptyList(), emptyList() ) }, 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 9b5c8b22ae..53a9e5b822 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 @@ -251,32 +251,13 @@ class ResourcesTest : GradlePluginTestBase() { "my-mvn/me/sample/library/cmplib-$target/1.0/cmplib-$target-1.0$ext" val aar = file(libpath("android", ".aar")) - val innerClassesJar = aar.parentFile.resolve("aar-inner-classes.jar") assertTrue(aar.exists(), "File not found: " + aar.path) - ZipFile(aar).use { zip -> - resourcesFiles - .filter { it.startsWith("font") } - .forEach { fontRes -> + ZipFile(aar).use { zip -> resourcesFiles.forEach { fontRes -> assertNotNull( zip.getEntry("assets/composeResources/$subdir/$fontRes"), "Resource not found: '$fontRes' in aar '${aar.path}'" ) - } - - innerClassesJar.writeBytes( - zip.getInputStream(zip.getEntry("classes.jar")).readBytes() - ) - } - ZipFile(innerClassesJar).use { zip -> - resourcesFiles - .filterNot { it.startsWith("font") } - .forEach { res -> - assertNotNull( - zip.getEntry("composeResources/$subdir/$res"), - "Resource not found: '$res' in aar/classes.jar '${aar.path}'" - ) - } - } + } } val jar = file(libpath("jvm", ".jar")) checkResourcesZip(jar, resourcesFiles, subdir) @@ -393,37 +374,37 @@ class ResourcesTest : GradlePluginTestBase() { .getConvertedResources(commonResourcesDir) gradle("build").checks { - check.taskSuccessful(":copyDemoDebugFontsToAndroidAssets") - check.taskSuccessful(":copyDemoReleaseFontsToAndroidAssets") - check.taskSuccessful(":copyFullDebugFontsToAndroidAssets") - check.taskSuccessful(":copyFullReleaseFontsToAndroidAssets") + check.taskSuccessful(":copyDemoDebugResourcesToAndroidAssets") + check.taskSuccessful(":copyDemoReleaseResourcesToAndroidAssets") + check.taskSuccessful(":copyFullDebugResourcesToAndroidAssets") + check.taskSuccessful(":copyFullReleaseResourcesToAndroidAssets") getAndroidApk("demo", "debug", "Resources-Test").let { apk -> checkResourcesInZip(apk, commonResourcesFiles, true) assertEquals( "android demo-debug", - readFileInZip(apk, "files/platform.txt").decodeToString() + readFileInZip(apk, "assets/files/platform.txt").decodeToString() ) } getAndroidApk("demo", "release", "Resources-Test").let { apk -> checkResourcesInZip(apk, commonResourcesFiles, true) assertEquals( "android demo-release", - readFileInZip(apk, "files/platform.txt").decodeToString() + readFileInZip(apk, "assets/files/platform.txt").decodeToString() ) } getAndroidApk("full", "debug", "Resources-Test").let { apk -> checkResourcesInZip(apk, commonResourcesFiles, true) assertEquals( "android full-debug", - readFileInZip(apk, "files/platform.txt").decodeToString() + readFileInZip(apk, "assets/files/platform.txt").decodeToString() ) } getAndroidApk("full", "release", "Resources-Test").let { apk -> checkResourcesInZip(apk, commonResourcesFiles, true) assertEquals( "android full-release", - readFileInZip(apk, "files/platform.txt").decodeToString() + readFileInZip(apk, "assets/files/platform.txt").decodeToString() ) } @@ -443,36 +424,6 @@ class ResourcesTest : GradlePluginTestBase() { } } - @Test - fun testAndroidFonts(): Unit = with(testProject("misc/commonResources")) { - val commonResourcesDir = file("src/commonMain/composeResources") - val commonResourcesFiles = commonResourcesDir.walkTopDown() - .filter { !it.isDirectory && !it.isHidden } - .getConvertedResources(commonResourcesDir) - - gradle("assembleDebug").checks { - check.taskSuccessful(":copyDebugFontsToAndroidAssets") - - getAndroidApk("", "debug", "Resources-Test").let { apk -> - checkResourcesInZip(apk, commonResourcesFiles, true) - } - } - - file("src/commonMain/composeResources/font-en").renameTo( - file("src/commonMain/composeResources/font-mdpi") - ) - val newCommonResourcesFiles = commonResourcesDir.walkTopDown() - .filter { !it.isDirectory && !it.isHidden } - .getConvertedResources(commonResourcesDir) - gradle("assembleDebug").checks { - check.taskSuccessful(":copyDebugFontsToAndroidAssets") - - getAndroidApk("", "debug", "Resources-Test").let { apk -> - checkResourcesInZip(apk, newCommonResourcesFiles, true) - } - } - } - private fun Sequence.getConvertedResources(baseDir: File) = map { file -> val newFile = if ( file.parentFile.name.startsWith("value") && @@ -486,7 +437,6 @@ class ResourcesTest : GradlePluginTestBase() { newFile.relativeTo(baseDir).invariantSeparatorsPath } - private fun File.writeNewFile(text: String) { parentFile.mkdirs() createNewFile() @@ -507,8 +457,8 @@ class ResourcesTest : GradlePluginTestBase() { ZipFile(file).use { zip -> commonResourcesFiles.forEach { res -> println("check '$res' file") - if (isAndroid && res.startsWith("font")) { - //android fonts should be only in assets + if (isAndroid) { + //android resources should be only in assets assertNull(zip.getEntry(res), "file = '$res'") assertNotNull(zip.getEntry("assets/$res"), "file = 'assets/$res'") } else { From be4dfa4543118ffaff64c86198e6da72ee586046 Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Thu, 20 Jun 2024 15:37:53 +0200 Subject: [PATCH 22/47] [resources] Properly close input stream when read resource bytes. --- .../org/jetbrains/compose/resources/ResourceReader.android.kt | 2 +- .../org/jetbrains/compose/resources/ResourceReader.desktop.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index be92557588..d8e5741966 100644 --- 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 @@ -18,7 +18,7 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou override suspend fun read(path: String): ByteArray { val resource = getResourceAsStream(path) - return resource.readBytes() + return resource.use { input -> input.readBytes() } } override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray { diff --git a/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt b/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt index da70a3899c..f90b9d516c 100644 --- a/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt +++ b/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt @@ -5,7 +5,7 @@ import java.io.InputStream internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader { override suspend fun read(path: String): ByteArray { val resource = getResourceAsStream(path) - return resource.readBytes() + return resource.use { input -> input.readBytes() } } override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray { From bb5623e479c5f54bb2b7ff2737a3783f2fe04a16 Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Thu, 20 Jun 2024 16:31:43 +0200 Subject: [PATCH 23/47] [gradle] Fix android assets configuration with AGP < 8.1.0. --- .../org/jetbrains/compose/resources/AndroidResources.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt index 8e98561fb9..0338f695a4 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt @@ -54,7 +54,10 @@ internal fun Project.configureAndroidComposeResources( ) // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work - addStaticSourceDirectory(copyResources.flatMap { it.outputDirectory.asFile }.get().path) + val outDir = copyResources.flatMap { it.outputDirectory.asFile }.get() + if (outDir.isDirectory) { + addStaticSourceDirectory(outDir.path) + } // addGeneratedSourceDirectory doesn't run the copyResources task during AS Compose Preview build tasks.configureEach { task -> @@ -117,7 +120,9 @@ internal fun Project.configureAndroidAssetsForPreview() { tasks.all { task -> if (task.name == kgpCopyAssetsTaskName) { task.outputs.files.forEach { file -> - addStaticSourceDirectory(file.path) + if (file.isDirectory) { + addStaticSourceDirectory(file.path) + } } } } From 3a922a8502187b30256478087730fb83a7fe704d Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Thu, 20 Jun 2024 17:21:57 +0200 Subject: [PATCH 24/47] [gradle] Fix android assets configuration with AGP >= 8.1.0. --- .../compose/resources/AndroidResources.kt | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt index 0338f695a4..8ca1fed8f9 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt @@ -1,18 +1,14 @@ package org.jetbrains.compose.resources +import com.android.build.api.AndroidPluginVersion import com.android.build.api.variant.AndroidComponentsExtension import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask import com.android.build.gradle.internal.lint.LintModelWriterTask import org.gradle.api.DefaultTask import org.gradle.api.Project -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.FileCollection -import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.* import org.gradle.api.provider.Property -import org.gradle.api.tasks.IgnoreEmptyDirectories -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.* import org.jetbrains.compose.internal.utils.registerTask import org.jetbrains.compose.internal.utils.uppercaseFirstChar import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension @@ -22,6 +18,8 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation import org.jetbrains.kotlin.gradle.utils.ObservableSet import javax.inject.Inject +private val agp_8_1_0 = AndroidPluginVersion(8, 1, 0) + internal fun Project.configureAndroidComposeResources( kotlinExtension: KotlinMultiplatformExtension ) { @@ -53,10 +51,10 @@ internal fun Project.configureAndroidComposeResources( wiredWith = CopyResourcesToAndroidAssetsTask::outputDirectory ) - // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work - val outDir = copyResources.flatMap { it.outputDirectory.asFile }.get() - if (outDir.isDirectory) { - addStaticSourceDirectory(outDir.path) + // https://issuetracker.google.com/348208777 + if (androidComponents.pluginVersion >= agp_8_1_0) { + // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work + addStaticSourceDirectory(copyResources.flatMap { it.outputDirectory.asFile }.get().path) } // addGeneratedSourceDirectory doesn't run the copyResources task during AS Compose Preview build @@ -110,17 +108,18 @@ internal fun Project.fixAndroidLintTaskDependencies() { } } +// https://issuetracker.google.com/348208777 internal fun Project.configureAndroidAssetsForPreview() { val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return androidComponents.onVariants { variant -> variant.sources.assets?.apply { val kgpCopyAssetsTaskName = "${variant.name}AssetsCopyForAGP" - // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work - tasks.all { task -> - if (task.name == kgpCopyAssetsTaskName) { - task.outputs.files.forEach { file -> - if (file.isDirectory) { + if (androidComponents.pluginVersion >= agp_8_1_0) { + // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work + tasks.all { task -> + if (task.name == kgpCopyAssetsTaskName) { + task.outputs.files.forEach { file -> addStaticSourceDirectory(file.path) } } From 4c6fe13653a593d0e4f8476584ee988060415f0a Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Fri, 21 Jun 2024 09:10:54 +0200 Subject: [PATCH 25/47] [components] Enable include gradle plugin build only if it exists. --- components/gradle.properties | 1 + components/settings.gradle.kts | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/gradle.properties b/components/gradle.properties index a16ea9956e..478504a76f 100644 --- a/components/gradle.properties +++ b/components/gradle.properties @@ -9,6 +9,7 @@ android.useAndroidX=true #Versions kotlin.version=1.9.23 agp.version=8.2.2 +compose.version=1.6.10 deploy.version=0.1.0-SNAPSHOT #Compose diff --git a/components/settings.gradle.kts b/components/settings.gradle.kts index d7283e752d..12272f6b5b 100644 --- a/components/settings.gradle.kts +++ b/components/settings.gradle.kts @@ -12,12 +12,15 @@ pluginManagement { plugins { kotlin("jvm").version(extra["kotlin.version"] as String) kotlin("multiplatform").version(extra["kotlin.version"] as String) - id("org.jetbrains.compose") //version is not required because the plugin is included to the build + id("org.jetbrains.compose").version(extra["compose.version"] as String) id("com.android.library").version(extra["agp.version"] as String) id("org.jetbrains.kotlinx.binary-compatibility-validator").version("0.15.0-Beta.2") } - includeBuild("../gradle-plugins") + val gradlePluginDir = rootDir.resolve("../gradle-plugins") + if (gradlePluginDir.exists()) { + includeBuild(gradlePluginDir) + } } dependencyResolutionManagement { From b501e0f794aecde9a6ce47cb4b5308939cbc7cc5 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Fri, 21 Jun 2024 13:25:58 +0200 Subject: [PATCH 26/47] Delete experimental/examples/intellij-plugin-with-experimental-shared (#5006) Fixes https://youtrack.jetbrains.com/issue/CMP-91 https://plugins.jetbrains.com/plugin/18439-compose-for-ide-plugin-development-experimental- is deprecated --- .../.gitignore | 3 - .../.run/runIde.run.xml | 23 --- .../README.md | 20 -- .../build.gradle.kts | 38 ---- .../gradle.properties | 5 - .../gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 - .../gradlew | 185 ------------------ .../gradlew.bat | 89 --------- .../screenshots/ide-run-configuration.png | Bin 2342 -> 0 bytes .../settings.gradle.kts | 8 - .../jetbrains/compose/ComposeDemoAction.kt | 70 ------- .../com/jetbrains/compose/IntellijTheme.kt | 28 --- .../compose/color/ColorLineMarkerProvider.kt | 108 ---------- .../jetbrains/compose/color/ColorPicker.kt | 128 ------------ .../kotlin/com/jetbrains/compose/color/HSV.kt | 75 ------- .../compose/panel/ComposeToolWindow.kt | 44 ----- .../jetbrains/compose/panel/CounterPanel.kt | 36 ---- .../jetbrains/compose/panel/CounterState.kt | 10 - .../com/jetbrains/compose/theme/Color.kt | 9 - .../com/jetbrains/compose/theme/Shape.kt | 11 -- .../com/jetbrains/compose/theme/Theme.kt | 46 ----- .../com/jetbrains/compose/theme/Type.kt | 43 ---- .../compose/theme/intellij/SwingColor.kt | 61 ------ .../theme/intellij/ThemeChangeListener.kt | 13 -- .../com/jetbrains/compose/widgets/Buttons.kt | 57 ------ .../compose/widgets/LazyScrollable.kt | 71 ------- .../com/jetbrains/compose/widgets/Loaders.kt | 39 ---- .../jetbrains/compose/widgets/TextInputs.kt | 53 ----- .../com/jetbrains/compose/widgets/Toggles.kt | 90 --------- .../src/main/resources/META-INF/plugin.xml | 29 --- .../src/main/resources/icons/compose.svg | 1 - .../compose/color/ColorPickerUITest.kt | 25 --- .../com/jetbrains/compose/color/HSVTest.kt | 22 --- 34 files changed, 1445 deletions(-) delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/.gitignore delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/.run/runIde.run.xml delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/README.md delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/build.gradle.kts delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/gradle.properties delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/gradle/wrapper/gradle-wrapper.jar delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/gradle/wrapper/gradle-wrapper.properties delete mode 100755 experimental/examples/intellij-plugin-with-experimental-shared-base/gradlew delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/gradlew.bat delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/screenshots/ide-run-configuration.png delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/settings.gradle.kts delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/ComposeDemoAction.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/IntellijTheme.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorLineMarkerProvider.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorPicker.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/HSV.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/ComposeToolWindow.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/CounterPanel.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/CounterState.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Color.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Shape.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Theme.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Type.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/intellij/SwingColor.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/intellij/ThemeChangeListener.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Buttons.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/LazyScrollable.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Loaders.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/TextInputs.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Toggles.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/resources/META-INF/plugin.xml delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/resources/icons/compose.svg delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/ColorPickerUITest.kt delete mode 100644 experimental/examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/HSVTest.kt diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/.gitignore b/experimental/examples/intellij-plugin-with-experimental-shared-base/.gitignore deleted file mode 100644 index 65ca50d416..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -.gradle/ -.idea/ \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/.run/runIde.run.xml b/experimental/examples/intellij-plugin-with-experimental-shared-base/.run/runIde.run.xml deleted file mode 100644 index 52aca017c2..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/.run/runIde.run.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - true - true - false - - - \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/README.md b/experimental/examples/intellij-plugin-with-experimental-shared-base/README.md deleted file mode 100644 index 8af73b48ec..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/README.md +++ /dev/null @@ -1,20 +0,0 @@ -## Example Compose Multiplatform based plugin for IntelliJ Idea. - -A plugin, demonstrating an Intellij plugin, showing a dialog window written with Compose. - -The only difference from [examples/intellij-plugin](../intellij-plugin) is that -this version does not bundle Compose runtime, which makes the plugin smaller -and allows sharing Compose runtime between multiple plugins -(Compose class files and native libraries are not loaded by each plugin). - -### Usage - -1. Start test IDE: - * Run the following command in terminal: `./gradlew runIde` - * Or choose **runIde** configuration in IDE and run it. - ![ide-run-configuration.png](screenshots/ide-run-configuration.png) -2. Create a new project or open any existing; -3. Select `Show Compose Demo...` from the `Tools` menu. - -![screen1](../intellij-plugin/screenshots/toolsshow.png) -![screen2](../intellij-plugin/screenshots/screenshot.png) diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/build.gradle.kts b/experimental/examples/intellij-plugin-with-experimental-shared-base/build.gradle.kts deleted file mode 100644 index 5cc4674c24..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/build.gradle.kts +++ /dev/null @@ -1,38 +0,0 @@ -import org.jetbrains.compose.compose - -plugins { - id("org.jetbrains.intellij") version "1.6.0" - java - kotlin("jvm") - id("org.jetbrains.compose") - id("idea") -} - -group = "org.jetbrains.compose.intellij.platform" -version = "1.0-SNAPSHOT" - -repositories { - mavenCentral() - google() - maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") } -} - -dependencies { -// compileOnly(compose.desktop.currentOs) runtime dependency is provided by org.jetbrains.compose.intellij.platform - testImplementation(kotlin("test")) -} - -// See https://github.com/JetBrains/gradle-intellij-plugin/ -intellij { - version.set("2021.3") - plugins.set( - listOf( - "org.jetbrains.compose.intellij.platform:0.1.0", - "org.jetbrains.kotlin" - ) - ) -} - -tasks.withType { - kotlinOptions.jvmTarget = "11" -} diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/gradle.properties b/experimental/examples/intellij-plugin-with-experimental-shared-base/gradle.properties deleted file mode 100644 index 121b219d4e..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/gradle.properties +++ /dev/null @@ -1,5 +0,0 @@ -kotlin.stdlib.default.dependency=false -kotlin.code.style=official -kotlin.version=1.8.0 -compose.version=1.3.0 - diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/gradle/wrapper/gradle-wrapper.jar b/experimental/examples/intellij-plugin-with-experimental-shared-base/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 62d4c053550b91381bbd28b1afc82d634bf73a8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58910 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 diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/gradle/wrapper/gradle-wrapper.properties b/experimental/examples/intellij-plugin-with-experimental-shared-base/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index ae04661ee7..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/gradlew b/experimental/examples/intellij-plugin-with-experimental-shared-base/gradlew deleted file mode 100755 index 4f906e0c81..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/gradlew.bat b/experimental/examples/intellij-plugin-with-experimental-shared-base/gradlew.bat deleted file mode 100644 index 107acd32c4..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/screenshots/ide-run-configuration.png b/experimental/examples/intellij-plugin-with-experimental-shared-base/screenshots/ide-run-configuration.png deleted file mode 100644 index 9dc9453a387120584367a0ab111e29add30f3963..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2342 zcmV+>3EB3EP)Nkl~pIZyHrXU_Ycg_qy_pZ9ss zK}T9>G0LIK%gZB8nnqK)+S*#uq-ivzb2uENNz-UbXS3NzjqG+iOTJ%|I*sM=R0Sah zORv|{g9i_&prAlg09065NOg5}gT{I+$ZrS%U{g~Q<>uycKF2<}y=wAL(wF+~-Mc)d zW6cp&K?nz|si~1y+FA@Vnwy*16|Gk5pmG0ymMRGE-@i{5i$y+$S`0JXZa3Y#caO(b zKJG7*9CinF+B^BDw6C*opWWuai>rnZ-&QRG8y=5`^7HdmL)hsU2u3i3^H(+3(CM6W zRBNawr^89EPAA>0xJ^j~m&t53`#qg_DIk3M^eH)o3pibF>J{5#HPk`~FzO5q^ke=7 z(i@uv6fRYcq(m+O-EWWYP)O2tiu?I9sy8&!tw-6kIVFzrgjxuZ3aeXdDI|G2#a{h@GK4ExtQK}D zdEep6F68NS)A@p*=#}%^C^{{k-b&p;TQ9~^Nkh44a}7G?MH(>(dwP1PQ#hSXqzuU6 zbW^w3Cy%xcVdui$gAus*(9lpa7!2|zs;;i46)RTw{d)y6h67aXP(z!Z;dl6v zl1BtWq}i4>BQ+V?2dJgJgB{Q#TnI_HN4Su|XdY% z_bnwC{#S8Y4myN@1L{(SH6m64G_t5&VzYaKXnfLtz@_g;ny+|Q9CM`+)^eYAi7 z{sG#%cP}L*B#_Z)lpi)A0tq1mI9^Q*@3Y<#=@{1x6>aGA`K_<`IQhATQcxj;D;dpJ>abYZ@c^5}<`9En^+Jew zARMttZ0iMpW~-fss!bXj8)^Lb@eEX3TN^_a5fMQrPoCsh) z>%J%|Dk}KeQBhH}W5*5_w&UaD6|JiV8bV0`1I~5em%~(4SEAUV;>C*x6cv$?k#yq33Ge5bGiOp+Ss8@xxl zp_GnMM5I5wC5!+SvvP-$gkv7+AMr8_oDS#4Po!W}#Sl`kbX~ys(aqzEucgDfk9_Cz zq!hv~;Y>)ga^U2G0~!RZy*?aJ3P8AE!GZx09y)Z0mM>q<^TqV^bQ(8q9F>=sv-=!A ze3<(H!ez^rF+_lH(xge8j&VNB7q4Hx&gppT)~yUE(lIJ!0G*wk&A;=RGiMk^f8zG- z+kSzI2qc8BC53TeayBoKU2Hgo31k-YNvr|F`Cjcy$Ebv{K<*Y5^K~)jxixJzd>g5d$9n*o zTC`{pr}(vN*YbQX*bxW_r6rorZhc85jSu^lOO&NWU&5HQb{H70f9Cwasr~JX?{TI4 zov6Og{+URJG86c^0RQ5HYhqqltQdM7$bELR%B)i8Ag2(lU+|{g-jjuYce?Bl(PcI1oyU z)$d*VkTM@-@tCNogohH?DAQN4(;>wJHl$oQB8n6UV4?t$_M-?=%9JjvGM8`IrtmS3 zQV8*f#!^d9&tFdigwkWX{}f#0lo{y9b~u;8)JAun;M1& diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/settings.gradle.kts b/experimental/examples/intellij-plugin-with-experimental-shared-base/settings.gradle.kts deleted file mode 100644 index dd1ac3675a..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/settings.gradle.kts +++ /dev/null @@ -1,8 +0,0 @@ -rootProject.name = "ComposeDemoPlugin" - -pluginManagement { - plugins { - kotlin("jvm").version(extra["kotlin.version"] as String) - id("org.jetbrains.compose").version(extra["compose.version"] as String) - } -} diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/ComposeDemoAction.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/ComposeDemoAction.kt deleted file mode 100644 index 581c8cbe91..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/ComposeDemoAction.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.jetbrains.compose - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.Surface -import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.ComposePanel -import androidx.compose.ui.layout.Layout -import androidx.compose.ui.layout.MeasurePolicy -import androidx.compose.ui.layout.onGloballyPositioned -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.project.DumbAwareAction -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DialogWrapper -import com.jetbrains.compose.theme.WidgetTheme -import com.jetbrains.compose.widgets.Buttons -import com.jetbrains.compose.widgets.LazyScrollable -import com.jetbrains.compose.widgets.Loaders -import com.jetbrains.compose.widgets.TextInputs -import com.jetbrains.compose.widgets.Toggles -import java.awt.Dimension -import javax.swing.JComponent -import javax.swing.SwingUtilities - - -/** - * @author Konstantin Bulenkov - */ -class ComposeDemoAction : DumbAwareAction() { - override fun actionPerformed(e: AnActionEvent) { - DemoDialog(e.project).show() - } - - class DemoDialog(project: Project?) : DialogWrapper(project) { - init { - title = "Demo" - init() - } - - override fun createCenterPanel(): JComponent { - return ComposePanel().apply { - setBounds(0, 0, 800, 600) - setContent { - WidgetTheme(darkTheme = true) { - Surface(modifier = Modifier.fillMaxSize()) { - Row { - Column( - modifier = Modifier.fillMaxHeight().weight(1f) - ) { - Buttons() - Loaders() - TextInputs() - Toggles() - } - Box( - modifier = Modifier.fillMaxHeight().weight(1f) - ) { - LazyScrollable() - } - } - } - } - } - } - } - } -} diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/IntellijTheme.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/IntellijTheme.kt deleted file mode 100644 index 8969581b72..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/IntellijTheme.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package com.jetbrains.compose - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.intellij.openapi.project.Project -import com.jetbrains.compose.theme.WidgetTheme -import org.intellij.datavis.r.inlays.components.GraphicsManager - -@Composable -fun IntellijTheme(project: Project, content: @Composable () -> Unit) { - val isDarkMode = try { - GraphicsManager.getInstance(project)?.isDarkModeEnabled ?: false - } catch (t: Throwable) { - false - } - WidgetTheme(darkTheme = isDarkMode) { - Surface(modifier = Modifier.fillMaxSize()) { - content() - } - } -} diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorLineMarkerProvider.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorLineMarkerProvider.kt deleted file mode 100644 index 1ab8977c62..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorLineMarkerProvider.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package com.jetbrains.compose.color - -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.awt.ComposePanel -import androidx.compose.ui.graphics.Color -import com.intellij.codeInsight.daemon.LineMarkerInfo -import com.intellij.codeInsight.daemon.LineMarkerProvider -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.editor.markup.GutterIconRenderer -import com.intellij.openapi.ui.DialogWrapper -import com.intellij.psi.PsiElement -import com.jetbrains.compose.IntellijTheme -import org.jetbrains.kotlin.psi.KtPsiFactory -import org.jetbrains.uast.* -import java.awt.Component -import java.awt.Graphics -import javax.swing.Icon -import javax.swing.JComponent - -class ColorLineMarkerProvider : LineMarkerProvider { - - override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { - val project = element.project - val ktPsiFactory = KtPsiFactory(project) - val uElement: UElement = element.toUElement() ?: return null - if (uElement is UCallExpression) { - if (uElement.kind == UastCallKind.METHOD_CALL && uElement.methodIdentifier?.name == "Color") { - val colorLongValue = (uElement.valueArguments.firstOrNull() as? ULiteralExpression)?.getLongValue() - val previousColor = try { - Color(colorLongValue!!) - } catch (t: Throwable) { - Color(0xffffffff) - } - - val iconSize = 20 - return LineMarkerInfo( - element, - element.textRange, - object : Icon { - override fun paintIcon(c: Component?, g: Graphics?, x: Int, y: Int) { - g?.color = java.awt.Color( - previousColor.red, - previousColor.green, - previousColor.blue, - previousColor.alpha - ) - g?.fillRect(0, 0, iconSize, iconSize) - } - - override fun getIconWidth(): Int = iconSize - override fun getIconHeight(): Int = iconSize - }, - null, - { _, psiElement: PsiElement -> - - - class ChooseColorDialog() : DialogWrapper(project) { - val colorState = mutableStateOf(previousColor) - - init { - title = "Choose color" - init() - } - - override fun createCenterPanel(): JComponent = - ComposePanel().apply { - setBounds(0, 0, 400, 400) - setContent { - IntellijTheme(project) { - ColorPicker(colorState) - } - } - } - } - - val chooseColorDialog = ChooseColorDialog() - val result = chooseColorDialog.showAndGet() - if (result) { - val color = chooseColorDialog.colorState.value - ApplicationManager.getApplication().runWriteAction { - psiElement.replace( - ktPsiFactory.createExpression( - "Color(${color.toHexString()})" - ) - ) - } - } - }, - GutterIconRenderer.Alignment.RIGHT, - { "change color literal" } - ) - } - } - return null - } - - override fun collectSlowLineMarkers( - elements: MutableList, - result: MutableCollection> - ) { - - } -} diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorPicker.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorPicker.kt deleted file mode 100644 index 9ef49add6e..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorPicker.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package com.jetbrains.compose.color - -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material.Divider -import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.input.pointer.isPrimaryPressed -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.unit.dp - -private const val VALUE_BAND_RATIO = 0.07f -private val DEFAULT_COLORS = - listOf(Color.Red, Color.Green, Color.Blue, Color.Black, Color.Gray, Color.Yellow, Color.Cyan, Color.Magenta) - -@Composable -fun ColorPicker(colorState: MutableState) { - var currentColor: Color by remember { colorState } - Column { - Row { - DEFAULT_COLORS.forEach { - Box(Modifier.size(30.dp).background(color = it).clickable { - currentColor = it - }) - } - } - Divider(Modifier.size(2.dp)) - Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { - Text("Result color:") - Divider(Modifier.size(2.dp)) - TextField(modifier = Modifier.width(120f.dp), value = currentColor.toHexString(), onValueChange = {}) - Divider(Modifier.size(2.dp)) - val size = 60f - Box(Modifier.size(size.dp).background(color = currentColor)) - } - Divider(Modifier.size(2.dp)) - var width by remember { mutableStateOf(300) } - var height by remember { mutableStateOf(256) } - val rainbowWidth by derivedStateOf { (width * (1 - VALUE_BAND_RATIO)).toInt() } - val bandWidth by derivedStateOf { width * VALUE_BAND_RATIO } - fun calcHue(x: Float) = limit0to1(x / rainbowWidth) * HSV.HUE_MAX_VALUE - fun calcSaturation(y: Float) = 1 - limit0to1(y / height) - fun calcValue(y: Float) = 1 - limit0to1(y / height) - Row(Modifier.fillMaxSize()) { - Canvas(Modifier.fillMaxSize().pointerInput(Unit) { - width = size.width - height = size.height - awaitPointerEventScope { - while (true) { - val event = awaitPointerEvent() - if (event.buttons.isPrimaryPressed) { - val position = event.changes.first().position - if (position.x < rainbowWidth) { - currentColor = try { - currentColor.toHsv().copy( - hue = calcHue(position.x), - saturation = calcSaturation(position.y) - ).toRgb() - } catch (t: Throwable) { - t.printStackTrace() - println("exception $t") - currentColor - } - } else { - currentColor = - currentColor.toHsv().copy( - value = calcValue(position.y) - ).toRgb() - } - } - } - } - }) { - for (x in 0..rainbowWidth) { - for (y in 0..height) { - drawRect( - color = currentColor.toHsv().copy( - hue = calcHue(x.toFloat()), - saturation = calcSaturation(y.toFloat()) - ).toRgb(), - topLeft = Offset(x.toFloat(), y.toFloat()), - size = Size(1f, 1f) - ) - } - } - val valueBandX = rainbowWidth + 1 - for (y in 0..height) { - drawRect( - color = currentColor.toHsv().copy(value = calcValue(y.toFloat())).toRgb(), - topLeft = Offset(valueBandX.toFloat(), y.toFloat()), - size = Size(bandWidth, 1f) - ) - } - val circleX = (currentColor.toHsv().hue / 360) * rainbowWidth - val circleY = (1 - currentColor.toHsv().saturation) * height - drawCircle( - center = Offset(circleX, circleY), - color = Color.Black, - radius = 5f, - style = Stroke(width = 3f) - ) - } - } - } -} - -fun Color.toHexString() = "0x" + toArgb().toUInt().toString(16) -fun limit(value: Float, min: Float, max: Float) = minOf( - maxOf(value, min), - max -) - -fun limit0to1(value: Float) = limit(value = value, 0f, 1f) diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/HSV.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/HSV.kt deleted file mode 100644 index 86fef306c4..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/HSV.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package com.jetbrains.compose.color - -import androidx.compose.ui.graphics.Color -import kotlin.math.abs - -data class HSV( - /** - * 0.0 .. 360.0 - */ - val hue: Float, - /** - * 0.0 .. 1.0 - */ - val saturation: Float, - /** - * 0.0 . 1.0¬ - */ - val value: Float -) { - companion object { - const val HUE_MAX_VALUE = 360f - } -} - -/** - * Convert to HSV color space - * https://www.rapidtables.com/convert/color/rgb-to-hsv.html - */ -fun Color.toHsv(): HSV { - val max = maxOf(red, green, blue) - val min = minOf(red, green, blue) - val delta = max - min - val h = when { - delta == 0f -> 0f - max == red -> 60 * ((green - blue) / delta).mod(6f) - max == green -> 60 * ((blue - red) / delta + 2) - max == blue -> 60 * ((red - green) / delta + 4) - else -> 0f - } - val s = when { - max == 0f -> 0f - else -> delta / max - } - val v = max - return HSV( - hue = h, - saturation = s, - value = v - ) -} - -/** - * Convert to RGB color space - * https://www.rapidtables.com/convert/color/hsv-to-rgb.html - */ -fun HSV.toRgb(): Color { - val c = value * saturation - val x = minOf(c * (1 - abs((hue / 60).mod(2f) - 1)), 1f) - val m = value - c - val tempColor = when { - hue >= 0 && hue < 60 -> Color(c, x, 0f) - hue >= 60 && hue < 120 -> Color(x, c, 0f) - hue >= 120 && hue < 180 -> Color(0f, c, x) - hue >= 180 && hue < 240 -> Color(0f, x, c) - hue >= 240 && hue < 300 -> Color(x, 0f, c) - else -> Color(c, 0f, x) - } - return Color(minOf(m + tempColor.red, 1f), minOf(m + tempColor.green, 1f), minOf(m + tempColor.blue, 1f)) -} - diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/ComposeToolWindow.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/ComposeToolWindow.kt deleted file mode 100644 index c7e66f37c9..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/ComposeToolWindow.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package com.jetbrains.compose.panel - -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.awt.ComposePanel -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.openapi.wm.ToolWindow -import com.intellij.openapi.wm.ToolWindowFactory -import com.intellij.ui.content.ContentFactory -import com.jetbrains.compose.IntellijTheme -import java.awt.Dimension - -class ComposeToolWindow : ToolWindowFactory, DumbAware { - - override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { - ApplicationManager.getApplication().invokeLater { - toolWindow.contentManager.addContent( - ContentFactory.SERVICE.getInstance().createContent( - ComposePanel().apply { - size = Dimension(300, 300) - setContent { - IntellijTheme(project) { - CounterPanel(stateWithIdeLifecycle) - } - } - }, - "Compose tool window", - false - ) - ) - } - } - - companion object { - val stateWithIdeLifecycle = mutableStateOf(CounterState()) - } - -} diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/CounterPanel.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/CounterPanel.kt deleted file mode 100644 index 24be64a9f9..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/CounterPanel.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package com.jetbrains.compose.panel - -import androidx.compose.foundation.layout.Column -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.* - -@Composable -fun CounterPanel(stateWithIdeLifecycle: MutableState) { - var stateInline by remember { mutableStateOf(CounterState()) } - Column { - Text("Counter with IDE lifecycle: ${stateWithIdeLifecycle.value.counter}") - Button(onClick = { - stateWithIdeLifecycle.value = stateWithIdeLifecycle.value.copy( - counter = stateWithIdeLifecycle.value.counter + 1 - ) - }) { - Text("Increment state with IDE lifecycle") - } - Text("Counter with @Composable lifecycle: ${stateInline.counter}") - Button(onClick = { - stateInline = stateInline.copy( - counter = stateInline.counter + 1 - ) - }) { - Text("Increment state with @Composable lifecycle") - } - } -} diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/CounterState.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/CounterState.kt deleted file mode 100644 index 5466e579f9..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/panel/CounterState.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package com.jetbrains.compose.panel - -data class CounterState( - val counter: Int = 0 -) diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Color.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Color.kt deleted file mode 100644 index 9ed4581b85..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Color.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.jetbrains.compose.theme - -import androidx.compose.ui.graphics.Color - -val green200 = Color(0xffa5d6a7) -val green500 = Color(0xff4caf50) -val green700 = Color(0xff388e3c) - -val teal200 = Color(0xff80deea) diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Shape.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Shape.kt deleted file mode 100644 index 3ac8795fc7..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Shape.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.jetbrains.compose.theme - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Shapes -import androidx.compose.ui.unit.dp - -val shapes = Shapes( - small = RoundedCornerShape(4.dp), - medium = RoundedCornerShape(4.dp), - large = RoundedCornerShape(0.dp) -) \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Theme.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Theme.kt deleted file mode 100644 index 78a3ec5181..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Theme.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.jetbrains.compose.theme - -import androidx.compose.material.MaterialTheme -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import com.jetbrains.compose.theme.intellij.SwingColor - -private val DarkGreenColorPalette = darkColors( - primary = green200, - primaryVariant = green700, - secondary = teal200, - onPrimary = Color.Black, - onSecondary = Color.White, - error = Color.Red, -) - -private val LightGreenColorPalette = lightColors( - primary = green500, - primaryVariant = green700, - secondary = teal200, - onPrimary = Color.White, - onSurface = Color.Black -) - -@Composable -fun WidgetTheme( - darkTheme: Boolean = false, - content: @Composable() () -> Unit, -) { - val colors = if (darkTheme) DarkGreenColorPalette else LightGreenColorPalette - val swingColor = SwingColor() - - MaterialTheme( - colors = colors.copy( - background = swingColor.background, - onBackground = swingColor.onBackground, - surface = swingColor.background, - onSurface = swingColor.onBackground, - ), - typography = typography, - shapes = shapes, - content = content - ) -} \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Type.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Type.kt deleted file mode 100644 index 4bc429afa8..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/Type.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.jetbrains.compose.theme - -import androidx.compose.material.Typography -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp - -val typography = Typography( - body1 = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp - ), - body2 = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 14.sp - ), - button = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.W500, - fontSize = 14.sp - ), - caption = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 12.sp, - ), - subtitle1 = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - color = Color.Gray - ), - subtitle2 = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 14.sp, - color = Color.Gray - ), -) \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/intellij/SwingColor.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/intellij/SwingColor.kt deleted file mode 100644 index 0aac1aaf50..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/intellij/SwingColor.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.jetbrains.compose.theme.intellij - -import androidx.compose.runtime.* -import androidx.compose.ui.graphics.Color -import com.intellij.ide.ui.LafManagerListener -import com.intellij.openapi.application.ApplicationManager -import javax.swing.UIManager -import java.awt.Color as AWTColor - -interface SwingColor { - val background: Color - val onBackground: Color -} - -@Composable -fun SwingColor(): SwingColor { - val swingColor = remember { SwingColorImpl() } - - val messageBus = remember { - ApplicationManager.getApplication().messageBus.connect() - } - - remember(messageBus) { - messageBus.subscribe( - LafManagerListener.TOPIC, - ThemeChangeListener(swingColor::updateCurrentColors) - ) - } - - DisposableEffect(messageBus) { - onDispose { - messageBus.disconnect() - } - } - - return swingColor -} - -private class SwingColorImpl : SwingColor { - private val _backgroundState: MutableState = mutableStateOf(getBackgroundColor) - private val _onBackgroundState: MutableState = mutableStateOf(getOnBackgroundColor) - - override val background: Color get() = _backgroundState.value - override val onBackground: Color get() = _onBackgroundState.value - - private val getBackgroundColor get() = getColor(BACKGROUND_KEY) - private val getOnBackgroundColor get() = getColor(ON_BACKGROUND_KEY) - - fun updateCurrentColors() { - _backgroundState.value = getBackgroundColor - _onBackgroundState.value = getOnBackgroundColor - } - - private val AWTColor.asComposeColor: Color get() = Color(red, green, blue, alpha) - private fun getColor(key: String): Color = UIManager.getColor(key).asComposeColor - - companion object { - private const val BACKGROUND_KEY = "Panel.background" - private const val ON_BACKGROUND_KEY = "Panel.foreground" - } -} \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/intellij/ThemeChangeListener.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/intellij/ThemeChangeListener.kt deleted file mode 100644 index 95a8324114..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/theme/intellij/ThemeChangeListener.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.jetbrains.compose.theme.intellij - -import com.intellij.ide.ui.LafManager -import com.intellij.ide.ui.LafManagerListener - -internal class ThemeChangeListener( - val updateColors: () -> Unit -) : LafManagerListener { - override fun lookAndFeelChanged(source: LafManager) { - updateColors() - } -} - diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Buttons.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Buttons.kt deleted file mode 100644 index 89fc8c2c11..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Buttons.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.jetbrains.compose.widgets - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.TextButton -import androidx.compose.material.OutlinedButton -import androidx.compose.material.Text -import androidx.compose.material.Icon -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.FavoriteBorder -import androidx.compose.material.icons.filled.Refresh -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun Buttons() { - Row { - val btnEnabled = remember { mutableStateOf(true) } - Button( - onClick = { btnEnabled.value = !btnEnabled.value}, - modifier = Modifier.padding(8.dp), - enabled = btnEnabled.value - ) { - Icon( - imageVector = Icons.Default.FavoriteBorder, - contentDescription = "FavoriteBorder", - modifier = Modifier.padding(end = 4.dp) - ) - Text(text = "Button") - } - val btnTextEnabled = remember { mutableStateOf(true) } - TextButton( - onClick = { btnTextEnabled.value = !btnTextEnabled.value }, - modifier = Modifier.padding(8.dp), - enabled = btnTextEnabled.value - ) { - Text(text = "Text Button") - } - OutlinedButton( - onClick = { - btnEnabled.value = true - btnTextEnabled.value = true - }, - modifier = Modifier.padding(8.dp) - ) { - Icon( - imageVector = Icons.Default.Refresh, - contentDescription = "Refresh", - modifier = Modifier.padding(0.dp) - ) - } - } -} \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/LazyScrollable.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/LazyScrollable.kt deleted file mode 100644 index e5bba421a0..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/LazyScrollable.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.jetbrains.compose.widgets - -import androidx.compose.desktop.DesktopTheme -import androidx.compose.foundation.background -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.rememberScrollbarAdapter -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.VerticalScrollbar -import androidx.compose.material.Text -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun LazyScrollable() { - MaterialTheme { - DesktopTheme { - Box( - modifier = Modifier.fillMaxSize() - .padding(10.dp) - ) { - - val state = rememberLazyListState() - val itemCount = 100 - - LazyColumn(Modifier.fillMaxSize().padding(end = 12.dp), state) { - items(itemCount) { x -> - TextBox("Item in ScrollableColumn #$x") - Spacer(modifier = Modifier.height(5.dp)) - } - } - VerticalScrollbar( - modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), - adapter = rememberScrollbarAdapter( - scrollState = state - ) - ) - } - } - } -} - -@Composable -private fun TextBox(text: String = "Item") { - Surface( - color = Color(135, 135, 135, 40), - shape = RoundedCornerShape(4.dp) - ) { - Box( - modifier = Modifier.height(32.dp) - .fillMaxWidth() - .padding(start = 10.dp), - contentAlignment = Alignment.CenterStart - ) { - Text(text = text) - } - } -} \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Loaders.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Loaders.kt deleted file mode 100644 index a18252459b..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Loaders.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.jetbrains.compose.widgets - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.LinearProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun Loaders() { - Row( - modifier = Modifier.fillMaxWidth().padding(16.dp) - ) { - Box( - modifier = Modifier.height(30.dp), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator( - modifier = Modifier.size(20.dp, 20.dp), - strokeWidth = 4.dp - ) - } - Box( - modifier = Modifier - .height(30.dp) - .padding(start = 8.dp), - contentAlignment = Alignment.Center - ) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) - } - } -} \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/TextInputs.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/TextInputs.kt deleted file mode 100644 index 5ca7a9aad6..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/TextInputs.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.jetbrains.compose.widgets - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.material.OutlinedTextField -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.dp - -@Composable -fun TextInputs() { - Column( - modifier = Modifier.fillMaxWidth().padding(16.dp) - ) { - var name by remember { mutableStateOf(TextFieldValue("")) } - var password by remember { mutableStateOf(TextFieldValue("")) } - - TextField( - value = name, - onValueChange = { newValue -> name = newValue }, - modifier = Modifier.padding(8.dp).fillMaxWidth(), - textStyle = TextStyle(fontFamily = FontFamily.SansSerif), - label = { Text("Account:") }, - placeholder = { Text("account name") } - ) - - OutlinedTextField( - value = password, - modifier = Modifier.padding(8.dp).fillMaxWidth(), - label = { Text(text = "Password:") }, - placeholder = { Text(text = "your password") }, - textStyle = TextStyle(fontFamily = FontFamily.SansSerif), - visualTransformation = PasswordVisualTransformation(), - onValueChange = { - password = it - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) - ) - } -} \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Toggles.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Toggles.kt deleted file mode 100644 index b4a7febac2..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/widgets/Toggles.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.jetbrains.compose.widgets - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.Checkbox -import androidx.compose.material.MaterialTheme -import androidx.compose.material.RadioButton -import androidx.compose.material.Slider -import androidx.compose.material.Switch -import androidx.compose.material.SwitchDefaults -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun Toggles() { - Column { - Row { - Column( - modifier = Modifier.padding(16.dp) - ) { - var checked by remember { mutableStateOf(true) } - Checkbox( - checked = checked, - modifier = Modifier.padding(8.dp), - onCheckedChange = { checked = !checked } - ) - var switched by remember { mutableStateOf(true) } - Switch( - checked = switched, - colors = SwitchDefaults.colors(checkedThumbColor = MaterialTheme.colors.primary), - modifier = Modifier.padding(8.dp), - onCheckedChange = { switched = it } - ) - } - Column( - modifier = Modifier.padding(16.dp) - ) { - var selected by remember { mutableStateOf("Kotlin") } - Row(verticalAlignment = Alignment.CenterVertically) { - RadioButton(selected = selected == "Kotlin", onClick = { selected = "Kotlin" }) - Text( - text = "Kotlin", - modifier = Modifier.clickable(onClick = { selected = "Kotlin" }).padding(start = 4.dp) - ) - } - Row(verticalAlignment = Alignment.CenterVertically) { - RadioButton(selected = selected == "Java", onClick = { selected = "Java" }) - Text( - text = "Java", - modifier = Modifier.clickable(onClick = { selected = "Java" }).padding(start = 4.dp) - ) - } - Row(verticalAlignment = Alignment.CenterVertically) { - RadioButton(selected = selected == "Swift", onClick = { selected = "Swift" }) - Text( - text = "Swift", - modifier = Modifier.clickable(onClick = { selected = "Swift" }).padding(start = 4.dp) - ) - } - } - } - - var sliderState by remember { mutableStateOf(0f) } - Slider(value = sliderState, modifier = Modifier.fillMaxWidth().padding(8.dp), - onValueChange = { newValue -> - sliderState = newValue - } - ) - - var sliderState2 by remember { mutableStateOf(20f) } - Slider(value = sliderState2, modifier = Modifier.fillMaxWidth().padding(8.dp), - valueRange = 0f..100f, - steps = 5, - onValueChange = { newValue -> - sliderState2 = newValue - } - ) - } -} \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/resources/META-INF/plugin.xml b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/resources/META-INF/plugin.xml deleted file mode 100644 index 5932048466..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/resources/META-INF/plugin.xml +++ /dev/null @@ -1,29 +0,0 @@ - - com.jetbrains.ComposeDemoPlugin - Jetpack Compose for Desktop Demo - JetBrains - - - - - com.intellij.modules.platform - org.jetbrains.compose.intellij.platform - org.jetbrains.kotlin - - - - - - - - - - - - - \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/resources/icons/compose.svg b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/resources/icons/compose.svg deleted file mode 100644 index 2154d497bd..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/main/resources/icons/compose.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/ColorPickerUITest.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/ColorPickerUITest.kt deleted file mode 100644 index 68ca6d2387..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/ColorPickerUITest.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package com.jetbrains.compose.color - -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.WindowState -import androidx.compose.ui.window.application - -fun main() = application { - val windowState = remember { WindowState(width = 400.dp, height = 400.dp) } - Window( - onCloseRequest = ::exitApplication, - title = "ColorPicker", - state = windowState - ) { - ColorPicker(mutableStateOf(Color(0xffaabbcc))) - } -} diff --git a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/HSVTest.kt b/experimental/examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/HSVTest.kt deleted file mode 100644 index b1c491b1fe..0000000000 --- a/experimental/examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/HSVTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package com.jetbrains.compose.color - -import androidx.compose.ui.graphics.Color -import org.junit.Test -import kotlin.test.assertEquals - -class HSVTest { - - @Test - fun testGreenToHsv() { - val greenRgb = Color(0xff00ff00) - val result = greenRgb.toHsv() - assertEquals(HSV(120f, 1f, 1f), result) - assertEquals(greenRgb, result.toRgb()) - } - -} From 0bd1536fc7e3b9635221533f88710b0c3d01193d Mon Sep 17 00:00:00 2001 From: issamux Date: Mon, 24 Jun 2024 12:34:28 -0400 Subject: [PATCH 27/47] [iOS/ImageViewer] Proposal of Fix share image button infinite loop (#4973) Proposal of a Fixes ( Unfortunately, I can't test the rest of the code as the iOS camera requires a real device. ) Fixes https://github.com/JetBrains/compose-multiplatform/issues/4970 --- .../example/imageviewer/ImageViewer.ios.kt | 33 ++++++++++--------- .../storage/IosImageStorage.ios.kt | 15 +++++++++ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/ImageViewer.ios.kt b/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/ImageViewer.ios.kt index 1f40b9740a..c42b994987 100755 --- a/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/ImageViewer.ios.kt +++ b/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/ImageViewer.ios.kt @@ -52,22 +52,23 @@ fun getDependencies(ioScope: CoroutineScope, toastState: MutableState + withContext(Dispatchers.Main) { + val window = UIApplication.sharedApplication.windows.last() as? UIWindow + val currentViewController = window?.rootViewController + val activityViewController = UIActivityViewController( + activityItems = listOf( + UIImage.imageWithContentsOfFile(imageUrl), + picture.description + ), + applicationActivities = null + ) + currentViewController?.presentViewController( + viewControllerToPresent = activityViewController, + animated = true, + completion = null, + ) + } } } } diff --git a/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt b/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt index b84d46d13c..732c467613 100644 --- a/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt +++ b/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/storage/IosImageStorage.ios.kt @@ -119,6 +119,21 @@ class IosImageStorage( } }.readData() } + + suspend fun getNSURLToShare(picture: PictureData): NSURL = withContext(Dispatchers.IO) { + when (picture) { + is PictureData.Camera -> { + picture.jpgFile + } + + is PictureData.Resource -> { + NSURL( + fileURLWithPath = NSBundle.mainBundle.resourcePath + "/" + picture.resource, + isDirectory = false + ) + } + } + } } @OptIn(ExperimentalForeignApi::class) From c86df6118324aa93fbc715c179ea6ceb704d72df Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 25 Jun 2024 10:57:03 +0200 Subject: [PATCH 28/47] [gradle] Add a customization for resources directories. (#5016) With the new API it is possible to customize compose resources directories. For example: ```kotlin abstract class GenerateAndroidRes : DefaultTask() { @get:Inject abstract val layout: ProjectLayout @get:OutputDirectory val outputDir = layout.buildDirectory.dir("generatedAndroidResources") @TaskAction fun run() {...} } compose.resources { customDirectory( sourceSetName = "androidMain", directoryProvider = tasks.register("generateAndroidRes").map { it.outputDir.get() } ) customDirectory( sourceSetName = "desktopMain", directoryProvider = provider { layout.projectDirectory.dir("desktopResources") } ) } ``` Fixes https://github.com/JetBrains/compose-multiplatform/issues/4718 Fixes https://github.com/JetBrains/compose-multiplatform/issues/4564 ## Release Notes ### Features - Resources - Add a customization for resources directories. Now it is possible to use e.g downloaded resources. --- .../resources/ComposeResourcesGeneration.kt | 2 +- .../resources/PrepareComposeResources.kt | 27 ++++++++++++------ .../compose/resources/ResourcesDSL.kt | 13 +++++++++ .../misc/commonResources/build.gradle.kts | 28 +++++++++++++++++++ .../composeResources/values/strings.xml | 3 -- 5 files changed, 60 insertions(+), 13 deletions(-) delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/androidMain/composeResources/values/strings.xml diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt index 8c28fa4f04..33cf4fa8ce 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt @@ -54,7 +54,7 @@ internal fun Project.configureComposeResourcesGeneration( } //common resources must be converted (XML -> CVR) - val preparedResourcesTask = registerPrepareComposeResourcesTask(sourceSet) + val preparedResourcesTask = registerPrepareComposeResourcesTask(sourceSet, config) val preparedResources = preparedResourcesTask.flatMap { it.outputDir.asFile } configureResourceAccessorsGeneration( sourceSet, diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/PrepareComposeResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/PrepareComposeResources.kt index 11131350fe..0d447b6a3c 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/PrepareComposeResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/PrepareComposeResources.kt @@ -1,6 +1,7 @@ package org.jetbrains.compose.resources import org.gradle.api.Project +import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.FileTree @@ -25,11 +26,19 @@ import javax.inject.Inject import javax.xml.parsers.DocumentBuilderFactory internal fun Project.registerPrepareComposeResourcesTask( - sourceSet: KotlinSourceSet + sourceSet: KotlinSourceSet, + config: Provider ): TaskProvider { - val resDir = "${sourceSet.name}/$COMPOSE_RESOURCES_DIR" - val userComposeResourcesDir = project.projectDir.resolve("src/$resDir") - val preparedComposeResourcesDir = layout.buildDirectory.dir("$RES_GEN_DIR/preparedResources/$resDir") + val userComposeResourcesDir: Provider = config.flatMap { ext -> + ext.customResourceDirectories[sourceSet.name] ?: provider { + //default path + layout.projectDirectory.dir("src/${sourceSet.name}/$COMPOSE_RESOURCES_DIR") + } + } + + val preparedComposeResourcesDir = layout.buildDirectory.dir( + "$RES_GEN_DIR/preparedResources/${sourceSet.name}/$COMPOSE_RESOURCES_DIR" + ) val convertXmlValueResources = tasks.register( "convertXmlValueResourcesFor${sourceSet.name.uppercaseFirstChar()}", @@ -61,11 +70,11 @@ internal fun Project.registerPrepareComposeResourcesTask( } internal fun Project.getPreparedComposeResourcesDir(sourceSet: KotlinSourceSet): Provider = tasks - .named( - getPrepareComposeResourcesTaskName(sourceSet), - PrepareComposeResourcesTask::class.java - ) - .flatMap { it.outputDir.asFile } + .named( + getPrepareComposeResourcesTaskName(sourceSet), + PrepareComposeResourcesTask::class.java + ) + .flatMap { it.outputDir.asFile } private fun getPrepareComposeResourcesTaskName(sourceSet: KotlinSourceSet) = "prepareComposeResourcesTaskFor${sourceSet.name.uppercaseFirstChar()}" diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesDSL.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesDSL.kt index 17abaf96ff..88406e1125 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesDSL.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesDSL.kt @@ -1,6 +1,7 @@ package org.jetbrains.compose.resources import org.gradle.api.Project +import org.gradle.api.file.Directory import org.gradle.api.provider.Provider import java.io.File @@ -36,6 +37,18 @@ abstract class ResourcesExtension { * - `never`: Never generate the Res class. */ var generateResClass: ResourceClassGeneration = auto + + internal val customResourceDirectories: MutableMap> = mutableMapOf() + + /** + * Associates a custom resource directory with a specific source set. + * + * @param sourceSetName the name of the source set to associate the custom resource directory with + * @param directoryProvider the provider that provides the custom directory + */ + fun customDirectory(sourceSetName: String, directoryProvider: Provider) { + customResourceDirectories[sourceSetName] = directoryProvider + } } internal fun Provider.getResourcePackage(project: Project) = map { config -> 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 beda4225bf..aeafd38a84 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 @@ -62,3 +62,31 @@ android { targetCompatibility = JavaVersion.VERSION_11 } } + +abstract class GenerateAndroidRes : DefaultTask() { + @get:Inject + abstract val layout: ProjectLayout + + @get:OutputDirectory + val outputDir = layout.buildDirectory.dir("generatedAndroidResources") + + @TaskAction + fun run() { + val dir = outputDir.get().asFile + dir.deleteRecursively() + File(dir, "values/strings.xml").apply { + parentFile.mkdirs() + writeText( + """ + + Android string + + """.trimIndent() + ) + } + } +} +compose.resources.customDirectory( + sourceSetName = "androidMain", + directoryProvider = tasks.register("generateAndroidRes").map { it.outputDir.get() } +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/androidMain/composeResources/values/strings.xml b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/androidMain/composeResources/values/strings.xml deleted file mode 100644 index 4855fd3e03..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/androidMain/composeResources/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Android string - \ No newline at end of file From 22dfb1431a85a76d2f8e6f0290f836a4a8547413 Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Fri, 28 Jun 2024 11:32:19 +0200 Subject: [PATCH 29/47] [gradle] Fix AGP lint tasks dependencies. --- .../jetbrains/compose/resources/AndroidResources.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt index 8ca1fed8f9..afd400483e 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt @@ -6,9 +6,14 @@ import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask import com.android.build.gradle.internal.lint.LintModelWriterTask import org.gradle.api.DefaultTask import org.gradle.api.Project -import org.gradle.api.file.* +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection +import org.gradle.api.file.FileSystemOperations import org.gradle.api.provider.Property -import org.gradle.api.tasks.* +import org.gradle.api.tasks.IgnoreEmptyDirectories +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction import org.jetbrains.compose.internal.utils.registerTask import org.jetbrains.compose.internal.utils.uppercaseFirstChar import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension @@ -131,6 +136,10 @@ internal fun Project.configureAndroidAssetsForPreview() { if (task.name == "compile${variant.name.uppercaseFirstChar()}Sources") { task.dependsOn(kgpCopyAssetsTaskName) } + //fix https://github.com/JetBrains/compose-multiplatform/issues/5038 + if (task is AndroidLintAnalysisTask || task is LintModelWriterTask) { + task.mustRunAfter(kgpCopyAssetsTaskName) + } } } } From a307ddd9ce25ba831a5e98514c4477f82ae95f06 Mon Sep 17 00:00:00 2001 From: issamux Date: Mon, 1 Jul 2024 06:17:11 -0400 Subject: [PATCH 30/47] [Desktop/ImageViewer] Fix test build error (#5035) --- .../shared/src/desktopTest/kotlin/ImageViewerTest.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/imageviewer/shared/src/desktopTest/kotlin/ImageViewerTest.kt b/examples/imageviewer/shared/src/desktopTest/kotlin/ImageViewerTest.kt index e0ebfc53b4..c94d9f3800 100644 --- a/examples/imageviewer/shared/src/desktopTest/kotlin/ImageViewerTest.kt +++ b/examples/imageviewer/shared/src/desktopTest/kotlin/ImageViewerTest.kt @@ -1,7 +1,12 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick -import example.imageviewer.* +import example.imageviewer.Dependencies +import example.imageviewer.DesktopImageStorage +import example.imageviewer.ImageViewerCommon +import example.imageviewer.Notification +import example.imageviewer.PopupNotification +import example.imageviewer.SharePicture import example.imageviewer.filter.PlatformContext import example.imageviewer.model.PictureData import kotlinx.coroutines.CoroutineScope @@ -18,9 +23,10 @@ class ImageViewerTest { override fun showPopUpMessage(text: String) { } } - override val imageStorage: DesktopImageStorage = DesktopImageStorage(pictures, CoroutineScope(Dispatchers.Main)) + override val imageStorage: DesktopImageStorage = + DesktopImageStorage(CoroutineScope(Dispatchers.Main)) override val sharePicture: SharePicture = object : SharePicture { - override fun share(context: PlatformContext, picture: PictureData) { } + override fun share(context: PlatformContext, picture: PictureData) {} } } From c7b640348ec9eb259516090af053aaa3f73aa4c0 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 1 Jul 2024 13:33:27 +0200 Subject: [PATCH 31/47] [gradle] Update Kotlin to 2.0 (#5043) Integration tests: - Kotlin -> 2.0.0 - max AGP -> 8.5.0 - max Gradle -> 8.8 Project with gradle plugins: - Kotlin -> 2.0.0 - Gradle -> 8.8 --- .github/workflows/gradle-plugin.yml | 4 +- gradle-plugins/compose/build.gradle.kts | 8 +- .../compose/resources/IosResources.kt | 14 +- .../compose/resources/IosResourcesTasks.kt | 13 +- .../integration/DesktopApplicationTest.kt | 125 +++----- .../tests/integration/GradlePluginTest.kt | 16 +- .../integration/KotlinCompatibilityTest.kt | 61 +++- .../test/tests/integration/ResourcesTest.kt | 121 ++++---- .../ComposeCompilerArtifactProviderTest.kt | 9 +- .../test/utils/GradleTestNameGenerator.kt | 2 +- .../compose/test/utils/TestProject.kt | 6 +- .../compose/test/utils/TestProjects.kt | 33 -- .../compose/test/utils/TestProperties.kt | 10 +- .../build.gradle | 34 --- .../gradle.properties | 2 - .../src/commonMain/kotlin/App.kt | 19 -- .../application/defaultArgs/build.gradle | 1 + .../application/defaultArgs/settings.gradle | 1 + .../defaultArgsOverride/build.gradle | 1 + .../defaultArgsOverride/settings.gradle | 1 + .../application/javaLogger/build.gradle | 1 + .../application/javaLogger/settings.gradle | 1 + .../application/jvm/build.gradle | 1 + .../application/jvm/settings.gradle | 1 + .../application/jvmKotlinDsl/build.gradle.kts | 1 + .../application/jvmKotlinDsl/settings.gradle | 1 + .../application/macOptions/build.gradle | 3 +- .../application/macOptions/settings.gradle | 1 + .../application/macSign/build.gradle | 3 +- .../application/macSign/settings.gradle | 1 + .../moduleClashCli/app/build.gradle | 3 +- .../moduleClashCli/settings.gradle | 1 + .../application/mpp/build.gradle | 12 +- .../application/mpp/settings.gradle | 1 + .../application/newAndroidTarget/build.gradle | 3 +- .../newAndroidTarget/settings.gradle | 1 + .../optionsWithSpaces/build.gradle | 1 + .../optionsWithSpaces/settings.gradle | 1 + .../application/proguard/build.gradle | 1 + .../proguard/main-methods.expected.txt | 4 +- .../application/proguard/settings.gradle | 1 + .../application/resources/build.gradle | 1 + .../application/resources/settings.gradle | 1 + .../application/unpackSkiko/build.gradle | 1 + .../application/unpackSkiko/settings.gradle | 1 + .../custom-compiler-args/build.gradle | 0 .../main-image.expected.png | Bin .../custom-compiler-args/settings.gradle | 0 .../src/desktopMain/kotlin/Main.kt | 0 .../src/jsMain/kotlin/Main.js.kt | 0 .../custom-compiler/build.gradle | 0 .../custom-compiler/main-image.expected.png | Bin .../custom-compiler/settings.gradle | 0 .../custom-compiler/src/main/kotlin/Main.kt | 0 .../beforeKotlin2/jsMpp/build.gradle | 27 ++ .../jsMpp}/settings.gradle | 5 +- .../jsMpp/src/commonMain/kotlin/platform.kt | 1 + .../jsMpp/src/jsMain/kotlin/platform.kt | 1 + .../jsMpp/src/jvmMain/kotlin/platform.kt | 1 + .../beforeKotlin2/mpp/build.gradle | 73 +++++ .../beforeKotlin2/mpp/gradle.properties | 1 + .../mpp}/settings.gradle | 9 +- .../mpp/src/jvmMain/kotlin/main.kt | 3 + .../bundledKotlinPoet/app/build.gradle.kts | 1 + .../misc/bundledKotlinPoet/build.gradle.kts | 1 + .../bundledKotlinPoet/settings.gradle.kts | 1 + .../misc/commonResources/build.gradle.kts | 7 +- .../my/lib/res/String0.androidMain.kt | 4 +- .../my/lib/res/Drawable0.commonMain.kt | 24 +- .../my/lib/res/Font0.commonMain.kt | 5 +- .../my/lib/res/Plurals0.commonMain.kt | 4 +- .../my/lib/res/String0.commonMain.kt | 32 +- .../commonResClass/my/lib/res/Res.kt | 5 +- .../my/lib/res/String0.desktopMain.kt | 2 +- .../resources/String0.androidMain.kt | 5 +- .../resources/Drawable0.commonMain.kt | 26 +- .../generated/resources/Font0.commonMain.kt | 6 +- .../resources/Plurals0.commonMain.kt | 5 +- .../generated/resources/String0.commonMain.kt | 40 ++- .../resources_test/generated/resources/Res.kt | 6 +- .../resources/String0.desktopMain.kt | 3 +- .../misc/commonResources/settings.gradle.kts | 1 + .../misc/emptyResources/build.gradle.kts | 1 + .../misc/emptyResources/expected/Res.kt | 6 +- .../misc/emptyResources/settings.gradle.kts | 1 + .../misc/iosResources/build.gradle.kts | 1 + .../expected/iosResources.podspec | 4 + .../misc/iosResources/settings.gradle.kts | 1 + .../test-projects/misc/jsMpp/build.gradle | 3 +- .../test-projects/misc/jsMpp/settings.gradle | 1 + .../misc/jvmOnlyResources/build.gradle.kts | 1 + .../misc/jvmOnlyResources/settings.gradle.kts | 1 + .../misc/jvmPreview/common/build.gradle | 1 + .../misc/jvmPreview/jvm/build.gradle | 1 + .../misc/jvmPreview/mpp/build.gradle | 1 + .../misc/jvmPreview/settings.gradle | 1 + .../misc/nativeCacheKind/build.gradle | 27 -- .../misc/nativeCacheKind/gradle.properties | 2 - .../misc/nativeCacheKind/settings.gradle | 27 -- .../src/commonMain/kotlin/App.kt | 10 - .../nativeCacheKind/subproject/build.gradle | 27 -- .../subproject/src/commonMain/kotlin/App.kt | 10 - .../misc/nativeCacheKindError/build.gradle | 20 -- .../nativeCacheKindError/gradle.properties | 1 - .../src/commonMain/kotlin/App.kt | 10 - .../subproject/build.gradle | 20 -- .../subproject/src/commonMain/kotlin/App.kt | 10 - .../test-projects/misc/skikoWasm/build.gradle | 3 +- .../misc/skikoWasm/gradle.properties | 1 + .../misc/skikoWasm/settings.gradle | 1 + gradle-plugins/gradle.properties | 10 +- gradle-plugins/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 43453 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- gradle-plugins/gradlew | 282 +++++++++++------- gradle-plugins/gradlew.bat | 54 ++-- 116 files changed, 674 insertions(+), 698 deletions(-) delete mode 100644 gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt delete mode 100644 gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/build.gradle delete mode 100644 gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/gradle.properties delete mode 100644 gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/src/commonMain/kotlin/App.kt rename gradle-plugins/compose/src/test/test-projects/{application => beforeKotlin2}/custom-compiler-args/build.gradle (100%) rename gradle-plugins/compose/src/test/test-projects/{application => beforeKotlin2}/custom-compiler-args/main-image.expected.png (100%) rename gradle-plugins/compose/src/test/test-projects/{application => beforeKotlin2}/custom-compiler-args/settings.gradle (100%) rename gradle-plugins/compose/src/test/test-projects/{application => beforeKotlin2}/custom-compiler-args/src/desktopMain/kotlin/Main.kt (100%) rename gradle-plugins/compose/src/test/test-projects/{application => beforeKotlin2}/custom-compiler-args/src/jsMain/kotlin/Main.js.kt (100%) rename gradle-plugins/compose/src/test/test-projects/{application => beforeKotlin2}/custom-compiler/build.gradle (100%) rename gradle-plugins/compose/src/test/test-projects/{application => beforeKotlin2}/custom-compiler/main-image.expected.png (100%) rename gradle-plugins/compose/src/test/test-projects/{application => beforeKotlin2}/custom-compiler/settings.gradle (100%) rename gradle-plugins/compose/src/test/test-projects/{application => beforeKotlin2}/custom-compiler/src/main/kotlin/Main.kt (100%) create mode 100644 gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/build.gradle rename gradle-plugins/compose/src/test/test-projects/{misc/nativeCacheKindError => beforeKotlin2/jsMpp}/settings.gradle (91%) create mode 100644 gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/commonMain/kotlin/platform.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/jsMain/kotlin/platform.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/jvmMain/kotlin/platform.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/build.gradle create mode 100644 gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/gradle.properties rename gradle-plugins/compose/src/test/test-projects/{application/customCompilerUnsupportedPlatformsWarning => beforeKotlin2/mpp}/settings.gradle (68%) create mode 100644 gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/src/jvmMain/kotlin/main.kt delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/build.gradle delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/gradle.properties delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/settings.gradle delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/src/commonMain/kotlin/App.kt delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/subproject/build.gradle delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/subproject/src/commonMain/kotlin/App.kt delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/build.gradle delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/gradle.properties delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/src/commonMain/kotlin/App.kt delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/subproject/build.gradle delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/subproject/src/commonMain/kotlin/App.kt diff --git a/.github/workflows/gradle-plugin.yml b/.github/workflows/gradle-plugin.yml index d5d688cc4a..53a51f7196 100644 --- a/.github/workflows/gradle-plugin.yml +++ b/.github/workflows/gradle-plugin.yml @@ -17,8 +17,8 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04, macos-14, windows-2022] - gradle: [7.4, 8.7] - agp: [8.1.0, 8.4.0] + gradle: [7.4, 8.8] + agp: [8.1.0, 8.5.0] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 diff --git a/gradle-plugins/compose/build.gradle.kts b/gradle-plugins/compose/build.gradle.kts index b49fe5f0e6..de5b015a58 100644 --- a/gradle-plugins/compose/build.gradle.kts +++ b/gradle-plugins/compose/build.gradle.kts @@ -43,12 +43,6 @@ val embeddedDependencies by configurations.creating { isTransitive = false } -val kgpResourcesDevVersion = "2.0.0-dev-17632" -//KMP resources API available since ^kgpResourcesDevVersion -repositories { - maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/") -} - dependencies { // By default, Gradle resolves plugins only via Gradle Plugin Portal. // To avoid declaring an additional repo, all dependencies must: @@ -63,7 +57,7 @@ dependencies { compileOnly(gradleApi()) compileOnly(localGroovy()) - compileOnly(kotlin("gradle-plugin", kgpResourcesDevVersion)) + compileOnly(kotlin("gradle-plugin")) compileOnly(kotlin("native-utils")) compileOnly(libs.plugin.android) compileOnly(libs.plugin.android.api) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt index 1a05e615e8..ae31edbafb 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt @@ -1,18 +1,20 @@ package org.jetbrains.compose.resources -import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.file.Directory import org.gradle.api.plugins.ExtensionAware -import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Copy -import org.gradle.api.tasks.TaskAction import org.jetbrains.compose.desktop.application.internal.ComposeProperties -import org.jetbrains.compose.internal.utils.* +import org.jetbrains.compose.internal.utils.dependsOn +import org.jetbrains.compose.internal.utils.joinLowerCamelCase +import org.jetbrains.compose.internal.utils.registerOrConfigure +import org.jetbrains.compose.internal.utils.uppercaseFirstChar import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension -import org.jetbrains.kotlin.gradle.plugin.mpp.* +import org.jetbrains.kotlin.gradle.plugin.mpp.Framework +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable import org.jetbrains.kotlin.konan.target.KonanTarget import java.io.File @@ -137,7 +139,7 @@ private fun KotlinNativeTarget.isIosSimulatorTarget(): Boolean = konanTarget === KonanTarget.IOS_X64 || konanTarget === KonanTarget.IOS_SIMULATOR_ARM64 private fun KotlinNativeTarget.isIosDeviceTarget(): Boolean = - konanTarget === KonanTarget.IOS_ARM64 || konanTarget === KonanTarget.IOS_ARM32 + konanTarget === KonanTarget.IOS_ARM64 private fun KotlinNativeTarget.isIosTarget(): Boolean = isIosSimulatorTarget() || isIosDeviceTarget() \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResourcesTasks.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResourcesTasks.kt index 90681d3166..b024e9412b 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResourcesTasks.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResourcesTasks.kt @@ -4,8 +4,16 @@ import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileCollection import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.* -import org.gradle.api.tasks.* +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction import org.jetbrains.kotlin.konan.target.KonanTarget import javax.inject.Inject @@ -95,7 +103,6 @@ private fun getRequestedKonanTargetsByXcode(platform: String, archs: List when (arch) { "arm64", "arm64e" -> KonanTarget.IOS_ARM64 - "armv7", "armv7s" -> KonanTarget.IOS_ARM32 else -> error("Unknown iOS device arch: '$arch'") } }) diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt index 0653807888..1eeb113f8b 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt @@ -12,20 +12,31 @@ import org.jetbrains.compose.internal.utils.currentArch import org.jetbrains.compose.internal.utils.currentOS import org.jetbrains.compose.internal.utils.currentTarget import org.jetbrains.compose.internal.utils.uppercaseFirstChar -import org.jetbrains.compose.test.utils.* - -import java.io.File -import java.util.* +import org.jetbrains.compose.test.utils.GradlePluginTestBase +import org.jetbrains.compose.test.utils.JDK_11_BYTECODE_VERSION +import org.jetbrains.compose.test.utils.ProcessRunResult +import org.jetbrains.compose.test.utils.TestProject +import org.jetbrains.compose.test.utils.assertEqualTextFiles +import org.jetbrains.compose.test.utils.assertNotEqualTextFiles +import org.jetbrains.compose.test.utils.checkContains +import org.jetbrains.compose.test.utils.checkExists +import org.jetbrains.compose.test.utils.checkNotExists +import org.jetbrains.compose.test.utils.checks +import org.jetbrains.compose.test.utils.modify +import org.jetbrains.compose.test.utils.readClassFileVersion +import org.jetbrains.compose.test.utils.runProcess import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import java.io.File +import java.util.* import java.util.jar.JarFile class DesktopApplicationTest : GradlePluginTestBase() { @Test - fun smokeTestRunTask() = with(testProject(TestProjects.jvm)) { + fun smokeTestRunTask() = with(testProject("application/jvm")) { file("build.gradle").modify { it + """ afterEvaluate { @@ -62,7 +73,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { } @Test - fun testRunMpp() = with(testProject(TestProjects.mpp)) { + fun testRunMpp() = with(testProject("application/mpp")) { val logLine = "Kotlin MPP app is running!" gradle("run").checks { check.taskSuccessful(":run") @@ -84,60 +95,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { } } - /** - * Test the version of Compose Compiler published by Google. - * See https://developer.android.com/jetpack/androidx/releases/compose-kotlin - */ - @Test - fun testAndroidxCompiler() = testProject( - TestProjects.customCompiler, defaultTestEnvironment.copy( - kotlinVersion = "1.8.0", - composeCompilerPlugin = "\"androidx.compose.compiler:compiler:1.4.0\"" - ) - ).checkCustomComposeCompiler() - - @Test - fun testSettingLatestCompiler() = testProject( - TestProjects.customCompiler, defaultTestEnvironment.copy( - kotlinVersion = "1.8.20", - composeCompilerPlugin = "dependencies.compiler.forKotlin(\"1.8.20\")", - ) - ).checkCustomComposeCompiler() - - @Test - fun testSettingAutoCompiler() = testProject( - TestProjects.customCompiler, defaultTestEnvironment.copy( - kotlinVersion = "1.8.10", - composeCompilerPlugin = "dependencies.compiler.auto", - ) - ).checkCustomComposeCompiler() - - @Test - fun testKotlinCheckDisabled() = testProject( - TestProjects.customCompilerArgs, defaultTestEnvironment.copy( - kotlinVersion = "1.9.21", - composeCompilerPlugin = "dependencies.compiler.forKotlin(\"1.9.20\")", - composeCompilerArgs = "\"suppressKotlinVersionCompatibilityCheck=1.9.21\"" - ) - ).checkCustomComposeCompiler(checkKJS = true) - - private fun TestProject.checkCustomComposeCompiler(checkKJS: Boolean = false) { - gradle(":runDistributable").checks { - val actualMainImage = file("main-image.actual.png") - val expectedMainImage = file("main-image.expected.png") - assert(actualMainImage.readBytes().contentEquals(expectedMainImage.readBytes())) { - "The actual image '$actualMainImage' does not match the expected image '$expectedMainImage'" - } - } - if (checkKJS) { - gradle(":jsBrowserProductionWebpack").checks { - check.taskSuccessful(":jsBrowserProductionWebpack") - } - } - } - @Test - fun kotlinDsl(): Unit = with(testProject(TestProjects.jvmKotlinDsl)) { + fun kotlinDsl(): Unit = with(testProject("application/jvmKotlinDsl")) { gradle(":packageDistributionForCurrentOS", "--dry-run") gradle(":packageReleaseDistributionForCurrentOS", "--dry-run") } @@ -145,7 +104,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun proguard(): Unit = with( testProject( - TestProjects.proguard, + "application/proguard", testEnvironment = defaultTestEnvironment.copy(composeVerbose = false)) ) { val enableObfuscation = """ @@ -188,12 +147,12 @@ class DesktopApplicationTest : GradlePluginTestBase() { } @Test - fun joinOutputJarsJvm() = with(testProject(TestProjects.jvm)) { + fun joinOutputJarsJvm() = with(testProject("application/jvm")) { joinOutputJars() } @Test - fun joinOutputJarsMpp() = with(testProject(TestProjects.mpp)) { + fun joinOutputJarsMpp() = with(testProject("application/mpp")) { joinOutputJars() } @@ -220,7 +179,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { } @Test - fun gradleBuildCache() = with(testProject(TestProjects.jvm)) { + fun gradleBuildCache() = with(testProject("application/jvm")) { modifyGradleProperties { setProperty("org.gradle.caching", "true") } @@ -246,12 +205,12 @@ class DesktopApplicationTest : GradlePluginTestBase() { } @Test - fun packageJvm() = with(testProject(TestProjects.jvm)) { + fun packageJvm() = with(testProject("application/jvm")) { testPackageJvmDistributions() } @Test - fun packageMpp() = with(testProject(TestProjects.mpp)) { + fun packageMpp() = with(testProject("application/mpp")) { testPackageJvmDistributions() } @@ -305,7 +264,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { } private fun customJdkProject(javaVersion: Int): TestProject = - testProject(TestProjects.jvm).apply { + testProject("application/jvm").apply { appendText("build.gradle") { """ compose.desktop.application { @@ -318,22 +277,22 @@ class DesktopApplicationTest : GradlePluginTestBase() { } @Test - fun packageUberJarForCurrentOSJvm() = with(testProject(TestProjects.jvm)) { + fun packageUberJarForCurrentOSJvm() = with(testProject("application/jvm")) { testPackageUberJarForCurrentOS(false) } @Test - fun packageUberJarForCurrentOSMpp() = with(testProject(TestProjects.mpp)) { + fun packageUberJarForCurrentOSMpp() = with(testProject("application/mpp")) { testPackageUberJarForCurrentOS(false) } @Test - fun packageReleaseUberJarForCurrentOSJvm() = with(testProject(TestProjects.jvm)) { + fun packageReleaseUberJarForCurrentOSJvm() = with(testProject("application/jvm")) { testPackageUberJarForCurrentOS(true) } @Test - fun packageReleaseUberJarForCurrentOSMpp() = with(testProject(TestProjects.mpp)) { + fun packageReleaseUberJarForCurrentOSMpp() = with(testProject("application/mpp")) { testPackageUberJarForCurrentOS(true) } @@ -366,7 +325,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { } @Test - fun testModuleClash() = with(testProject(TestProjects.moduleClashCli)) { + fun testModuleClash() = with(testProject("application/moduleClashCli")) { gradle(":app:runDistributable").checks { check.taskSuccessful(":app:createDistributable") check.taskSuccessful(":app:runDistributable") @@ -376,7 +335,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { } @Test - fun testJavaLogger() = with(testProject(TestProjects.javaLogger)) { + fun testJavaLogger() = with(testProject("application/javaLogger")) { gradle(":runDistributable").checks { check.taskSuccessful(":runDistributable") check.logContains("Compose Gradle plugin test log warning!") @@ -393,7 +352,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { Assumptions.assumeTrue(currentOS == OS.MacOS) - with(testProject(TestProjects.macOptions)) { + with(testProject("application/macOptions")) { gradle(":runDistributable").checks { check.taskSuccessful(":runDistributable") check.logContains("Hello, from Mac OS!") @@ -411,7 +370,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { fun testMacSignConfiguration() { Assumptions.assumeTrue(currentOS == OS.MacOS) - with(testProject(TestProjects.macSign)) { + with(testProject("application/macSign")) { gradle("--dry-run", ":createDistributable") } } @@ -444,7 +403,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { } } - with(testProject(TestProjects.macSign)) { + with(testProject("application/macSign")) { val keychain = file("compose.test.keychain") val password = "compose.test" @@ -474,7 +433,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun testOptionsWithSpaces() { - with(testProject(TestProjects.optionsWithSpaces)) { + with(testProject("application/optionsWithSpaces")) { fun testRunTask(runTask: String) { gradle(runTask).checks { check.taskSuccessful(runTask) @@ -496,7 +455,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun testDefaultArgs() { - with(testProject(TestProjects.defaultArgs)) { + with(testProject("application/defaultArgs")) { fun testRunTask(runTask: String) { gradle(runTask).checks { check.taskSuccessful(runTask) @@ -515,7 +474,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun testDefaultArgsOverride() { - with(testProject(TestProjects.defaultArgsOverride)) { + with(testProject("application/defaultArgsOverride")) { fun testRunTask(runTask: String) { gradle(runTask).checks { check.taskSuccessful(runTask) @@ -534,7 +493,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun testSuggestModules() { - with(testProject(TestProjects.jvm)) { + with(testProject("application/jvm")) { gradle(":suggestRuntimeModules").checks { check.taskSuccessful(":suggestRuntimeModules") check.logContains("Suggested runtime modules to include:") @@ -544,12 +503,12 @@ class DesktopApplicationTest : GradlePluginTestBase() { } @Test - fun testUnpackSkiko() = with(testProject(TestProjects.unpackSkiko)) { + fun testUnpackSkiko() = with(testProject("application/unpackSkiko")) { testUnpackSkiko(":runDistributable") } @Test - fun testUnpackSkikoFromUberJar() = with(testProject(TestProjects.unpackSkiko)) { + fun testUnpackSkikoFromUberJar() = with(testProject("application/unpackSkiko")) { enableJoinOutputJars() testUnpackSkiko(":runReleaseDistributable") } @@ -576,7 +535,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { } @Test - fun resources() = with(testProject(TestProjects.resources)) { + fun resources() = with(testProject("application/resources")) { gradle(":run").checks { check.taskSuccessful(":run") } @@ -590,7 +549,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { fun testWixUnzip() { Assumptions.assumeTrue(currentOS == OS.Windows) { "The test is only relevant for Windows" } - with(testProject(TestProjects.jvm)) { + with(testProject("application/jvm")) { gradle(":unzipWix").checks { check.taskSuccessful(":unzipWix") diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt index 866e46c945..261b32054c 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt @@ -11,7 +11,8 @@ import org.gradle.util.GradleVersion import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewLogger import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.RemoteConnection import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.receiveConfigFromGradle -import org.jetbrains.compose.test.utils.* +import org.jetbrains.compose.test.utils.GradlePluginTestBase +import org.jetbrains.compose.test.utils.checkExists import org.jetbrains.compose.test.utils.checks import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.Test @@ -27,7 +28,7 @@ class GradlePluginTest : GradlePluginTestBase() { @Test fun skikoWasm() = with( testProject( - TestProjects.skikoWasm, + "misc/skikoWasm", // TODO: enable the configuration cache after moving all test projects to kotlin 2.0 or newer defaultTestEnvironment.copy(useGradleConfigurationCache = false) ) @@ -66,7 +67,7 @@ class GradlePluginTest : GradlePluginTestBase() { @Test fun newAndroidTarget() { Assumptions.assumeTrue(defaultTestEnvironment.parsedGradleVersion >= GradleVersion.version("8.0.0")) - with(testProject(TestProjects.newAndroidTarget)) { + with(testProject("application/newAndroidTarget")) { gradle("build", "--dry-run").checks { } } @@ -75,12 +76,7 @@ class GradlePluginTest : GradlePluginTestBase() { @Test fun jsMppIsNotBroken() = with( - testProject( - TestProjects.jsMpp, - testEnvironment = defaultTestEnvironment.copy( - kotlinVersion = TestProperties.composeJsCompilerCompatibleKotlinVersion - ) - ) + testProject("misc/jsMpp") ) { gradle(":compileKotlinJs").checks { check.taskSuccessful(":compileKotlinJs") @@ -145,7 +141,7 @@ class GradlePluginTest : GradlePluginTestBase() { private fun testConfigureDesktopPreviewImpl(port: Int) { check(port > 0) { "Invalid port: $port" } - with(testProject(TestProjects.jvmPreview)) { + with(testProject("misc/jvmPreview")) { val portProperty = "-Pcompose.desktop.preview.ide.port=$port" val previewTargetProperty = "-Pcompose.desktop.preview.target=PreviewKt.ExamplePreview" val jvmTask = ":jvm:configureDesktopPreview" diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt index a1987fd9ec..0a452fd67c 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt @@ -5,10 +5,9 @@ package org.jetbrains.compose.test.tests.integration -import org.jetbrains.compose.newCompilerIsAvailableVersion import org.jetbrains.compose.newComposeCompilerError import org.jetbrains.compose.test.utils.GradlePluginTestBase -import org.jetbrains.compose.test.utils.TestProjects +import org.jetbrains.compose.test.utils.TestProject import org.jetbrains.compose.test.utils.checks import org.junit.jupiter.api.Test @@ -27,7 +26,7 @@ class KotlinCompatibilityTest : GradlePluginTestBase() { private fun testMpp(kotlinVersion: String) = with( testProject( - TestProjects.mpp, + "beforeKotlin2/mpp", testEnvironment = defaultTestEnvironment.copy(kotlinVersion = kotlinVersion) ) ) { @@ -40,7 +39,7 @@ class KotlinCompatibilityTest : GradlePluginTestBase() { private fun testJsMpp(kotlinVersion: String) = with( testProject( - TestProjects.jsMpp, + "beforeKotlin2/jsMpp", testEnvironment = defaultTestEnvironment.copy(kotlinVersion = kotlinVersion) ) ) { @@ -52,11 +51,63 @@ class KotlinCompatibilityTest : GradlePluginTestBase() { @Test fun testNewCompilerPluginError() { val testProject = testProject( - TestProjects.mpp, + "beforeKotlin2/mpp", testEnvironment = defaultTestEnvironment.copy(kotlinVersion = "2.0.0") ) testProject.gradleFailure("tasks").checks { check.logContains(newComposeCompilerError) } } + + /** + * Test the version of Compose Compiler published by Google. + * See https://developer.android.com/jetpack/androidx/releases/compose-kotlin + */ + @Test + fun testAndroidxCompiler() = testProject( + "beforeKotlin2/custom-compiler", defaultTestEnvironment.copy( + kotlinVersion = "1.8.0", + composeCompilerPlugin = "\"androidx.compose.compiler:compiler:1.4.0\"" + ) + ).checkCustomComposeCompiler() + + @Test + fun testSettingLatestCompiler() = testProject( + "beforeKotlin2/custom-compiler", defaultTestEnvironment.copy( + kotlinVersion = "1.8.20", + composeCompilerPlugin = "dependencies.compiler.forKotlin(\"1.8.20\")", + ) + ).checkCustomComposeCompiler() + + @Test + fun testSettingAutoCompiler() = testProject( + "beforeKotlin2/custom-compiler", defaultTestEnvironment.copy( + kotlinVersion = "1.8.10", + composeCompilerPlugin = "dependencies.compiler.auto", + ) + ).checkCustomComposeCompiler() + + @Test + fun testKotlinCheckDisabled() = testProject( + "beforeKotlin2/custom-compiler-args", defaultTestEnvironment.copy( + kotlinVersion = "1.9.21", + composeCompilerPlugin = "dependencies.compiler.forKotlin(\"1.9.20\")", + composeCompilerArgs = "\"suppressKotlinVersionCompatibilityCheck=1.9.21\"" + ) + ).checkCustomComposeCompiler(checkKJS = true) + + private fun TestProject.checkCustomComposeCompiler(checkKJS: Boolean = false) { + gradle(":runDistributable").checks { + val actualMainImage = file("main-image.actual.png") + val expectedMainImage = file("main-image.expected.png") + assert(actualMainImage.readBytes().contentEquals(expectedMainImage.readBytes())) { + "The actual image '$actualMainImage' does not match the expected image '$expectedMainImage'" + } + } + if (checkKJS) { + gradle(":jsBrowserProductionWebpack").checks { + check.taskSuccessful(":jsBrowserProductionWebpack") + } + } + } } 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 53a9e5b822..5ef8f5f393 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 @@ -2,15 +2,30 @@ package org.jetbrains.compose.test.tests.integration import org.gradle.util.GradleVersion import org.jetbrains.compose.desktop.application.internal.ComposeProperties -import org.jetbrains.compose.internal.utils.* +import org.jetbrains.compose.internal.utils.Arch +import org.jetbrains.compose.internal.utils.OS +import org.jetbrains.compose.internal.utils.currentArch +import org.jetbrains.compose.internal.utils.currentOS import org.jetbrains.compose.resources.XmlValuesConverterTask -import org.jetbrains.compose.test.utils.* +import org.jetbrains.compose.test.utils.GradlePluginTestBase +import org.jetbrains.compose.test.utils.TestProject +import org.jetbrains.compose.test.utils.assertEqualTextFiles +import org.jetbrains.compose.test.utils.assertNotEqualTextFiles +import org.jetbrains.compose.test.utils.checkExists +import org.jetbrains.compose.test.utils.checks +import org.jetbrains.compose.test.utils.modify import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.Test import java.io.File import java.util.zip.ZipFile +import kotlin.io.path.Path +import kotlin.io.path.invariantSeparatorsPathString import kotlin.io.path.relativeTo -import kotlin.test.* +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue class ResourcesTest : GradlePluginTestBase() { @Test @@ -244,38 +259,31 @@ class ResourcesTest : GradlePluginTestBase() { val resDir = file("cmplib/src/commonMain/composeResources") val resourcesFiles = resDir.walkTopDown() .filter { !it.isDirectory && !it.isHidden } - .getConvertedResources(resDir) - val subdir = "me.sample.library.resources" + .getConvertedResources(resDir, "composeResources/me.sample.library.resources") fun libpath(target: String, ext: String) = "my-mvn/me/sample/library/cmplib-$target/1.0/cmplib-$target-1.0$ext" val aar = file(libpath("android", ".aar")) - assertTrue(aar.exists(), "File not found: " + aar.path) - ZipFile(aar).use { zip -> resourcesFiles.forEach { fontRes -> - assertNotNull( - zip.getEntry("assets/composeResources/$subdir/$fontRes"), - "Resource not found: '$fontRes' in aar '${aar.path}'" - ) - } } + checkResourcesZip(aar, resourcesFiles, true) val jar = file(libpath("jvm", ".jar")) - checkResourcesZip(jar, resourcesFiles, subdir) + checkResourcesZip(jar, resourcesFiles, false) if (currentOS == OS.MacOS) { val iosx64ResZip = file(libpath("iosx64", "-kotlin_resources.kotlin_resources.zip")) - checkResourcesZip(iosx64ResZip, resourcesFiles, subdir) + checkResourcesZip(iosx64ResZip, resourcesFiles, false) val iosarm64ResZip = file(libpath("iosarm64", "-kotlin_resources.kotlin_resources.zip")) - checkResourcesZip(iosarm64ResZip, resourcesFiles, subdir) + checkResourcesZip(iosarm64ResZip, resourcesFiles, false) val iossimulatorarm64ResZip = file( libpath("iossimulatorarm64", "-kotlin_resources.kotlin_resources.zip") ) - checkResourcesZip(iossimulatorarm64ResZip, resourcesFiles, subdir) + checkResourcesZip(iossimulatorarm64ResZip, resourcesFiles, false) } val jsResZip = file(libpath("js", "-kotlin_resources.kotlin_resources.zip")) - checkResourcesZip(jsResZip, resourcesFiles, subdir) + checkResourcesZip(jsResZip, resourcesFiles, false) val wasmjsResZip = file(libpath("wasm-js", "-kotlin_resources.kotlin_resources.zip")) - checkResourcesZip(wasmjsResZip, resourcesFiles, subdir) + checkResourcesZip(wasmjsResZip, resourcesFiles, false) } file("settings.gradle.kts").modify { content -> @@ -323,14 +331,19 @@ class ResourcesTest : GradlePluginTestBase() { } } - private fun checkResourcesZip(zipFile: File, resourcesFiles: Sequence, subdir: String) { + private fun checkResourcesZip(zipFile: File, resourcesFiles: Sequence, isAndroid: Boolean) { + println("check ZIP: '${zipFile.path}'") assertTrue(zipFile.exists(), "File not found: " + zipFile.path) ZipFile(zipFile).use { zip -> resourcesFiles.forEach { res -> - assertNotNull( - zip.getEntry("composeResources/$subdir/$res"), - "Resource not found: '$res' in zip '${zipFile.path}'" - ) + println("check '$res' file") + if (isAndroid) { + //android resources should be only in assets + assertNull(zip.getEntry(res), "file = '$res'") + assertNotNull(zip.getEntry("assets/$res"), "file = 'assets/$res'") + } else { + assertNotNull(zip.getEntry(res), "file = '$res'") + } } } } @@ -369,50 +382,51 @@ class ResourcesTest : GradlePluginTestBase() { file("src/jsMain/composeResources/files/platform.txt").writeNewFile("js") val commonResourcesDir = file("src/commonMain/composeResources") + val repackDir = "composeResources/app.group.resources_test.generated.resources" val commonResourcesFiles = commonResourcesDir.walkTopDown() .filter { !it.isDirectory && !it.isHidden } - .getConvertedResources(commonResourcesDir) + .getConvertedResources(commonResourcesDir, repackDir) gradle("build").checks { - check.taskSuccessful(":copyDemoDebugResourcesToAndroidAssets") - check.taskSuccessful(":copyDemoReleaseResourcesToAndroidAssets") - check.taskSuccessful(":copyFullDebugResourcesToAndroidAssets") - check.taskSuccessful(":copyFullReleaseResourcesToAndroidAssets") + check.taskSuccessful(":demoDebugAssetsCopyForAGP") + check.taskSuccessful(":demoReleaseAssetsCopyForAGP") + check.taskSuccessful(":fullDebugAssetsCopyForAGP") + check.taskSuccessful(":fullReleaseAssetsCopyForAGP") getAndroidApk("demo", "debug", "Resources-Test").let { apk -> - checkResourcesInZip(apk, commonResourcesFiles, true) + checkResourcesZip(apk, commonResourcesFiles, true) assertEquals( "android demo-debug", - readFileInZip(apk, "assets/files/platform.txt").decodeToString() + readFileInZip(apk, "assets/$repackDir/files/platform.txt").decodeToString() ) } getAndroidApk("demo", "release", "Resources-Test").let { apk -> - checkResourcesInZip(apk, commonResourcesFiles, true) + checkResourcesZip(apk, commonResourcesFiles, true) assertEquals( "android demo-release", - readFileInZip(apk, "assets/files/platform.txt").decodeToString() + readFileInZip(apk, "assets/$repackDir/files/platform.txt").decodeToString() ) } getAndroidApk("full", "debug", "Resources-Test").let { apk -> - checkResourcesInZip(apk, commonResourcesFiles, true) + checkResourcesZip(apk, commonResourcesFiles, true) assertEquals( "android full-debug", - readFileInZip(apk, "assets/files/platform.txt").decodeToString() + readFileInZip(apk, "assets/$repackDir/files/platform.txt").decodeToString() ) } getAndroidApk("full", "release", "Resources-Test").let { apk -> - checkResourcesInZip(apk, commonResourcesFiles, true) + checkResourcesZip(apk, commonResourcesFiles, true) assertEquals( "android full-release", - readFileInZip(apk, "assets/files/platform.txt").decodeToString() + readFileInZip(apk, "assets/$repackDir/files/platform.txt").decodeToString() ) } file("build/libs/Resources-Test-desktop.jar").let { jar -> - checkResourcesInZip(jar, commonResourcesFiles, false) + checkResourcesZip(jar, commonResourcesFiles, false) assertEquals( "desktop", - readFileInZip(jar, "files/platform.txt").decodeToString() + readFileInZip(jar, "$repackDir/files/platform.txt").decodeToString() ) } @@ -420,11 +434,11 @@ class ResourcesTest : GradlePluginTestBase() { commonResourcesFiles.forEach { res -> assertTrue(jsBuildDir.resolve(res).exists()) } - assertEquals("js", jsBuildDir.resolve("files/platform.txt").readText()) + assertEquals("js", jsBuildDir.resolve("$repackDir/files/platform.txt").readText()) } } - private fun Sequence.getConvertedResources(baseDir: File) = map { file -> + private fun Sequence.getConvertedResources(baseDir: File, repackDir: String) = map { file -> val newFile = if ( file.parentFile.name.startsWith("value") && file.extension.equals("xml", true) @@ -434,7 +448,7 @@ class ResourcesTest : GradlePluginTestBase() { } else { file } - newFile.relativeTo(baseDir).invariantSeparatorsPath + Path(repackDir, newFile.relativeTo(baseDir).path).invariantSeparatorsPathString } private fun File.writeNewFile(text: String) { @@ -451,23 +465,6 @@ class ResourcesTest : GradlePluginTestBase() { } } - private fun checkResourcesInZip(file: File, commonResourcesFiles: Sequence, isAndroid: Boolean) { - println("check ZIP: '${file.path}'") - assertTrue(file.exists()) - ZipFile(file).use { zip -> - commonResourcesFiles.forEach { res -> - println("check '$res' file") - if (isAndroid) { - //android resources should be only in assets - assertNull(zip.getEntry(res), "file = '$res'") - assertNotNull(zip.getEntry("assets/$res"), "file = 'assets/$res'") - } else { - assertNotNull(zip.getEntry(res), "file = '$res'") - } - } - } - } - private fun readFileInZip(file: File, path: String): ByteArray = ZipFile(file).use { zip -> val platformTxt = zip.getEntry(path) assertNotNull(platformTxt, "file = '$path'") @@ -654,8 +651,8 @@ class ResourcesTest : GradlePluginTestBase() { check.taskNoSource(":prepareComposeResourcesTaskForIosX64Main") check.taskSkipped(":generateResourceAccessorsForIosX64Main") - file("build/compose/cocoapods/compose-resources/drawable/compose-multiplatform.xml").checkExists() - file("build/compose/cocoapods/compose-resources/drawable/icon.xml").checkExists() + file("build/compose/cocoapods/compose-resources/composeResources/iosresources.generated.resources/drawable/compose-multiplatform.xml").checkExists() + file("build/compose/cocoapods/compose-resources/composeResources/iosresources.generated.resources/drawable/icon.xml").checkExists() } gradle(":podspec", "-Pkotlin.native.cocoapods.generate.wrapper=true").checks { @@ -676,8 +673,8 @@ class ResourcesTest : GradlePluginTestBase() { check.taskSkipped(":linkDebugTestIosX64") } gradle(":copyTestComposeResourcesForIosX64").checks { - file("build/bin/iosX64/debugTest/compose-resources/drawable/compose-multiplatform.xml").checkExists() - file("build/bin/iosX64/debugTest/compose-resources/drawable/icon.xml").checkExists() + file("build/bin/iosX64/debugTest/compose-resources/composeResources/iosresources.generated.resources/drawable/compose-multiplatform.xml").checkExists() + file("build/bin/iosX64/debugTest/compose-resources/composeResources/iosresources.generated.resources/drawable/icon.xml").checkExists() } } } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/unit/ComposeCompilerArtifactProviderTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/unit/ComposeCompilerArtifactProviderTest.kt index eb9e2e7879..d6d93797ff 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/unit/ComposeCompilerArtifactProviderTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/unit/ComposeCompilerArtifactProviderTest.kt @@ -7,12 +7,10 @@ package org.jetbrains.compose.test.tests.unit import org.jetbrains.compose.internal.ComposeCompilerArtifactProvider import org.jetbrains.compose.internal.copy -import org.jetbrains.compose.test.utils.TestProperties import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Assertions.* - internal class ComposeCompilerArtifactProviderTest { @Test fun customVersion() { @@ -69,12 +67,9 @@ internal class ComposeCompilerArtifactProviderTest { get() = SubpluginArtifact( groupId = "org.jetbrains.compose.compiler", artifactId = "compiler", - version = TestProperties.composeCompilerVersion + version = "1.9.20" ) - val jbCompilerHosted: SubpluginArtifact - get() = jbCompiler.copy(artifactId = "compiler-hosted") - val googleCompiler: SubpluginArtifact get() = jbCompiler.copy(groupId = "androidx.compose.compiler") } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/GradleTestNameGenerator.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/GradleTestNameGenerator.kt index cfb9b0984a..760db9d166 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/GradleTestNameGenerator.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/GradleTestNameGenerator.kt @@ -13,7 +13,7 @@ class GradleTestNameGenerator : DisplayNameGenerator.Standard() { override fun generateDisplayNameForMethod(testClass: Class<*>, testMethod: Method) = testMethod.name + with(TestProperties) { mutableListOf().apply { - muteException { add("kotlin=$composeCompilerCompatibleKotlinVersion") } + muteException { add("kotlin=$kotlinVersion") } muteException { add("gradle=$gradleVersion") } muteException { add("agp=$agpVersion") } }.joinToString(prefix = "(", separator = ", ", postfix = ")") diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt index a48097a094..c8db18f955 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt @@ -9,14 +9,12 @@ import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner import org.gradle.util.GradleVersion import org.jetbrains.compose.desktop.application.internal.ComposeProperties -import org.junit.jupiter.params.provider.Arguments import java.io.File -import java.util.Properties -import java.util.stream.Stream +import java.util.* data class TestEnvironment( val workingDir: File, - val kotlinVersion: String = TestProperties.composeCompilerCompatibleKotlinVersion, + val kotlinVersion: String = TestProperties.kotlinVersion, val gradleVersion: String = TestProperties.gradleVersion, val agpVersion: String = TestProperties.agpVersion, val composeGradlePluginVersion: String = TestProperties.composeGradlePluginVersion, diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt deleted file mode 100644 index 24f59310ed..0000000000 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.test.utils - -object TestProjects { - const val jvm = "application/jvm" - const val mpp = "application/mpp" - const val newAndroidTarget = "application/newAndroidTarget" - const val proguard = "application/proguard" - const val customCompiler = "application/custom-compiler" - const val customCompilerArgs = "application/custom-compiler-args" - const val customCompilerUnsupportedPlatformsWarning = "application/customCompilerUnsupportedPlatformsWarning" - const val jvmKotlinDsl = "application/jvmKotlinDsl" - const val moduleClashCli = "application/moduleClashCli" - const val javaLogger = "application/javaLogger" - const val macOptions = "application/macOptions" - const val macSign = "application/macSign" - const val optionsWithSpaces = "application/optionsWithSpaces" - const val defaultArgs = "application/defaultArgs" - const val defaultArgsOverride = "application/defaultArgsOverride" - const val unpackSkiko = "application/unpackSkiko" - const val resources = "application/resources" - const val jsMpp = "misc/jsMpp" - const val skikoWasm = "misc/skikoWasm" - const val jvmPreview = "misc/jvmPreview" - const val iosResources = "misc/iosResources" - const val iosMokoResources = "misc/iosMokoResources" - const val nativeCacheKind = "misc/nativeCacheKind" - const val nativeCacheKindError = "misc/nativeCacheKindError" -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt index 11caa75036..3f4be6cbb8 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt @@ -8,14 +8,8 @@ package org.jetbrains.compose.test.utils import java.io.File object TestProperties { - val composeCompilerVersion: String - get() = notNullSystemProperty("compose.tests.compiler.version") - - val composeCompilerCompatibleKotlinVersion: String - get() = notNullSystemProperty("compose.tests.compiler.compatible.kotlin.version") - - val composeJsCompilerCompatibleKotlinVersion: String - get() = notNullSystemProperty("compose.tests.js.compiler.compatible.kotlin.version") + val kotlinVersion: String + get() = notNullSystemProperty("compose.tests.kotlin.version") val composeGradlePluginVersion: String get() = notNullSystemProperty("compose.tests.compose.gradle.plugin.version") diff --git a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/build.gradle deleted file mode 100644 index 04c2be309b..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" - id "org.jetbrains.compose" -} - -kotlin { - def platforms = project.property("platforms").split(",") - if (platforms.contains("jvm")) { - jvm() - } - if (platforms.contains("js")) { - js(IR) { - browser() - binaries.executable() - } - } - if (platforms.contains("ios")) { - ios() - } - - sourceSets { - commonMain { - dependencies { - implementation compose.runtime - implementation compose.material - implementation compose.foundation - } - } - } -} - -compose { - kotlinCompilerPlugin.set(COMPOSE_COMPILER_PLUGIN_PLACEHOLDER) -} diff --git a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/gradle.properties b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/gradle.properties deleted file mode 100644 index e209558321..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -org.jetbrains.compose.experimental.jscanvas.enabled=true -org.jetbrains.compose.experimental.uikit.enabled=true \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/src/commonMain/kotlin/App.kt deleted file mode 100644 index 79dcb39588..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/src/commonMain/kotlin/App.kt +++ /dev/null @@ -1,19 +0,0 @@ -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember - -@Composable -fun App() { - MaterialTheme { - var message by remember { mutableStateOf("Press the button!") } - - Button( - onClick = { message = "Welcome to Compose Multiplatform!" } - ) { - Text(message) - } - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/defaultArgs/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/defaultArgs/build.gradle index 9d1d21d1cd..0cd904a78f 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/defaultArgs/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/defaultArgs/build.gradle @@ -2,6 +2,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/defaultArgs/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/defaultArgs/settings.gradle index 1531cffa13..f87e919ea2 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/defaultArgs/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/defaultArgs/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/defaultArgsOverride/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/defaultArgsOverride/build.gradle index 427b63b1ad..af907c4297 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/defaultArgsOverride/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/defaultArgsOverride/build.gradle @@ -2,6 +2,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/defaultArgsOverride/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/defaultArgsOverride/settings.gradle index 1531cffa13..f87e919ea2 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/defaultArgsOverride/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/defaultArgsOverride/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/javaLogger/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/javaLogger/build.gradle index 87fbb5d4de..28b9e0c623 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/javaLogger/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/javaLogger/build.gradle @@ -1,5 +1,6 @@ plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/javaLogger/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/javaLogger/settings.gradle index de29cd8544..78832dc01a 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/javaLogger/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/javaLogger/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/jvm/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/jvm/build.gradle index 4ac0b9f8b2..6b93854d0c 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/jvm/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/jvm/build.gradle @@ -2,6 +2,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/jvm/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/jvm/settings.gradle index 9df72a09ce..6d72c98406 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/jvm/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/jvm/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/jvmKotlinDsl/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/application/jvmKotlinDsl/build.gradle.kts index c8f6ac82b5..3a3c5aa20f 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/jvmKotlinDsl/build.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/application/jvmKotlinDsl/build.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.kotlin.plugin.compose") id("org.jetbrains.compose") } diff --git a/gradle-plugins/compose/src/test/test-projects/application/jvmKotlinDsl/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/jvmKotlinDsl/settings.gradle index 9df72a09ce..6d72c98406 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/jvmKotlinDsl/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/jvmKotlinDsl/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle index 122189c034..d239392b13 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle @@ -1,7 +1,6 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat - plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/macOptions/settings.gradle index 1531cffa13..f87e919ea2 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/macOptions/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/macOptions/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/macSign/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/macSign/build.gradle index 25af20375d..0f1ab82832 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/macSign/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/macSign/build.gradle @@ -1,7 +1,6 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat - plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/macSign/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/macSign/settings.gradle index 1531cffa13..f87e919ea2 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/macSign/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/macSign/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/build.gradle index 6791935473..7265e9830d 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/build.gradle @@ -1,7 +1,6 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat - plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/settings.gradle index 89f91ba82f..4e3e135f88 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/mpp/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/mpp/build.gradle index 33fc77b284..b4a447b868 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/mpp/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/mpp/build.gradle @@ -3,6 +3,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { id "com.android.application" id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } @@ -21,6 +22,9 @@ kotlin { } } } + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } } android { @@ -33,14 +37,6 @@ android { } } -kotlin { - jvm { - jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(11)) - } - } -} - compose.desktop { application { mainClass = "MainKt" diff --git a/gradle-plugins/compose/src/test/test-projects/application/mpp/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/mpp/settings.gradle index 0276bf4c39..96b75d539d 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/mpp/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/mpp/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' id 'com.android.application' version 'AGP_VERSION_PLACEHOLDER' } diff --git a/gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/build.gradle index 33188819c9..ef45b4a99e 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/build.gradle @@ -1,7 +1,6 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat - plugins { id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.compose" id "com.android.kotlin.multiplatform.library" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/settings.gradle index 142c0d28b4..86985a3b8e 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' id 'com.android.kotlin.multiplatform.library' version '8.2.0-alpha13' } diff --git a/gradle-plugins/compose/src/test/test-projects/application/optionsWithSpaces/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/optionsWithSpaces/build.gradle index f0df53799f..2451e05bed 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/optionsWithSpaces/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/optionsWithSpaces/build.gradle @@ -2,6 +2,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/optionsWithSpaces/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/optionsWithSpaces/settings.gradle index 1531cffa13..f87e919ea2 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/optionsWithSpaces/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/optionsWithSpaces/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/proguard/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/proguard/build.gradle index 7fe038689d..4b3f792180 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/proguard/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/proguard/build.gradle @@ -3,6 +3,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/proguard/main-methods.expected.txt b/gradle-plugins/compose/src/test/test-projects/application/proguard/main-methods.expected.txt index 3fc7a4f8c7..2932270c74 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/proguard/main-methods.expected.txt +++ b/gradle-plugins/compose/src/test/test-projects/application/proguard/main-methods.expected.txt @@ -1,3 +1,5 @@ keptByKeepRule +keptByKeepRule$lambda$5 main -mainShape \ No newline at end of file +mainShape +mainShape$lambda$2 \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/proguard/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/proguard/settings.gradle index 9df72a09ce..6d72c98406 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/proguard/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/proguard/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/resources/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/resources/build.gradle index 9db2a01e22..616c3825c7 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/resources/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/resources/build.gradle @@ -2,6 +2,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/resources/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/resources/settings.gradle index 9df72a09ce..6d72c98406 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/resources/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/resources/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/unpackSkiko/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/unpackSkiko/build.gradle index d49191985c..2ccc034b0f 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/unpackSkiko/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/unpackSkiko/build.gradle @@ -2,6 +2,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/unpackSkiko/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/unpackSkiko/settings.gradle index 9df72a09ce..6d72c98406 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/unpackSkiko/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/unpackSkiko/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/build.gradle b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/build.gradle similarity index 100% rename from gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/build.gradle rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/build.gradle diff --git a/gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/main-image.expected.png b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/main-image.expected.png similarity index 100% rename from gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/main-image.expected.png rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/main-image.expected.png diff --git a/gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/settings.gradle b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/settings.gradle similarity index 100% rename from gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/settings.gradle rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/settings.gradle diff --git a/gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/src/desktopMain/kotlin/Main.kt b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/src/desktopMain/kotlin/Main.kt similarity index 100% rename from gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/src/desktopMain/kotlin/Main.kt rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/src/desktopMain/kotlin/Main.kt diff --git a/gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/src/jsMain/kotlin/Main.js.kt b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/src/jsMain/kotlin/Main.js.kt similarity index 100% rename from gradle-plugins/compose/src/test/test-projects/application/custom-compiler-args/src/jsMain/kotlin/Main.js.kt rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler-args/src/jsMain/kotlin/Main.js.kt diff --git a/gradle-plugins/compose/src/test/test-projects/application/custom-compiler/build.gradle b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler/build.gradle similarity index 100% rename from gradle-plugins/compose/src/test/test-projects/application/custom-compiler/build.gradle rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler/build.gradle diff --git a/gradle-plugins/compose/src/test/test-projects/application/custom-compiler/main-image.expected.png b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler/main-image.expected.png similarity index 100% rename from gradle-plugins/compose/src/test/test-projects/application/custom-compiler/main-image.expected.png rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler/main-image.expected.png diff --git a/gradle-plugins/compose/src/test/test-projects/application/custom-compiler/settings.gradle b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler/settings.gradle similarity index 100% rename from gradle-plugins/compose/src/test/test-projects/application/custom-compiler/settings.gradle rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler/settings.gradle diff --git a/gradle-plugins/compose/src/test/test-projects/application/custom-compiler/src/main/kotlin/Main.kt b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler/src/main/kotlin/Main.kt similarity index 100% rename from gradle-plugins/compose/src/test/test-projects/application/custom-compiler/src/main/kotlin/Main.kt rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/custom-compiler/src/main/kotlin/Main.kt diff --git a/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/build.gradle b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/build.gradle new file mode 100644 index 0000000000..348b3a4b80 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/build.gradle @@ -0,0 +1,27 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.compose" +} + +kotlin { + js(IR) { + browser() + } + jvm {} + + sourceSets { + named("commonMain") { + } + named("jsMain") { + dependencies { + implementation(compose.html.core) + implementation(compose.runtime) + } + } + named("jvmMain") { + dependencies { + implementation(compose.desktop.currentOs) + } + } + } +} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/settings.gradle b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/settings.gradle similarity index 91% rename from gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/settings.gradle rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/settings.gradle index 5a99487916..afca8638e9 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/settings.gradle @@ -15,13 +15,12 @@ pluginManagement { } dependencyResolutionManagement { repositories { - mavenCentral() mavenLocal() + mavenCentral() google() maven { url 'https://maven.pkg.jetbrains.space/public/p/compose/dev' } } } -rootProject.name = "nativeCacheKind" -include(":subproject") \ No newline at end of file +rootProject.name = "jsMpp" \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/commonMain/kotlin/platform.kt b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/commonMain/kotlin/platform.kt new file mode 100644 index 0000000000..f60eab5ff6 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/commonMain/kotlin/platform.kt @@ -0,0 +1 @@ +expect fun getPlatformName(): String \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/jsMain/kotlin/platform.kt b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/jsMain/kotlin/platform.kt new file mode 100644 index 0000000000..b6d4a07a09 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/jsMain/kotlin/platform.kt @@ -0,0 +1 @@ +actual fun getPlatformName(): String = "js" \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/jvmMain/kotlin/platform.kt b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/jvmMain/kotlin/platform.kt new file mode 100644 index 0000000000..144ba29291 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/jsMpp/src/jvmMain/kotlin/platform.kt @@ -0,0 +1 @@ +actual fun getPlatformName(): String = "jvm" \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/build.gradle b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/build.gradle new file mode 100644 index 0000000000..33fc77b284 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/build.gradle @@ -0,0 +1,73 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + id "com.android.application" + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.compose" +} + +kotlin { + // empty stub (no actual android app) to detect configuration conflicts + // like https://github.com/JetBrains/compose-jb/issues/2345 + android() + + jvm() + sourceSets { + jvmMain { + dependsOn(commonMain) + + dependencies { + implementation(compose.desktop.currentOs) + } + } + } +} + +android { + namespace = "org.jetbrains.compose.testapp" + compileSdk = 31 + + defaultConfig { + minSdk = 21 + targetSdk = 31 + } +} + +kotlin { + jvm { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } + } +} + +compose.desktop { + application { + mainClass = "MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + + packageVersion = "1.0.0" + packageName = "TestPackage" + description = "Test description" + copyright = "Test Copyright Holder" + vendor = "Test Vendor" + + linux { + shortcut = true + packageName = "test-package" + debMaintainer = "example@example.com" + menuGroup = "menu-group" + } + windows { + console = true + dirChooser = true + perUserInstall = true + shortcut = true + menu = true + menuGroup = "compose" + upgradeUuid = "2d6ff464-75be-40ad-a256-56420b9cc374" + } + } + } +} diff --git a/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/gradle.properties b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/gradle.properties new file mode 100644 index 0000000000..2d8d1e4dd1 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/settings.gradle b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/settings.gradle similarity index 68% rename from gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/settings.gradle rename to gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/settings.gradle index fe85d46d01..0276bf4c39 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/settings.gradle @@ -2,6 +2,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' + id 'com.android.application' version 'AGP_VERSION_PLACEHOLDER' } repositories { mavenLocal() @@ -11,6 +12,9 @@ pluginManagement { maven { url 'https://maven.pkg.jetbrains.space/public/p/compose/dev' } + maven { + url 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/' + } } } dependencyResolutionManagement { @@ -21,6 +25,9 @@ dependencyResolutionManagement { maven { url 'https://maven.pkg.jetbrains.space/public/p/compose/dev' } + maven { + url 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/' + } } } -rootProject.name = "customCompilerUnsupportedPlatformsWarning" +rootProject.name = "mpp" \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/src/jvmMain/kotlin/main.kt b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/src/jvmMain/kotlin/main.kt new file mode 100644 index 0000000000..4d7210e2be --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/beforeKotlin2/mpp/src/jvmMain/kotlin/main.kt @@ -0,0 +1,3 @@ +fun main() { + println("Kotlin MPP app is running!") +} \ 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 index 5e0fd3e9fa..c139eff8a7 100644 --- 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 @@ -1,5 +1,6 @@ plugins { kotlin("multiplatform") + kotlin("plugin.compose") id("org.jetbrains.compose") id("com.github.gmazzo.buildconfig") } 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 index 53f913fce7..f31c0a79f5 100644 --- 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 @@ -1,4 +1,5 @@ plugins { kotlin("multiplatform").apply(false) + kotlin("plugin.compose").apply(false) id("org.jetbrains.compose").apply(false) } 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 index af0c57f19b..30b8363492 100644 --- 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 @@ -9,6 +9,7 @@ pluginManagement { } plugins { id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER") + id("org.jetbrains.kotlin.plugin.compose").version("KOTLIN_VERSION_PLACEHOLDER") id("org.jetbrains.compose").version("COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER") id("com.github.gmazzo.buildconfig").version("5.3.5") } 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 aeafd38a84..5629e0ad3e 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,5 +1,6 @@ plugins { kotlin("multiplatform") + kotlin("plugin.compose") id("com.android.application") id("org.jetbrains.compose") } @@ -9,8 +10,10 @@ group = "app.group" kotlin { androidTarget { compilations.all { - kotlinOptions { - jvmTarget = "11" + compileTaskProvider { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } } } } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceAccessors/my/lib/res/String0.androidMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceAccessors/my/lib/res/String0.androidMain.kt index 4c07207da0..5d0d9e00b0 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceAccessors/my/lib/res/String0.androidMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceAccessors/my/lib/res/String0.androidMain.kt @@ -16,7 +16,7 @@ public val Res.string.android_str: StringResource private fun init_android_str(): StringResource = org.jetbrains.compose.resources.StringResource( "string:android_str", "android_str", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.androidMain.cvr", 10, - 39), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.androidMain.cvr", 10, 39), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt index d85c4af1ad..fc67e497cb 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt @@ -29,7 +29,8 @@ private fun init__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", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/drawable/3-strange-name.xml", -1, -1), ) ) @@ -40,7 +41,8 @@ private fun init_camelCaseName(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:camelCaseName", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/camelCaseName.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/drawable/camelCaseName.xml", -1, -1), ) ) @@ -50,7 +52,8 @@ public val Res.drawable.`is`: DrawableResource private fun init_is(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:is", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/is.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/drawable/is.xml", -1, -1), ) ) @@ -62,18 +65,20 @@ private fun init_vector(): DrawableResource = org.jetbrains.compose.resources.Dr setOf( org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("ast"), - ), "drawable-ast/vector.xml", -1, -1), + ), "composeResources/my.lib.res/drawable-ast/vector.xml", -1, -1), org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("au"), - org.jetbrains.compose.resources.RegionQualifier("US"), ), "drawable-au-rUS/vector.xml", -1, -1), + org.jetbrains.compose.resources.RegionQualifier("US"), ), + "composeResources/my.lib.res/drawable-au-rUS/vector.xml", -1, -1), org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.ThemeQualifier.DARK, org.jetbrains.compose.resources.LanguageQualifier("ge"), ), - "drawable-dark-ge/vector.xml", -1, -1), + "composeResources/my.lib.res/drawable-dark-ge/vector.xml", -1, -1), org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), - ), "drawable-en/vector.xml", -1, -1), - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml", -1, -1), + ), "composeResources/my.lib.res/drawable-en/vector.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/drawable/vector.xml", -1, -1), ) ) @@ -83,6 +88,7 @@ public val Res.drawable.vector_2: DrawableResource private fun init_vector_2(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:vector_2", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector_2.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/drawable/vector_2.xml", -1, -1), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Font0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Font0.commonMain.kt index 1d69a111bb..c754abef21 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Font0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Font0.commonMain.kt @@ -18,7 +18,8 @@ private fun init_emptyFont(): FontResource = org.jetbrains.compose.resources.Fon setOf( org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), - ), "font-en/emptyFont.otf", -1, -1), - org.jetbrains.compose.resources.ResourceItem(setOf(), "font/emptyFont.otf", -1, -1), + ), "composeResources/my.lib.res/font-en/emptyFont.otf", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/font/emptyFont.otf", -1, -1), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Plurals0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Plurals0.commonMain.kt index 95ecbbca59..e97fd6730d 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Plurals0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Plurals0.commonMain.kt @@ -17,7 +17,7 @@ private fun init_numberOfSongsAvailable(): PluralStringResource = org.jetbrains.compose.resources.PluralStringResource( "plurals:numberOfSongsAvailable", "numberOfSongsAvailable", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 10, - 124), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 10, 124), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt index e7ddb90a51..401c58853a 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt @@ -37,8 +37,8 @@ public val Res.string.PascalCase: StringResource private fun init_PascalCase(): StringResource = org.jetbrains.compose.resources.StringResource( "string:PascalCase", "PascalCase", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 172, - 34), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 172, 34), ) ) @@ -48,8 +48,8 @@ public val Res.string._1_kebab_case: StringResource private fun init__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.commonMain.cvr", 135, - 36), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 135, 36), ) ) @@ -59,8 +59,8 @@ public val Res.string.app_name: StringResource private fun init_app_name(): StringResource = org.jetbrains.compose.resources.StringResource( "string:app_name", "app_name", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 207, - 44), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 207, 44), ) ) @@ -70,8 +70,8 @@ public val Res.string.camelCase: StringResource private fun init_camelCase(): StringResource = org.jetbrains.compose.resources.StringResource( "string:camelCase", "camelCase", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 252, - 29), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 252, 29), ) ) @@ -81,8 +81,8 @@ public val Res.string.hello: StringResource private fun init_hello(): StringResource = org.jetbrains.compose.resources.StringResource( "string:hello", "hello", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 282, - 37), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 282, 37), ) ) @@ -93,8 +93,8 @@ private fun `init_info_using_release_$x`(): StringResource = org.jetbrains.compose.resources.StringResource( "string:info_using_release_${'$'}x", "info_using_release_${'$'}x", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 320, - 57), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 320, 57), ) ) @@ -104,8 +104,8 @@ public val Res.string.multi_line: StringResource private fun init_multi_line(): StringResource = org.jetbrains.compose.resources.StringResource( "string:multi_line", "multi_line", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 378, - 178), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 378, 178), ) ) @@ -115,7 +115,7 @@ public val Res.string.str_template: StringResource private fun init_str_template(): StringResource = org.jetbrains.compose.resources.StringResource( "string:str_template", "str_template", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 557, - 76), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 557, 76), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonResClass/my/lib/res/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonResClass/my/lib/res/Res.kt index e5a1409634..50ce381bf1 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonResClass/my/lib/res/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonResClass/my/lib/res/Res.kt @@ -22,7 +22,8 @@ public object Res { * @return The content of the file as a byte array. */ @ExperimentalResourceApi - public suspend fun readBytes(path: String): ByteArray = readResourceBytes("" + path) + public suspend fun readBytes(path: String): ByteArray = + readResourceBytes("composeResources/my.lib.res/" + path) /** * Returns the URI string of the resource file at the specified path. @@ -33,7 +34,7 @@ public object Res { * @return The URI string of the file. */ @ExperimentalResourceApi - public fun getUri(path: String): String = getResourceUri("" + path) + public fun getUri(path: String): String = getResourceUri("composeResources/my.lib.res/" + path) public object drawable diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceAccessors/my/lib/res/String0.desktopMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceAccessors/my/lib/res/String0.desktopMain.kt index 4fcecd75b6..d002471d46 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceAccessors/my/lib/res/String0.desktopMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceAccessors/my/lib/res/String0.desktopMain.kt @@ -17,6 +17,6 @@ private fun init_desktop_str(): StringResource = org.jetbrains.compose.resources "string:desktop_str", "desktop_str", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), - "values/desktop_strings.desktopMain.cvr", 10, 39), + "composeResources/my.lib.res/values/desktop_strings.desktopMain.cvr", 10, 39), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceAccessors/app/group/resources_test/generated/resources/String0.androidMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceAccessors/app/group/resources_test/generated/resources/String0.androidMain.kt index 98e0355454..0b672fbfde 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceAccessors/app/group/resources_test/generated/resources/String0.androidMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceAccessors/app/group/resources_test/generated/resources/String0.androidMain.kt @@ -16,7 +16,8 @@ internal val Res.string.android_str: StringResource private fun init_android_str(): StringResource = org.jetbrains.compose.resources.StringResource( "string:android_str", "android_str", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.androidMain.cvr", 10, - 39), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.androidMain.cvr", + 10, 39), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt index 0853a96b34..d4b0e2aa25 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt @@ -29,7 +29,8 @@ private fun init__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", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/drawable/3-strange-name.xml", -1, -1), ) ) @@ -40,7 +41,8 @@ private fun init_camelCaseName(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:camelCaseName", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/camelCaseName.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/drawable/camelCaseName.xml", -1, -1), ) ) @@ -50,7 +52,8 @@ internal val Res.drawable.`is`: DrawableResource private fun init_is(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:is", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/is.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/drawable/is.xml", -1, -1), ) ) @@ -62,18 +65,22 @@ private fun init_vector(): DrawableResource = org.jetbrains.compose.resources.Dr setOf( org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("ast"), - ), "drawable-ast/vector.xml", -1, -1), + ), + "composeResources/app.group.resources_test.generated.resources/drawable-ast/vector.xml", -1, -1), org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("au"), - org.jetbrains.compose.resources.RegionQualifier("US"), ), "drawable-au-rUS/vector.xml", -1, -1), + org.jetbrains.compose.resources.RegionQualifier("US"), ), + "composeResources/app.group.resources_test.generated.resources/drawable-au-rUS/vector.xml", -1, -1), org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.ThemeQualifier.DARK, org.jetbrains.compose.resources.LanguageQualifier("ge"), ), - "drawable-dark-ge/vector.xml", -1, -1), + "composeResources/app.group.resources_test.generated.resources/drawable-dark-ge/vector.xml", -1, -1), org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), - ), "drawable-en/vector.xml", -1, -1), - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml", -1, -1), + ), + "composeResources/app.group.resources_test.generated.resources/drawable-en/vector.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/drawable/vector.xml", -1, -1), ) ) @@ -83,6 +90,7 @@ internal val Res.drawable.vector_2: DrawableResource private fun init_vector_2(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:vector_2", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector_2.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/drawable/vector_2.xml", -1, -1), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Font0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Font0.commonMain.kt index 89ff6eb7b8..432d74d5f6 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Font0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Font0.commonMain.kt @@ -18,7 +18,9 @@ private fun init_emptyFont(): FontResource = org.jetbrains.compose.resources.Fon setOf( org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), - ), "font-en/emptyFont.otf", -1, -1), - org.jetbrains.compose.resources.ResourceItem(setOf(), "font/emptyFont.otf", -1, -1), + ), + "composeResources/app.group.resources_test.generated.resources/font-en/emptyFont.otf", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/font/emptyFont.otf", -1, -1), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Plurals0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Plurals0.commonMain.kt index 9674a83744..b6b1abf697 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Plurals0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Plurals0.commonMain.kt @@ -17,7 +17,8 @@ private fun init_numberOfSongsAvailable(): PluralStringResource = org.jetbrains.compose.resources.PluralStringResource( "plurals:numberOfSongsAvailable", "numberOfSongsAvailable", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 10, - 124), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 10, 124), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt index 570cccd780..9576f03c4f 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt @@ -37,8 +37,9 @@ internal val Res.string.PascalCase: StringResource private fun init_PascalCase(): StringResource = org.jetbrains.compose.resources.StringResource( "string:PascalCase", "PascalCase", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 172, - 34), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 172, 34), ) ) @@ -48,8 +49,9 @@ internal val Res.string._1_kebab_case: StringResource private fun init__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.commonMain.cvr", 135, - 36), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 135, 36), ) ) @@ -59,8 +61,9 @@ internal val Res.string.app_name: StringResource private fun init_app_name(): StringResource = org.jetbrains.compose.resources.StringResource( "string:app_name", "app_name", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 207, - 44), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 207, 44), ) ) @@ -70,8 +73,9 @@ internal val Res.string.camelCase: StringResource private fun init_camelCase(): StringResource = org.jetbrains.compose.resources.StringResource( "string:camelCase", "camelCase", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 252, - 29), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 252, 29), ) ) @@ -81,8 +85,9 @@ internal val Res.string.hello: StringResource private fun init_hello(): StringResource = org.jetbrains.compose.resources.StringResource( "string:hello", "hello", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 282, - 37), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 282, 37), ) ) @@ -93,8 +98,9 @@ private fun `init_info_using_release_$x`(): StringResource = org.jetbrains.compose.resources.StringResource( "string:info_using_release_${'$'}x", "info_using_release_${'$'}x", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 320, - 57), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 320, 57), ) ) @@ -104,8 +110,9 @@ internal val Res.string.multi_line: StringResource private fun init_multi_line(): StringResource = org.jetbrains.compose.resources.StringResource( "string:multi_line", "multi_line", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 378, - 178), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 378, 178), ) ) @@ -115,7 +122,8 @@ internal val Res.string.str_template: StringResource private fun init_str_template(): StringResource = org.jetbrains.compose.resources.StringResource( "string:str_template", "str_template", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "values/strings.commonMain.cvr", 557, - 76), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 557, 76), ) ) \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonResClass/app/group/resources_test/generated/resources/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonResClass/app/group/resources_test/generated/resources/Res.kt index 9b19cafa4c..58b19ebe7a 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonResClass/app/group/resources_test/generated/resources/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonResClass/app/group/resources_test/generated/resources/Res.kt @@ -22,7 +22,8 @@ internal object Res { * @return The content of the file as a byte array. */ @ExperimentalResourceApi - public suspend fun readBytes(path: String): ByteArray = readResourceBytes("" + path) + public suspend fun readBytes(path: String): ByteArray = + readResourceBytes("composeResources/app.group.resources_test.generated.resources/" + path) /** * Returns the URI string of the resource file at the specified path. @@ -33,7 +34,8 @@ internal object Res { * @return The URI string of the file. */ @ExperimentalResourceApi - public fun getUri(path: String): String = getResourceUri("" + path) + public fun getUri(path: String): String = + getResourceUri("composeResources/app.group.resources_test.generated.resources/" + path) public object drawable diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceAccessors/app/group/resources_test/generated/resources/String0.desktopMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceAccessors/app/group/resources_test/generated/resources/String0.desktopMain.kt index badfa7b5b2..0eabfd7621 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceAccessors/app/group/resources_test/generated/resources/String0.desktopMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceAccessors/app/group/resources_test/generated/resources/String0.desktopMain.kt @@ -17,6 +17,7 @@ private fun init_desktop_str(): StringResource = org.jetbrains.compose.resources "string:desktop_str", "desktop_str", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), - "values/desktop_strings.desktopMain.cvr", 10, 39), + "composeResources/app.group.resources_test.generated.resources/values/desktop_strings.desktopMain.cvr", + 10, 39), ) ) \ No newline at end of file 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 19b673273f..30b9d60bc6 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 @@ -9,6 +9,7 @@ pluginManagement { plugins { id("com.android.application").version("AGP_VERSION_PLACEHOLDER") id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER") + id("org.jetbrains.kotlin.plugin.compose").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/emptyResources/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/build.gradle.kts index 699c9aa0f2..f321450f7c 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/build.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("multiplatform") + kotlin("plugin.compose") id("org.jetbrains.compose") } 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 b6fbefa22e..b6af81dff5 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 @@ -22,7 +22,8 @@ internal object Res { * @return The content of the file as a byte array. */ @ExperimentalResourceApi - public suspend fun readBytes(path: String): ByteArray = readResourceBytes("" + path) + public suspend fun readBytes(path: String): ByteArray = + readResourceBytes("composeResources/app.group.empty_res.generated.resources/" + path) /** * Returns the URI string of the resource file at the specified path. @@ -33,7 +34,8 @@ internal object Res { * @return The URI string of the file. */ @ExperimentalResourceApi - public fun getUri(path: String): String = getResourceUri("" + path) + public fun getUri(path: String): String = + getResourceUri("composeResources/app.group.empty_res.generated.resources/" + path) public object drawable diff --git a/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/settings.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/settings.gradle.kts index 38898d0d94..65030c40e7 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/settings.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/settings.gradle.kts @@ -8,6 +8,7 @@ pluginManagement { } plugins { id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER") + id("org.jetbrains.kotlin.plugin.compose").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/iosResources/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/iosResources/build.gradle.kts index daeacab621..e8f759c1b8 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/iosResources/build.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/iosResources/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("multiplatform") + kotlin("plugin.compose") kotlin("native.cocoapods") id("org.jetbrains.compose") } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/iosResources/expected/iosResources.podspec b/gradle-plugins/compose/src/test/test-projects/misc/iosResources/expected/iosResources.podspec index f08c84b958..aad9ba4363 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/iosResources/expected/iosResources.podspec +++ b/gradle-plugins/compose/src/test/test-projects/misc/iosResources/expected/iosResources.podspec @@ -22,6 +22,10 @@ Pod::Spec.new do |spec| Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" end + spec.xcconfig = { + 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO', + } + spec.pod_target_xcconfig = { 'KOTLIN_PROJECT_PATH' => '', 'PRODUCT_MODULE_NAME' => 'shared', diff --git a/gradle-plugins/compose/src/test/test-projects/misc/iosResources/settings.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/iosResources/settings.gradle.kts index 5efe902b20..af172cd475 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/iosResources/settings.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/iosResources/settings.gradle.kts @@ -8,6 +8,7 @@ pluginManagement { } plugins { id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER") + id("org.jetbrains.kotlin.plugin.compose").version("KOTLIN_VERSION_PLACEHOLDER") id("org.jetbrains.kotlin.native.cocoapods").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/jsMpp/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/jsMpp/build.gradle index e409ff5ffc..53e1c74582 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jsMpp/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/misc/jsMpp/build.gradle @@ -1,7 +1,6 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat - plugins { id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jsMpp/settings.gradle b/gradle-plugins/compose/src/test/test-projects/misc/jsMpp/settings.gradle index afca8638e9..a7062f47ee 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jsMpp/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/misc/jsMpp/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/build.gradle.kts index 269826d575..57abbb99b6 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/build.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("org.jetbrains.compose") + kotlin("plugin.compose") kotlin("jvm") } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/settings.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/settings.gradle.kts index db2097cc90..379274b6cc 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/settings.gradle.kts +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/settings.gradle.kts @@ -8,6 +8,7 @@ pluginManagement { } plugins { id("org.jetbrains.kotlin.jvm").version("KOTLIN_VERSION_PLACEHOLDER") + id("org.jetbrains.kotlin.plugin.compose").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/jvmPreview/common/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/common/build.gradle index af37d00512..420faf2b99 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/common/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/common/build.gradle @@ -1,5 +1,6 @@ plugins { id 'org.jetbrains.kotlin.multiplatform' + id 'org.jetbrains.kotlin.plugin.compose' id 'org.jetbrains.compose' } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/jvm/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/jvm/build.gradle index 75721fd9c7..80b5af1c9c 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/jvm/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/jvm/build.gradle @@ -1,5 +1,6 @@ plugins { id 'org.jetbrains.kotlin.jvm' + id 'org.jetbrains.kotlin.plugin.compose' id 'org.jetbrains.compose' } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/mpp/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/mpp/build.gradle index 884f35288f..a567a308bb 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/mpp/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/mpp/build.gradle @@ -1,5 +1,6 @@ plugins { id 'org.jetbrains.kotlin.multiplatform' + id 'org.jetbrains.kotlin.plugin.compose' id 'org.jetbrains.compose' } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/settings.gradle b/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/settings.gradle index 11b571ba96..ceb7bfdecf 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmPreview/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.kotlin.jvm' 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/nativeCacheKind/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/build.gradle deleted file mode 100644 index 1e43a147b8..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" - id "org.jetbrains.compose" -} - -kotlin { - iosX64 { - binaries.framework { - isStatic = true - baseName = "shared" - } - } - iosArm64 { - binaries.framework { - isStatic = true - baseName = "shared" - } - } - - sourceSets { - commonMain { - dependencies { - implementation(compose.runtime) - } - } - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/gradle.properties b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/gradle.properties deleted file mode 100644 index 695204ddbf..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -org.gradle.jvmargs=-Xmx8096M -org.jetbrains.compose.experimental.uikit.enabled=true \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/settings.gradle b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/settings.gradle deleted file mode 100644 index 5717a21c96..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/settings.gradle +++ /dev/null @@ -1,27 +0,0 @@ -pluginManagement { - plugins { - id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER' - id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' - } - repositories { - mavenLocal() - gradlePluginPortal() - mavenCentral() - google() - maven { - url 'https://maven.pkg.jetbrains.space/public/p/compose/dev' - } - } -} -dependencyResolutionManagement { - repositories { - mavenLocal() - mavenCentral() - google() - maven { - url 'https://maven.pkg.jetbrains.space/public/p/compose/dev' - } - } -} -rootProject.name = "nativeCacheKind" -include(":subproject") \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/src/commonMain/kotlin/App.kt deleted file mode 100644 index 2fe6d0719f..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/src/commonMain/kotlin/App.kt +++ /dev/null @@ -1,10 +0,0 @@ -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue - -@Composable -fun App() { - var text by remember { mutableStateOf("Hello, World!") } -} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/subproject/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/subproject/build.gradle deleted file mode 100644 index 1e43a147b8..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/subproject/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" - id "org.jetbrains.compose" -} - -kotlin { - iosX64 { - binaries.framework { - isStatic = true - baseName = "shared" - } - } - iosArm64 { - binaries.framework { - isStatic = true - baseName = "shared" - } - } - - sourceSets { - commonMain { - dependencies { - implementation(compose.runtime) - } - } - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/subproject/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/subproject/src/commonMain/kotlin/App.kt deleted file mode 100644 index 2fe6d0719f..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/subproject/src/commonMain/kotlin/App.kt +++ /dev/null @@ -1,10 +0,0 @@ -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue - -@Composable -fun App() { - var text by remember { mutableStateOf("Hello, World!") } -} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/build.gradle deleted file mode 100644 index 40170b68cb..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/build.gradle +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" - id "org.jetbrains.compose" -} - -kotlin { - iosX64() - iosArm64() - iosSimulatorArm64() - macosX64() - macosArm64() - - sourceSets { - commonMain { - dependencies { - implementation(compose.runtime) - } - } - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/gradle.properties b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/gradle.properties deleted file mode 100644 index 689880ee3f..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -org.jetbrains.compose.experimental.uikit.enabled=true \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/src/commonMain/kotlin/App.kt deleted file mode 100644 index 2fe6d0719f..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/src/commonMain/kotlin/App.kt +++ /dev/null @@ -1,10 +0,0 @@ -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue - -@Composable -fun App() { - var text by remember { mutableStateOf("Hello, World!") } -} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/subproject/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/subproject/build.gradle deleted file mode 100644 index 40170b68cb..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/subproject/build.gradle +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" - id "org.jetbrains.compose" -} - -kotlin { - iosX64() - iosArm64() - iosSimulatorArm64() - macosX64() - macosArm64() - - sourceSets { - commonMain { - dependencies { - implementation(compose.runtime) - } - } - } -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/subproject/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/subproject/src/commonMain/kotlin/App.kt deleted file mode 100644 index 2fe6d0719f..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindError/subproject/src/commonMain/kotlin/App.kt +++ /dev/null @@ -1,10 +0,0 @@ -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue - -@Composable -fun App() { - var text by remember { mutableStateOf("Hello, World!") } -} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle index dc861c0809..f9dc490179 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle @@ -1,7 +1,6 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat - plugins { id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.compose" id "org.jetbrains.compose" } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/gradle.properties b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/gradle.properties index 9c8f6b37b5..4fae7bbeff 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/gradle.properties +++ b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/gradle.properties @@ -1 +1,2 @@ +org.gradle.jvmargs=-Xmx8096M org.jetbrains.compose.experimental.jscanvas.enabled=true diff --git a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/settings.gradle b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/settings.gradle index 3e41d3e3e9..56a55d1288 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/settings.gradle +++ b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/settings.gradle @@ -1,6 +1,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' } repositories { diff --git a/gradle-plugins/gradle.properties b/gradle-plugins/gradle.properties index 43681791fc..af381fcbcb 100644 --- a/gradle-plugins/gradle.properties +++ b/gradle-plugins/gradle.properties @@ -9,18 +9,14 @@ dev.junit.parallel=false # Default version of Compose Libraries used by Gradle plugin compose.version=1.6.10 -# The latest version of Compose Compiler used by Gradle plugin. Used only in tests/CI. -compose.tests.compiler.version=1.5.14 # The latest version of Kotlin compatible with compose.tests.compiler.version. Used only in tests/CI. -compose.tests.compiler.compatible.kotlin.version=1.9.24 -# The latest version of Kotlin compatible with compose.tests.compiler.version for JS target. Used only on CI. -compose.tests.js.compiler.compatible.kotlin.version=1.9.24 +compose.tests.kotlin.version=2.0.0 # __SUPPORTED_GRADLE_VERSIONS__ # Don't forget to edit versions in .github/workflows/gradle-plugin.yml as well # and Publish.Subtasks.buildTypes.gradle.GradlePluginTestKt#gradleVersions in the TC config # minimal and current gradle version -compose.tests.gradle.versions=7.4, 8.7 -compose.tests.agp.versions=8.1.0, 8.4.0 +compose.tests.gradle.versions=7.4, 8.8 +compose.tests.agp.versions=8.1.0, 8.5.0 # A version of Gradle plugin, that will be published, # unless overridden by COMPOSE_GRADLE_PLUGIN_VERSION env var. diff --git a/gradle-plugins/gradle/libs.versions.toml b/gradle-plugins/gradle/libs.versions.toml index 5a2fc3056a..b8f8f04a8c 100644 --- a/gradle-plugins/gradle/libs.versions.toml +++ b/gradle-plugins/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.9.0" +kotlin = "2.0.0" gradle-download-plugin = "5.5.0" kotlin-poet = "1.16.0" plugin-android = "7.3.0" diff --git a/gradle-plugins/gradle/wrapper/gradle-wrapper.jar b/gradle-plugins/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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

NnX)EH+Nua)3Y(c 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 diff --git a/gradle-plugins/gradle/wrapper/gradle-wrapper.properties b/gradle-plugins/gradle/wrapper/gradle-wrapper.properties index 48c0a02ca4..a4413138c9 100644 --- a/gradle-plugins/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugins/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle-plugins/gradlew b/gradle-plugins/gradlew index fbd7c51583..1aa94a4269 100755 --- a/gradle-plugins/gradlew +++ b/gradle-plugins/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,99 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradle-plugins/gradlew.bat b/gradle-plugins/gradlew.bat index a9f778a7a9..7101f8e467 100644 --- a/gradle-plugins/gradlew.bat +++ b/gradle-plugins/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -54,31 +55,16 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From dea37a012d06dd64bde9e1fb27e387145eb7d453 Mon Sep 17 00:00:00 2001 From: Evgeniy Zhelenskiy <55230817+zhelenskiy@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:05:18 +0200 Subject: [PATCH 32/47] File associations (#4957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add file associations support to Compose Desktop Fixes #773 ## Testing Tested on the [sample project](https://github.com/zhelenskiy/file-associations-demo). Behaviours per OSs: - MacOS Sonoma: associations work for distributables. - Windows 11: associations work after the installation of the MSI. - Kubuntu: associations do not work, but everything else works fine. However, IDEA also does not have associations there, so I assume this is fine. I didn't write any unit tests because I don’t know which of them you are expecting me to write. So, I'm looking forward to your feedback and suggestions. This should be tested by QA ## Release Notes ### Highlight - Desktop - Introduction of the new DSL function in `nativeDistributions` block: ```kotlin fun fileAssociation(mimeType: String, extension: String, description: String): Unit ``` --- .../application/dsl/FileAssociation.kt | 11 ++ .../dsl/JvmApplicationDistributions.kt | 11 ++ .../application/dsl/PlatformSettings.kt | 8 + .../application/internal/InfoPlistBuilder.kt | 65 ++++++- .../internal/configureJvmApplication.kt | 3 + .../application/tasks/AbstractJPackageTask.kt | 93 ++++++++++ .../macOptions/Expected-Info.plist | 161 +++++++++++++----- .../macOptions/Kotlin_icon_big.icns | Bin 0 -> 78153 bytes .../macOptions/Kotlin_icon_big.ico | Bin 0 -> 4286 bytes .../macOptions/Kotlin_icon_big.png | Bin 0 -> 36487 bytes .../application/macOptions/build.gradle | 46 +++-- .../macOptions/subdir/Kotlin_icon_big.icns | Bin 0 -> 78153 bytes 12 files changed, 337 insertions(+), 61 deletions(-) create mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/FileAssociation.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/application/macOptions/Kotlin_icon_big.icns create mode 100644 gradle-plugins/compose/src/test/test-projects/application/macOptions/Kotlin_icon_big.ico create mode 100644 gradle-plugins/compose/src/test/test-projects/application/macOptions/Kotlin_icon_big.png create mode 100644 gradle-plugins/compose/src/test/test-projects/application/macOptions/subdir/Kotlin_icon_big.icns diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/FileAssociation.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/FileAssociation.kt new file mode 100644 index 0000000000..a9e2bbff22 --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/FileAssociation.kt @@ -0,0 +1,11 @@ +package org.jetbrains.compose.desktop.application.dsl + +import java.io.File +import java.io.Serializable + +internal data class FileAssociation( + val mimeType: String, + val extension: String, + val description: String, + val iconFile: File?, +) : Serializable diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/JvmApplicationDistributions.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/JvmApplicationDistributions.kt index e9fb935df6..f59b838b6a 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/JvmApplicationDistributions.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/JvmApplicationDistributions.kt @@ -6,6 +6,7 @@ package org.jetbrains.compose.desktop.application.dsl import org.gradle.api.Action +import java.io.File internal val DEFAULT_RUNTIME_MODULES = arrayOf( "java.base", "java.desktop", "java.logging", "jdk.crypto.ec" @@ -32,4 +33,14 @@ abstract class JvmApplicationDistributions : AbstractDistributions() { fun windows(fn: Action) { fn.execute(windows) } + + @JvmOverloads + fun fileAssociation( + mimeType: String, extension: String, description: String, + linuxIconFile: File? = null, windowsIconFile: File? = null, macOSIconFile: File? = null, + ) { + linux.fileAssociation(mimeType, extension, description, linuxIconFile) + windows.fileAssociation(mimeType, extension, description, windowsIconFile) + macOS.fileAssociation(mimeType, extension, description, macOSIconFile) + } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/PlatformSettings.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/PlatformSettings.kt index f8b3e3450b..d70ca65adf 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/PlatformSettings.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/PlatformSettings.kt @@ -8,6 +8,7 @@ package org.jetbrains.compose.desktop.application.dsl import org.gradle.api.Action import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory +import java.io.File import javax.inject.Inject abstract class AbstractPlatformSettings { @@ -17,6 +18,13 @@ abstract class AbstractPlatformSettings { val iconFile: RegularFileProperty = objects.fileProperty() var packageVersion: String? = null var installationPath: String? = null + + internal val fileAssociations: MutableSet = mutableSetOf() + + @JvmOverloads + fun fileAssociation(mimeType: String, extension: String, description: String, iconFile: File? = null) { + fileAssociations.add(FileAssociation(mimeType, extension, description, iconFile)) + } } abstract class AbstractMacOSPlatformSettings : AbstractPlatformSettings() { diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/InfoPlistBuilder.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/InfoPlistBuilder.kt index e349305730..aca1d8e858 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/InfoPlistBuilder.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/InfoPlistBuilder.kt @@ -5,14 +5,58 @@ package org.jetbrains.compose.desktop.application.internal +import org.jetbrains.compose.desktop.application.internal.InfoPlistBuilder.InfoPlistValue.* import java.io.File import kotlin.reflect.KProperty +private const val indent = " " +private fun indentForLevel(level: Int) = indent.repeat(level) + internal class InfoPlistBuilder(private val extraPlistKeysRawXml: String? = null) { - private val values = LinkedHashMap() + internal sealed class InfoPlistValue { + abstract fun asPlistEntry(nestingLevel: Int): String + data class InfoPlistListValue(val elements: List) : InfoPlistValue() { + override fun asPlistEntry(nestingLevel: Int): String = + if (elements.isEmpty()) "${indentForLevel(nestingLevel)}" + else elements.joinToString( + separator = "\n", + prefix = "${indentForLevel(nestingLevel)}\n", + postfix = "\n${indentForLevel(nestingLevel)}" + ) { + it.asPlistEntry(nestingLevel + 1) + } + + constructor(vararg elements: InfoPlistValue) : this(elements.asList()) + } + + data class InfoPlistMapValue(val elements: Map) : InfoPlistValue() { + override fun asPlistEntry(nestingLevel: Int): String = + if (elements.isEmpty()) "${indentForLevel(nestingLevel)}" + else elements.entries.joinToString( + separator = "\n", + prefix = "${indentForLevel(nestingLevel)}\n", + postfix = "\n${indentForLevel(nestingLevel)}", + ) { (key, value) -> + "${indentForLevel(nestingLevel + 1)}${key.name}\n${value.asPlistEntry(nestingLevel + 1)}" + } + + constructor(vararg elements: Pair) : this(elements.toMap()) + } + + data class InfoPlistStringValue(val value: String) : InfoPlistValue() { + override fun asPlistEntry(nestingLevel: Int): String = if (value.isEmpty()) "${indentForLevel(nestingLevel)}" else "${indentForLevel(nestingLevel)}$value" + } + } + + private val values = LinkedHashMap() + + operator fun get(key: InfoPlistKey): InfoPlistValue? = values[key] + operator fun set(key: InfoPlistKey, value: String?) = set(key, value?.let(::InfoPlistStringValue)) + operator fun set(key: InfoPlistKey, value: List?) = set(key, value?.let(::InfoPlistListValue)) + operator fun set(key: InfoPlistKey, value: Map?) = + set(key, value?.let(::InfoPlistMapValue)) - operator fun get(key: InfoPlistKey): String? = values[key] - operator fun set(key: InfoPlistKey, value: String?) { + operator fun set(key: InfoPlistKey, value: InfoPlistValue?) { if (value != null) { values[key] = value } else { @@ -26,13 +70,13 @@ internal class InfoPlistBuilder(private val extraPlistKeysRawXml: String? = null appendLine("") appendLine("") appendLine("") - appendLine(" ") + appendLine("${indentForLevel(1)}") for ((k, v) in values) { - appendLine(" ${k.name}") - appendLine(" $v") + appendLine("${indentForLevel(2)}${k.name}") + appendLine(v.asPlistEntry(2)) } extraPlistKeysRawXml?.let { appendLine(it) } - appendLine(" ") + appendLine("${indentForLevel(1)}") appendLine("") } } @@ -48,6 +92,13 @@ internal object PlistKeys { val LSMinimumSystemVersion by this val CFBundleDevelopmentRegion by this val CFBundleAllowMixedLocalizations by this + val CFBundleDocumentTypes by this + val CFBundleTypeRole by this + val CFBundleTypeExtensions by this + val CFBundleTypeIconFile by this + val CFBundleTypeMIMETypes by this + val CFBundleTypeName by this + val CFBundleTypeOSTypes by this val CFBundleExecutable by this val CFBundleIconFile by this val CFBundleIdentifier by this diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt index d53ad3dd62..83d51089ef 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt @@ -375,6 +375,7 @@ internal fun JvmApplicationContext.configurePlatformSettings( packageTask.linuxRpmLicenseType.set(provider { linux.rpmLicenseType }) packageTask.iconFile.set(linux.iconFile.orElse(defaultResources.get { linuxIcon })) packageTask.installationPath.set(linux.installationPath) + packageTask.fileAssociations.set(provider { linux.fileAssociations }) } } OS.Windows -> { @@ -388,6 +389,7 @@ internal fun JvmApplicationContext.configurePlatformSettings( packageTask.winUpgradeUuid.set(provider { win.upgradeUuid }) packageTask.iconFile.set(win.iconFile.orElse(defaultResources.get { windowsIcon })) packageTask.installationPath.set(win.installationPath) + packageTask.fileAssociations.set(provider { win.fileAssociations }) } } OS.MacOS -> { @@ -414,6 +416,7 @@ internal fun JvmApplicationContext.configurePlatformSettings( packageTask.nonValidatedMacSigningSettings = app.nativeDistributions.macOS.signing packageTask.iconFile.set(mac.iconFile.orElse(defaultResources.get { macIcon })) packageTask.installationPath.set(mac.installationPath) + packageTask.fileAssociations.set(provider { mac.fileAssociations }) } } } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt index ad56727c05..20c3926cd9 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt @@ -9,19 +9,23 @@ import org.gradle.api.file.* import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* import org.gradle.api.tasks.Optional import org.gradle.process.ExecResult import org.gradle.work.ChangeType import org.gradle.work.InputChanges +import org.jetbrains.compose.desktop.application.dsl.FileAssociation import org.jetbrains.compose.desktop.application.dsl.MacOSSigningSettings import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.internal.* +import org.jetbrains.compose.desktop.application.internal.InfoPlistBuilder.InfoPlistValue.* import org.jetbrains.compose.desktop.application.internal.files.* import org.jetbrains.compose.desktop.application.internal.files.MacJarSignFileCopyingProcessor import org.jetbrains.compose.desktop.application.internal.JvmRuntimeProperties import org.jetbrains.compose.desktop.application.internal.validation.validate import org.jetbrains.compose.internal.utils.* +import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated import java.io.* import java.nio.file.LinkOption import java.util.* @@ -244,6 +248,39 @@ abstract class AbstractJPackageTask @Inject constructor( @get:Optional val javaRuntimePropertiesFile: RegularFileProperty = objects.fileProperty() + @get:Input + internal val fileAssociations: SetProperty = objects.setProperty(FileAssociation::class.java) + + private val iconMapping by lazy { + val icons = fileAssociations.get().mapNotNull { it.iconFile }.distinct() + if (icons.isEmpty()) return@lazy emptyMap() + val iconTempNames: List = mutableListOf().apply { + val usedNames = mutableSetOf("${packageName.get()}.icns") + for (icon in icons) { + if (!icon.exists()) continue + if (usedNames.add(icon.name)) { + add(icon.name) + continue + } + val nameWithoutExtension = icon.nameWithoutExtension + val extension = icon.extension + for (n in 1UL..ULong.MAX_VALUE) { + val newName = "$nameWithoutExtension ($n).$extension" + if (usedNames.add(newName)) { + add(newName) + break + } + } + } + } + val appDir = destinationDir.ioFile.resolve("${packageName.get()}.app") + val iconsDir = appDir.resolve("Contents").resolve("Resources") + if (iconsDir.exists()) { + iconsDir.deleteRecursively() + } + icons.zip(iconTempNames) { icon, newName -> icon to iconsDir.resolve(newName) }.toMap() + } + private lateinit var jvmRuntimeInfo: JvmRuntimeProperties @get:Optional @@ -273,6 +310,9 @@ abstract class AbstractJPackageTask @Inject constructor( @get:LocalState protected val skikoDir: Provider = project.layout.buildDirectory.dir("compose/tmp/skiko") + @get:LocalState + protected val propertyFilesDir: Provider = project.layout.buildDirectory.dir("compose/tmp/propertyFiles") + @get:Internal private val libsDir: Provider = workingDir.map { it.dir("libs") @@ -368,6 +408,33 @@ abstract class AbstractJPackageTask @Inject constructor( cliArg("--license-file", licenseFile) cliArg("--resource-dir", jpackageResources) + val propertyFilesDirJava = propertyFilesDir.ioFile + fileOperations.clearDirs(propertyFilesDir) + + val fileAssociationFiles = fileAssociations.get() + .groupBy { it.extension } + .mapValues { (extension, associations) -> + associations.mapIndexed { index, association -> + propertyFilesDirJava.resolve("FA${extension}${if (index > 0) index.toString() else ""}.properties") + .apply { + val withoutIcon = """ + mime-type=${association.mimeType} + extension=${association.extension} + description=${association.description} + """.trimIndent() + writeText( + if (association.iconFile == null) withoutIcon + else "${withoutIcon}\nicon=${association.iconFile.normalizedPath()}" + ) + } + } + }.values.flatten() + + for (fileAssociationFile in fileAssociationFiles) { + cliArg("--file-associations", fileAssociationFile) + } + + when (currentOS) { OS.Linux -> { cliArg("--linux-shortcut", linuxShortcut) @@ -569,6 +636,15 @@ abstract class AbstractJPackageTask @Inject constructor( macSigner.sign(runtimeDir, runtimeEntitlementsFile, forceEntitlements = true) macSigner.sign(appDir, appEntitlementsFile, forceEntitlements = true) + + if (iconMapping.isNotEmpty()) { + for ((originalIcon, newIcon) in iconMapping) { + if (originalIcon.exists()) { + newIcon.ensureParentDirsCreated() + originalIcon.copyTo(newIcon) + } + } + } } override fun initState() { @@ -620,6 +696,23 @@ abstract class AbstractJPackageTask @Inject constructor( ?: "Copyright (C) $year" plist[PlistKeys.NSSupportsAutomaticGraphicsSwitching] = "true" plist[PlistKeys.NSHighResolutionCapable] = "true" + val fileAssociationMutableSet = fileAssociations.get() + if (fileAssociationMutableSet.isNotEmpty()) { + plist[PlistKeys.CFBundleDocumentTypes] = fileAssociationMutableSet + .groupBy { it.mimeType to it.description } + .map { (key, extensions) -> + val (mimeType, description) = key + val iconPath = extensions.firstNotNullOfOrNull { it.iconFile }?.let { iconMapping[it]?.name } + InfoPlistMapValue( + PlistKeys.CFBundleTypeRole to InfoPlistStringValue("Editor"), + PlistKeys.CFBundleTypeExtensions to InfoPlistListValue(extensions.map { InfoPlistStringValue(it.extension) }), + PlistKeys.CFBundleTypeIconFile to InfoPlistStringValue(iconPath ?: "$packageName.icns"), + PlistKeys.CFBundleTypeMIMETypes to InfoPlistStringValue(mimeType), + PlistKeys.CFBundleTypeName to InfoPlistStringValue(description), + PlistKeys.CFBundleTypeOSTypes to InfoPlistListValue(InfoPlistStringValue("****")), + ) + } + } } } diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/Expected-Info.plist b/gradle-plugins/compose/src/test/test-projects/application/macOptions/Expected-Info.plist index a54a6e2a56..9f703cf88c 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/macOptions/Expected-Info.plist +++ b/gradle-plugins/compose/src/test/test-projects/application/macOptions/Expected-Info.plist @@ -1,48 +1,123 @@ - - LSMinimumSystemVersion - 12.0 - CFBundleDevelopmentRegion - English - CFBundleAllowMixedLocalizations - true - CFBundleExecutable - TestPackage - CFBundleIconFile - TestPackage.icns - CFBundleIdentifier - MainKt - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - TestPackage - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0.0 - LSApplicationCategoryType - Unknown - CFBundleVersion - 1.0.0 - NSHumanReadableCopyright - Copyright (C) CURRENT_YEAR - NSSupportsAutomaticGraphicsSwitching - true - NSHighResolutionCapable - true + + LSMinimumSystemVersion + 12.0 + CFBundleDevelopmentRegion + English + CFBundleAllowMixedLocalizations + true + CFBundleExecutable + TestPackage + CFBundleIconFile + TestPackage.icns + CFBundleIdentifier + MainKt + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + TestPackage + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0.0 + LSApplicationCategoryType + Unknown + CFBundleVersion + 1.0.0 + NSHumanReadableCopyright + Copyright (C) CURRENT_YEAR + NSSupportsAutomaticGraphicsSwitching + true + NSHighResolutionCapable + true + CFBundleDocumentTypes + + + CFBundleTypeRole + Editor + CFBundleTypeExtensions + + kot + + CFBundleTypeIconFile + Kotlin_icon_big.icns + CFBundleTypeMIMETypes + text/kotlin + CFBundleTypeName + Kotlin Source File0 + CFBundleTypeOSTypes + + **** + + + + CFBundleTypeRole + Editor + CFBundleTypeExtensions + + kot1 + + CFBundleTypeIconFile + TestPackage.icns + CFBundleTypeMIMETypes + text/kotlin + CFBundleTypeName + Kotlin Source File1 + CFBundleTypeOSTypes + + **** + + + + CFBundleTypeRole + Editor + CFBundleTypeExtensions + + kott + + CFBundleTypeIconFile + Kotlin_icon_big (1).icns + CFBundleTypeMIMETypes + text/kotlin + CFBundleTypeName + Kotlin Source File2 + CFBundleTypeOSTypes + + **** + + + + CFBundleTypeRole + Editor + CFBundleTypeExtensions + + kott1 + + CFBundleTypeIconFile + TestPackage.icns + CFBundleTypeMIMETypes + text/kotlin + CFBundleTypeName + Kotlin Source File3 + CFBundleTypeOSTypes + + **** + + + - CFBundleURLTypes - - - CFBundleURLName - Exameple URL - CFBundleURLSchemes - - exampleUrl - - - - + CFBundleURLTypes + + + CFBundleURLName + Example URL + CFBundleURLSchemes + + exampleUrl + + + + diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/Kotlin_icon_big.icns b/gradle-plugins/compose/src/test/test-projects/application/macOptions/Kotlin_icon_big.icns new file mode 100644 index 0000000000000000000000000000000000000000..fedf6a3f2ff35a80c8c78a04df82a0acf1fa24c5 GIT binary patch literal 78153 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
X*}%BAy2(Ci{~5o5 z6W@ z^#HFO;ga;4Q;kReSfmG)KjQaoQ)4=%Ck>UJMx+@ngWEJ|&NyG&N zU`*S17~uc;pO;|i{vJ0}u9zJ}K;u%}776+LAaq3d06SM)fUA#T_2#>X1q2)%xVJLC za4U}l)F<@Z%9p6U{D1;~V~2t`s!isr*ruvEx~c6i14x*ZKqeQI8`5W_1CFv#Z03>lLSj9(mC%WQ&$lnY(18HWJPq?H9TAfBeVf&kr{!Dse@j)+q@>d6 z8LBYa0YTvP0jzlP=nWy*jec5C$!>g$7BBE3aG-Px5F1)o%(kgQb<WB)RO-6j4VF|NCR9i^!@V`gSc2L$V=T2m z3{AtwMwQ$KM9d{O%hJ|?JoM-eYEK(qkwhy#7Rd`jmJB48Lhy`xRRJj(gJGCcSDEnKciev}K`tKeZC`ohk(-Y8Y)%)rhZ_P6;}1mYtG-le&_ zebN+lIdqrd;4hMHFMd*IU3Z}O+k20%VpB+EI3l^I!jY749l~En_++o$$qdNQoZhvE^<)L&=2mk=MQ@&{VILu2CsVvtE6Qrn4rTE^2}rBVy=CLT2ldbVCfG1Lp<49 z%H(ylBB2)E_6h_F(sLj_hTj@#KG;LMXd_08(G7tc@RUwexte2orx|pW>ZLZ3Ll_&% zE<6mKbN_;*ySCJQ+;uNDTG3OW6jX?rwGSxAL
^jXjFtg?j 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 literal 0 HcmV?d00001 diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/Kotlin_icon_big.ico b/gradle-plugins/compose/src/test/test-projects/application/macOptions/Kotlin_icon_big.ico new file mode 100644 index 0000000000000000000000000000000000000000..3e9c11bb6d78e4e4fba85bc6352f4bd55d43f4a3 GIT binary patch literal 4286 zcmeI0X-`yF9LA^LpoI#EXjzoS5fDT~kxD66Ta9T$YQI1~flc2uO-!0LQY@C>0wQkI zmX=6Jpa!v8)B+ZVvdqp5tIWRd%bn%vxpSO&9fw6IZC`jN&%O7Y`{H;0>p2-7?=|vE zPv^0B-p{Y|cu71SFPDf+@hXYf`N&1SWff4-2_6rxuK$k*d<-z71C$zsm0BVl!U|S; zgeHmP1}d2m!H7%7O;s)N6}9=>-P;5~2_?)?|w z+Sd%{o=b4-z5;vkb=Y>^fF-{jX8tcQ<=%uby9bm)ylIcC9Fu;4Xh34A75N|GiZh4v1p#JW4vGFo8UUwM(cZ>YAlw?9f#y5IN$w|_Lg<1Z!Rm|dr)qs zJvHj{rQ)4>52{qsTTaa)>|egO#N)-z1!ZQ~-|iqi^`~cjEna=I?Bx4cNFIRg>-iP+ zwpdoYaZqM~b$=)A=Zp2!D0{;nm`@nQOv*5fwJL;`>Fx1&v2#JG1(v;CbT5h5&$;Zx zAs#+Oz`wG5U_=gR^BzK#M0yL$ zUM}8IpU1L`<)|%D;Hy$4PF75#uhjx(1#4i0PH46bU?K4#?JaLSiqy9-iPqTQCYtAc z=``0mx6ycC+fIFc?y23iC6f{Mmt|AqJdu3=Vm!q{pfeQzeXsDXP>FQ~G^WF?j z9-hM&N9OU_(I+@jqr&Y@Cqh9^!Nt6WJs~g>H~c~ud_p(8L>`eFZX&1H4TsnRyTk(< zkyYw}Me2n~=7o{SAooHq_dz%5gO*55q*C~?K=efE#~gX8<`@J6E6atLrM()ilLw;z J1N>LdzX6Qq#BBfo literal 0 HcmV?d00001 diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/Kotlin_icon_big.png b/gradle-plugins/compose/src/test/test-projects/application/macOptions/Kotlin_icon_big.png new file mode 100644 index 0000000000000000000000000000000000000000..1f93755e7526adfd0324298cfad986e2759a430a GIT binary patch literal 36487 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 literal 0 HcmV?d00001 diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle index d239392b13..754636aa75 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle @@ -10,29 +10,53 @@ dependencies { } def extraInfoPlistKeys = """ - CFBundleURLTypes - - - CFBundleURLName - Exameple URL - CFBundleURLSchemes - - exampleUrl - - - """ + CFBundleURLTypes + + + CFBundleURLName + Example URL + CFBundleURLSchemes + + exampleUrl + + + """ compose.desktop { application { mainClass = "MainKt" nativeDistributions { packageName = "TestPackage" + fileAssociation( + "text/kotlin", + "kot", + "Kotlin Source File0", + project.file("Kotlin_icon_big.png"), + project.file("Kotlin_icon_big.ico"), + project.file("Kotlin_icon_big.icns"), + ) + fileAssociation( + "text/kotlin", + "kot1", + "Kotlin Source File1", + ) macOS { dockName = "CustomDockName" minimumSystemVersion = "12.0" infoPlist { extraKeysRawXml = extraInfoPlistKeys } + fileAssociation( + "text/kotlin", + "kott", + "Kotlin Source File2", + project.file("subdir/Kotlin_icon_big.icns"), + ) + fileAssociation( + "text/kotlin", + "kott1", + "Kotlin Source File3", + ) } } } diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/subdir/Kotlin_icon_big.icns b/gradle-plugins/compose/src/test/test-projects/application/macOptions/subdir/Kotlin_icon_big.icns new file mode 100644 index 0000000000000000000000000000000000000000..fedf6a3f2ff35a80c8c78a04df82a0acf1fa24c5 GIT binary patch literal 78153 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
X*}%BAy2(Ci{~5o5 z6W@ z^#HFO;ga;4Q;kReSfmG)KjQaoQ)4=%Ck>UJMx+@ngWEJ|&NyG&N zU`*S17~uc;pO;|i{vJ0}u9zJ}K;u%}776+LAaq3d06SM)fUA#T_2#>X1q2)%xVJLC za4U}l)F<@Z%9p6U{D1;~V~2t`s!isr*ruvEx~c6i14x*ZKqeQI8`5W_1CFv#Z03>lLSj9(mC%WQ&$lnY(18HWJPq?H9TAfBeVf&kr{!Dse@j)+q@>d6 z8LBYa0YTvP0jzlP=nWy*jec5C$!>g$7BBE3aG-Px5F1)o%(kgQb<WB)RO-6j4VF|NCR9i^!@V`gSc2L$V=T2m z3{AtwMwQ$KM9d{O%hJ|?JoM-eYEK(qkwhy#7Rd`jmJB48Lhy`xRRJj(gJGCcSDEnKciev}K`tKeZC`ohk(-Y8Y)%)rhZ_P6;}1mYtG-le&_ zebN+lIdqrd;4hMHFMd*IU3Z}O+k20%VpB+EI3l^I!jY749l~En_++o$$qdNQoZhvE^<)L&=2mk=MQ@&{VILu2CsVvtE6Qrn4rTE^2}rBVy=CLT2ldbVCfG1Lp<49 z%H(ylBB2)E_6h_F(sLj_hTj@#KG;LMXd_08(G7tc@RUwexte2orx|pW>ZLZ3Ll_&% zE<6mKbN_;*ySCJQ+;uNDTG3OW6jX?rwGSxAL
^jXjFtg?j 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 literal 0 HcmV?d00001 From f01f195f2a208af36af7a6779798ed85acae4f58 Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Wed, 3 Jul 2024 18:46:29 +0200 Subject: [PATCH 33/47] Add 1.7.0-alpha01 to changelog (#5052) --- CHANGELOG.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb42d0c04a..3ad7671eca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,101 @@ +# 1.7.0-alpha01 (July 2024) + +_Changes since 1.6.11_ + +## Highlights + +- [Compose Multiplatform resources are stored in the android assets now. This fixes Android Studio Preview and cases such as a rendering resource files in WebViews or Media Players](https://github.com/JetBrains/compose-multiplatform/pull/4965) +- [Shared Element Transitions](https://developer.android.com/develop/ui/compose/animation/shared-elements) +- [Safe Args in Navigation Compose](https://developer.android.com/guide/navigation/design/type-safety) + +## Breaking changes + +### Android + +- [Minimal supported AGP raised to 8.1.0](https://github.com/JetBrains/compose-multiplatform/pull/4840) + +## Features + +### iOS + +- [Initial iOS floating cursor support](https://github.com/JetBrains/compose-multiplatform-core/pull/1312) +- [Added `accessibilityEnabled: Boolean = true` argument to `UIKitView` and `UIKitViewController`](https://github.com/JetBrains/compose-multiplatform-core/pull/1350) +- [`preferredStatusBarStyle`, `preferredStatysBarAnimation` and `prefersStatusBarHidden` are added to `ComposeUIViewControllerDelegate` to allow status bar appearance modification](https://github.com/JetBrains/compose-multiplatform-core/pull/1378) + +### Desktop + +- [Add constructor with `RenderSettings` to `ComposePanel`. Added a class `RenderSettings` with `val isVsyncEnabled: Boolean?`. When set to `true` gives a hint to renderer implementation of the particular `ComposePanel` to reduce the latency between the input and visual changes in exchange for possible screen tearing](https://github.com/JetBrains/compose-multiplatform-core/pull/1377) +- [Add public `moveEnabled` and `positionPercentage` setters in `SplitPaneState`](https://github.com/JetBrains/compose-multiplatform/pull/3974) + +### Resources + +- [Speed resources web rendering up by the reading a cached value instantly](https://github.com/JetBrains/compose-multiplatform/pull/4893) +- [If there is no resource with suitable density, use resource with the most suitable density, otherwise use default (similar to the Android logic)](https://github.com/JetBrains/compose-multiplatform/pull/4969) +- [Add a customization for resources directories. Now it is possible to use e.g downloaded resources](https://github.com/JetBrains/compose-multiplatform/pull/5016) + +## Fixes + +### Multiple Platforms + +- [Fix "ComposePanel. Focus moves to child after focusing/unfocusing the main window"](https://github.com/JetBrains/compose-multiplatform-core/pull/1398) +- [Don't show code completion for non-existenst API in `commonMain` that fails on Android with `NoSuchMethodException`](https://github.com/JetBrains/compose-multiplatform-core/pull/1328) +- [Fix order of interop elements in some cases](https://github.com/JetBrains/compose-multiplatform-core/pull/1340) +- [Fixed `Popup` jerking during ripple effect animation](https://github.com/JetBrains/compose-multiplatform-core/pull/1385) +- [Fix applying `ShaderBrush` to part of `AnnotatedString`](https://github.com/JetBrains/compose-multiplatform-core/pull/1389) +- [Fix text `brush` animation and optimized updating some visual text properties (applying time is reduced up to 40%)](https://github.com/JetBrains/compose-multiplatform-core/pull/1395) +- [Fix initial cursor position in the empty `TextField` with explicitly set `TextAlignment`](https://github.com/JetBrains/compose-multiplatform-core/pull/1354) +- [Fix focus for editable `TextField` inside `ExposedDropdownMenuBox`](https://github.com/JetBrains/compose-multiplatform-core/pull/1423) + +### iOS + +- [Pressing directional keys on a physical keyboard connected to iOS device doesn't cause a crash](https://github.com/JetBrains/compose-multiplatform-core/pull/1383) +- [Dismissing popup or dialogue within a very short timespan after its creation doesn't cause a crash](https://github.com/JetBrains/compose-multiplatform-core/pull/1384) +- [Fix missing invalidations during native view resize](https://github.com/JetBrains/compose-multiplatform-core/pull/1387) +- [Fixed a memory spike when continuously resizing the `ComposeUIViewController` (such as when used in modal sheet presentation context with different detents)](https://github.com/JetBrains/compose-multiplatform-core/pull/1390) +- [visibility of selection handles in single-line textfields with LTR + RTL text in iOS](https://github.com/JetBrains/compose-multiplatform-core/pull/1331) + +### Desktop + +- [Fix possible `UninitializedPropertyAccessException` in `desktopTest`](https://github.com/JetBrains/compose-multiplatform-core/pull/1343) +- [Fixed `ComposePanel.requestFocus()`, making it correctly assign focus to the first focusable child](https://github.com/JetBrains/compose-multiplatform-core/pull/1352) +- [When using `ComposePanel` inside a Swing application on macOS, VoiceOver will now correctly go into the `ComposePanel` when traversing accessible elements](https://github.com/JetBrains/compose-multiplatform-core/pull/1362) +- [When using `ComposePanel` inside a Swing application on Windows with NVDA turned on, focus will now correctly go into the `ComposePanel` when traversing with (ctrl)-shift-tab](https://github.com/JetBrains/compose-multiplatform-core/pull/1363) +- [Correctly save `WindowState` with unspecified `size` instead of crashing](https://github.com/JetBrains/compose-multiplatform-core/pull/1394) +- [Fix `IndexOutOfBoundsException` crash on Windows when traversing a11y elements](https://github.com/JetBrains/compose-multiplatform-core/pull/1415) + +### Web + +- [Process `keydown` and `keyup` keys for identified keys from virtual keyboard](https://github.com/JetBrains/compose-multiplatform-core/pull/1380) +- [Allow preloading the fallback fonts. This enables the usage of emojis and other unicode characters without manually composing the Text with AnnotatedString](https://github.com/JetBrains/compose-multiplatform-core/pull/1400) +- [Make sure the web app distribution doesn't contain a duplicate `skiko.wasm`](https://github.com/JetBrains/compose-multiplatform/pull/4958) + +### Resources + +- [Delete `contextClassLoader` usage on JVM targets](https://github.com/JetBrains/compose-multiplatform/pull/4895) +- [Create an empty resource dir with "podspec" task instead "podInstall"](https://github.com/JetBrains/compose-multiplatform/pull/4900) +- [Fix resource accessors escaping. Now it is possible to use resources with names: "package", "is", "item_$xxx" etc](https://github.com/JetBrains/compose-multiplatform/pull/4901) +- [Read exactly requested count of bytes from InputStream on jvm platforms](https://github.com/JetBrains/compose-multiplatform/pull/4943) + +### Gradle Plugin + +- [Make sure tryGetSkikoRuntimeIfNeeded is executed only during the task execution](https://github.com/JetBrains/compose-multiplatform/pull/4918) +- [Delete outdated build services](https://github.com/JetBrains/compose-multiplatform/pull/4959) + +## Dependencies + +- Gradle Plugin `org.jetbrains.compose`, version `1.7.0-alpha01`. Based on Jetpack Compose libraries: + - [Compiler 1.5.14](https://developer.android.com/jetpack/androidx/releases/compose-compiler#1.5.14) + - [Runtime 1.7.0-beta03](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.7.0-beta03) + - [UI 1.7.0-beta03](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.7.0-beta03) + - [Foundation 1.7.0-beta03](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0-beta03) + - [Material 1.7.0-beta03](https://developer.android.com/jetpack/androidx/releases/compose-material#1.7.0-beta03) + - [Material3 1.3.0-beta03](https://developer.android.com/jetpack/androidx/releases/compose-material3#1.3.0-beta03) + +- Lifecycle libraries `org.jetbrains.androidx.lifecycle:lifecycle-*:2.8.0`. Based on [Jetpack Lifecycle 2.8.0](https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.0) +- Navigation libraries `org.jetbrains.androidx.navigation:navigation-*:2.8.0-alpha08`. Based on [Jetpack Navigation 2.8.0-beta03](https://developer.android.com/jetpack/androidx/releases/navigation#2.8.0-beta03) + +___ + # 1.6.11 (June 2024) _Changes since 1.6.10_ From 9f8a3a242c58c1935fa905d90db446f533707f58 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 8 Jul 2024 14:22:28 +0200 Subject: [PATCH 34/47] [gradle] Fix task configuration (use configureEach instead all). (#5076) Fixes https://github.com/JetBrains/compose-multiplatform/issues/5061 ## Release Notes ### Fixes - Gradle Plugin - _(prerelease fix)_ Fix "InvalidUserDataException: Cannot change hierarchy of dependency configuration" on Gradle sync --- .../kotlin/org/jetbrains/compose/resources/AndroidResources.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt index afd400483e..a19baca67a 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt @@ -122,7 +122,7 @@ internal fun Project.configureAndroidAssetsForPreview() { if (androidComponents.pluginVersion >= agp_8_1_0) { // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work - tasks.all { task -> + tasks.configureEach { task -> if (task.name == kgpCopyAssetsTaskName) { task.outputs.files.forEach { file -> addStaticSourceDirectory(file.path) From fbd04ab48d184af27bd73a2d5e096fc7441adcd2 Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Mon, 8 Jul 2024 17:16:10 +0300 Subject: [PATCH 35/47] Remove UI_Testing tutorial (#5079) --- tutorials/README.md | 2 +- tutorials/UI_Testing/README.md | 78 ---------------------------------- 2 files changed, 1 insertion(+), 79 deletions(-) delete mode 100644 tutorials/UI_Testing/README.md diff --git a/tutorials/README.md b/tutorials/README.md index af299b64ce..1e5681c774 100644 --- a/tutorials/README.md +++ b/tutorials/README.md @@ -17,7 +17,7 @@ * [Navigation](Navigation) * [Accessibility](Accessibility) * [Building a native distribution](Native_distributions_and_local_execution) -* [UI testing](UI_Testing) +* [UI testing](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-test.html) ## Web (based on Wasm) * [Getting started](https://kotl.in/wasm-compose-example) diff --git a/tutorials/UI_Testing/README.md b/tutorials/UI_Testing/README.md deleted file mode 100644 index 51daf01e06..0000000000 --- a/tutorials/UI_Testing/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# UI Testing -The API for unit testing in Compose for Desktop is nearly identical to the [Jetpack Compose Android testing API](https://developer.android.com/jetpack/compose/testing). We highly recommended reading that first before moving on to this tutorial. - -## Setting up -To start using the testing API, you will need to add the dependency on `compose.uiTestJUnit4` to your `build.gradle` file and create the directory for your tests. - -If the module is desktop-only (`kotlin("jvm")` is applied), add the dependency via: -``` kotlin -dependencies { - testImplementation(compose.desktop.uiTestJUnit4) - testImplementation(compose.desktop.currentOs) -} -``` - -and the directory for tests will be `src/test/kotlin` - -If the module is multiplatform (`kotlin(“multiplatform”)` is applied), add it via: - -``` kotlin -kotlin { - sourceSets { - val desktopTest by getting { - dependencies { - implementation(compose.desktop.uiTestJUnit4) - implementation(compose.desktop.currentOs) - } - } - } -} -``` - -And the directory for tests will be `src/desktopTest/kotlin` - -## Creating your first test -In the tests directory, create a file named `ExampleTest.kt` and paste this code into it: - -```kotlin -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.* -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.test.junit4.createComposeRule -import org.junit.Rule -import org.junit.Test - -class ExampleTest { - @get:Rule - val rule = createComposeRule() - - @Test - fun myTest(){ - rule.setContent { - var text by remember { mutableStateOf("Hello") } - Text( - text = text, - modifier = Modifier.testTag("text") - ) - Button( - onClick = { text = "Compose" }, - modifier = Modifier.testTag("button") - ){ - Text("Click me") - } - } - - rule.onNodeWithTag("text").assertTextEquals("Hello") - rule.onNodeWithTag("button").performClick() - rule.onNodeWithTag("text").assertTextEquals("Compose") - } -} -``` - -Now you can run the test by either clicking ![run](https://github.com/JetBrains/compose-multiplatform/assets/5963351/2eac4041-757e-48b0-9dc2-baef82f21a7b) button in your IDE, or from the command line with -``` -./gradlew test -``` - From d93351911034b723706000a8aaec203c13501783 Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Mon, 8 Jul 2024 17:01:43 +0200 Subject: [PATCH 36/47] [gradle] Update local version of Compose Libraries used by Gradle plugin to "1.7.0-alpha01" --- gradle-plugins/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugins/gradle.properties b/gradle-plugins/gradle.properties index af381fcbcb..7f087451cd 100644 --- a/gradle-plugins/gradle.properties +++ b/gradle-plugins/gradle.properties @@ -8,7 +8,7 @@ kotlin.code.style=official dev.junit.parallel=false # Default version of Compose Libraries used by Gradle plugin -compose.version=1.6.10 +compose.version=1.7.0-alpha01 # The latest version of Kotlin compatible with compose.tests.compiler.version. Used only in tests/CI. compose.tests.kotlin.version=2.0.0 # __SUPPORTED_GRADLE_VERSIONS__ From 5e810a0b021bd55f81a2697e378803b450dec007 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Tue, 9 Jul 2024 10:34:25 +0200 Subject: [PATCH 37/47] Update compose compiler version for kotlin 1.9.24 (#5080) This fixes https://youtrack.jetbrains.com/issue/CMP-1612 and https://youtrack.jetbrains.com/issue/CMP-1602/Html-js-error-content-is-not-a-function --- .../org/jetbrains/compose/ComposeCompilerCompatibility.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b92424f092..3be60601ff 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 @@ -24,7 +24,7 @@ internal object ComposeCompilerCompatibility { "1.9.21" to "1.5.4", "1.9.22" to "1.5.8.1", "1.9.23" to "1.5.13.5", - "1.9.24" to "1.5.14", + "1.9.24" to "1.5.14.1-beta02", "2.0.0-Beta1" to "1.5.4-dev1-kt2.0.0-Beta1", "2.0.0-Beta4" to "1.5.9-kt-2.0.0-Beta4", "2.0.0-Beta5" to "1.5.11-kt-2.0.0-Beta5", From a98b37dc9b757b6ea75225ca030cabeab3bf8a4e Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 10 Jul 2024 14:40:49 +0200 Subject: [PATCH 38/47] [gradle] Use `addStaticSourceDirectory` in AGP to fix AS previews with compose resources. (#5090) More info: https://issuetracker.google.com/348208777 Fixes https://github.com/JetBrains/compose-multiplatform/issues/5053 ## Release Notes ### Fixes - Resources - _(prerelease fix)_ Fix an android app compose resources packaging broken after introduction AS previews --- .../compose/resources/AndroidResources.kt | 151 ++++++------------ .../compose/resources/ComposeResources.kt | 12 +- 2 files changed, 57 insertions(+), 106 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt index a19baca67a..ac36cf5bb3 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt @@ -1,95 +1,71 @@ package org.jetbrains.compose.resources -import com.android.build.api.AndroidPluginVersion import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.Variant import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask import com.android.build.gradle.internal.lint.LintModelWriterTask -import org.gradle.api.DefaultTask import org.gradle.api.Project -import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileCollection -import org.gradle.api.file.FileSystemOperations -import org.gradle.api.provider.Property -import org.gradle.api.tasks.IgnoreEmptyDirectories -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction -import org.jetbrains.compose.internal.utils.registerTask +import org.gradle.api.tasks.Copy +import org.jetbrains.compose.internal.utils.dir import org.jetbrains.compose.internal.utils.uppercaseFirstChar import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation -import org.jetbrains.kotlin.gradle.utils.ObservableSet -import javax.inject.Inject -private val agp_8_1_0 = AndroidPluginVersion(8, 1, 0) +internal fun Project.getAndroidVariantComposeResources( + kotlinExtension: KotlinMultiplatformExtension, + variant: Variant +): FileCollection = project.files({ + kotlinExtension.targets.withType(KotlinAndroidTarget::class.java).flatMap { androidTarget -> + androidTarget.compilations.flatMap { compilation -> + if (compilation.androidVariant.name == variant.name) { + compilation.allKotlinSourceSets.map { kotlinSourceSet -> + getPreparedComposeResourcesDir(kotlinSourceSet) + } + } else emptyList() + } + } +}) + +internal fun Project.getKgpAndroidVariantComposeResources( + variant: Variant +): FileCollection = project.files({ + val taskName = "${variant.name}AssetsCopyForAGP" + if (tasks.names.contains(taskName)) tasks.named(taskName).map { it.outputs.files } + else files() +}) internal fun Project.configureAndroidComposeResources( - kotlinExtension: KotlinMultiplatformExtension + getVariantComposeResources: (Variant) -> FileCollection ) { //copy all compose resources to android assets val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return androidComponents.onVariants { variant -> - val variantResources = project.files() - - kotlinExtension.targets.withType(KotlinAndroidTarget::class.java).all { androidTarget -> - androidTarget.compilations.all { compilation: KotlinJvmAndroidCompilation -> - if (compilation.androidVariant.name == variant.name) { - project.logger.info("Configure resources for variant ${variant.name}") - (compilation.allKotlinSourceSets as? ObservableSet)?.forAll { kotlinSourceSet -> - val preparedComposeResources = getPreparedComposeResourcesDir(kotlinSourceSet) - variantResources.from(preparedComposeResources) - } - } - } - } - - val copyResources = registerTask( - "copy${variant.name.uppercaseFirstChar()}ResourcesToAndroidAssets" - ) { - from.set(variantResources) + val variantAssets = getVariantComposeResources(variant) + val variantAssetsDir = layout.buildDirectory.dir(RES_GEN_DIR).dir(variant.name + "AndroidAssets") + + val copyVariantAssets = tasks.register( + "copy${variant.name.uppercaseFirstChar()}ComposeResourcesToAndroidAssets", + Copy::class.java + ) { task -> + task.from(variantAssets) + task.into(variantAssetsDir) } - variant.sources.assets?.apply { - addGeneratedSourceDirectory( - taskProvider = copyResources, - wiredWith = CopyResourcesToAndroidAssetsTask::outputDirectory - ) - // https://issuetracker.google.com/348208777 - if (androidComponents.pluginVersion >= agp_8_1_0) { - // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work - addStaticSourceDirectory(copyResources.flatMap { it.outputDirectory.asFile }.get().path) + //https://issuetracker.google.com/348208777 + variant.sources.assets?.addStaticSourceDirectory(variantAssetsDir.get().asFile.path) + tasks.configureEach { task -> + if (task.name == "merge${variant.name.uppercaseFirstChar()}Assets") { + task.dependsOn(copyVariantAssets) } - - // addGeneratedSourceDirectory doesn't run the copyResources task during AS Compose Preview build - tasks.configureEach { task -> - if (task.name == "compile${variant.name.uppercaseFirstChar()}Sources") { - task.dependsOn(copyResources) - } + //fix task dependencies for AndroidStudio preview + if (task.name == "compile${variant.name.uppercaseFirstChar()}Sources") { + task.dependsOn(copyVariantAssets) + } + //fix linter task dependencies for `build` task + if (task is AndroidLintAnalysisTask || task is LintModelWriterTask) { + task.mustRunAfter(copyVariantAssets) } - } - } -} - -//Copy task doesn't work with 'variant.sources?.assets?.addGeneratedSourceDirectory' API -internal abstract class CopyResourcesToAndroidAssetsTask : DefaultTask() { - @get:Inject - abstract val fileSystem: FileSystemOperations - - @get:InputFiles - @get:IgnoreEmptyDirectories - abstract val from: Property - - @get:OutputDirectory - abstract val outputDirectory: DirectoryProperty - - @TaskAction - fun action() { - fileSystem.copy { - it.includeEmptyDirs = false - it.from(from) - it.into(outputDirectory) } } } @@ -109,38 +85,5 @@ internal fun Project.fixAndroidLintTaskDependencies() { it is AndroidLintAnalysisTask || it is LintModelWriterTask }.configureEach { it.mustRunAfter(tasks.withType(GenerateResourceAccessorsTask::class.java)) - it.mustRunAfter(tasks.withType(CopyResourcesToAndroidAssetsTask::class.java)) - } -} - -// https://issuetracker.google.com/348208777 -internal fun Project.configureAndroidAssetsForPreview() { - val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return - androidComponents.onVariants { variant -> - variant.sources.assets?.apply { - val kgpCopyAssetsTaskName = "${variant.name}AssetsCopyForAGP" - - if (androidComponents.pluginVersion >= agp_8_1_0) { - // addGeneratedSourceDirectory doesn't mark the output directory as assets hence AS Compose Preview doesn't work - tasks.configureEach { task -> - if (task.name == kgpCopyAssetsTaskName) { - task.outputs.files.forEach { file -> - addStaticSourceDirectory(file.path) - } - } - } - } - - // addGeneratedSourceDirectory doesn't run the copyResources task during AS Compose Preview build - tasks.configureEach { task -> - if (task.name == "compile${variant.name.uppercaseFirstChar()}Sources") { - task.dependsOn(kgpCopyAssetsTaskName) - } - //fix https://github.com/JetBrains/compose-multiplatform/issues/5038 - if (task is AndroidLintAnalysisTask || task is LintModelWriterTask) { - task.mustRunAfter(kgpCopyAssetsTaskName) - } - } - } } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt index 7b7ba44529..2a85d3e2bd 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt @@ -40,7 +40,13 @@ private fun Project.onKgpApplied(config: Provider, kgp: Kotl if (kmpResourcesAreAvailable) { configureKmpResources(kotlinExtension, extraProperties.get(KMP_RES_EXT)!!, config) onAgpApplied { - configureAndroidAssetsForPreview() + //workaround to fix AndroidStudio preview works with compose resources: + //it copies android assets and mark it as a static assets dir + //yes, the same resources will be registered in AGP twice: from KGP and here as static dirs + //but it works fine and there are no problems during merge android assets + configureAndroidComposeResources { variant -> + getKgpAndroidVariantComposeResources(variant) + } fixAndroidLintTaskDependencies() } } else { @@ -63,7 +69,9 @@ private fun Project.onKgpApplied(config: Provider, kgp: Kotl configureComposeResources(kotlinExtension, commonMain, config) onAgpApplied { - configureAndroidComposeResources(kotlinExtension) + configureAndroidComposeResources { variant -> + getAndroidVariantComposeResources(kotlinExtension, variant) + } fixAndroidLintTaskDependencies() } } From e4c77036db9c3efee8853f3fb7caa5cb17995d78 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 10 Jul 2024 14:55:28 +0200 Subject: [PATCH 39/47] Generate functions to find resources by a string ID. (#5068) The PR adds a generation special properties with maps a string ID to the resource for each type of resources: ```kotlin val Res.allDrawableResources: Map val Res.allStringResources: Map val Res.allStringArrayResources: Map val Res.allPluralStringResources: Map val Res.allFontResources: Map ``` Fixes https://github.com/JetBrains/compose-multiplatform/issues/4880 Fixes https://youtrack.jetbrains.com/issue/CMP-1607 ## Testing I checked it in the sample project but this should be tested by QA (KMP and JVM only projects) ## Release Notes ### Features - Resources - Now the gradle plugin generates resources map to find a resource by a string ID --- .../resources/ComposeResourcesGeneration.kt | 136 +++++++++++++++++- .../GenerateResourceCollectorsTask.kt | 116 +++++++++++++++ .../resources/GeneratedResClassSpec.kt | 108 +++++++++++++- .../test/tests/integration/ResourcesTest.kt | 4 +- .../my/lib/res/String0.androidMain.kt | 22 ++- .../my/lib/res/ActualResourceCollectors.kt | 53 +++++++ .../my/lib/res/Drawable0.commonMain.kt | 90 +++++++----- .../my/lib/res/Font0.commonMain.kt | 24 ++-- .../my/lib/res/Plurals0.commonMain.kt | 26 ++-- .../my/lib/res/String0.commonMain.kt | 113 ++++++++------- .../my/lib/res/ExpectResourceCollectors.kt | 25 ++++ .../commonResClass/my/lib/res/Res.kt | 4 +- .../my/lib/res/String0.desktopMain.kt | 22 ++- .../my/lib/res/ActualResourceCollectors.kt | 53 +++++++ .../resources/String0.androidMain.kt | 24 ++-- .../resources/ActualResourceCollectors.kt | 53 +++++++ .../resources/Drawable0.commonMain.kt | 94 ++++++------ .../generated/resources/Font0.commonMain.kt | 26 ++-- .../resources/Plurals0.commonMain.kt | 28 ++-- .../generated/resources/String0.commonMain.kt | 133 +++++++++-------- .../resources/ExpectResourceCollectors.kt | 25 ++++ .../resources_test/generated/resources/Res.kt | 6 +- .../resources/String0.desktopMain.kt | 24 ++-- .../resources/ActualResourceCollectors.kt | 53 +++++++ .../misc/emptyResources/expected/Res.kt | 49 ------- .../resources/ExpectResourceCollectors.kt | 25 ++++ .../empty_res/generated/resources/Res.kt | 49 +++++++ .../resources/ActualResourceCollectors.kt | 48 +++++++ .../generated/resources/Res.kt | 68 ++++----- .../generated/resources/Drawable0.main.kt | 20 ++- .../resources/ActualResourceCollectors.kt | 49 +++++++ 31 files changed, 1218 insertions(+), 352 deletions(-) create mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceCollectorsTask.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceCollectors/my/lib/res/ActualResourceCollectors.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceCollectors/my/lib/res/ExpectResourceCollectors.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceCollectors/my/lib/res/ActualResourceCollectors.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceCollectors/app/group/resources_test/generated/resources/ActualResourceCollectors.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceCollectors/app/group/resources_test/generated/resources/ExpectResourceCollectors.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceCollectors/app/group/resources_test/generated/resources/ActualResourceCollectors.kt delete mode 100644 gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/Res.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/commonMainResourceCollectors/app/group/empty_res/generated/resources/ExpectResourceCollectors.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/commonResClass/app/group/empty_res/generated/resources/Res.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/desktopMainResourceCollectors/app/group/empty_res/generated/resources/ActualResourceCollectors.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/mainResourceCollectors/me/app/jvmonlyresources/generated/resources/ActualResourceCollectors.kt diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt index 33cf4fa8ce..672a479d0d 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt @@ -6,8 +6,14 @@ import org.jetbrains.compose.ComposePlugin import org.jetbrains.compose.internal.IDEA_IMPORT_TASK_NAME import org.jetbrains.compose.internal.IdeaImportTask import org.jetbrains.compose.internal.utils.uppercaseFirstChar +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension +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.mpp.KotlinMetadataTarget +import org.jetbrains.kotlin.tooling.core.withClosure import java.io.File internal fun Project.configureComposeResourcesGeneration( @@ -67,6 +73,8 @@ internal fun Project.configureComposeResourcesGeneration( ) } + configureResourceCollectorsGeneration(kotlinExtension, shouldGenerateCode, packageName, makeAccessorsPublic) + //setup task execution during IDE import tasks.configureEach { importTask -> if (importTask.name == IDEA_IMPORT_TASK_NAME) { @@ -115,7 +123,7 @@ private fun Project.configureResourceAccessorsGeneration( logger.info("Configure resource accessors generation for ${sourceSet.name}") val genTask = tasks.register( - "generateResourceAccessorsFor${sourceSet.name.uppercaseFirstChar()}", + sourceSet.getResourceAccessorsGenerationTaskName(), GenerateResourceAccessorsTask::class.java ) { task -> task.packageName.set(packageName) @@ -130,6 +138,132 @@ private fun Project.configureResourceAccessorsGeneration( } } + //register generated source set + sourceSet.kotlin.srcDir(genTask.map { it.codeDir }) +} + +private fun KotlinSourceSet.getResourceAccessorsGenerationTaskName(): String { + return "generateResourceAccessorsFor${this.name.uppercaseFirstChar()}" +} + +//we have to generate actual resource collector functions for each leaf source set +private fun Project.configureResourceCollectorsGeneration( + kotlinExtension: KotlinProjectExtension, + shouldGenerateCode: Provider, + packageName: Provider, + makeAccessorsPublic: Provider +) { + if (kotlinExtension is KotlinMultiplatformExtension) { + kotlinExtension.sourceSets + .matching { it.name == KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME } + .all { commonMainSourceSet -> + configureExpectResourceCollectorsGeneration( + commonMainSourceSet, + shouldGenerateCode, + packageName, + makeAccessorsPublic + ) + } + + kotlinExtension.targets.all { target -> + if (target is KotlinAndroidTarget) { + kotlinExtension.sourceSets.matching { it.name == "androidMain" }.all { androidMain -> + configureActualResourceCollectorsGeneration( + androidMain, + shouldGenerateCode, + packageName, + makeAccessorsPublic, + true + ) + } + } else if (target !is KotlinMetadataTarget) { + target.compilations.matching { it.name == KotlinCompilation.MAIN_COMPILATION_NAME }.all { compilation -> + configureActualResourceCollectorsGeneration( + compilation.defaultSourceSet, + shouldGenerateCode, + packageName, + makeAccessorsPublic, + true + ) + } + } + } + } else if (kotlinExtension is KotlinSingleTargetExtension<*>) { + //JVM only projects + kotlinExtension.target.compilations + .findByName(KotlinCompilation.MAIN_COMPILATION_NAME) + ?.let { compilation -> + configureActualResourceCollectorsGeneration( + compilation.defaultSourceSet, + shouldGenerateCode, + packageName, + makeAccessorsPublic, + false + ) + } + } + +} + +private fun Project.configureExpectResourceCollectorsGeneration( + sourceSet: KotlinSourceSet, + shouldGenerateCode: Provider, + packageName: Provider, + makeAccessorsPublic: Provider +) { + logger.info("Configure expect resource collectors generation for ${sourceSet.name}") + + + val genTask = tasks.register( + "generateExpectResourceCollectorsFor${sourceSet.name.uppercaseFirstChar()}", + GenerateExpectResourceCollectorsTask::class.java + ) { task -> + task.packageName.set(packageName) + task.shouldGenerateCode.set(shouldGenerateCode) + task.makeAccessorsPublic.set(makeAccessorsPublic) + task.codeDir.set(layout.buildDirectory.dir("$RES_GEN_DIR/kotlin/${sourceSet.name}ResourceCollectors")) + } + + //register generated source set + sourceSet.kotlin.srcDir(genTask.map { it.codeDir }) +} + +private fun Project.configureActualResourceCollectorsGeneration( + sourceSet: KotlinSourceSet, + shouldGenerateCode: Provider, + packageName: Provider, + makeAccessorsPublic: Provider, + useActualModifier: Boolean +) { + val taskName = "generateActualResourceCollectorsFor${sourceSet.name.uppercaseFirstChar()}" + if (tasks.names.contains(taskName)) { + logger.info("Actual resource collectors generation for ${sourceSet.name} is already configured") + return + } + logger.info("Configure actual resource collectors generation for ${sourceSet.name}") + + val accessorDirs = project.files({ + val allSourceSets = sourceSet.withClosure { it.dependsOn } + allSourceSets.mapNotNull { item -> + val accessorsTaskName = item.getResourceAccessorsGenerationTaskName() + if (tasks.names.contains(accessorsTaskName)) { + tasks.named(accessorsTaskName, GenerateResourceAccessorsTask::class.java).map { it.codeDir } + } else null + } + }) + + val genTask = tasks.register( + taskName, + GenerateActualResourceCollectorsTask::class.java + ) { task -> + task.packageName.set(packageName) + task.shouldGenerateCode.set(shouldGenerateCode) + task.makeAccessorsPublic.set(makeAccessorsPublic) + task.useActualModifier.set(useActualModifier) + task.resourceAccessorDirs.from(accessorDirs) + task.codeDir.set(layout.buildDirectory.dir("$RES_GEN_DIR/kotlin/${sourceSet.name}ResourceCollectors")) + } + //register generated source set sourceSet.kotlin.srcDir(genTask.map { it.codeDir }) } \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceCollectorsTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceCollectorsTask.kt new file mode 100644 index 0000000000..bd67c804f5 --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceCollectorsTask.kt @@ -0,0 +1,116 @@ +package org.jetbrains.compose.resources + +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.jetbrains.compose.internal.IdeaImportTask +import org.jetbrains.compose.internal.utils.uppercaseFirstChar + +internal abstract class GenerateExpectResourceCollectorsTask : IdeaImportTask() { + @get:Input + abstract val packageName: Property + + @get:Input + abstract val shouldGenerateCode: Property + + @get:Input + abstract val makeAccessorsPublic: Property + + @get:OutputDirectory + abstract val codeDir: DirectoryProperty + + override fun safeAction() { + val kotlinDir = codeDir.get().asFile + + logger.info("Clean directory $kotlinDir") + kotlinDir.deleteRecursively() + kotlinDir.mkdirs() + + if (shouldGenerateCode.get()) { + logger.info("Generate expect ResourceCollectors for $kotlinDir") + + val pkgName = packageName.get() + val isPublic = makeAccessorsPublic.get() + val spec = getExpectResourceCollectorsFileSpec(pkgName, "ExpectResourceCollectors", isPublic) + spec.writeTo(kotlinDir) + } + } +} + +internal abstract class GenerateActualResourceCollectorsTask : IdeaImportTask() { + @get:Input + abstract val packageName: Property + + @get:Input + abstract val shouldGenerateCode: Property + + @get:Input + abstract val makeAccessorsPublic: Property + + @get:Input + abstract val useActualModifier: Property + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val resourceAccessorDirs: ConfigurableFileCollection + + @get:OutputDirectory + abstract val codeDir: DirectoryProperty + + override fun safeAction() { + val kotlinDir = codeDir.get().asFile + val inputDirs = resourceAccessorDirs.files + + logger.info("Clean directory $kotlinDir") + kotlinDir.deleteRecursively() + kotlinDir.mkdirs() + + val inputFiles = inputDirs.flatMap { dir -> + dir.walkTopDown().filter { !it.isHidden && it.isFile && it.extension == "kt" }.toList() + } + + if (shouldGenerateCode.get()) { + logger.info("Generate actual ResourceCollectors for $kotlinDir") + val funNames = inputFiles.mapNotNull { inputFile -> + if (inputFile.nameWithoutExtension.contains('.')) { + val (fileName, suffix) = inputFile.nameWithoutExtension.split('.') + val type = ResourceType.values().firstOrNull { fileName.startsWith(it.accessorName, true) } + val name = "_collect${suffix.uppercaseFirstChar()}${fileName}Resources" + + if (type == null) { + logger.warn("Unknown resources type: `$inputFile`") + null + } else if (!inputFile.readText().contains(name)) { + logger.warn("A function '$name' is not found in the `$inputFile` file!") + null + } else { + logger.info("Found collector function: `$name`") + type to name + } + } else { + logger.warn("Unknown file name: `$inputFile`") + null + } + }.groupBy({ it.first }, { it.second }) + + val pkgName = packageName.get() + val isPublic = makeAccessorsPublic.get() + val useActual = useActualModifier.get() + val spec = getActualResourceCollectorsFileSpec( + pkgName, + "ActualResourceCollectors", + isPublic, + useActual, + funNames + ) + spec.writeTo(kotlinDir) + } else { + logger.info("Generation ResourceCollectors for $kotlinDir is disabled") + } + } +} diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GeneratedResClassSpec.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GeneratedResClassSpec.kt index df8add1101..559f0471b3 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GeneratedResClassSpec.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GeneratedResClassSpec.kt @@ -1,6 +1,19 @@ package org.jetbrains.compose.resources -import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MAP +import com.squareup.kotlinpoet.MUTABLE_MAP +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.withIndent import org.jetbrains.compose.internal.utils.uppercaseFirstChar import java.nio.file.Path import java.util.* @@ -45,6 +58,9 @@ private val resourceItemClass = ClassName("org.jetbrains.compose.resources", "Re private val experimentalAnnotation = AnnotationSpec.builder( ClassName("org.jetbrains.compose.resources", "ExperimentalResourceApi") ).build() +private val internalAnnotation = AnnotationSpec.builder( + ClassName("org.jetbrains.compose.resources", "InternalResourceApi") +).build() private fun CodeBlock.Builder.addQualifiers(resourceItem: ResourceItem): CodeBlock.Builder { val languageQualifier = ClassName("org.jetbrains.compose.resources", "LanguageQualifier") @@ -247,6 +263,23 @@ private fun getChunkFileSpec( }.build() chunkFile.addType(objectSpec) + //__collect${chunkClassName}Resources function + chunkFile.addFunction( + FunSpec.builder("_collect${chunkClassName}Resources") + .addAnnotation(internalAnnotation) + .addModifiers(KModifier.INTERNAL) + .addParameter( + "map", + MUTABLE_MAP.parameterizedBy(String::class.asClassName(), type.getClassName()) + ) + .also { collectFun -> + idToResources.keys.forEach { resName -> + collectFun.addStatement("map.put(%S, $chunkClassName.%N)", resName, resName) + } + } + .build() + ) + idToResources.forEach { (resName, items) -> val accessor = PropertySpec.builder(resName, type.getClassName(), resModifier) .receiver(ClassName(packageName, "Res", type.accessorName)) @@ -285,6 +318,79 @@ private fun getChunkFileSpec( }.build() } +internal fun getExpectResourceCollectorsFileSpec( + packageName: String, + fileName: String, + isPublic: Boolean +): FileSpec { + val resModifier = if (isPublic) KModifier.PUBLIC else KModifier.INTERNAL + return FileSpec.builder(packageName, fileName).also { file -> + ResourceType.values().forEach { type -> + val typeClassName = type.getClassName() + file.addProperty( + PropertySpec + .builder( + "all${typeClassName.simpleName}s", + MAP.parameterizedBy(String::class.asClassName(), typeClassName), + KModifier.EXPECT, + resModifier + ) + .addAnnotation(experimentalAnnotation) + .receiver(ClassName(packageName, "Res")) + .build() + ) + } + }.build() +} + +internal fun getActualResourceCollectorsFileSpec( + packageName: String, + fileName: String, + isPublic: Boolean, + useActualModifier: Boolean, //e.g. java only project doesn't need actual modifiers + typeToCollectorFunctions: Map> +): FileSpec = FileSpec.builder(packageName, fileName).also { file -> + val resModifier = if (isPublic) KModifier.PUBLIC else KModifier.INTERNAL + + file.addAnnotation( + AnnotationSpec.builder(ClassName("kotlin", "OptIn")) + .addMember("org.jetbrains.compose.resources.InternalResourceApi::class") + .build() + ) + + ResourceType.values().forEach { type -> + val typeClassName = type.getClassName() + val initBlock = CodeBlock.builder() + .addStatement("lazy {").withIndent { + addStatement("val map = mutableMapOf()", typeClassName) + typeToCollectorFunctions.get(type).orEmpty().forEach { item -> + addStatement("%N(map)", item) + } + addStatement("return@lazy map") + } + .addStatement("}") + .build() + + val mods = if (useActualModifier) { + listOf(KModifier.ACTUAL, resModifier) + } else { + listOf(resModifier) + } + + val property = PropertySpec + .builder( + "all${typeClassName.simpleName}s", + MAP.parameterizedBy(String::class.asClassName(), typeClassName), + mods + ) + .addAnnotation(experimentalAnnotation) + .receiver(ClassName(packageName, "Res")) + .delegate(initBlock) + .build() + file.addProperty(property) + } +}.build() + private fun sortResources( resources: Map>> ): TreeMap>> { 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 5ef8f5f393..0aeb94a2d8 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 @@ -513,9 +513,9 @@ class ResourcesTest : GradlePluginTestBase() { @Test fun testEmptyResClass(): Unit = with(testProject("misc/emptyResources")) { - gradle("generateComposeResClass").checks { + gradle("prepareKotlinIdeaImport").checks { assertDirectoriesContentEquals( - file("build/generated/compose/resourceGenerator/kotlin/commonResClass/app/group/empty_res/generated/resources"), + file("build/generated/compose/resourceGenerator/kotlin"), file("expected") ) } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceAccessors/my/lib/res/String0.androidMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceAccessors/my/lib/res/String0.androidMain.kt index 5d0d9e00b0..9d1d0e7a42 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceAccessors/my/lib/res/String0.androidMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceAccessors/my/lib/res/String0.androidMain.kt @@ -3,20 +3,28 @@ package my.lib.res import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap +import org.jetbrains.compose.resources.InternalResourceApi import org.jetbrains.compose.resources.StringResource private object AndroidMainString0 { - public val android_str: StringResource by - lazy { init_android_str() } + public val android_str: StringResource by + lazy { init_android_str() } +} + +@InternalResourceApi +internal fun _collectAndroidMainString0Resources(map: MutableMap) { + map.put("android_str", AndroidMainString0.android_str) } public val Res.string.android_str: StringResource - get() = AndroidMainString0.android_str + get() = AndroidMainString0.android_str private fun init_android_str(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:android_str", "android_str", + "string:android_str", "android_str", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.androidMain.cvr", 10, 39), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.androidMain.cvr", 10, 39), ) -) \ No newline at end of file +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceCollectors/my/lib/res/ActualResourceCollectors.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceCollectors/my/lib/res/ActualResourceCollectors.kt new file mode 100644 index 0000000000..9f91d73840 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/androidMainResourceCollectors/my/lib/res/ActualResourceCollectors.kt @@ -0,0 +1,53 @@ +@file:OptIn(org.jetbrains.compose.resources.InternalResourceApi::class) + +package my.lib.res + +import kotlin.OptIn +import kotlin.String +import kotlin.collections.Map +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringArrayResource +import org.jetbrains.compose.resources.StringResource + +@ExperimentalResourceApi +public actual val Res.allDrawableResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainDrawable0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +public actual val Res.allStringResources: Map by lazy { + val map = mutableMapOf() + _collectAndroidMainString0Resources(map) + _collectCommonMainString0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +public actual val Res.allStringArrayResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +public actual val Res.allPluralStringResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainPlurals0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +public actual val Res.allFontResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainFont0Resources(map) + return@lazy map +} + diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt index fc67e497cb..060e66ad48 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Drawable0.commonMain.kt @@ -3,58 +3,70 @@ package my.lib.res import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.InternalResourceApi private object CommonMainDrawable0 { - public val _3_strange_name: DrawableResource by - lazy { init__3_strange_name() } + public val _3_strange_name: DrawableResource by + lazy { init__3_strange_name() } - public val camelCaseName: DrawableResource by - lazy { init_camelCaseName() } + public val camelCaseName: DrawableResource by + lazy { init_camelCaseName() } - public val `is`: DrawableResource by - lazy { init_is() } + public val `is`: DrawableResource by + lazy { init_is() } - public val vector: DrawableResource by - lazy { init_vector() } + public val vector: DrawableResource by + lazy { init_vector() } - public val vector_2: DrawableResource by - lazy { init_vector_2() } + public val vector_2: DrawableResource by + lazy { init_vector_2() } +} + +@InternalResourceApi +internal fun _collectCommonMainDrawable0Resources(map: MutableMap) { + map.put("_3_strange_name", CommonMainDrawable0._3_strange_name) + map.put("camelCaseName", CommonMainDrawable0.camelCaseName) + map.put("is", CommonMainDrawable0.`is`) + map.put("vector", CommonMainDrawable0.vector) + map.put("vector_2", CommonMainDrawable0.vector_2) } public val Res.drawable._3_strange_name: DrawableResource get() = CommonMainDrawable0._3_strange_name private fun init__3_strange_name(): DrawableResource = - org.jetbrains.compose.resources.DrawableResource( - "drawable:_3_strange_name", + org.jetbrains.compose.resources.DrawableResource( + "drawable:_3_strange_name", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/drawable/3-strange-name.xml", -1, -1), + "composeResources/my.lib.res/drawable/3-strange-name.xml", -1, -1), ) - ) +) public val Res.drawable.camelCaseName: DrawableResource get() = CommonMainDrawable0.camelCaseName private fun init_camelCaseName(): DrawableResource = - org.jetbrains.compose.resources.DrawableResource( - "drawable:camelCaseName", + org.jetbrains.compose.resources.DrawableResource( + "drawable:camelCaseName", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/drawable/camelCaseName.xml", -1, -1), + "composeResources/my.lib.res/drawable/camelCaseName.xml", -1, -1), ) - ) +) public val Res.drawable.`is`: DrawableResource get() = CommonMainDrawable0.`is` private fun init_is(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:is", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/drawable/is.xml", -1, -1), - ) + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/drawable/is.xml", -1, -1), + ) ) public val Res.drawable.vector: DrawableResource @@ -62,24 +74,24 @@ public val Res.drawable.vector: DrawableResource private fun init_vector(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:vector", - setOf( - + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("ast"), ), "composeResources/my.lib.res/drawable-ast/vector.xml", -1, -1), - + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("au"), - org.jetbrains.compose.resources.RegionQualifier("US"), ), - "composeResources/my.lib.res/drawable-au-rUS/vector.xml", -1, -1), - + org.jetbrains.compose.resources.RegionQualifier("US"), ), + "composeResources/my.lib.res/drawable-au-rUS/vector.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.ThemeQualifier.DARK, - org.jetbrains.compose.resources.LanguageQualifier("ge"), ), - "composeResources/my.lib.res/drawable-dark-ge/vector.xml", -1, -1), - + org.jetbrains.compose.resources.LanguageQualifier("ge"), ), + "composeResources/my.lib.res/drawable-dark-ge/vector.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), ), "composeResources/my.lib.res/drawable-en/vector.xml", -1, -1), - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/drawable/vector.xml", -1, -1), - ) + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/drawable/vector.xml", -1, -1), + ) ) public val Res.drawable.vector_2: DrawableResource @@ -87,8 +99,8 @@ public val Res.drawable.vector_2: DrawableResource private fun init_vector_2(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:vector_2", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/drawable/vector_2.xml", -1, -1), - ) -) \ No newline at end of file + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/drawable/vector_2.xml", -1, -1), + ) +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Font0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Font0.commonMain.kt index c754abef21..f6e1193540 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Font0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Font0.commonMain.kt @@ -3,11 +3,19 @@ package my.lib.res import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.InternalResourceApi private object CommonMainFont0 { - public val emptyFont: FontResource by - lazy { init_emptyFont() } + public val emptyFont: FontResource by + lazy { init_emptyFont() } +} + +@InternalResourceApi +internal fun _collectCommonMainFont0Resources(map: MutableMap) { + map.put("emptyFont", CommonMainFont0.emptyFont) } public val Res.font.emptyFont: FontResource @@ -15,11 +23,11 @@ public val Res.font.emptyFont: FontResource private fun init_emptyFont(): FontResource = org.jetbrains.compose.resources.FontResource( "font:emptyFont", - setOf( - + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), ), "composeResources/my.lib.res/font-en/emptyFont.otf", -1, -1), - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/font/emptyFont.otf", -1, -1), - ) -) \ No newline at end of file + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/font/emptyFont.otf", -1, -1), + ) +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Plurals0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Plurals0.commonMain.kt index e97fd6730d..7a397b4235 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Plurals0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/Plurals0.commonMain.kt @@ -3,21 +3,29 @@ package my.lib.res import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap +import org.jetbrains.compose.resources.InternalResourceApi import org.jetbrains.compose.resources.PluralStringResource private object CommonMainPlurals0 { - public val numberOfSongsAvailable: PluralStringResource by - lazy { init_numberOfSongsAvailable() } + public val numberOfSongsAvailable: PluralStringResource by + lazy { init_numberOfSongsAvailable() } +} + +@InternalResourceApi +internal fun _collectCommonMainPlurals0Resources(map: MutableMap) { + map.put("numberOfSongsAvailable", CommonMainPlurals0.numberOfSongsAvailable) } public val Res.plurals.numberOfSongsAvailable: PluralStringResource - get() = CommonMainPlurals0.numberOfSongsAvailable + get() = CommonMainPlurals0.numberOfSongsAvailable private fun init_numberOfSongsAvailable(): PluralStringResource = org.jetbrains.compose.resources.PluralStringResource( - "plurals:numberOfSongsAvailable", "numberOfSongsAvailable", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.commonMain.cvr", 10, 124), - ) - ) \ No newline at end of file + "plurals:numberOfSongsAvailable", "numberOfSongsAvailable", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 10, 124), + ) +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt index 401c58853a..49fa3f8a39 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceAccessors/my/lib/res/String0.commonMain.kt @@ -3,32 +3,47 @@ package my.lib.res import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap +import org.jetbrains.compose.resources.InternalResourceApi import org.jetbrains.compose.resources.StringResource private object CommonMainString0 { - public val PascalCase: StringResource by - lazy { init_PascalCase() } + public val PascalCase: StringResource by + lazy { init_PascalCase() } - public val _1_kebab_case: StringResource by - lazy { init__1_kebab_case() } + public val _1_kebab_case: StringResource by + lazy { init__1_kebab_case() } - public val app_name: StringResource by - lazy { init_app_name() } + public val app_name: StringResource by + lazy { init_app_name() } - public val camelCase: StringResource by - lazy { init_camelCase() } + public val camelCase: StringResource by + lazy { init_camelCase() } - public val hello: StringResource by - lazy { init_hello() } + public val hello: StringResource by + lazy { init_hello() } - public val `info_using_release_$x`: StringResource by - lazy { `init_info_using_release_$x`() } + public val `info_using_release_$x`: StringResource by + lazy { `init_info_using_release_$x`() } - public val multi_line: StringResource by - lazy { init_multi_line() } + public val multi_line: StringResource by + lazy { init_multi_line() } - public val str_template: StringResource by - lazy { init_str_template() } + public val str_template: StringResource by + lazy { init_str_template() } +} + +@InternalResourceApi +internal fun _collectCommonMainString0Resources(map: MutableMap) { + map.put("PascalCase", CommonMainString0.PascalCase) + map.put("_1_kebab_case", CommonMainString0._1_kebab_case) + map.put("app_name", CommonMainString0.app_name) + map.put("camelCase", CommonMainString0.camelCase) + map.put("hello", CommonMainString0.hello) + map.put("info_using_release_${'$'}x", CommonMainString0.`info_using_release_$x`) + map.put("multi_line", CommonMainString0.multi_line) + map.put("str_template", CommonMainString0.str_template) } public val Res.string.PascalCase: StringResource @@ -36,10 +51,10 @@ public val Res.string.PascalCase: StringResource private fun init_PascalCase(): StringResource = org.jetbrains.compose.resources.StringResource( "string:PascalCase", "PascalCase", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.commonMain.cvr", 172, 34), - ) + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 172, 34), + ) ) public val Res.string._1_kebab_case: StringResource @@ -47,10 +62,10 @@ public val Res.string._1_kebab_case: StringResource private fun init__1_kebab_case(): StringResource = org.jetbrains.compose.resources.StringResource( "string:_1_kebab_case", "_1_kebab_case", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.commonMain.cvr", 135, 36), - ) + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 135, 36), + ) ) public val Res.string.app_name: StringResource @@ -58,10 +73,10 @@ public val Res.string.app_name: StringResource private fun init_app_name(): StringResource = org.jetbrains.compose.resources.StringResource( "string:app_name", "app_name", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.commonMain.cvr", 207, 44), - ) + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 207, 44), + ) ) public val Res.string.camelCase: StringResource @@ -69,10 +84,10 @@ public val Res.string.camelCase: StringResource private fun init_camelCase(): StringResource = org.jetbrains.compose.resources.StringResource( "string:camelCase", "camelCase", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.commonMain.cvr", 252, 29), - ) + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 252, 29), + ) ) public val Res.string.hello: StringResource @@ -80,33 +95,33 @@ public val Res.string.hello: StringResource private fun init_hello(): StringResource = org.jetbrains.compose.resources.StringResource( "string:hello", "hello", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.commonMain.cvr", 282, 37), - ) + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 282, 37), + ) ) public val Res.string.`info_using_release_$x`: StringResource get() = CommonMainString0.`info_using_release_$x` private fun `init_info_using_release_$x`(): StringResource = - org.jetbrains.compose.resources.StringResource( - "string:info_using_release_${'$'}x", "info_using_release_${'$'}x", + org.jetbrains.compose.resources.StringResource( + "string:info_using_release_${'$'}x", "info_using_release_${'$'}x", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.commonMain.cvr", 320, 57), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 320, 57), ) - ) +) public val Res.string.multi_line: StringResource get() = CommonMainString0.multi_line private fun init_multi_line(): StringResource = org.jetbrains.compose.resources.StringResource( "string:multi_line", "multi_line", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.commonMain.cvr", 378, 178), - ) + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 378, 178), + ) ) public val Res.string.str_template: StringResource @@ -114,8 +129,8 @@ public val Res.string.str_template: StringResource private fun init_str_template(): StringResource = org.jetbrains.compose.resources.StringResource( "string:str_template", "str_template", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/strings.commonMain.cvr", 557, 76), - ) -) \ No newline at end of file + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/strings.commonMain.cvr", 557, 76), + ) +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceCollectors/my/lib/res/ExpectResourceCollectors.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceCollectors/my/lib/res/ExpectResourceCollectors.kt new file mode 100644 index 0000000000..6ea528da50 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonMainResourceCollectors/my/lib/res/ExpectResourceCollectors.kt @@ -0,0 +1,25 @@ +package my.lib.res + +import kotlin.String +import kotlin.collections.Map +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringArrayResource +import org.jetbrains.compose.resources.StringResource + +@ExperimentalResourceApi +public expect val Res.allDrawableResources: Map + +@ExperimentalResourceApi +public expect val Res.allStringResources: Map + +@ExperimentalResourceApi +public expect val Res.allStringArrayResources: Map + +@ExperimentalResourceApi +public expect val Res.allPluralStringResources: Map + +@ExperimentalResourceApi +public expect val Res.allFontResources: Map diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonResClass/my/lib/res/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonResClass/my/lib/res/Res.kt index 50ce381bf1..1cfbcd76e6 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonResClass/my/lib/res/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/commonResClass/my/lib/res/Res.kt @@ -23,7 +23,7 @@ public object Res { */ @ExperimentalResourceApi public suspend fun readBytes(path: String): ByteArray = - readResourceBytes("composeResources/my.lib.res/" + path) + readResourceBytes("composeResources/my.lib.res/" + path) /** * Returns the URI string of the resource file at the specified path. @@ -45,4 +45,4 @@ public object Res { public object plurals public object font -} \ No newline at end of file +} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceAccessors/my/lib/res/String0.desktopMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceAccessors/my/lib/res/String0.desktopMain.kt index d002471d46..f3e28a5b40 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceAccessors/my/lib/res/String0.desktopMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceAccessors/my/lib/res/String0.desktopMain.kt @@ -3,20 +3,28 @@ package my.lib.res import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap +import org.jetbrains.compose.resources.InternalResourceApi import org.jetbrains.compose.resources.StringResource private object DesktopMainString0 { - public val desktop_str: StringResource by - lazy { init_desktop_str() } + public val desktop_str: StringResource by + lazy { init_desktop_str() } +} + +@InternalResourceApi +internal fun _collectDesktopMainString0Resources(map: MutableMap) { + map.put("desktop_str", DesktopMainString0.desktop_str) } public val Res.string.desktop_str: StringResource - get() = DesktopMainString0.desktop_str + get() = DesktopMainString0.desktop_str private fun init_desktop_str(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:desktop_str", "desktop_str", + "string:desktop_str", "desktop_str", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/my.lib.res/values/desktop_strings.desktopMain.cvr", 10, 39), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/my.lib.res/values/desktop_strings.desktopMain.cvr", 10, 39), ) -) \ No newline at end of file +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceCollectors/my/lib/res/ActualResourceCollectors.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceCollectors/my/lib/res/ActualResourceCollectors.kt new file mode 100644 index 0000000000..5b8690a54a --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/desktopMainResourceCollectors/my/lib/res/ActualResourceCollectors.kt @@ -0,0 +1,53 @@ +@file:OptIn(org.jetbrains.compose.resources.InternalResourceApi::class) + +package my.lib.res + +import kotlin.OptIn +import kotlin.String +import kotlin.collections.Map +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringArrayResource +import org.jetbrains.compose.resources.StringResource + +@ExperimentalResourceApi +public actual val Res.allDrawableResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainDrawable0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +public actual val Res.allStringResources: Map by lazy { + val map = mutableMapOf() + _collectDesktopMainString0Resources(map) + _collectCommonMainString0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +public actual val Res.allStringArrayResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +public actual val Res.allPluralStringResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainPlurals0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +public actual val Res.allFontResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainFont0Resources(map) + return@lazy map +} + diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceAccessors/app/group/resources_test/generated/resources/String0.androidMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceAccessors/app/group/resources_test/generated/resources/String0.androidMain.kt index 0b672fbfde..020c0d9dce 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceAccessors/app/group/resources_test/generated/resources/String0.androidMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceAccessors/app/group/resources_test/generated/resources/String0.androidMain.kt @@ -3,21 +3,29 @@ package app.group.resources_test.generated.resources import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap +import org.jetbrains.compose.resources.InternalResourceApi import org.jetbrains.compose.resources.StringResource private object AndroidMainString0 { - public val android_str: StringResource by - lazy { init_android_str() } + public val android_str: StringResource by + lazy { init_android_str() } +} + +@InternalResourceApi +internal fun _collectAndroidMainString0Resources(map: MutableMap) { + map.put("android_str", AndroidMainString0.android_str) } internal val Res.string.android_str: StringResource - get() = AndroidMainString0.android_str + get() = AndroidMainString0.android_str private fun init_android_str(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:android_str", "android_str", + "string:android_str", "android_str", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.androidMain.cvr", - 10, 39), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.androidMain.cvr", + 10, 39), ) -) \ No newline at end of file +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceCollectors/app/group/resources_test/generated/resources/ActualResourceCollectors.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceCollectors/app/group/resources_test/generated/resources/ActualResourceCollectors.kt new file mode 100644 index 0000000000..b747b87218 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/androidMainResourceCollectors/app/group/resources_test/generated/resources/ActualResourceCollectors.kt @@ -0,0 +1,53 @@ +@file:OptIn(org.jetbrains.compose.resources.InternalResourceApi::class) + +package app.group.resources_test.generated.resources + +import kotlin.OptIn +import kotlin.String +import kotlin.collections.Map +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringArrayResource +import org.jetbrains.compose.resources.StringResource + +@ExperimentalResourceApi +internal actual val Res.allDrawableResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainDrawable0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allStringResources: Map by lazy { + val map = mutableMapOf() + _collectAndroidMainString0Resources(map) + _collectCommonMainString0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allStringArrayResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allPluralStringResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainPlurals0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allFontResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainFont0Resources(map) + return@lazy map +} + diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt index d4b0e2aa25..b648204784 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Drawable0.commonMain.kt @@ -3,58 +3,70 @@ package app.group.resources_test.generated.resources import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.InternalResourceApi private object CommonMainDrawable0 { - public val _3_strange_name: DrawableResource by - lazy { init__3_strange_name() } + public val _3_strange_name: DrawableResource by + lazy { init__3_strange_name() } - public val camelCaseName: DrawableResource by - lazy { init_camelCaseName() } + public val camelCaseName: DrawableResource by + lazy { init_camelCaseName() } - public val `is`: DrawableResource by - lazy { init_is() } + public val `is`: DrawableResource by + lazy { init_is() } - public val vector: DrawableResource by - lazy { init_vector() } + public val vector: DrawableResource by + lazy { init_vector() } - public val vector_2: DrawableResource by - lazy { init_vector_2() } + public val vector_2: DrawableResource by + lazy { init_vector_2() } +} + +@InternalResourceApi +internal fun _collectCommonMainDrawable0Resources(map: MutableMap) { + map.put("_3_strange_name", CommonMainDrawable0._3_strange_name) + map.put("camelCaseName", CommonMainDrawable0.camelCaseName) + map.put("is", CommonMainDrawable0.`is`) + map.put("vector", CommonMainDrawable0.vector) + map.put("vector_2", CommonMainDrawable0.vector_2) } internal val Res.drawable._3_strange_name: DrawableResource get() = CommonMainDrawable0._3_strange_name private fun init__3_strange_name(): DrawableResource = - org.jetbrains.compose.resources.DrawableResource( - "drawable:_3_strange_name", + org.jetbrains.compose.resources.DrawableResource( + "drawable:_3_strange_name", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/drawable/3-strange-name.xml", -1, -1), + "composeResources/app.group.resources_test.generated.resources/drawable/3-strange-name.xml", -1, -1), ) - ) +) internal val Res.drawable.camelCaseName: DrawableResource get() = CommonMainDrawable0.camelCaseName private fun init_camelCaseName(): DrawableResource = - org.jetbrains.compose.resources.DrawableResource( - "drawable:camelCaseName", + org.jetbrains.compose.resources.DrawableResource( + "drawable:camelCaseName", setOf( org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/drawable/camelCaseName.xml", -1, -1), + "composeResources/app.group.resources_test.generated.resources/drawable/camelCaseName.xml", -1, -1), ) - ) +) internal val Res.drawable.`is`: DrawableResource get() = CommonMainDrawable0.`is` private fun init_is(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:is", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/drawable/is.xml", -1, -1), - ) + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/drawable/is.xml", -1, -1), + ) ) internal val Res.drawable.vector: DrawableResource @@ -62,26 +74,26 @@ internal val Res.drawable.vector: DrawableResource private fun init_vector(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:vector", - setOf( - + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("ast"), ), - "composeResources/app.group.resources_test.generated.resources/drawable-ast/vector.xml", -1, -1), - + "composeResources/app.group.resources_test.generated.resources/drawable-ast/vector.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("au"), - org.jetbrains.compose.resources.RegionQualifier("US"), ), - "composeResources/app.group.resources_test.generated.resources/drawable-au-rUS/vector.xml", -1, -1), - + org.jetbrains.compose.resources.RegionQualifier("US"), ), + "composeResources/app.group.resources_test.generated.resources/drawable-au-rUS/vector.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.ThemeQualifier.DARK, - org.jetbrains.compose.resources.LanguageQualifier("ge"), ), - "composeResources/app.group.resources_test.generated.resources/drawable-dark-ge/vector.xml", -1, -1), - + org.jetbrains.compose.resources.LanguageQualifier("ge"), ), + "composeResources/app.group.resources_test.generated.resources/drawable-dark-ge/vector.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), ), - "composeResources/app.group.resources_test.generated.resources/drawable-en/vector.xml", -1, -1), - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/drawable/vector.xml", -1, -1), - ) + "composeResources/app.group.resources_test.generated.resources/drawable-en/vector.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/drawable/vector.xml", -1, -1), + ) ) internal val Res.drawable.vector_2: DrawableResource @@ -89,8 +101,8 @@ internal val Res.drawable.vector_2: DrawableResource private fun init_vector_2(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( "drawable:vector_2", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/drawable/vector_2.xml", -1, -1), - ) -) \ No newline at end of file + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/drawable/vector_2.xml", -1, -1), + ) +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Font0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Font0.commonMain.kt index 432d74d5f6..6f89aaafa0 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Font0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Font0.commonMain.kt @@ -3,11 +3,19 @@ package app.group.resources_test.generated.resources import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.InternalResourceApi private object CommonMainFont0 { - public val emptyFont: FontResource by - lazy { init_emptyFont() } + public val emptyFont: FontResource by + lazy { init_emptyFont() } +} + +@InternalResourceApi +internal fun _collectCommonMainFont0Resources(map: MutableMap) { + map.put("emptyFont", CommonMainFont0.emptyFont) } internal val Res.font.emptyFont: FontResource @@ -15,12 +23,12 @@ internal val Res.font.emptyFont: FontResource private fun init_emptyFont(): FontResource = org.jetbrains.compose.resources.FontResource( "font:emptyFont", - setOf( - + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(org.jetbrains.compose.resources.LanguageQualifier("en"), ), - "composeResources/app.group.resources_test.generated.resources/font-en/emptyFont.otf", -1, -1), - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/font/emptyFont.otf", -1, -1), - ) -) \ No newline at end of file + "composeResources/app.group.resources_test.generated.resources/font-en/emptyFont.otf", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/font/emptyFont.otf", -1, -1), + ) +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Plurals0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Plurals0.commonMain.kt index b6b1abf697..c4c172ec20 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Plurals0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/Plurals0.commonMain.kt @@ -3,22 +3,30 @@ package app.group.resources_test.generated.resources import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap +import org.jetbrains.compose.resources.InternalResourceApi import org.jetbrains.compose.resources.PluralStringResource private object CommonMainPlurals0 { - public val numberOfSongsAvailable: PluralStringResource by - lazy { init_numberOfSongsAvailable() } + public val numberOfSongsAvailable: PluralStringResource by + lazy { init_numberOfSongsAvailable() } +} + +@InternalResourceApi +internal fun _collectCommonMainPlurals0Resources(map: MutableMap) { + map.put("numberOfSongsAvailable", CommonMainPlurals0.numberOfSongsAvailable) } internal val Res.plurals.numberOfSongsAvailable: PluralStringResource - get() = CommonMainPlurals0.numberOfSongsAvailable + get() = CommonMainPlurals0.numberOfSongsAvailable private fun init_numberOfSongsAvailable(): PluralStringResource = org.jetbrains.compose.resources.PluralStringResource( - "plurals:numberOfSongsAvailable", "numberOfSongsAvailable", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", - 10, 124), - ) - ) \ No newline at end of file + "plurals:numberOfSongsAvailable", "numberOfSongsAvailable", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 10, 124), + ) +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt index 9576f03c4f..50ab7a156f 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceAccessors/app/group/resources_test/generated/resources/String0.commonMain.kt @@ -3,127 +3,142 @@ package app.group.resources_test.generated.resources import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap +import org.jetbrains.compose.resources.InternalResourceApi import org.jetbrains.compose.resources.StringResource private object CommonMainString0 { - public val PascalCase: StringResource by - lazy { init_PascalCase() } + public val PascalCase: StringResource by + lazy { init_PascalCase() } - public val _1_kebab_case: StringResource by - lazy { init__1_kebab_case() } + public val _1_kebab_case: StringResource by + lazy { init__1_kebab_case() } - public val app_name: StringResource by - lazy { init_app_name() } + public val app_name: StringResource by + lazy { init_app_name() } - public val camelCase: StringResource by - lazy { init_camelCase() } + public val camelCase: StringResource by + lazy { init_camelCase() } - public val hello: StringResource by - lazy { init_hello() } + public val hello: StringResource by + lazy { init_hello() } - public val `info_using_release_$x`: StringResource by - lazy { `init_info_using_release_$x`() } + public val `info_using_release_$x`: StringResource by + lazy { `init_info_using_release_$x`() } - public val multi_line: StringResource by - lazy { init_multi_line() } + public val multi_line: StringResource by + lazy { init_multi_line() } - public val str_template: StringResource by - lazy { init_str_template() } + public val str_template: StringResource by + lazy { init_str_template() } +} + +@InternalResourceApi +internal fun _collectCommonMainString0Resources(map: MutableMap) { + map.put("PascalCase", CommonMainString0.PascalCase) + map.put("_1_kebab_case", CommonMainString0._1_kebab_case) + map.put("app_name", CommonMainString0.app_name) + map.put("camelCase", CommonMainString0.camelCase) + map.put("hello", CommonMainString0.hello) + map.put("info_using_release_${'$'}x", CommonMainString0.`info_using_release_$x`) + map.put("multi_line", CommonMainString0.multi_line) + map.put("str_template", CommonMainString0.str_template) } internal val Res.string.PascalCase: StringResource - get() = CommonMainString0.PascalCase + get() = CommonMainString0.PascalCase private fun init_PascalCase(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:PascalCase", "PascalCase", + "string:PascalCase", "PascalCase", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", - 172, 34), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 172, 34), ) ) internal val Res.string._1_kebab_case: StringResource - get() = CommonMainString0._1_kebab_case + get() = CommonMainString0._1_kebab_case private fun init__1_kebab_case(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:_1_kebab_case", "_1_kebab_case", + "string:_1_kebab_case", "_1_kebab_case", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", - 135, 36), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 135, 36), ) ) internal val Res.string.app_name: StringResource - get() = CommonMainString0.app_name + get() = CommonMainString0.app_name private fun init_app_name(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:app_name", "app_name", + "string:app_name", "app_name", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", - 207, 44), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 207, 44), ) ) internal val Res.string.camelCase: StringResource - get() = CommonMainString0.camelCase + get() = CommonMainString0.camelCase private fun init_camelCase(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:camelCase", "camelCase", + "string:camelCase", "camelCase", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", - 252, 29), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 252, 29), ) ) internal val Res.string.hello: StringResource - get() = CommonMainString0.hello + get() = CommonMainString0.hello private fun init_hello(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:hello", "hello", + "string:hello", "hello", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", - 282, 37), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 282, 37), ) ) internal val Res.string.`info_using_release_$x`: StringResource - get() = CommonMainString0.`info_using_release_$x` + get() = CommonMainString0.`info_using_release_$x` private fun `init_info_using_release_$x`(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:info_using_release_${'$'}x", "info_using_release_${'$'}x", - setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", - 320, 57), - ) + "string:info_using_release_${'$'}x", "info_using_release_${'$'}x", + setOf( + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 320, 57), ) +) internal val Res.string.multi_line: StringResource - get() = CommonMainString0.multi_line + get() = CommonMainString0.multi_line private fun init_multi_line(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:multi_line", "multi_line", + "string:multi_line", "multi_line", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", - 378, 178), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 378, 178), ) ) internal val Res.string.str_template: StringResource - get() = CommonMainString0.str_template + get() = CommonMainString0.str_template private fun init_str_template(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:str_template", "str_template", + "string:str_template", "str_template", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", - 557, 76), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/strings.commonMain.cvr", + 557, 76), ) -) \ No newline at end of file +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceCollectors/app/group/resources_test/generated/resources/ExpectResourceCollectors.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceCollectors/app/group/resources_test/generated/resources/ExpectResourceCollectors.kt new file mode 100644 index 0000000000..e287403867 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonMainResourceCollectors/app/group/resources_test/generated/resources/ExpectResourceCollectors.kt @@ -0,0 +1,25 @@ +package app.group.resources_test.generated.resources + +import kotlin.String +import kotlin.collections.Map +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringArrayResource +import org.jetbrains.compose.resources.StringResource + +@ExperimentalResourceApi +internal expect val Res.allDrawableResources: Map + +@ExperimentalResourceApi +internal expect val Res.allStringResources: Map + +@ExperimentalResourceApi +internal expect val Res.allStringArrayResources: Map + +@ExperimentalResourceApi +internal expect val Res.allPluralStringResources: Map + +@ExperimentalResourceApi +internal expect val Res.allFontResources: Map diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonResClass/app/group/resources_test/generated/resources/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonResClass/app/group/resources_test/generated/resources/Res.kt index 58b19ebe7a..73506c4d6d 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonResClass/app/group/resources_test/generated/resources/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/commonResClass/app/group/resources_test/generated/resources/Res.kt @@ -23,7 +23,7 @@ internal object Res { */ @ExperimentalResourceApi public suspend fun readBytes(path: String): ByteArray = - readResourceBytes("composeResources/app.group.resources_test.generated.resources/" + path) + readResourceBytes("composeResources/app.group.resources_test.generated.resources/" + path) /** * Returns the URI string of the resource file at the specified path. @@ -35,7 +35,7 @@ internal object Res { */ @ExperimentalResourceApi public fun getUri(path: String): String = - getResourceUri("composeResources/app.group.resources_test.generated.resources/" + path) + getResourceUri("composeResources/app.group.resources_test.generated.resources/" + path) public object drawable @@ -46,4 +46,4 @@ internal object Res { public object plurals public object font -} \ No newline at end of file +} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceAccessors/app/group/resources_test/generated/resources/String0.desktopMain.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceAccessors/app/group/resources_test/generated/resources/String0.desktopMain.kt index 0eabfd7621..2948107939 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceAccessors/app/group/resources_test/generated/resources/String0.desktopMain.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceAccessors/app/group/resources_test/generated/resources/String0.desktopMain.kt @@ -3,21 +3,29 @@ package app.group.resources_test.generated.resources import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap +import org.jetbrains.compose.resources.InternalResourceApi import org.jetbrains.compose.resources.StringResource private object DesktopMainString0 { - public val desktop_str: StringResource by - lazy { init_desktop_str() } + public val desktop_str: StringResource by + lazy { init_desktop_str() } +} + +@InternalResourceApi +internal fun _collectDesktopMainString0Resources(map: MutableMap) { + map.put("desktop_str", DesktopMainString0.desktop_str) } internal val Res.string.desktop_str: StringResource - get() = DesktopMainString0.desktop_str + get() = DesktopMainString0.desktop_str private fun init_desktop_str(): StringResource = org.jetbrains.compose.resources.StringResource( - "string:desktop_str", "desktop_str", + "string:desktop_str", "desktop_str", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), - "composeResources/app.group.resources_test.generated.resources/values/desktop_strings.desktopMain.cvr", - 10, 39), + org.jetbrains.compose.resources.ResourceItem(setOf(), + "composeResources/app.group.resources_test.generated.resources/values/desktop_strings.desktopMain.cvr", + 10, 39), ) -) \ No newline at end of file +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceCollectors/app/group/resources_test/generated/resources/ActualResourceCollectors.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceCollectors/app/group/resources_test/generated/resources/ActualResourceCollectors.kt new file mode 100644 index 0000000000..d349d941b4 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/desktopMainResourceCollectors/app/group/resources_test/generated/resources/ActualResourceCollectors.kt @@ -0,0 +1,53 @@ +@file:OptIn(org.jetbrains.compose.resources.InternalResourceApi::class) + +package app.group.resources_test.generated.resources + +import kotlin.OptIn +import kotlin.String +import kotlin.collections.Map +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringArrayResource +import org.jetbrains.compose.resources.StringResource + +@ExperimentalResourceApi +internal actual val Res.allDrawableResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainDrawable0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allStringResources: Map by lazy { + val map = mutableMapOf() + _collectDesktopMainString0Resources(map) + _collectCommonMainString0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allStringArrayResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allPluralStringResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainPlurals0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allFontResources: Map by lazy { + val map = mutableMapOf() + _collectCommonMainFont0Resources(map) + return@lazy map +} + 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 deleted file mode 100644 index b6af81dff5..0000000000 --- a/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/Res.kt +++ /dev/null @@ -1,49 +0,0 @@ -@file:OptIn( - org.jetbrains.compose.resources.InternalResourceApi::class, - org.jetbrains.compose.resources.ExperimentalResourceApi::class, -) - -package app.group.empty_res.generated.resources - -import kotlin.ByteArray -import kotlin.OptIn -import kotlin.String -import org.jetbrains.compose.resources.ExperimentalResourceApi -import org.jetbrains.compose.resources.getResourceUri -import org.jetbrains.compose.resources.readResourceBytes - -internal object Res { - /** - * 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. - */ - @ExperimentalResourceApi - public suspend fun readBytes(path: String): ByteArray = - readResourceBytes("composeResources/app.group.empty_res.generated.resources/" + path) - - /** - * Returns the URI string of the resource file at the specified path. - * - * Example: `val uri = Res.getUri("files/key.bin")` - * - * @param path The path of the file in the compose resource's directory. - * @return The URI string of the file. - */ - @ExperimentalResourceApi - public fun getUri(path: String): String = - getResourceUri("composeResources/app.group.empty_res.generated.resources/" + path) - - public object drawable - - public object string - - public object array - - public object plurals - - public object font -} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/commonMainResourceCollectors/app/group/empty_res/generated/resources/ExpectResourceCollectors.kt b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/commonMainResourceCollectors/app/group/empty_res/generated/resources/ExpectResourceCollectors.kt new file mode 100644 index 0000000000..ff58b70797 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/commonMainResourceCollectors/app/group/empty_res/generated/resources/ExpectResourceCollectors.kt @@ -0,0 +1,25 @@ +package app.group.empty_res.generated.resources + +import kotlin.String +import kotlin.collections.Map +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringArrayResource +import org.jetbrains.compose.resources.StringResource + +@ExperimentalResourceApi +internal expect val Res.allDrawableResources: Map + +@ExperimentalResourceApi +internal expect val Res.allStringResources: Map + +@ExperimentalResourceApi +internal expect val Res.allStringArrayResources: Map + +@ExperimentalResourceApi +internal expect val Res.allPluralStringResources: Map + +@ExperimentalResourceApi +internal expect val Res.allFontResources: Map diff --git a/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/commonResClass/app/group/empty_res/generated/resources/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/commonResClass/app/group/empty_res/generated/resources/Res.kt new file mode 100644 index 0000000000..32518fbda7 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/commonResClass/app/group/empty_res/generated/resources/Res.kt @@ -0,0 +1,49 @@ +@file:OptIn( + org.jetbrains.compose.resources.InternalResourceApi::class, + org.jetbrains.compose.resources.ExperimentalResourceApi::class, +) + +package app.group.empty_res.generated.resources + +import kotlin.ByteArray +import kotlin.OptIn +import kotlin.String +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.getResourceUri +import org.jetbrains.compose.resources.readResourceBytes + +internal object Res { + /** + * 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. + */ + @ExperimentalResourceApi + public suspend fun readBytes(path: String): ByteArray = + readResourceBytes("composeResources/app.group.empty_res.generated.resources/" + path) + + /** + * Returns the URI string of the resource file at the specified path. + * + * Example: `val uri = Res.getUri("files/key.bin")` + * + * @param path The path of the file in the compose resource's directory. + * @return The URI string of the file. + */ + @ExperimentalResourceApi + public fun getUri(path: String): String = + getResourceUri("composeResources/app.group.empty_res.generated.resources/" + path) + + public object drawable + + public object string + + public object array + + public object plurals + + public object font +} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/desktopMainResourceCollectors/app/group/empty_res/generated/resources/ActualResourceCollectors.kt b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/desktopMainResourceCollectors/app/group/empty_res/generated/resources/ActualResourceCollectors.kt new file mode 100644 index 0000000000..be6a2f9dae --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/desktopMainResourceCollectors/app/group/empty_res/generated/resources/ActualResourceCollectors.kt @@ -0,0 +1,48 @@ +@file:OptIn(org.jetbrains.compose.resources.InternalResourceApi::class) + +package app.group.empty_res.generated.resources + +import kotlin.OptIn +import kotlin.String +import kotlin.collections.Map +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringArrayResource +import org.jetbrains.compose.resources.StringResource + +@ExperimentalResourceApi +internal actual val Res.allDrawableResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allStringResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allStringArrayResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allPluralStringResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +internal actual val Res.allFontResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/commonResClass/me/app/jvmonlyresources/generated/resources/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/commonResClass/me/app/jvmonlyresources/generated/resources/Res.kt index 7004b885d8..b532771763 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/commonResClass/me/app/jvmonlyresources/generated/resources/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/commonResClass/me/app/jvmonlyresources/generated/resources/Res.kt @@ -1,6 +1,6 @@ @file:OptIn( - org.jetbrains.compose.resources.InternalResourceApi::class, - org.jetbrains.compose.resources.ExperimentalResourceApi::class, + org.jetbrains.compose.resources.InternalResourceApi::class, + org.jetbrains.compose.resources.ExperimentalResourceApi::class, ) package me.app.jvmonlyresources.generated.resources @@ -13,35 +13,35 @@ import org.jetbrains.compose.resources.getResourceUri import org.jetbrains.compose.resources.readResourceBytes internal object Res { - /** - * 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. - */ - @ExperimentalResourceApi - public suspend fun readBytes(path: String): ByteArray = readResourceBytes("" + path) - - /** - * Returns the URI string of the resource file at the specified path. - * - * Example: `val uri = Res.getUri("files/key.bin")` - * - * @param path The path of the file in the compose resource's directory. - * @return The URI string of the file. - */ - @ExperimentalResourceApi - public fun getUri(path: String): String = getResourceUri("" + path) - - public object drawable - - public object string - - public object array - - public object plurals - - public object font -} \ No newline at end of file + /** + * 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. + */ + @ExperimentalResourceApi + public suspend fun readBytes(path: String): ByteArray = readResourceBytes("" + path) + + /** + * Returns the URI string of the resource file at the specified path. + * + * Example: `val uri = Res.getUri("files/key.bin")` + * + * @param path The path of the file in the compose resource's directory. + * @return The URI string of the file. + */ + @ExperimentalResourceApi + public fun getUri(path: String): String = getResourceUri("" + path) + + public object drawable + + public object string + + public object array + + public object plurals + + public object font +} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/mainResourceAccessors/me/app/jvmonlyresources/generated/resources/Drawable0.main.kt b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/mainResourceAccessors/me/app/jvmonlyresources/generated/resources/Drawable0.main.kt index 98f4998f2b..21c01fd922 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/mainResourceAccessors/me/app/jvmonlyresources/generated/resources/Drawable0.main.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/mainResourceAccessors/me/app/jvmonlyresources/generated/resources/Drawable0.main.kt @@ -3,19 +3,27 @@ package me.app.jvmonlyresources.generated.resources import kotlin.OptIn +import kotlin.String +import kotlin.collections.MutableMap import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.InternalResourceApi private object MainDrawable0 { - public val vector: DrawableResource by - lazy { init_vector() } + public val vector: DrawableResource by + lazy { init_vector() } +} + +@InternalResourceApi +internal fun _collectMainDrawable0Resources(map: MutableMap) { + map.put("vector", MainDrawable0.vector) } internal val Res.drawable.vector: DrawableResource - get() = MainDrawable0.vector + get() = MainDrawable0.vector private fun init_vector(): DrawableResource = org.jetbrains.compose.resources.DrawableResource( - "drawable:vector", + "drawable:vector", setOf( - org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml", -1, -1), + org.jetbrains.compose.resources.ResourceItem(setOf(), "drawable/vector.xml", -1, -1), ) -) \ No newline at end of file +) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/mainResourceCollectors/me/app/jvmonlyresources/generated/resources/ActualResourceCollectors.kt b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/mainResourceCollectors/me/app/jvmonlyresources/generated/resources/ActualResourceCollectors.kt new file mode 100644 index 0000000000..1a915e8926 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/mainResourceCollectors/me/app/jvmonlyresources/generated/resources/ActualResourceCollectors.kt @@ -0,0 +1,49 @@ +@file:OptIn(org.jetbrains.compose.resources.InternalResourceApi::class) + +package me.app.jvmonlyresources.generated.resources + +import kotlin.OptIn +import kotlin.String +import kotlin.collections.Map +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.FontResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringArrayResource +import org.jetbrains.compose.resources.StringResource + +@ExperimentalResourceApi +internal val Res.allDrawableResources: Map by lazy { + val map = mutableMapOf() + _collectMainDrawable0Resources(map) + return@lazy map +} + + +@ExperimentalResourceApi +internal val Res.allStringResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +internal val Res.allStringArrayResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +internal val Res.allPluralStringResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + + +@ExperimentalResourceApi +internal val Res.allFontResources: Map by lazy { + val map = mutableMapOf() + return@lazy map +} + From 5da48904e5c24aa567da17fb780707073f920af3 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 10 Jul 2024 16:51:32 +0200 Subject: [PATCH 40/47] [gradle] Disable code generation tasks if they are not needed. (#5091) Small refactor: instead of the run tasks in a dumb mode just skip them if the resource accessors generation is disabled. --- .../resources/ComposeResourcesGeneration.kt | 32 +++++-- .../compose/resources/GenerateResClassTask.kt | 17 ++-- .../GenerateResourceAccessorsTask.kt | 54 +++++------- .../GenerateResourceCollectorsTask.kt | 87 ++++++++----------- .../test/tests/integration/ResourcesTest.kt | 13 ++- 5 files changed, 98 insertions(+), 105 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt index 672a479d0d..9b45b9be32 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt @@ -98,17 +98,21 @@ private fun Project.configureResClassGeneration( GenerateResClassTask::class.java ) { task -> task.packageName.set(packageName) - task.shouldGenerateCode.set(shouldGenerateCode) task.makeAccessorsPublic.set(makeAccessorsPublic) task.codeDir.set(layout.buildDirectory.dir("$RES_GEN_DIR/kotlin/commonResClass")) if (generateModulePath) { task.packagingDir.set(packagingDir) } + task.onlyIf { shouldGenerateCode.get() } } //register generated source set - resClassSourceSet.kotlin.srcDir(genTask.map { it.codeDir }) + resClassSourceSet.kotlin.srcDir( + genTask.zip(shouldGenerateCode) { task, flag -> + if (flag) listOf(task.codeDir) else emptyList() + } + ) } private fun Project.configureResourceAccessorsGeneration( @@ -128,7 +132,6 @@ private fun Project.configureResourceAccessorsGeneration( ) { task -> task.packageName.set(packageName) task.sourceSetName.set(sourceSet.name) - task.shouldGenerateCode.set(shouldGenerateCode) task.makeAccessorsPublic.set(makeAccessorsPublic) task.resDir.set(resourcesDir) task.codeDir.set(layout.buildDirectory.dir("$RES_GEN_DIR/kotlin/${sourceSet.name}ResourceAccessors")) @@ -136,10 +139,15 @@ private fun Project.configureResourceAccessorsGeneration( if (generateModulePath) { task.packagingDir.set(packagingDir) } + task.onlyIf { shouldGenerateCode.get() } } //register generated source set - sourceSet.kotlin.srcDir(genTask.map { it.codeDir }) + sourceSet.kotlin.srcDir( + genTask.zip(shouldGenerateCode) { task, flag -> + if (flag) listOf(task.codeDir) else emptyList() + } + ) } private fun KotlinSourceSet.getResourceAccessorsGenerationTaskName(): String { @@ -219,13 +227,17 @@ private fun Project.configureExpectResourceCollectorsGeneration( GenerateExpectResourceCollectorsTask::class.java ) { task -> task.packageName.set(packageName) - task.shouldGenerateCode.set(shouldGenerateCode) task.makeAccessorsPublic.set(makeAccessorsPublic) task.codeDir.set(layout.buildDirectory.dir("$RES_GEN_DIR/kotlin/${sourceSet.name}ResourceCollectors")) + task.onlyIf { shouldGenerateCode.get() } } //register generated source set - sourceSet.kotlin.srcDir(genTask.map { it.codeDir }) + sourceSet.kotlin.srcDir( + genTask.zip(shouldGenerateCode) { task, flag -> + if (flag) listOf(task.codeDir) else emptyList() + } + ) } private fun Project.configureActualResourceCollectorsGeneration( @@ -257,13 +269,17 @@ private fun Project.configureActualResourceCollectorsGeneration( GenerateActualResourceCollectorsTask::class.java ) { task -> task.packageName.set(packageName) - task.shouldGenerateCode.set(shouldGenerateCode) task.makeAccessorsPublic.set(makeAccessorsPublic) task.useActualModifier.set(useActualModifier) task.resourceAccessorDirs.from(accessorDirs) task.codeDir.set(layout.buildDirectory.dir("$RES_GEN_DIR/kotlin/${sourceSet.name}ResourceCollectors")) + task.onlyIf { shouldGenerateCode.get() } } //register generated source set - sourceSet.kotlin.srcDir(genTask.map { it.codeDir }) + sourceSet.kotlin.srcDir( + genTask.zip(shouldGenerateCode) { task, flag -> + if (flag) listOf(task.codeDir) else emptyList() + } + ) } \ No newline at end of file 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 5ce79a5268..676715343f 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 @@ -20,9 +20,6 @@ internal abstract class GenerateResClassTask : IdeaImportTask() { @get:Optional abstract val packagingDir: Property - @get:Input - abstract val shouldGenerateCode: Property - @get:Input abstract val makeAccessorsPublic: Property @@ -34,15 +31,11 @@ internal abstract class GenerateResClassTask : IdeaImportTask() { dir.deleteRecursively() dir.mkdirs() - if (shouldGenerateCode.get()) { - logger.info("Generate $RES_FILE_NAME.kt") + logger.info("Generate $RES_FILE_NAME.kt") - val pkgName = packageName.get() - val moduleDirectory = packagingDir.getOrNull()?.let { it.invariantSeparatorsPath + "/" } ?: "" - val isPublic = makeAccessorsPublic.get() - getResFileSpec(pkgName, RES_FILE_NAME, moduleDirectory, isPublic).writeTo(dir) - } else { - logger.info("Generation Res class is disabled") - } + val pkgName = packageName.get() + val moduleDirectory = packagingDir.getOrNull()?.let { it.invariantSeparatorsPath + "/" } ?: "" + val isPublic = makeAccessorsPublic.get() + getResFileSpec(pkgName, RES_FILE_NAME, moduleDirectory, isPublic).writeTo(dir) } } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceAccessorsTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceAccessorsTask.kt index 9aca01b3ff..6d346c5069 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceAccessorsTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceAccessorsTask.kt @@ -9,7 +9,6 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.SkipWhenEmpty -import org.gradle.api.tasks.TaskAction import org.jetbrains.compose.internal.IdeaImportTask import java.io.File import java.nio.file.Path @@ -26,9 +25,6 @@ internal abstract class GenerateResourceAccessorsTask : IdeaImportTask() { @get:Optional abstract val packagingDir: Property - @get:Input - abstract val shouldGenerateCode: Property - @get:Input abstract val makeAccessorsPublic: Property @@ -49,37 +45,33 @@ internal abstract class GenerateResourceAccessorsTask : IdeaImportTask() { kotlinDir.deleteRecursively() kotlinDir.mkdirs() - if (shouldGenerateCode.get()) { - logger.info("Generate accessors for $rootResDir") + logger.info("Generate accessors for $rootResDir") - //get first level dirs - val dirs = rootResDir.listNotHiddenFiles() + //get first level dirs + val dirs = rootResDir.listNotHiddenFiles() - dirs.forEach { f -> - if (!f.isDirectory) { - error("${f.name} is not directory! Raw files should be placed in '${rootResDir.name}/files' directory.") - } + dirs.forEach { f -> + if (!f.isDirectory) { + error("${f.name} is not directory! Raw files should be placed in '${rootResDir.name}/files' directory.") } - - //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 } } - - val pkgName = packageName.get() - val moduleDirectory = packagingDir.getOrNull()?.let { it.invariantSeparatorsPath + "/" } ?: "" - val isPublic = makeAccessorsPublic.get() - getAccessorsSpecs( - resources, pkgName, sourceSet, moduleDirectory, isPublic - ).forEach { it.writeTo(kotlinDir) } - } else { - logger.info("Generation accessors for $rootResDir is disabled") } + + //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 } } + + val pkgName = packageName.get() + val moduleDirectory = packagingDir.getOrNull()?.let { it.invariantSeparatorsPath + "/" } ?: "" + val isPublic = makeAccessorsPublic.get() + getAccessorsSpecs( + resources, pkgName, sourceSet, moduleDirectory, isPublic + ).forEach { it.writeTo(kotlinDir) } } private fun File.fileToResourceItems( diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceCollectorsTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceCollectorsTask.kt index bd67c804f5..19b9a371a1 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceCollectorsTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceCollectorsTask.kt @@ -15,9 +15,6 @@ internal abstract class GenerateExpectResourceCollectorsTask : IdeaImportTask() @get:Input abstract val packageName: Property - @get:Input - abstract val shouldGenerateCode: Property - @get:Input abstract val makeAccessorsPublic: Property @@ -31,14 +28,12 @@ internal abstract class GenerateExpectResourceCollectorsTask : IdeaImportTask() kotlinDir.deleteRecursively() kotlinDir.mkdirs() - if (shouldGenerateCode.get()) { - logger.info("Generate expect ResourceCollectors for $kotlinDir") + logger.info("Generate expect ResourceCollectors for $kotlinDir") - val pkgName = packageName.get() - val isPublic = makeAccessorsPublic.get() - val spec = getExpectResourceCollectorsFileSpec(pkgName, "ExpectResourceCollectors", isPublic) - spec.writeTo(kotlinDir) - } + val pkgName = packageName.get() + val isPublic = makeAccessorsPublic.get() + val spec = getExpectResourceCollectorsFileSpec(pkgName, "ExpectResourceCollectors", isPublic) + spec.writeTo(kotlinDir) } } @@ -46,9 +41,6 @@ internal abstract class GenerateActualResourceCollectorsTask : IdeaImportTask() @get:Input abstract val packageName: Property - @get:Input - abstract val shouldGenerateCode: Property - @get:Input abstract val makeAccessorsPublic: Property @@ -73,44 +65,39 @@ internal abstract class GenerateActualResourceCollectorsTask : IdeaImportTask() val inputFiles = inputDirs.flatMap { dir -> dir.walkTopDown().filter { !it.isHidden && it.isFile && it.extension == "kt" }.toList() } - - if (shouldGenerateCode.get()) { - logger.info("Generate actual ResourceCollectors for $kotlinDir") - val funNames = inputFiles.mapNotNull { inputFile -> - if (inputFile.nameWithoutExtension.contains('.')) { - val (fileName, suffix) = inputFile.nameWithoutExtension.split('.') - val type = ResourceType.values().firstOrNull { fileName.startsWith(it.accessorName, true) } - val name = "_collect${suffix.uppercaseFirstChar()}${fileName}Resources" - - if (type == null) { - logger.warn("Unknown resources type: `$inputFile`") - null - } else if (!inputFile.readText().contains(name)) { - logger.warn("A function '$name' is not found in the `$inputFile` file!") - null - } else { - logger.info("Found collector function: `$name`") - type to name - } - } else { - logger.warn("Unknown file name: `$inputFile`") + logger.info("Generate actual ResourceCollectors for $kotlinDir") + val funNames = inputFiles.mapNotNull { inputFile -> + if (inputFile.nameWithoutExtension.contains('.')) { + val (fileName, suffix) = inputFile.nameWithoutExtension.split('.') + val type = ResourceType.values().firstOrNull { fileName.startsWith(it.accessorName, true) } + val name = "_collect${suffix.uppercaseFirstChar()}${fileName}Resources" + + if (type == null) { + logger.warn("Unknown resources type: `$inputFile`") + null + } else if (!inputFile.readText().contains(name)) { + logger.warn("A function '$name' is not found in the `$inputFile` file!") null + } else { + logger.info("Found collector function: `$name`") + type to name } - }.groupBy({ it.first }, { it.second }) - - val pkgName = packageName.get() - val isPublic = makeAccessorsPublic.get() - val useActual = useActualModifier.get() - val spec = getActualResourceCollectorsFileSpec( - pkgName, - "ActualResourceCollectors", - isPublic, - useActual, - funNames - ) - spec.writeTo(kotlinDir) - } else { - logger.info("Generation ResourceCollectors for $kotlinDir is disabled") - } + } else { + logger.warn("Unknown file name: `$inputFile`") + null + } + }.groupBy({ it.first }, { it.second }) + + val pkgName = packageName.get() + val isPublic = makeAccessorsPublic.get() + val useActual = useActualModifier.get() + val spec = getActualResourceCollectorsFileSpec( + pkgName, + "ActualResourceCollectors", + isPublic, + useActual, + funNames + ) + spec.writeTo(kotlinDir) } } 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 0aeb94a2d8..95b599bfcb 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 @@ -488,8 +488,7 @@ class ResourcesTest : GradlePluginTestBase() { ) } gradle("prepareKotlinIdeaImport").checks { - check.taskSuccessful(":generateComposeResClass") - assertFalse(file("build/generated/compose/resourceGenerator/kotlin/commonResClass/app/group/resources_test/generated/resources/Res.kt").exists()) + check.taskSkipped(":generateComposeResClass") } modifyText("build.gradle.kts") { str -> @@ -571,8 +570,14 @@ class ResourcesTest : GradlePluginTestBase() { } """.trimIndent() ) - gradle("generateComposeResClass").checks { - check.logContains("Generation Res class is disabled") + gradle("prepareKotlinIdeaImport").checks { + check.taskSkipped(":generateComposeResClass") + check.taskSkipped(":generateResourceAccessorsForCommonMain") + check.taskSkipped(":generateResourceAccessorsForDesktopMain") + check.taskSkipped(":generateResourceAccessorsForAndroidMain") + check.taskSkipped(":generateExpectResourceCollectorsForCommonMain") + check.taskSkipped(":generateActualResourceCollectorsForDesktopMain") + check.taskSkipped(":generateActualResourceCollectorsForAndroidMain") } } From 8b172451dde7327ee35d4e655b5733eaf8b1e829 Mon Sep 17 00:00:00 2001 From: Victor Kropp Date: Thu, 11 Jul 2024 11:20:01 +0200 Subject: [PATCH 41/47] move issue tracker to JetBrains YouTrack --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..725144d983 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Create + url: https://youtrack.jetbrains.com/newIssue?project=CMP + about: Please report new issues to the JetBrains YouTrack From 738b6c33fe988c4ac1db0abe0712428fc019155c Mon Sep 17 00:00:00 2001 From: Victor Kropp Date: Thu, 11 Jul 2024 11:21:09 +0200 Subject: [PATCH 42/47] delete issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 58 --------------------------- .github/ISSUE_TEMPLATE/enhancement.md | 8 ---- .github/ISSUE_TEMPLATE/performance.md | 48 ---------------------- 3 files changed, 114 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/enhancement.md delete mode 100644 .github/ISSUE_TEMPLATE/performance.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 277f01d1ee..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: ['submitted', 'bug'] -assignees: '' - ---- - - - -**Describe the bug** -A clear and concise description of what the bug is. - -**Affected platforms** - -- Desktop (Windows, Linux, macOS) -- iOS -- Web (K/Wasm) - Canvas based API -- Web (K/JS) - Canvas based API -- Web (K/JS) - HTML library - -**Versions** -- Libraries: - - Compose Multiplatform version: - - Navigation Multiplatform version (for related issues): - - ... -- Kotlin version: -- OS version(s) (required for Desktop and iOS issues): -- OS architecture (x86 or arm64): -- Device (model or simulator for iOS issues): -- JDK (for desktop issues): - -**To Reproduce** -Steps to reproduce the behavior: -1. Run this code snippet: - ```kt - @Composable - fun BugReproduction() { - // ... - } - ``` -2. Click on '...' -3. Scroll down to '...' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md deleted file mode 100644 index d039a554c8..0000000000 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: Enhancement -about: 'New feature or request' -title: '' -labels: 'submitted, enhancement' -assignees: '' - ---- diff --git a/.github/ISSUE_TEMPLATE/performance.md b/.github/ISSUE_TEMPLATE/performance.md deleted file mode 100644 index 7cd4658e8b..0000000000 --- a/.github/ISSUE_TEMPLATE/performance.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: Performance problem -about: Create a report to help us improve -title: '' -labels: ['submitted', 'performance'] -assignees: '' - ---- - -**Describe the problem** -Explain the performance issue you're experiencing, including the following details: - -- What specific issue did you encounter? (e.g. missing frames, high CPU usage, memory leaks) -- Have you noticed any patterns or specific circumstances under which the problem occurs? - -**Affected platforms** -Select one of the platforms below: -- All -- Desktop -- Web (K/Wasm) - Canvas based API -- Web (K/JS) - Canvas based API -- Web (K/JS) - HTML library -- iOS -- Other - -If the problem is Android-only, report it in the [Jetpack Compose tracker](https://issuetracker.google.com/issues/new?component=612128) - -**Versions** -- Kotlin version: -- Compose Multiplatform version: -- OS version(s) (required for Desktop and iOS issues): -- OS architecture (x86 or arm64): -- JDK (for desktop issues): - -**Sample code** -If possible, provide a small piece of code that reproduces the problem. If the code snippet is too large to paste here, please link to a Gist, a GitHub repo, or any other public code repository. - -**Reproduction steps** -Please provide a detailed step-by-step guide on how to reproduce the issue you are experiencing. - -**Video** -If you're reporting slow app work or missing frames, please provide a video of the problem. - -**Profiling data** -Please provide any relevant profiling data that might be helpful. This could include information like FPS, memory usage, CPU time, or any other data that could provide insight into the performance issue. - -**Additional information** -Provide any other details that you think might be helpful for us to understand the problem. This could include things like the system configuration, external factors, etc. From 5d8183f1652e6533fa43b81388eac6331e9c34e4 Mon Sep 17 00:00:00 2001 From: Nikita Lipsky Date: Thu, 11 Jul 2024 16:28:51 +0300 Subject: [PATCH 43/47] Update benchmarks to the latest Compose+Kotlin versions (#5096) --- benchmarks/ios/jvm-vs-kotlin-native/build.gradle.kts | 1 + benchmarks/ios/jvm-vs-kotlin-native/gradle.properties | 10 ++-------- .../ios/jvm-vs-kotlin-native/settings.gradle.kts | 1 + .../drawable}/compose-multiplatform.xml | 0 .../kotlin/benchmarks/animation/AnimatedVisibility.kt | 4 +++- benchmarks/kn-performance/build.gradle.kts | 1 + benchmarks/kn-performance/gradle.properties | 4 ++-- benchmarks/kn-performance/settings.gradle.kts | 1 + .../drawable}/compose-multiplatform.xml | 0 .../kotlin/benchmarks/animation/AnimatedVisibility.kt | 4 +++- 10 files changed, 14 insertions(+), 12 deletions(-) rename benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/{resources => composeResources/drawable}/compose-multiplatform.xml (100%) rename benchmarks/kn-performance/src/commonMain/{resources => composeResources/drawable}/compose-multiplatform.xml (100%) diff --git a/benchmarks/ios/jvm-vs-kotlin-native/build.gradle.kts b/benchmarks/ios/jvm-vs-kotlin-native/build.gradle.kts index 2b4f81ce91..f2e0a3ea6c 100644 --- a/benchmarks/ios/jvm-vs-kotlin-native/build.gradle.kts +++ b/benchmarks/ios/jvm-vs-kotlin-native/build.gradle.kts @@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("multiplatform") + id("org.jetbrains.kotlin.plugin.compose") id("org.jetbrains.compose") } diff --git a/benchmarks/ios/jvm-vs-kotlin-native/gradle.properties b/benchmarks/ios/jvm-vs-kotlin-native/gradle.properties index ea3799cf6b..bca924c522 100644 --- a/benchmarks/ios/jvm-vs-kotlin-native/gradle.properties +++ b/benchmarks/ios/jvm-vs-kotlin-native/gradle.properties @@ -1,13 +1,7 @@ -compose.version=1.4.1 -kotlin.version=1.8.20 +compose.version=1.6.11 +kotlin.version=2.0.0 agp.version=7.0.4 org.gradle.jvmargs=-Xmx3g -kotlin.code.style=official -kotlin.native.useEmbeddableCompilerJar=true -kotlin.native.enableDependencyPropagation=false -kotlin.mpp.enableGranularSourceSetsMetadata=true -# Enable kotlin/native experimental memory model -kotlin.native.binary.memoryModel=experimental compose.desktop.verbose=true android.useAndroidX=true kotlin.js.webpack.major.version=4 diff --git a/benchmarks/ios/jvm-vs-kotlin-native/settings.gradle.kts b/benchmarks/ios/jvm-vs-kotlin-native/settings.gradle.kts index b39db44fad..5234bd02ca 100644 --- a/benchmarks/ios/jvm-vs-kotlin-native/settings.gradle.kts +++ b/benchmarks/ios/jvm-vs-kotlin-native/settings.gradle.kts @@ -10,6 +10,7 @@ pluginManagement { plugins { val kotlinVersion = extra["kotlin.version"] as String kotlin("multiplatform").version(kotlinVersion) + id("org.jetbrains.kotlin.plugin.compose").version(kotlinVersion) val composeVersion = extra["compose.version"] as String id("org.jetbrains.compose").version(composeVersion) } diff --git a/benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/resources/compose-multiplatform.xml b/benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/composeResources/drawable/compose-multiplatform.xml similarity index 100% rename from benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/resources/compose-multiplatform.xml rename to benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/composeResources/drawable/compose-multiplatform.xml diff --git a/benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt b/benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt index ea3144ff18..29e13d2244 100644 --- a/benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt +++ b/benchmarks/ios/jvm-vs-kotlin-native/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt @@ -13,6 +13,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import compose_benchmarks.generated.resources.Res +import compose_benchmarks.generated.resources.compose_multiplatform import kotlinx.coroutines.delay import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.painterResource @@ -29,7 +31,7 @@ fun AnimatedVisibility() { Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { AnimatedVisibility(showImage) { Image( - painterResource("compose-multiplatform.xml"), + painterResource(Res.drawable.compose_multiplatform), null ) } diff --git a/benchmarks/kn-performance/build.gradle.kts b/benchmarks/kn-performance/build.gradle.kts index 2b4f81ce91..f2e0a3ea6c 100644 --- a/benchmarks/kn-performance/build.gradle.kts +++ b/benchmarks/kn-performance/build.gradle.kts @@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("multiplatform") + id("org.jetbrains.kotlin.plugin.compose") id("org.jetbrains.compose") } diff --git a/benchmarks/kn-performance/gradle.properties b/benchmarks/kn-performance/gradle.properties index 17ba8bd122..334ef1c1f1 100644 --- a/benchmarks/kn-performance/gradle.properties +++ b/benchmarks/kn-performance/gradle.properties @@ -1,5 +1,5 @@ -compose.version=1.5.10 -kotlin.version=1.9.20 +compose.version=1.6.11 +kotlin.version=2.0.0 org.gradle.jvmargs=-Xmx3g kotlin.native.useEmbeddableCompilerJar=true compose.desktop.verbose=true diff --git a/benchmarks/kn-performance/settings.gradle.kts b/benchmarks/kn-performance/settings.gradle.kts index b39db44fad..5234bd02ca 100644 --- a/benchmarks/kn-performance/settings.gradle.kts +++ b/benchmarks/kn-performance/settings.gradle.kts @@ -10,6 +10,7 @@ pluginManagement { plugins { val kotlinVersion = extra["kotlin.version"] as String kotlin("multiplatform").version(kotlinVersion) + id("org.jetbrains.kotlin.plugin.compose").version(kotlinVersion) val composeVersion = extra["compose.version"] as String id("org.jetbrains.compose").version(composeVersion) } diff --git a/benchmarks/kn-performance/src/commonMain/resources/compose-multiplatform.xml b/benchmarks/kn-performance/src/commonMain/composeResources/drawable/compose-multiplatform.xml similarity index 100% rename from benchmarks/kn-performance/src/commonMain/resources/compose-multiplatform.xml rename to benchmarks/kn-performance/src/commonMain/composeResources/drawable/compose-multiplatform.xml diff --git a/benchmarks/kn-performance/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt b/benchmarks/kn-performance/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt index ea3144ff18..29e13d2244 100644 --- a/benchmarks/kn-performance/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt +++ b/benchmarks/kn-performance/src/commonMain/kotlin/benchmarks/animation/AnimatedVisibility.kt @@ -13,6 +13,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import compose_benchmarks.generated.resources.Res +import compose_benchmarks.generated.resources.compose_multiplatform import kotlinx.coroutines.delay import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.painterResource @@ -29,7 +31,7 @@ fun AnimatedVisibility() { Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { AnimatedVisibility(showImage) { Image( - painterResource("compose-multiplatform.xml"), + painterResource(Res.drawable.compose_multiplatform), null ) } From 9a9602d10f57122fb6b2fd8c1937bb512b9a58ae Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Fri, 12 Jul 2024 14:05:39 +0200 Subject: [PATCH 44/47] [gradle] Create static android assets directory if it doesn't exist --- .../org/jetbrains/compose/resources/AndroidResources.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt index ac36cf5bb3..dc1d177d4a 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt @@ -53,7 +53,9 @@ internal fun Project.configureAndroidComposeResources( } //https://issuetracker.google.com/348208777 - variant.sources.assets?.addStaticSourceDirectory(variantAssetsDir.get().asFile.path) + val staticDir = variantAssetsDir.get().asFile + staticDir.mkdirs() + variant.sources.assets?.addStaticSourceDirectory(staticDir.path) tasks.configureEach { task -> if (task.name == "merge${variant.name.uppercaseFirstChar()}Assets") { task.dependsOn(copyVariantAssets) From 5d22f7ca20d6e20a90d63e14196067d28cb03945 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 17 Jul 2024 19:17:31 +0200 Subject: [PATCH 45/47] [resources] Downscale drawable resources if it came from upper dpi. (#5101) Now drawables from upper DPIs will be downscalled to the expected size. (the same behavior as on Android) **BEFORE** **AFTER** Fixes https://youtrack.jetbrains.com/issue/CMP-5657 ## Release Notes ### Fixes - Resources - Now drawables from upper DPIs will be downscalled to the expected size. (the same behavior as on Android) --- .../resources/ImageResources.android.kt | 13 ++++++-- .../compose/resources/ImageResources.kt | 24 +++++++++----- .../compose/resources/ImageResources.skiko.kt | 31 +++++++++++++++++-- 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ImageResources.android.kt b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ImageResources.android.kt index 0e411ce719..fa8d7c8824 100644 --- a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ImageResources.android.kt +++ b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ImageResources.android.kt @@ -6,8 +6,17 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Density -internal actual fun ByteArray.toImageBitmap(): ImageBitmap = - BitmapFactory.decodeByteArray(this, 0, size).asImageBitmap() +internal actual fun ByteArray.toImageBitmap(resourceDensity: Int, targetDensity: Int): ImageBitmap { + val options = BitmapFactory.Options().apply { + //https://youtrack.jetbrains.com/issue/CMP-5657 + //android only downscales drawables. If there is only low dpi resource then use it as is (not upscale) + if (resourceDensity > targetDensity) { + inDensity = resourceDensity + inTargetDensity = targetDensity + } + } + return BitmapFactory.decodeByteArray(this, 0, size, options).asImageBitmap() +} internal actual class SvgElement diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt index 30892cf43f..9a2de24183 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt @@ -56,10 +56,17 @@ private val emptyImageBitmap: ImageBitmap by lazy { ImageBitmap(1, 1) } @Composable fun imageResource(resource: DrawableResource): ImageBitmap { val resourceReader = LocalResourceReader.currentOrPreview - val imageBitmap by rememberResourceState(resource, resourceReader, { emptyImageBitmap }) { env -> - val path = resource.getResourceItemByEnvironment(env).path - val cached = loadImage(path, resourceReader) { - ImageCache.Bitmap(it.toImageBitmap()) + val resourceEnvironment = rememberResourceEnvironment() + val imageBitmap by rememberResourceState( + resource, resourceReader, resourceEnvironment, { emptyImageBitmap } + ) { env -> + val item = resource.getResourceItemByEnvironment(env) + val resourceDensityQualifier = item.qualifiers.firstOrNull { it is DensityQualifier } as? DensityQualifier + val resourceDensity = resourceDensityQualifier?.dpi ?: DensityQualifier.MDPI.dpi + val screenDensity = resourceEnvironment.density.dpi + val path = item.path + val cached = loadImage(path, "$path-${screenDensity}dpi", resourceReader) { + ImageCache.Bitmap(it.toImageBitmap(resourceDensity, screenDensity)) } as ImageCache.Bitmap cached.bitmap } @@ -82,7 +89,7 @@ fun vectorResource(resource: DrawableResource): ImageVector { val density = LocalDensity.current val imageVector by rememberResourceState(resource, resourceReader, density, { emptyImageVector }) { env -> val path = resource.getResourceItemByEnvironment(env).path - val cached = loadImage(path, resourceReader) { + val cached = loadImage(path, path, resourceReader) { ImageCache.Vector(it.toXmlElement().toImageVector(density)) } as ImageCache.Vector cached.vector @@ -102,7 +109,7 @@ private fun svgPainter(resource: DrawableResource): Painter { val density = LocalDensity.current val svgPainter by rememberResourceState(resource, resourceReader, density, { emptySvgPainter }) { env -> val path = resource.getResourceItemByEnvironment(env).path - val cached = loadImage(path, resourceReader) { + val cached = loadImage(path, path, resourceReader) { ImageCache.Svg(it.toSvgElement().toSvgPainter(density)) } as ImageCache.Svg cached.painter @@ -126,7 +133,7 @@ suspend fun getDrawableResourceBytes( return DefaultResourceReader.read(resourceItem.path) } -internal expect fun ByteArray.toImageBitmap(): ImageBitmap +internal expect fun ByteArray.toImageBitmap(resourceDensity: Int, targetDensity: Int): ImageBitmap internal expect fun ByteArray.toXmlElement(): Element internal expect fun ByteArray.toSvgElement(): SvgElement @@ -145,6 +152,7 @@ internal fun dropImageCache() { private suspend fun loadImage( path: String, + cacheKey: String, resourceReader: ResourceReader, decode: (ByteArray) -> ImageCache -): ImageCache = imageCache.getOrLoad(path) { decode(resourceReader.read(path)) } +): ImageCache = imageCache.getOrLoad(cacheKey) { decode(resourceReader.read(path)) } diff --git a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ImageResources.skiko.kt b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ImageResources.skiko.kt index a046534554..3ac9eefc10 100644 --- a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ImageResources.skiko.kt +++ b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ImageResources.skiko.kt @@ -6,10 +6,37 @@ import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.unit.Density import org.jetbrains.skia.Data import org.jetbrains.skia.Image +import org.jetbrains.skia.Paint +import org.jetbrains.skia.Rect +import org.jetbrains.skia.SamplingMode +import org.jetbrains.skia.Surface import org.jetbrains.skia.svg.SVGDOM -internal actual fun ByteArray.toImageBitmap(): ImageBitmap = - Image.makeFromEncoded(this).toComposeImageBitmap() +internal actual fun ByteArray.toImageBitmap(resourceDensity: Int, targetDensity: Int): ImageBitmap { + val image = Image.makeFromEncoded(this) + + val targetImage: Image + //https://youtrack.jetbrains.com/issue/CMP-5657 + //android only downscales drawables. If there is only low dpi resource then use it as is (not upscale) + //we need a consistent behavior on all platforms + if (resourceDensity > targetDensity) { + val scale = targetDensity.toFloat() / resourceDensity.toFloat() + val targetH = image.height * scale + val targetW = image.width * scale + val srcRect = Rect.Companion.makeWH(image.width.toFloat(), image.height.toFloat()) + val dstRect = Rect.Companion.makeWH(targetW, targetH) + + targetImage = Surface.makeRasterN32Premul(targetW.toInt(), targetH.toInt()).run { + val paint = Paint().apply { isAntiAlias = true } + canvas.drawImageRect(image, srcRect, dstRect, SamplingMode.LINEAR, paint, true) + makeImageSnapshot() + } + } else { + targetImage = image + } + + return targetImage.toComposeImageBitmap() +} internal actual class SvgElement(val svgdom: SVGDOM) From 283cb84ecf5c5210521ca520ff7f25bd796fb84d Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Thu, 18 Jul 2024 12:57:44 +0200 Subject: [PATCH 46/47] [gradle] Fix AGP task dependencies. Fixes https://youtrack.jetbrains.com/issue/CMP-5681 --- .../compose/resources/AndroidResources.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt index dc1d177d4a..6bec880943 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt @@ -41,11 +41,12 @@ internal fun Project.configureAndroidComposeResources( //copy all compose resources to android assets val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return androidComponents.onVariants { variant -> + val camelVariantName = variant.name.uppercaseFirstChar() val variantAssets = getVariantComposeResources(variant) val variantAssetsDir = layout.buildDirectory.dir(RES_GEN_DIR).dir(variant.name + "AndroidAssets") val copyVariantAssets = tasks.register( - "copy${variant.name.uppercaseFirstChar()}ComposeResourcesToAndroidAssets", + "copy${camelVariantName}ComposeResourcesToAndroidAssets", Copy::class.java ) { task -> task.from(variantAssets) @@ -56,12 +57,16 @@ internal fun Project.configureAndroidComposeResources( val staticDir = variantAssetsDir.get().asFile staticDir.mkdirs() variant.sources.assets?.addStaticSourceDirectory(staticDir.path) + + val agpTaskNames = listOf( + //fix agp task dependencies for build and allTests tasks + "merge${camelVariantName}Assets", + "package${camelVariantName}Assets", + //fix agp task dependencies for AndroidStudio preview + "compile${camelVariantName}Sources", + ) tasks.configureEach { task -> - if (task.name == "merge${variant.name.uppercaseFirstChar()}Assets") { - task.dependsOn(copyVariantAssets) - } - //fix task dependencies for AndroidStudio preview - if (task.name == "compile${variant.name.uppercaseFirstChar()}Sources") { + if (task.name in agpTaskNames) { task.dependsOn(copyVariantAssets) } //fix linter task dependencies for `build` task From 78940c1998d2b6eaa9809e8a110815078ddf7767 Mon Sep 17 00:00:00 2001 From: Sebastian Aigner Date: Fri, 19 Jul 2024 16:39:28 +0200 Subject: [PATCH 47/47] Add support for high-refresh rate displays in examples (#5104) Turns out a number of our samples were still missing ``` CADisableMinimumFrameDurationOnPhone ``` --- examples/chat/iosApp/iosApp/Info.plist | 2 ++ examples/codeviewer/iosApp/iosApp/Info.plist | 2 ++ examples/graphics-2d/iosApp/iosApp/Info.plist | 2 ++ examples/imageviewer/iosApp/iosApp/Info.plist | 2 ++ examples/jetsnack/ios/iosApp/Info.plist | 2 ++ examples/todoapp-lite/iosApp/iosApp/Info.plist | 2 ++ examples/widgets-gallery/iosApp/iosApp/Info.plist | 2 ++ 7 files changed, 14 insertions(+) diff --git a/examples/chat/iosApp/iosApp/Info.plist b/examples/chat/iosApp/iosApp/Info.plist index 9a269f5eaa..412e378128 100644 --- a/examples/chat/iosApp/iosApp/Info.plist +++ b/examples/chat/iosApp/iosApp/Info.plist @@ -20,6 +20,8 @@ 1 LSRequiresIPhoneOS + CADisableMinimumFrameDurationOnPhone + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/examples/codeviewer/iosApp/iosApp/Info.plist b/examples/codeviewer/iosApp/iosApp/Info.plist index 9a269f5eaa..412e378128 100644 --- a/examples/codeviewer/iosApp/iosApp/Info.plist +++ b/examples/codeviewer/iosApp/iosApp/Info.plist @@ -20,6 +20,8 @@ 1 LSRequiresIPhoneOS + CADisableMinimumFrameDurationOnPhone + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/examples/graphics-2d/iosApp/iosApp/Info.plist b/examples/graphics-2d/iosApp/iosApp/Info.plist index 9a269f5eaa..412e378128 100644 --- a/examples/graphics-2d/iosApp/iosApp/Info.plist +++ b/examples/graphics-2d/iosApp/iosApp/Info.plist @@ -20,6 +20,8 @@ 1 LSRequiresIPhoneOS + CADisableMinimumFrameDurationOnPhone + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/examples/imageviewer/iosApp/iosApp/Info.plist b/examples/imageviewer/iosApp/iosApp/Info.plist index 5b28bf5219..8540150120 100644 --- a/examples/imageviewer/iosApp/iosApp/Info.plist +++ b/examples/imageviewer/iosApp/iosApp/Info.plist @@ -22,6 +22,8 @@ 1 LSRequiresIPhoneOS + CADisableMinimumFrameDurationOnPhone + NSCameraUsageDescription This app uses camera for capturing photos UIApplicationSceneManifest diff --git a/examples/jetsnack/ios/iosApp/Info.plist b/examples/jetsnack/ios/iosApp/Info.plist index 5b28bf5219..8540150120 100644 --- a/examples/jetsnack/ios/iosApp/Info.plist +++ b/examples/jetsnack/ios/iosApp/Info.plist @@ -22,6 +22,8 @@ 1 LSRequiresIPhoneOS + CADisableMinimumFrameDurationOnPhone + NSCameraUsageDescription This app uses camera for capturing photos UIApplicationSceneManifest diff --git a/examples/todoapp-lite/iosApp/iosApp/Info.plist b/examples/todoapp-lite/iosApp/iosApp/Info.plist index 9a269f5eaa..412e378128 100644 --- a/examples/todoapp-lite/iosApp/iosApp/Info.plist +++ b/examples/todoapp-lite/iosApp/iosApp/Info.plist @@ -20,6 +20,8 @@ 1 LSRequiresIPhoneOS + CADisableMinimumFrameDurationOnPhone + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/examples/widgets-gallery/iosApp/iosApp/Info.plist b/examples/widgets-gallery/iosApp/iosApp/Info.plist index 9a269f5eaa..412e378128 100644 --- a/examples/widgets-gallery/iosApp/iosApp/Info.plist +++ b/examples/widgets-gallery/iosApp/iosApp/Info.plist @@ -20,6 +20,8 @@ 1 LSRequiresIPhoneOS + CADisableMinimumFrameDurationOnPhone + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes