Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[resources] Add functions to retrieve bytes from drawable or font resources. #4651

Merged
merged 10 commits into from
Apr 23, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.jetbrains.compose.resources

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.ui.text.font.*

/**
Expand Down Expand Up @@ -32,4 +33,20 @@ expect fun Font(
resource: FontResource,
igordmn marked this conversation as resolved.
Show resolved Hide resolved
weight: FontWeight = FontWeight.Normal,
style: FontStyle = FontStyle.Normal
): Font
): Font

/**
* Retrieves the byte array of the font resource.
*
* @param environment The optional resource environment.
* @param resource The font resource.
* @return The byte array representing the font resource.
*/
@ExperimentalResourceApi
suspend fun getFontResourceBytes(
environment: ResourceEnvironment = getResourceEnvironment(),
igordmn marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

@igordmn igordmn Apr 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having environment on the first place forces users to use resource name explicitly. Was this the intention?

I don't have a strong opinion here. On one hand, it is better for users to make a conscience choice to use the system environment, on the other hand it is verbose.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was done to avoid parameters after a vararg in

suspend fun getString(
    environment: ResourceEnvironment,
    resource: StringResource,
    vararg formatArgs: Any
): String

Copy link
Collaborator

@igordmn igordmn Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I would suggest to do the same for the other functions - split them into 2:

@ExperimentalResourceApi
suspend fun getFontResourceBytes(
    environment: ResourceEnvironment,
    resource: FontResource
): ByteArray

@ExperimentalResourceApi
suspend fun getFontResourceBytes(
    environment: ResourceEnvironment,
): ByteArray = getFontResourceBytes(getSystemResourceEnvironment(), resource)

Otherwise we can't write getDrawableResourceBytes(Res.drawable.title), only getDrawableResourceBytes(resource = Res.drawable.title)

resource: FontResource
): ByteArray {
val resourceItem = resource.getResourceItemByEnvironment(environment)
return DefaultResourceReader.read(resourceItem.path)
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ internal expect fun SvgElement.toSvgPainter(density: Density): Painter

private val emptySvgPainter: Painter by lazy { BitmapPainter(emptyImageBitmap) }

@OptIn(ExperimentalResourceApi::class)
@Composable
private fun svgPainter(resource: DrawableResource): Painter {
val resourceReader = LocalResourceReader.current
Expand All @@ -110,6 +109,22 @@ private fun svgPainter(resource: DrawableResource): Painter {
return svgPainter
}

/**
* Retrieves the byte array of the drawable resource.
*
* @param environment The optional resource environment.
* @param resource The drawable resource.
* @return The byte array representing the drawable resource.
*/
@ExperimentalResourceApi
suspend fun getDrawableResourceBytes(
environment: ResourceEnvironment = getResourceEnvironment(),
resource: DrawableResource
): ByteArray {
val resourceItem = resource.getResourceItemByEnvironment(environment)
return DefaultResourceReader.read(resourceItem.path)
}

internal expect fun ByteArray.toImageBitmap(): ImageBitmap
internal expect fun ByteArray.toXmlElement(): Element
internal expect fun ByteArray.toSvgElement(): SvgElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ fun pluralStringResource(resource: PluralStringResource, quantity: Int): String
suspend fun getPluralString(resource: PluralStringResource, quantity: Int): String =
loadPluralString(resource, quantity, DefaultResourceReader, getResourceEnvironment())

/**
* Loads a string using the specified string resource.
*
* @param environment The resource environment.
* @param resource The string resource to be used.
* @param quantity The quantity of the pluralization to use.
* @return The loaded string resource.
*
* @throws IllegalArgumentException If the provided ID or the pluralization is not found in the resource file.
*/
suspend fun getPluralString(
igordmn marked this conversation as resolved.
Show resolved Hide resolved
environment: ResourceEnvironment,
resource: PluralStringResource,
quantity: Int
): String = loadPluralString(resource, quantity, DefaultResourceReader, environment)

private suspend fun loadPluralString(
resource: PluralStringResource,
quantity: Int,
Expand Down Expand Up @@ -102,6 +118,29 @@ suspend fun getPluralString(resource: PluralStringResource, quantity: Int, varar
getResourceEnvironment(),
)

/**
* Loads a string using the specified string resource.
*
* @param environment The resource environment.
* @param resource The string resource to be used.
* @param quantity The quantity of the pluralization to use.
* @param formatArgs The arguments to be inserted into the formatted string.
* @return The loaded string resource.
*
* @throws IllegalArgumentException If the provided ID or the pluralization is not found in the resource file.
*/
suspend fun getPluralString(
igordmn marked this conversation as resolved.
Show resolved Hide resolved
environment: ResourceEnvironment,
resource: PluralStringResource,
quantity: Int,
vararg formatArgs: Any
): String = loadPluralString(
resource, quantity,
formatArgs.map { it.toString() },
DefaultResourceReader,
environment
)

private suspend fun loadPluralString(
resource: PluralStringResource,
quantity: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.intl.Locale

internal data class ResourceEnvironment(
val language: LanguageQualifier,
val region: RegionQualifier,
val theme: ThemeQualifier,
val density: DensityQualifier
data class ResourceEnvironment internal constructor(
igordmn marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal val language: LanguageQualifier,
internal val region: RegionQualifier,
internal val theme: ThemeQualifier,
internal val density: DensityQualifier
)

internal interface ComposeEnvironment {
Expand Down Expand Up @@ -39,6 +39,20 @@ internal val DefaultComposeEnvironment = object : ComposeEnvironment {
//ComposeEnvironment provider will be overridden for tests
internal val LocalComposeEnvironment = staticCompositionLocalOf { DefaultComposeEnvironment }

/**
* Returns an instance of [ResourceEnvironment].
*
* The [ResourceEnvironment] class represents the environment for resources.
*
* @return An instance of [ResourceEnvironment] representing the current environment.
*/
@ExperimentalResourceApi
@Composable
fun rememberResourceEnvironment(): ResourceEnvironment {
val composeEnvironment = LocalComposeEnvironment.current
return composeEnvironment.rememberEnvironment()
}

internal expect fun getSystemEnvironment(): ResourceEnvironment

//the function reference will be overridden for tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ fun stringArrayResource(resource: StringArrayResource): List<String> {
suspend fun getStringArray(resource: StringArrayResource): List<String> =
loadStringArray(resource, DefaultResourceReader, getResourceEnvironment())

/**
* Loads a list of strings using the specified string array resource.
*
* @param environment The resource environment.
* @param resource The string array resource to be used.
* @return A list of strings representing the items in the string array.
*
* @throws IllegalStateException if the string array with the given ID is not found.
*/
suspend fun getStringArray(
igordmn marked this conversation as resolved.
Show resolved Hide resolved
environment: ResourceEnvironment,
resource: StringArrayResource
): List<String> = loadStringArray(resource, DefaultResourceReader, environment)

private suspend fun loadStringArray(
resource: StringArrayResource,
resourceReader: ResourceReader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ fun stringResource(resource: StringResource): String {
suspend fun getString(resource: StringResource): String =
loadString(resource, DefaultResourceReader, getResourceEnvironment())

/**
* Loads a string using the specified string resource.
*
* @param environment The resource environment.
* @param resource The string resource to be used.
* @return The loaded string resource.
*
* @throws IllegalArgumentException If the provided ID is not found in the resource file.
*/
suspend fun getString(environment: ResourceEnvironment, resource: StringResource): String =
loadString(resource, DefaultResourceReader, environment)

private suspend fun loadString(
resource: StringResource,
resourceReader: ResourceReader,
Expand Down Expand Up @@ -86,6 +98,27 @@ suspend fun getString(resource: StringResource, vararg formatArgs: Any): String
getResourceEnvironment()
)

/**
* Loads a formatted string using the specified string resource and arguments.
*
* @param environment The resource environment.
* @param resource The string resource to be used.
* @param formatArgs The arguments to be inserted into the formatted string.
* @return The formatted string resource.
*
* @throws IllegalArgumentException If the provided ID is not found in the resource file.
*/
suspend fun getString(
environment: ResourceEnvironment,
resource: StringResource,
vararg formatArgs: Any
): String = loadString(
resource,
formatArgs.map { it.toString() },
DefaultResourceReader,
environment
)

private suspend fun loadString(
resource: StringResource,
args: List<String>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ 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, InternalResourceApi::class)
Expand Down Expand Up @@ -304,4 +303,26 @@ class ComposeResourceTest {
assertTrue(uri1.endsWith("/1.png"))
assertTrue(uri2.endsWith("/2.png"))
}

@OptIn(ExperimentalResourceApi::class)
@Test
fun testGetResourceBytes() = runTest {
val imageBytes = getDrawableResourceBytes(resource = TestDrawableResource("1.png"))
assertEquals(946, imageBytes.size)
val fontBytes = getFontResourceBytes(resource = TestFontResource("font_awesome.otf"))
assertEquals(134808, fontBytes.size)
}

@OptIn(ExperimentalResourceApi::class)
@Test
fun testGetResourceEnvironment() = runComposeUiTest {
var environment: ResourceEnvironment? = null
setContent {
environment = rememberResourceEnvironment()
}
waitForIdle()

val systemEnvironment = getSystemEnvironment()
assertEquals(systemEnvironment, environment)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ internal fun TestDrawableResource(path: String) = DrawableResource(
setOf(ResourceItem(emptySet(), path, -1, -1))
)

internal fun TestFontResource(path: String) = FontResource(
path,
setOf(ResourceItem(emptySet(), path, -1, -1))
)

internal fun parsePluralSamples(samples: String): List<Int> {
return samples.split(',').flatMap {
val range = it.trim()
Expand Down