diff --git a/components/.gitignore b/components/.gitignore
index ba8435b9c5..92750f9c5f 100644
--- a/components/.gitignore
+++ b/components/.gitignore
@@ -12,4 +12,5 @@
build/
/captures
.externalNativeBuild
-.cxx
\ No newline at end of file
+.cxx
+kotlin-js-store
diff --git a/components/README.md b/components/README.md
new file mode 100644
index 0000000000..7c58b9785b
--- /dev/null
+++ b/components/README.md
@@ -0,0 +1,24 @@
+# Libraries for Compose Multiplatform
+
+## Resources
+Library to load resources, like images.
+
+### How to run demo project:
+*Prerequisites*: to run on iOS and Android, you should have "Kotlin Multiplatform Mobile" plugin installed either
+in Android Studio or in AppCode with [installed CocoaPods](https://kotlinlang.org/docs/native-cocoapods.html).
+
+### Run on desktop via Gradle:
+`./gradlew :resources:demo:desktopApp:run`
+
+### Run JS in browser with WebAssembly Skia via Gradle:
+`./gradlew :resources:demo:shared:jsBrowserDevelopmentRun`
+
+### Run MacOS via Gradle:
+ - on Intel CPU: `./gradlew :resources:demo:shared:runDebugExecutableMacosX64`
+ - on Apple Silicon: `./gradlew :resources:demo:shared:runDebugExecutableMacosArm64`
+
+# Tests
+Run script:
+```bash
+./test.sh
+```
diff --git a/components/build.gradle.kts b/components/build.gradle.kts
index 7095180735..6e2452bd06 100644
--- a/components/build.gradle.kts
+++ b/components/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
kotlin("multiplatform") apply false
+ id("com.android.library") apply false
}
subprojects {
@@ -9,6 +10,7 @@ subprojects {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+ mavenLocal()
}
plugins.withId("java") {
diff --git a/components/gradle.properties b/components/gradle.properties
index bc6a73e091..23817d322e 100644
--- a/components/gradle.properties
+++ b/components/gradle.properties
@@ -5,4 +5,18 @@ kotlin.code.style=official
# __KOTLIN_COMPOSE_VERSION__
kotlin.version=1.7.20
# __LATEST_COMPOSE_RELEASE_VERSION__
-compose.version=1.2.1
+compose.version=1.3.0-beta02
+agp.version=7.3.1
+org.jetbrains.compose.experimental.jscanvas.enabled=true
+org.jetbrains.compose.experimental.macos.enabled=true
+org.jetbrains.compose.experimental.uikit.enabled=true
+
+kotlin.native.cacheKind=none
+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
+kotlin.js.webpack.major.version=4
+xcodeproj=./resources/demo/iosApp
diff --git a/components/gradle/wrapper/gradle-wrapper.properties b/components/gradle/wrapper/gradle-wrapper.properties
index 2e6e5897b5..ae04661ee7 100644
--- a/components/gradle/wrapper/gradle-wrapper.properties
+++ b/components/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/components/resources/demo/.gitignore b/components/resources/demo/.gitignore
new file mode 100644
index 0000000000..3c2c4931fd
--- /dev/null
+++ b/components/resources/demo/.gitignore
@@ -0,0 +1,6 @@
+iosApp/Podfile.lock
+iosApp/Pods/*
+iosApp/ResourcesDemo.xcworkspace/*
+iosApp/ResourcesDemo.xcodeproj/*
+!iosApp/ResourcesDemo.xcodeproj/project.pbxproj
+shared/shared.podspec
diff --git a/components/resources/demo/androidApp/build.gradle.kts b/components/resources/demo/androidApp/build.gradle.kts
new file mode 100644
index 0000000000..4aa54613a1
--- /dev/null
+++ b/components/resources/demo/androidApp/build.gradle.kts
@@ -0,0 +1,33 @@
+plugins {
+ id("com.android.application")
+ kotlin("android")
+}
+
+dependencies {
+ implementation(project(":resources:demo:shared"))
+ implementation("androidx.appcompat:appcompat:1.5.1")
+ implementation("androidx.activity:activity-compose:1.6.1")
+ implementation("androidx.compose.foundation:foundation:1.3.1")
+ implementation("androidx.compose.ui:ui:1.3.1")
+}
+
+android {
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.3.2"
+ }
+ compileSdk = 33
+ defaultConfig {
+ applicationId = "me.user.androidApp"
+ minSdk = 24
+ targetSdk = 33
+ versionCode = 1
+ versionName = "1.0"
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+}
diff --git a/components/resources/demo/androidApp/src/main/AndroidManifest.xml b/components/resources/demo/androidApp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..c5060697da
--- /dev/null
+++ b/components/resources/demo/androidApp/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/resources/demo/androidApp/src/main/kotlin/org/jetbrains/compose/resources/demo/MainActivity.kt b/components/resources/demo/androidApp/src/main/kotlin/org/jetbrains/compose/resources/demo/MainActivity.kt
new file mode 100644
index 0000000000..18f3f41531
--- /dev/null
+++ b/components/resources/demo/androidApp/src/main/kotlin/org/jetbrains/compose/resources/demo/MainActivity.kt
@@ -0,0 +1,15 @@
+package org.jetbrains.compose.resources.demo
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import org.jetbrains.compose.resources.demo.shared.MainView
+
+class MainActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ MainView()
+ }
+ }
+}
diff --git a/components/resources/demo/androidApp/src/main/res/values/strings.xml b/components/resources/demo/androidApp/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..bf85b09e49
--- /dev/null
+++ b/components/resources/demo/androidApp/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Resources demo
+
\ No newline at end of file
diff --git a/components/resources/demo/desktopApp/build.gradle.kts b/components/resources/demo/desktopApp/build.gradle.kts
new file mode 100644
index 0000000000..3562f1fad2
--- /dev/null
+++ b/components/resources/demo/desktopApp/build.gradle.kts
@@ -0,0 +1,22 @@
+plugins {
+ kotlin("multiplatform")
+ id("org.jetbrains.compose")
+}
+
+kotlin {
+ jvm {}
+ sourceSets {
+ val jvmMain by getting {
+ dependencies {
+ implementation(compose.desktop.currentOs)
+ implementation(project(":resources:demo:shared"))
+ }
+ }
+ }
+}
+
+compose.desktop {
+ application {
+ mainClass = "MainKt"
+ }
+}
diff --git a/components/resources/demo/desktopApp/src/jvmMain/kotlin/Main.kt b/components/resources/demo/desktopApp/src/jvmMain/kotlin/Main.kt
new file mode 100644
index 0000000000..2b4a3afd90
--- /dev/null
+++ b/components/resources/demo/desktopApp/src/jvmMain/kotlin/Main.kt
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.WindowState
+import androidx.compose.ui.window.singleWindowApplication
+import org.jetbrains.compose.resources.demo.shared.MainView
+
+fun main() =
+ singleWindowApplication(
+ title = "Resources demo",
+ state = WindowState(size = DpSize(800.dp, 800.dp))
+ ) {
+ MainView()
+ }
diff --git a/components/resources/demo/iosApp/Configuration/TeamId.xcconfig b/components/resources/demo/iosApp/Configuration/TeamId.xcconfig
new file mode 100644
index 0000000000..bf06eb27e9
--- /dev/null
+++ b/components/resources/demo/iosApp/Configuration/TeamId.xcconfig
@@ -0,0 +1 @@
+TEAM_ID=
diff --git a/components/resources/demo/iosApp/Podfile b/components/resources/demo/iosApp/Podfile
new file mode 100644
index 0000000000..f66ae95df2
--- /dev/null
+++ b/components/resources/demo/iosApp/Podfile
@@ -0,0 +1,5 @@
+target 'ResourcesDemo' do
+ use_frameworks!
+ platform :ios, '14.1'
+ pod 'shared', :path => '../shared'
+end
\ No newline at end of file
diff --git a/components/resources/demo/iosApp/ResourcesDemo.xcodeproj/project.pbxproj b/components/resources/demo/iosApp/ResourcesDemo.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..3f014cf4ad
--- /dev/null
+++ b/components/resources/demo/iosApp/ResourcesDemo.xcodeproj/project.pbxproj
@@ -0,0 +1,398 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 2152FB042600AC8F00CF470E /* iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iosApp.swift */; };
+ C1FC908188C4E8695729CB06 /* Pods_ResourcesDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DE96E47030356CE6AD9794A /* Pods_ResourcesDemo.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 1EB65E27D2C0F884D0A1A133 /* Pods-ResourcesDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ResourcesDemo.debug.xcconfig"; path = "Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo.debug.xcconfig"; sourceTree = ""; };
+ 2152FB032600AC8F00CF470E /* iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosApp.swift; sourceTree = ""; };
+ 3D7A606AB0AD7636269BD9D0 /* Pods-ResourcesDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ResourcesDemo.release.xcconfig"; path = "Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo.release.xcconfig"; sourceTree = ""; };
+ 7555FF7B242A565900829871 /* ResourcesDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ResourcesDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 8DE96E47030356CE6AD9794A /* Pods_ResourcesDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ResourcesDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ AB3632DC29227652001CCB65 /* TeamId.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = TeamId.xcconfig; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 9964867F0862B4D9FB6ABFC7 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ C1FC908188C4E8695729CB06 /* Pods_ResourcesDemo.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 7555FF72242A565900829871 = {
+ isa = PBXGroup;
+ children = (
+ AB1DB47929225F7C00F7AF9C /* Configuration */,
+ 7555FF7D242A565900829871 /* iosApp */,
+ 7555FF7C242A565900829871 /* Products */,
+ E1DAFBE8E1CFC0878361EF0E /* Pods */,
+ B62309C7396AD7BF607A63B2 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 7555FF7C242A565900829871 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 7555FF7B242A565900829871 /* ResourcesDemo.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 7555FF7D242A565900829871 /* iosApp */ = {
+ isa = PBXGroup;
+ children = (
+ 7555FF8C242A565B00829871 /* Info.plist */,
+ 2152FB032600AC8F00CF470E /* iosApp.swift */,
+ );
+ path = iosApp;
+ sourceTree = "";
+ };
+ AB1DB47929225F7C00F7AF9C /* Configuration */ = {
+ isa = PBXGroup;
+ children = (
+ AB3632DC29227652001CCB65 /* TeamId.xcconfig */,
+ );
+ path = Configuration;
+ sourceTree = "";
+ };
+ B62309C7396AD7BF607A63B2 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 8DE96E47030356CE6AD9794A /* Pods_ResourcesDemo.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ E1DAFBE8E1CFC0878361EF0E /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 1EB65E27D2C0F884D0A1A133 /* Pods-ResourcesDemo.debug.xcconfig */,
+ 3D7A606AB0AD7636269BD9D0 /* Pods-ResourcesDemo.release.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 7555FF7A242A565900829871 /* ResourcesDemo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "ResourcesDemo" */;
+ buildPhases = (
+ E8D673591E7196AEA2EA10E2 /* [CP] Check Pods Manifest.lock */,
+ 7555FF77242A565900829871 /* Sources */,
+ 7555FF79242A565900829871 /* Resources */,
+ 9964867F0862B4D9FB6ABFC7 /* Frameworks */,
+ A51DDDB74597C98E89765935 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = ResourcesDemo;
+ productName = iosApp;
+ productReference = 7555FF7B242A565900829871 /* ResourcesDemo.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 7555FF73242A565900829871 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 1130;
+ LastUpgradeCheck = 1130;
+ ORGANIZATIONNAME = org.jetbrains;
+ TargetAttributes = {
+ 7555FF7A242A565900829871 = {
+ CreatedOnToolsVersion = 11.3.1;
+ };
+ };
+ };
+ buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "ResourcesDemo" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 7555FF72242A565900829871;
+ productRefGroup = 7555FF7C242A565900829871 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 7555FF7A242A565900829871 /* ResourcesDemo */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 7555FF79242A565900829871 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ A51DDDB74597C98E89765935 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo-resources-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Copy Pods Resources";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ E8D673591E7196AEA2EA10E2 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-ResourcesDemo-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 7555FF77242A565900829871 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2152FB042600AC8F00CF470E /* iosApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 7555FFA3242A565B00829871 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = AB3632DC29227652001CCB65 /* TeamId.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 7555FFA4242A565B00829871 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = AB3632DC29227652001CCB65 /* TeamId.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 7555FFA6242A565B00829871 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 1EB65E27D2C0F884D0A1A133 /* Pods-ResourcesDemo.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_ASSET_PATHS = "";
+ DEVELOPMENT_TEAM = "${TEAM_ID}";
+ ENABLE_PREVIEWS = YES;
+ INFOPLIST_FILE = iosApp/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "org.jetbrains.ResourcesDemo${TEAM_ID}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 7555FFA7242A565B00829871 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 3D7A606AB0AD7636269BD9D0 /* Pods-ResourcesDemo.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_ASSET_PATHS = "";
+ DEVELOPMENT_TEAM = "${TEAM_ID}";
+ ENABLE_PREVIEWS = YES;
+ INFOPLIST_FILE = iosApp/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "org.jetbrains.ResourcesDemo${TEAM_ID}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 7555FF76242A565900829871 /* Build configuration list for PBXProject "ResourcesDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7555FFA3242A565B00829871 /* Debug */,
+ 7555FFA4242A565B00829871 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "ResourcesDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7555FFA6242A565B00829871 /* Debug */,
+ 7555FFA7242A565B00829871 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 7555FF73242A565900829871 /* Project object */;
+}
diff --git a/components/resources/demo/iosApp/iosApp/Info.plist b/components/resources/demo/iosApp/iosApp/Info.plist
new file mode 100644
index 0000000000..9a269f5eaa
--- /dev/null
+++ b/components/resources/demo/iosApp/iosApp/Info.plist
@@ -0,0 +1,48 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+
+ UILaunchScreen
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/components/resources/demo/iosApp/iosApp/iosApp.swift b/components/resources/demo/iosApp/iosApp/iosApp.swift
new file mode 100644
index 0000000000..b42016a6fc
--- /dev/null
+++ b/components/resources/demo/iosApp/iosApp/iosApp.swift
@@ -0,0 +1,15 @@
+import UIKit
+import shared
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+ var window: UIWindow?
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ window = UIWindow(frame: UIScreen.main.bounds)
+ let mainViewController = Main_iosKt.MainViewController()
+ window?.rootViewController = mainViewController
+ window?.makeKeyAndVisible()
+ return true
+ }
+}
diff --git a/components/resources/demo/shared/build.gradle.kts b/components/resources/demo/shared/build.gradle.kts
new file mode 100644
index 0000000000..297f9775ea
--- /dev/null
+++ b/components/resources/demo/shared/build.gradle.kts
@@ -0,0 +1,101 @@
+plugins {
+ kotlin("multiplatform")
+ kotlin("native.cocoapods")
+ id("com.android.library")
+ id("org.jetbrains.compose")
+}
+
+version = "1.0-SNAPSHOT"
+
+kotlin {
+ android()
+ jvm("desktop")
+ ios()
+ iosSimulatorArm64()
+ js(IR) {
+ browser()
+ binaries.executable()
+ }
+ macosX64 {
+ binaries {
+ executable {
+ entryPoint = "main"
+ }
+ }
+ }
+ macosArm64 {
+ binaries {
+ executable {
+ entryPoint = "main"
+ }
+ }
+ }
+
+ cocoapods {
+ summary = "Shared code for the sample"
+ homepage = "https://github.com/JetBrains/compose-jb"
+ ios.deploymentTarget = "14.1"
+ podfile = project.file("../iosApp/Podfile")
+ framework {
+ baseName = "shared"
+ isStatic = true
+ }
+ extraSpecAttributes["resources"] = "['src/commonMain/resources/**', 'src/iosMain/resources/**']"
+ }
+
+ sourceSets {
+ val commonMain by getting {
+ dependencies {
+ implementation(compose.ui)
+ implementation(compose.foundation)
+ implementation(compose.material)
+ implementation(compose.runtime)
+ implementation(project(":resources:library"))
+ }
+ }
+ val iosMain by getting
+ val iosTest by getting
+ val iosSimulatorArm64Main by getting {
+ dependsOn(iosMain)
+ }
+ val iosSimulatorArm64Test by getting {
+ dependsOn(iosTest)
+ }
+ val desktopMain by getting {
+ dependencies {
+ implementation(compose.desktop.common)
+ }
+ }
+ val macosMain by creating {
+ dependsOn(commonMain)
+ }
+ val macosX64Main by getting {
+ dependsOn(macosMain)
+ }
+ val macosArm64Main by getting {
+ dependsOn(macosMain)
+ }
+ }
+}
+
+android {
+ compileSdk = 33
+ sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
+ defaultConfig {
+ minSdk = 24
+ targetSdk = 33
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ sourceSets {
+ named("main") {
+ resources.srcDir("src/commonMain/resources")
+ }
+ }
+}
+
+compose.experimental {
+ web.application {}
+}
diff --git a/components/resources/demo/shared/src/androidMain/AndroidManifest.xml b/components/resources/demo/shared/src/androidMain/AndroidManifest.xml
new file mode 100644
index 0000000000..c01a0139ab
--- /dev/null
+++ b/components/resources/demo/shared/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000000..37f0bf45d7
--- /dev/null
+++ b/components/resources/demo/shared/src/androidMain/kotlin/org/jetbrains/compose/resources/demo/shared/main.android.kt
@@ -0,0 +1,13 @@
+/*
+ * 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.resources.demo.shared
+
+import androidx.compose.runtime.Composable
+
+@Composable
+fun MainView() {
+ UseResources()
+}
diff --git a/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/UseResources.kt b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/UseResources.kt
new file mode 100644
index 0000000000..9b89bae350
--- /dev/null
+++ b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/UseResources.kt
@@ -0,0 +1,23 @@
+package org.jetbrains.compose.resources.demo.shared
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import org.jetbrains.compose.resources.*
+
+@OptIn(ExperimentalResourceApi::class)
+@Composable
+internal fun UseResources() {
+ Column {
+ Text("Hello, resources")
+ Image(
+ bitmap = resource("dir/img.png").rememberImageBitmap().orEmpty(),
+ contentDescription = null,
+ )
+ Image(
+ bitmap = resource("img.webp").rememberImageBitmap().orEmpty(),
+ contentDescription = null,
+ )
+ }
+}
diff --git a/components/resources/demo/shared/src/commonMain/resources/dir/img.png b/components/resources/demo/shared/src/commonMain/resources/dir/img.png
new file mode 100644
index 0000000000..f33b6c2622
Binary files /dev/null and b/components/resources/demo/shared/src/commonMain/resources/dir/img.png differ
diff --git a/components/resources/demo/shared/src/commonMain/resources/img.webp b/components/resources/demo/shared/src/commonMain/resources/img.webp
new file mode 100644
index 0000000000..0da983e2ce
Binary files /dev/null and b/components/resources/demo/shared/src/commonMain/resources/img.webp differ
diff --git a/components/resources/demo/shared/src/desktopMain/kotlin/org/jetbrains/compose/resources/demo/shared/main.desktop.kt b/components/resources/demo/shared/src/desktopMain/kotlin/org/jetbrains/compose/resources/demo/shared/main.desktop.kt
new file mode 100644
index 0000000000..d1997f205b
--- /dev/null
+++ b/components/resources/demo/shared/src/desktopMain/kotlin/org/jetbrains/compose/resources/demo/shared/main.desktop.kt
@@ -0,0 +1,20 @@
+/*
+ * 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.resources.demo.shared
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.runtime.Composable
+
+@Composable
+fun MainView() {
+ UseResources()
+}
+
+@Preview
+@Composable
+fun Preview() {
+ MainView()
+}
diff --git a/components/resources/demo/shared/src/iosMain/kotlin/main.ios.kt b/components/resources/demo/shared/src/iosMain/kotlin/main.ios.kt
new file mode 100644
index 0000000000..8614d4176a
--- /dev/null
+++ b/components/resources/demo/shared/src/iosMain/kotlin/main.ios.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Application
+import org.jetbrains.compose.resources.demo.shared.UseResources
+import platform.UIKit.UIViewController
+
+fun MainViewController(): UIViewController =
+ Application("Resources demo") {
+ Column {
+ Box(
+ modifier = Modifier
+ .height(100.dp)
+ )
+ UseResources()
+ }
+ }
diff --git a/components/resources/demo/shared/src/jsMain/kotlin/main.js.kt b/components/resources/demo/shared/src/jsMain/kotlin/main.js.kt
new file mode 100644
index 0000000000..2eb351f69a
--- /dev/null
+++ b/components/resources/demo/shared/src/jsMain/kotlin/main.js.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Window
+import org.jetbrains.compose.resources.demo.shared.UseResources
+import org.jetbrains.skiko.wasm.onWasmReady
+
+fun main() {
+ onWasmReady {
+ Window("Resources demo") {
+ MainView()
+ }
+ }
+}
+
+@Composable
+fun MainView() {
+ Column(modifier = Modifier.fillMaxSize()) {
+ Spacer(modifier = Modifier.height(24.dp))
+ UseResources()
+ }
+}
diff --git a/components/resources/demo/shared/src/jsMain/resources/index.html b/components/resources/demo/shared/src/jsMain/resources/index.html
new file mode 100644
index 0000000000..971817e6a8
--- /dev/null
+++ b/components/resources/demo/shared/src/jsMain/resources/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+ compose multiplatform web demo
+
+
+
+
+ compose multiplatform web demo
+
+
+
+
+
+
diff --git a/components/resources/demo/shared/src/jsMain/resources/styles.css b/components/resources/demo/shared/src/jsMain/resources/styles.css
new file mode 100644
index 0000000000..e5b3293a7a
--- /dev/null
+++ b/components/resources/demo/shared/src/jsMain/resources/styles.css
@@ -0,0 +1,8 @@
+#root {
+ width: 100%;
+ height: 100vh;
+}
+
+#root > .compose-web-column > div {
+ position: relative;
+}
\ No newline at end of file
diff --git a/components/resources/demo/shared/src/macosMain/kotlin/main.macos.kt b/components/resources/demo/shared/src/macosMain/kotlin/main.macos.kt
new file mode 100644
index 0000000000..963d3badf7
--- /dev/null
+++ b/components/resources/demo/shared/src/macosMain/kotlin/main.macos.kt
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+import androidx.compose.ui.window.Window
+import org.jetbrains.compose.resources.demo.shared.UseResources
+import platform.AppKit.NSApp
+import platform.AppKit.NSApplication
+
+fun main() {
+ NSApplication.sharedApplication()
+ Window("Resources demo") {
+ UseResources()
+ }
+ NSApp?.run()
+}
diff --git a/components/resources/library/build.gradle.kts b/components/resources/library/build.gradle.kts
index 0f4368531b..796ef6019e 100644
--- a/components/resources/library/build.gradle.kts
+++ b/components/resources/library/build.gradle.kts
@@ -1,24 +1,114 @@
-import org.jetbrains.compose.compose
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
id("maven-publish")
+ id("com.android.library")
}
+val composeVersion = extra["compose.version"] as String
+
kotlin {
jvm("desktop")
+ android {
+ publishLibraryVariants("release")
+ }
+ ios()
+ iosSimulatorArm64()
+ js(IR) {
+ browser()
+ }
+ macosX64()
+ macosArm64()
sourceSets {
- named("commonMain") {
+ val commonMain by getting {
+ dependencies {
+ implementation("org.jetbrains.compose.runtime:runtime:$composeVersion")
+ implementation("org.jetbrains.compose.foundation:foundation:$composeVersion")
+ }
+ }
+ val commonTest by getting {
dependencies {
- api(compose.runtime)
- api(compose.foundation)
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
+ implementation(kotlin("test"))
+ }
+ }
+ val skikoMain by creating {
+ dependsOn(commonMain)
+ }
+ val desktopMain by getting {
+ dependsOn(skikoMain)
+ }
+ val desktopTest by getting {
+ dependencies {
+ implementation(compose.desktop.currentOs)
+ implementation("org.jetbrains.compose.ui:ui-test-junit4:$composeVersion")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.6.4")
+ }
+ }
+ val androidMain by getting {}
+ val androidTest by getting {
+ dependencies {
+
}
}
- named("desktopMain") {}
+ val iosMain by getting {
+ dependsOn(skikoMain)
+ }
+ val iosTest by getting
+ val iosSimulatorArm64Main by getting
+ iosSimulatorArm64Main.dependsOn(iosMain)
+ val iosSimulatorArm64Test by getting
+ iosSimulatorArm64Test.dependsOn(iosTest)
+ val jsMain by getting {
+ dependsOn(skikoMain)
+ }
+ val macosMain by creating {
+ dependsOn(skikoMain)
+ }
+ val macosX64Main by getting {
+ dependsOn(macosMain)
+ }
+ val macosArm64Main by getting {
+ dependsOn(macosMain)
+ }
+ }
+}
+
+android {
+ compileSdk = 33
+ sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
+ defaultConfig {
+ minSdk = 24
+ targetSdk = 33
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
}
+ testOptions {
+ managedDevices {
+ devices {
+ maybeCreate("pixel5").apply {
+ device = "Pixel 5"
+ apiLevel = 31
+ systemImageSource = "aosp"
+ }
+ }
+ }
+ }
+}
+
+dependencies {
+ //Android integration tests
+ testImplementation("androidx.test:core:1.5.0")
+ androidTestImplementation("androidx.compose.ui:ui-test-manifest:1.3.1")
+ androidTestImplementation("androidx.compose.ui:ui-test:1.3.1")
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.3.1")
+ androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
}
// TODO it seems that argument isn't applied to the common sourceSet. Figure out why
@@ -30,4 +120,4 @@ configureMavenPublication(
groupId = "org.jetbrains.compose.components",
artifactId = "components-resources",
name = "Resources for Compose JB"
-)
\ No newline at end of file
+)
diff --git a/components/resources/library/src/androidAndroidTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt b/components/resources/library/src/androidAndroidTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt
new file mode 100644
index 0000000000..2803875019
--- /dev/null
+++ b/components/resources/library/src/androidAndroidTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt
@@ -0,0 +1,61 @@
+package org.jetbrains.compose.resources
+
+import androidx.compose.foundation.Image
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.test.junit4.createComposeRule
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalResourceApi::class, ExperimentalCoroutinesApi::class)
+class ComposeResourceTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun testMissingResource() = runTest (UnconfinedTestDispatcher()) {
+ var recompositionCount = 0
+ rule.setContent {
+ CountRecompositions(resource("missing.png").rememberImageBitmap().orEmpty()) {
+ recompositionCount++
+ }
+ }
+ rule.awaitIdle()
+ assertEquals(2, recompositionCount)
+ }
+
+ @Test
+ fun testCountRecompositions() = runTest (UnconfinedTestDispatcher()) {
+ val mutableStateFlow = MutableStateFlow(true)
+ var recompositionCount = 0
+ rule.setContent {
+ val state: Boolean by mutableStateFlow.collectAsState(true)
+ val resource = resource(if (state) "1.png" else "2.png")
+ CountRecompositions(resource.rememberImageBitmap().orEmpty()) {
+ recompositionCount++
+ }
+ }
+ rule.awaitIdle()
+ mutableStateFlow.value = false
+ rule.awaitIdle()
+ assertEquals(4, recompositionCount)
+ }
+
+}
+
+@Composable
+private fun CountRecompositions(imageBitmap: ImageBitmap?, onRecomposition: () -> Unit) {
+ onRecomposition()
+ if (imageBitmap != null) {
+ Image(bitmap = imageBitmap, contentDescription = null)
+ }
+}
diff --git a/components/resources/library/src/androidAndroidTest/resources/1.png b/components/resources/library/src/androidAndroidTest/resources/1.png
new file mode 100644
index 0000000000..c11cb2ec65
Binary files /dev/null and b/components/resources/library/src/androidAndroidTest/resources/1.png differ
diff --git a/components/resources/library/src/androidAndroidTest/resources/2.png b/components/resources/library/src/androidAndroidTest/resources/2.png
new file mode 100644
index 0000000000..5fa326654e
Binary files /dev/null and b/components/resources/library/src/androidAndroidTest/resources/2.png differ
diff --git a/components/resources/library/src/androidMain/AndroidManifest.xml b/components/resources/library/src/androidMain/AndroidManifest.xml
new file mode 100644
index 0000000000..8cbd70ab54
--- /dev/null
+++ b/components/resources/library/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ComposableResource.android.kt b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ComposableResource.android.kt
new file mode 100644
index 0000000000..01c312d1db
--- /dev/null
+++ b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ComposableResource.android.kt
@@ -0,0 +1,18 @@
+/*
+ * 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.resources
+
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asImageBitmap
+
+@ExperimentalResourceApi
+internal actual fun ByteArray.toImageBitmap(): ImageBitmap = toAndroidBitmap().asImageBitmap()
+
+private fun ByteArray.toAndroidBitmap(): Bitmap {
+ return BitmapFactory.decodeByteArray(this, 0, size);
+}
diff --git a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/Resource.android.kt b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/Resource.android.kt
new file mode 100644
index 0000000000..18190a95e4
--- /dev/null
+++ b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/Resource.android.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.resources
+
+import java.io.IOException
+
+@ExperimentalResourceApi
+actual fun resource(path: String): Resource = AndroidResourceImpl(path)
+
+@ExperimentalResourceApi
+private class AndroidResourceImpl(path: String) : AbstractResourceImpl(path) {
+ override suspend fun readBytes(): ByteArray {
+ val resource = (::AndroidResourceImpl.javaClass.classLoader).getResourceAsStream(path)
+ if (resource != null) {
+ return resource.readBytes()
+ } else {
+ throw MissingResourceException(path)
+ }
+ }
+}
+
+internal actual class MissingResourceException actual constructor(path: String) :
+ IOException("Missing resource with path: $path")
diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ComposeResource.common.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ComposeResource.common.kt
new file mode 100644
index 0000000000..f39c1fcde1
--- /dev/null
+++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ComposeResource.common.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.resources
+
+import androidx.compose.runtime.*
+import androidx.compose.ui.graphics.ImageBitmap
+
+private val emptyImageBitmap: ImageBitmap by lazy { ImageBitmap(1, 1) }
+
+/**
+ * Get and remember resource. While loading and if resource not exists result will be null.
+ */
+@ExperimentalResourceApi
+@Composable
+fun Resource.rememberImageBitmap(): LoadState {
+ val state: MutableState> = remember(this) { mutableStateOf(LoadState.Loading()) }
+ LaunchedEffect(this) {
+ state.value = try {
+ LoadState.Success(readBytes().toImageBitmap())
+ } catch (e: Exception) {
+ LoadState.Error(e)
+ }
+ }
+ return state.value
+}
+
+/**
+ * return current ImageBitmap or return empty while loading
+ */
+@ExperimentalResourceApi
+fun LoadState.orEmpty(): ImageBitmap = when (this) {
+ is LoadState.Loading -> emptyImageBitmap
+ is LoadState.Success -> this.value
+ is LoadState.Error -> emptyImageBitmap
+}
+
+internal expect fun ByteArray.toImageBitmap(): ImageBitmap
diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ExperimentalResourceApi.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ExperimentalResourceApi.kt
new file mode 100644
index 0000000000..eb1e8a60be
--- /dev/null
+++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ExperimentalResourceApi.kt
@@ -0,0 +1,9 @@
+/*
+ * 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.resources
+
+@RequiresOptIn("This API is experimental and is likely to change in the future.")
+annotation class ExperimentalResourceApi
diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.common.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.common.kt
new file mode 100644
index 0000000000..59f4463e1d
--- /dev/null
+++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.common.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.resources
+
+/**
+ * Should implement equals() and hashCode()
+ */
+@ExperimentalResourceApi
+interface Resource {
+ suspend fun readBytes(): ByteArray //todo in future use streaming
+}
+
+/**
+ * Get a resource from /resources (for example, from commonMain/resources).
+ */
+@ExperimentalResourceApi
+expect fun resource(path: String): Resource
+
+internal expect class MissingResourceException(path: String)
+
+@OptIn(ExperimentalResourceApi::class)
+internal abstract class AbstractResourceImpl(val path: String) : Resource {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ return if (other is AbstractResourceImpl) {
+ path == other.path
+ } else {
+ false
+ }
+ }
+
+ override fun hashCode(): Int {
+ return path.hashCode()
+ }
+}
diff --git a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt
new file mode 100644
index 0000000000..6d47e32c03
--- /dev/null
+++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.resources
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+
+@OptIn(ExperimentalResourceApi::class)
+class ResourceTest {
+ @Test
+ fun testResourceEquals() {
+ assertEquals(resource("a"), resource("a"))
+ }
+
+ @Test
+ fun testResourceNotEquals() {
+ assertNotEquals(resource("a"), resource("b"))
+ }
+}
diff --git a/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/Resource.desktop.kt b/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/Resource.desktop.kt
new file mode 100644
index 0000000000..b6379f63e3
--- /dev/null
+++ b/components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/Resource.desktop.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.resources
+
+import java.io.IOException
+
+@ExperimentalResourceApi
+actual fun resource(path: String): Resource = DesktopResourceImpl(path)
+
+@ExperimentalResourceApi
+private class DesktopResourceImpl(path: String) : AbstractResourceImpl(path) {
+ override suspend fun readBytes(): ByteArray {
+ val resource = (::DesktopResourceImpl.javaClass.classLoader).getResourceAsStream(path)
+ if (resource != null) {
+ return resource.readBytes()
+ } else {
+ throw MissingResourceException(path)
+ }
+ }
+}
+
+internal actual class MissingResourceException actual constructor(path: String) :
+ IOException("Missing resource with path: $path")
diff --git a/components/resources/library/src/desktopTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt b/components/resources/library/src/desktopTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt
new file mode 100644
index 0000000000..2803875019
--- /dev/null
+++ b/components/resources/library/src/desktopTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt
@@ -0,0 +1,61 @@
+package org.jetbrains.compose.resources
+
+import androidx.compose.foundation.Image
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.test.junit4.createComposeRule
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalResourceApi::class, ExperimentalCoroutinesApi::class)
+class ComposeResourceTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun testMissingResource() = runTest (UnconfinedTestDispatcher()) {
+ var recompositionCount = 0
+ rule.setContent {
+ CountRecompositions(resource("missing.png").rememberImageBitmap().orEmpty()) {
+ recompositionCount++
+ }
+ }
+ rule.awaitIdle()
+ assertEquals(2, recompositionCount)
+ }
+
+ @Test
+ fun testCountRecompositions() = runTest (UnconfinedTestDispatcher()) {
+ val mutableStateFlow = MutableStateFlow(true)
+ var recompositionCount = 0
+ rule.setContent {
+ val state: Boolean by mutableStateFlow.collectAsState(true)
+ val resource = resource(if (state) "1.png" else "2.png")
+ CountRecompositions(resource.rememberImageBitmap().orEmpty()) {
+ recompositionCount++
+ }
+ }
+ rule.awaitIdle()
+ mutableStateFlow.value = false
+ rule.awaitIdle()
+ assertEquals(4, recompositionCount)
+ }
+
+}
+
+@Composable
+private fun CountRecompositions(imageBitmap: ImageBitmap?, onRecomposition: () -> Unit) {
+ onRecomposition()
+ if (imageBitmap != null) {
+ Image(bitmap = imageBitmap, contentDescription = null)
+ }
+}
diff --git a/components/resources/library/src/desktopTest/resources/1.png b/components/resources/library/src/desktopTest/resources/1.png
new file mode 100644
index 0000000000..c11cb2ec65
Binary files /dev/null and b/components/resources/library/src/desktopTest/resources/1.png differ
diff --git a/components/resources/library/src/desktopTest/resources/2.png b/components/resources/library/src/desktopTest/resources/2.png
new file mode 100644
index 0000000000..5fa326654e
Binary files /dev/null and b/components/resources/library/src/desktopTest/resources/2.png differ
diff --git a/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/Resource.ios.kt b/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/Resource.ios.kt
new file mode 100644
index 0000000000..c081555e1b
--- /dev/null
+++ b/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/Resource.ios.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.resources
+
+
+import kotlinx.cinterop.addressOf
+import kotlinx.cinterop.usePinned
+import platform.Foundation.NSBundle
+import platform.Foundation.NSData
+import platform.Foundation.NSFileManager
+import platform.posix.memcpy
+
+@ExperimentalResourceApi
+actual fun resource(path: String): Resource = UIKitResourceImpl(path)
+
+@ExperimentalResourceApi
+private class UIKitResourceImpl(path: String) : AbstractResourceImpl(path) {
+ override suspend fun readBytes(): ByteArray {
+ val absolutePath = NSBundle.mainBundle.resourcePath + "/" + path
+ val contentsAtPath: NSData? = NSFileManager.defaultManager().contentsAtPath(absolutePath)
+ if (contentsAtPath != null) {
+ val byteArray = ByteArray(contentsAtPath.length.toInt())
+ byteArray.usePinned {
+ memcpy(it.addressOf(0), contentsAtPath.bytes, contentsAtPath.length)
+ }
+ return byteArray
+ } else {
+ throw MissingResourceException(path)
+ }
+ }
+}
+
+internal actual class MissingResourceException actual constructor(path: String) :
+ Exception("Missing resource with path: $path")
diff --git a/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt b/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt
new file mode 100644
index 0000000000..e0a78a5193
--- /dev/null
+++ b/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.resources
+
+import org.khronos.webgl.ArrayBuffer
+import org.khronos.webgl.Int8Array
+import org.w3c.xhr.ARRAYBUFFER
+import org.w3c.xhr.XMLHttpRequest
+import org.w3c.xhr.XMLHttpRequestResponseType
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+@ExperimentalResourceApi
+actual fun resource(path: String): Resource = JSResourceImpl(path)
+
+@ExperimentalResourceApi
+private class JSResourceImpl(path: String) : AbstractResourceImpl(path) {
+ override suspend fun readBytes(): ByteArray {
+ return suspendCoroutine { continuation ->
+ val req = XMLHttpRequest()
+ req.open("GET", "/$path", true)
+ req.responseType = XMLHttpRequestResponseType.ARRAYBUFFER
+
+ req.onload = { event ->
+ val arrayBuffer = req.response
+ if (arrayBuffer is ArrayBuffer) {
+ continuation.resume(arrayBuffer.toByteArray())
+ } else {
+ continuation.resumeWithException(MissingResourceException(path))
+ }
+ }
+ req.send(null)
+ }
+ }
+}
+
+private fun ArrayBuffer.toByteArray() = Int8Array(this, 0, byteLength).unsafeCast()
+
+internal actual class MissingResourceException actual constructor(path: String) :
+ Exception("Missing resource with path: $path")
diff --git a/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/Resource.macos.kt b/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/Resource.macos.kt
new file mode 100644
index 0000000000..fedc16c725
--- /dev/null
+++ b/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/Resource.macos.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.resources
+
+import kotlinx.cinterop.addressOf
+import kotlinx.cinterop.usePinned
+import platform.Foundation.NSData
+import platform.Foundation.NSFileManager
+import platform.posix.memcpy
+
+@ExperimentalResourceApi
+actual fun resource(path: String): Resource = MacOSResourceImpl(path)
+
+@ExperimentalResourceApi
+private class MacOSResourceImpl(path: String) : AbstractResourceImpl(path) {
+ override suspend fun readBytes(): ByteArray {
+ val currentDirectoryPath = NSFileManager.defaultManager().currentDirectoryPath
+ val contentsAtPath: NSData? = NSFileManager.defaultManager().run {
+ //todo in future bundle resources with app and use all sourceSets (skikoMain, nativeMain)
+ contentsAtPath("$currentDirectoryPath/src/macosMain/resources/$path")
+ ?: contentsAtPath("$currentDirectoryPath/src/commonMain/resources/$path")
+ }
+ if (contentsAtPath != null) {
+ val byteArray = ByteArray(contentsAtPath.length.toInt())
+ byteArray.usePinned {
+ memcpy(it.addressOf(0), contentsAtPath.bytes, contentsAtPath.length)
+ }
+ return byteArray
+ } else {
+ throw MissingResourceException(path)
+ }
+ }
+}
+
+internal actual class MissingResourceException actual constructor(path: String) :
+ Exception("Missing resource with path: $path")
diff --git a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ComposeResource.skiko.kt b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ComposeResource.skiko.kt
new file mode 100644
index 0000000000..5400b51630
--- /dev/null
+++ b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ComposeResource.skiko.kt
@@ -0,0 +1,13 @@
+/*
+ * 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.resources
+
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.toComposeImageBitmap
+import org.jetbrains.skia.Image
+
+internal actual fun ByteArray.toImageBitmap(): ImageBitmap =
+ Image.makeFromEncoded(this).toComposeImageBitmap()
diff --git a/components/settings.gradle.kts b/components/settings.gradle.kts
index e071ccafb8..0e9c93eb23 100644
--- a/components/settings.gradle.kts
+++ b/components/settings.gradle.kts
@@ -4,12 +4,14 @@ pluginManagement {
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
google()
+ mavenLocal()
}
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("com.android.library").version(extra["agp.version"] as String)
}
}
@@ -17,4 +19,7 @@ include(":SplitPane:library")
include(":SplitPane:demo")
include(":AnimatedImage:library")
include("AnimatedImage:demo")
-include("resources:library")
+include(":resources:library")
+include(":resources:demo:androidApp")
+include(":resources:demo:desktopApp")
+include(":resources:demo:shared")
diff --git a/components/test.sh b/components/test.sh
new file mode 100755
index 0000000000..a16a91fcc0
--- /dev/null
+++ b/components/test.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+cd "$(dirname "$0")" # Run always in current dir
+set -euo pipefail # Fail fast
+
+# Unit tests
+./gradlew :resources:library:test
+./gradlew :resources:library:desktopTest
+
+# Android integration tests
+./gradlew :resources:library:pixel5DebugAndroidTest
+
diff --git a/compose/README.md b/compose/README.md
index 48accf680c..932c396d03 100644
--- a/compose/README.md
+++ b/compose/README.md
@@ -88,6 +88,7 @@ Run native macos sample:
export COMPOSE_CUSTOM_VERSION=0.0.0-custom-version &&\
./scripts/publishToMavenLocal -Pcompose.platforms=all &&\
./scripts/publishGradlePluginToMavenLocal &&\
+./scripts/publishComponentsToMavenLocal &&\
./scripts/publishWebComponentsToMavenLocal
```
`-Pcompose.platforms=all` could be replace with comma-separated list of platforms, such as `js,jvm,androidDebug,androidRelease,macosx64,uikit`.
diff --git a/compose/scripts/publishComponentsToMavenLocal b/compose/scripts/publishComponentsToMavenLocal
new file mode 100755
index 0000000000..34e97cc777
--- /dev/null
+++ b/compose/scripts/publishComponentsToMavenLocal
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+cd "$(dirname "$0")"
+
+if [[ -z "$COMPOSE_CUSTOM_VERSION" ]]; then
+ echo "Must provide COMPOSE_CUSTOM_VERSION in environment" 1>&2
+ exit 1
+fi
+
+pushd ../../components
+./gradlew publishToMavenLocal -Pcompose.version="$COMPOSE_CUSTOM_VERSION" || exit 1
+popd
+
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureTaskToGenerateXcodeProject.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureTaskToGenerateXcodeProject.kt
index 4cb618cec9..5f55f5c8ba 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureTaskToGenerateXcodeProject.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/configureTaskToGenerateXcodeProject.kt
@@ -18,6 +18,9 @@ internal fun Project.configureTaskToGenerateXcodeProject(
): TaskProvider = tasks.composeIosTask("iosGenerateXcodeProject$id") {
dependsOn(taskInstallXcodeGen)
doLast {
+ val commonMainResources = file("src/commonMain/resources").absolutePath
+ val uikitMainResources = file("src/uikitMain/resources").absolutePath
+ val iosMainResources = file("src/iosMain/resources").absolutePath
val buildIosDir = getBuildIosDir(id)
buildIosDir.mkdirs()
buildIosDir.resolve("project.yml").writeText(
@@ -44,6 +47,16 @@ internal fun Project.configureTaskToGenerateXcodeProject(
ENABLE_BITCODE: "YES"
ONLY_ACTIVE_ARCH: "NO"
VALID_ARCHS: "arm64"
+ sources:
+ - path: $commonMainResources
+ optional: true
+ buildPhase: resources
+ - path: $uikitMainResources
+ optional: true
+ buildPhase: resources
+ - path: $iosMainResources
+ optional: true
+ buildPhase: resources
""".trimIndent()
)
runExternalTool(xcodeGenExecutable, emptyList(), workingDir = buildIosDir)