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 e457804f18..f4630dcc32 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 @@ -19,15 +19,35 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou return result } + @OptIn(ExperimentalResourceApi::class) + 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() + } + @OptIn(ExperimentalResourceApi::class) private fun getResourceAsStream(path: String): InputStream { - val classLoader = Thread.currentThread().contextClassLoader ?: this.javaClass.classLoader + val classLoader = getClassLoader() val resource = classLoader.getResourceAsStream(path) ?: run { //try to find a font in the android assets - if (File(path).parentFile?.name.orEmpty().startsWith("font")) { + 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") + } + + private fun getClassLoader(): ClassLoader { + return Thread.currentThread().contextClassLoader ?: this.javaClass.classLoader!! + } } \ No newline at end of file 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 adacab3229..d34f3b782e 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 @@ -14,9 +14,19 @@ class MissingResourceException(path: String) : Exception("Missing resource with @InternalResourceApi suspend fun readResourceBytes(path: String): ByteArray = DefaultResourceReader.read(path) +/** + * Provides the platform dependent URI for a given resource path. + * + * @param path The path to the file in the resource's directory. + * @return The URI string of the specified resource. + */ +@InternalResourceApi +fun getResourceUri(path: String): String = DefaultResourceReader.getUri(path) + internal interface ResourceReader { suspend fun read(path: String): ByteArray suspend fun readPart(path: String, offset: Long, size: Long): ByteArray + fun getUri(path: String): String } internal expect fun getPlatformResourceReader(): ResourceReader 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 81b98b343a..fba9c1daad 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 @@ -6,6 +6,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.runComposeUiTest import kotlinx.coroutines.test.runTest +import org.jetbrains.skiko.URIManager import kotlin.test.* @OptIn(ExperimentalTestApi::class, ExperimentalResourceApi::class, InternalResourceApi::class) @@ -286,4 +287,21 @@ class ComposeResourceTest { bytes.decodeToString() ) } + + @Test + fun testGetResourceUri() = runComposeUiTest { + var uri1 = "" + var uri2 = "" + setContent { + CompositionLocalProvider(LocalComposeEnvironment provides TestComposeEnvironment) { + val resourceReader = LocalResourceReader.current + uri1 = resourceReader.getUri("1.png") + uri2 = resourceReader.getUri("2.png") + } + } + waitForIdle() + + assertTrue(uri1.endsWith("/1.png")) + assertTrue(uri2.endsWith("/2.png")) + } } diff --git a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestResourceReader.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestResourceReader.kt index 8b8366a2d4..a5ae9f9420 100644 --- a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestResourceReader.kt +++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestResourceReader.kt @@ -13,4 +13,8 @@ internal class TestResourceReader : ResourceReader { readPathsList.add("$path/$offset-$size") return DefaultResourceReader.readPart(path, offset, size) } + + override fun getUri(path: String): String { + return DefaultResourceReader.getUri(path) + } } \ 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 419682c7b3..ab9ffe4a3a 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 @@ -18,9 +18,20 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou return result } + @OptIn(ExperimentalResourceApi::class) + override fun getUri(path: String): String { + val classLoader = getClassLoader() + val resource = classLoader.getResource(path) ?: throw MissingResourceException(path) + return resource.toURI().toString() + } + @OptIn(ExperimentalResourceApi::class) private fun getResourceAsStream(path: String): InputStream { - val classLoader = Thread.currentThread().contextClassLoader ?: this.javaClass.classLoader + val classLoader = getClassLoader() return classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path) } + + private fun getClassLoader(): ClassLoader { + return Thread.currentThread().contextClassLoader ?: this.javaClass.classLoader!! + } } \ No newline at end of file diff --git a/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt b/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt index 3de80b7d9c..9d4ee1c9a6 100644 --- a/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt +++ b/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt @@ -21,6 +21,10 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou } } + override fun getUri(path: String): String { + return NSURL.fileURLWithPath(getPathInBundle(path)).toString() + } + private fun readData(path: String): NSData { return NSFileManager.defaultManager().contentsAtPath(path) ?: throw MissingResourceException(path) } diff --git a/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt b/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt index 851cf50114..f249c676ce 100644 --- a/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt +++ b/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt @@ -18,6 +18,11 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou return part.asByteArray() } + override fun getUri(path: String): String { + val location = window.location + return getResourceUrl(location.origin, location.pathname, path) + } + private suspend fun readAsBlob(path: String): Blob { val resPath = WebResourcesConfiguration.getResourcePath(path) val response = window.fetch(resPath).await() diff --git a/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt b/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt index 5f8ba0cc51..10a5969b08 100644 --- a/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt +++ b/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt @@ -21,6 +21,10 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou } } + override fun getUri(path: String): String { + return NSURL.fileURLWithPath(getPathOnDisk(path)).toString() + } + private fun readData(path: String): NSData { return NSFileManager.defaultManager().contentsAtPath(path) ?: throw MissingResourceException(path) } diff --git a/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt index 02c330f435..c3dbae6c41 100644 --- a/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt +++ b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt @@ -33,6 +33,11 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou return part.asByteArray() } + override fun getUri(path: String): String { + val location = window.location + return getResourceUrl(location.origin, location.pathname, path) + } + private suspend fun readAsBlob(path: String): Blob { val resPath = WebResourcesConfiguration.getResourcePath(path) val response = window.fetch(resPath).await() diff --git a/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt b/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt index ae96944465..f1ebbc5531 100644 --- a/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt +++ b/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt @@ -44,4 +44,14 @@ object WebResourcesConfiguration { @ExperimentalResourceApi fun configureWebResources(configure: WebResourcesConfiguration.() -> Unit) { WebResourcesConfiguration.configure() +} + +@OptIn(ExperimentalResourceApi::class) +internal fun getResourceUrl(windowOrigin: String, windowPathname: String, resourcePath: String): String { + val path = WebResourcesConfiguration.getResourcePath(resourcePath) + return when { + path.startsWith("/") -> windowOrigin + path + path.startsWith("http://") || path.startsWith("https://") -> path + else -> windowOrigin + windowPathname + path + } } \ No newline at end of file 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 23b541ef32..8acb4e0c33 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 @@ -160,6 +160,27 @@ internal fun getResFileSpecs( .addStatement("""return %M("$moduleDir" + path)""", readResourceBytes) .build() ) + + //getUri + val getResourceUri = MemberName("org.jetbrains.compose.resources", "getResourceUri") + resObject.addFunction( + FunSpec.builder("getUri") + .addKdoc( + """ + 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. + """.trimIndent() + ) + .addParameter("path", String::class) + .returns(String::class) + .addStatement("""return %M("$moduleDir" + path)""", getResourceUri) + .build() + ) + ResourceType.values().forEach { type -> resObject.addType(TypeSpec.objectBuilder(type.accessorName).build()) } diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/Res.kt index 16edee76a2..4e2092bc2d 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected-open-res/Res.kt @@ -9,6 +9,7 @@ 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 @ExperimentalResourceApi @@ -23,6 +24,16 @@ public object Res { */ 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. + */ + public fun getUri(path: String): String = getResourceUri("" + path) + public object drawable public object string diff --git a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt index 0471dc8135..e3beeb0da9 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt @@ -9,6 +9,7 @@ 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 @ExperimentalResourceApi @@ -23,6 +24,16 @@ internal object Res { */ 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. + */ + public fun getUri(path: String): String = getResourceUri("" + path) + public object drawable public object string @@ -32,4 +43,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/emptyResources/expected/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/emptyResources/expected/Res.kt index 1d087e14d8..ccceb9263d 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 @@ -3,12 +3,13 @@ org.jetbrains.compose.resources.ExperimentalResourceApi::class, ) -package app.group.empty_res.generated.resources +package app.group.resources_test.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 @ExperimentalResourceApi @@ -23,6 +24,16 @@ internal object Res { */ 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. + */ + public fun getUri(path: String): String = getResourceUri("" + path) + public object drawable public object string diff --git a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt index 74e01c770b..ccceb9263d 100644 --- a/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt +++ b/gradle-plugins/compose/src/test/test-projects/misc/jvmOnlyResources/expected/Res.kt @@ -3,12 +3,13 @@ org.jetbrains.compose.resources.ExperimentalResourceApi::class, ) -package me.app.jvmonlyresources.generated.resources +package app.group.resources_test.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 @ExperimentalResourceApi @@ -23,6 +24,16 @@ internal object Res { */ 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. + */ + public fun getUri(path: String): String = getResourceUri("" + path) + public object drawable public object string @@ -32,4 +43,4 @@ internal object Res { public object plurals public object font -} +} \ No newline at end of file