Konstantin
11 months ago
committed by
GitHub
43 changed files with 784 additions and 251 deletions
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@ -1,14 +1,14 @@ |
|||||||
package org.jetbrains.compose.resources |
package org.jetbrains.compose.resources |
||||||
|
|
||||||
import androidx.compose.runtime.Composable |
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.remember |
||||||
import androidx.compose.ui.platform.LocalContext |
import androidx.compose.ui.platform.LocalContext |
||||||
import androidx.compose.ui.text.font.Font |
import androidx.compose.ui.text.font.* |
||||||
import androidx.compose.ui.text.font.FontStyle |
|
||||||
import androidx.compose.ui.text.font.FontWeight |
|
||||||
|
|
||||||
@ExperimentalResourceApi |
@ExperimentalResourceApi |
||||||
@Composable |
@Composable |
||||||
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font { |
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font { |
||||||
val path = resource.getPathByEnvironment() |
val environment = rememberEnvironment() |
||||||
|
val path = remember(environment) { resource.getPathByEnvironment(environment) } |
||||||
return Font(path, LocalContext.current.assets, weight, style) |
return Font(path, LocalContext.current.assets, weight, style) |
||||||
} |
} |
@ -0,0 +1,18 @@ |
|||||||
|
package org.jetbrains.compose.resources |
||||||
|
|
||||||
|
import android.content.res.Configuration |
||||||
|
import android.content.res.Resources |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
internal actual fun getResourceEnvironment(): ResourceEnvironment { |
||||||
|
val locale = Locale.getDefault() |
||||||
|
val configuration = Resources.getSystem().configuration |
||||||
|
val isDarkTheme = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES |
||||||
|
val dpi = configuration.densityDpi |
||||||
|
return ResourceEnvironment( |
||||||
|
language = LanguageQualifier(locale.language), |
||||||
|
region = RegionQualifier(locale.country), |
||||||
|
theme = ThemeQualifier.selectByValue(isDarkTheme), |
||||||
|
density = DensityQualifier.selectByValue(dpi) |
||||||
|
) |
||||||
|
} |
@ -1,14 +0,0 @@ |
|||||||
package org.jetbrains.compose.resources |
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.runtime.State |
|
||||||
import androidx.compose.runtime.mutableStateOf |
|
||||||
import androidx.compose.runtime.remember |
|
||||||
import kotlinx.coroutines.runBlocking |
|
||||||
|
|
||||||
@Composable |
|
||||||
internal actual fun <T> rememberState(key: Any, getDefault: () -> T, block: suspend () -> T): State<T> = remember(key) { |
|
||||||
mutableStateOf( |
|
||||||
runBlocking { block() } |
|
||||||
) |
|
||||||
} |
|
@ -0,0 +1,18 @@ |
|||||||
|
package org.jetbrains.compose.resources |
||||||
|
|
||||||
|
import androidx.compose.runtime.* |
||||||
|
import kotlinx.coroutines.runBlocking |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal actual fun <T> rememberResourceState( |
||||||
|
key: Any, |
||||||
|
getDefault: () -> T, |
||||||
|
block: suspend (ResourceEnvironment) -> T |
||||||
|
): State<T> { |
||||||
|
val environment = rememberEnvironment() |
||||||
|
return remember(key, environment) { |
||||||
|
mutableStateOf( |
||||||
|
runBlocking { block(environment) } |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
package org.jetbrains.compose.resources |
||||||
|
|
||||||
|
interface Qualifier |
||||||
|
|
||||||
|
data class LanguageQualifier( |
||||||
|
val language: String |
||||||
|
) : Qualifier |
||||||
|
|
||||||
|
data class RegionQualifier( |
||||||
|
val region: String |
||||||
|
) : Qualifier |
||||||
|
|
||||||
|
enum class ThemeQualifier : Qualifier { |
||||||
|
LIGHT, |
||||||
|
DARK; |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun selectByValue(isDark: Boolean) = |
||||||
|
if (isDark) DARK else LIGHT |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//https://developer.android.com/guide/topics/resources/providing-resources |
||||||
|
enum class DensityQualifier(val dpi: Int) : Qualifier { |
||||||
|
LDPI(120), |
||||||
|
MDPI(160), |
||||||
|
HDPI(240), |
||||||
|
XHDPI(320), |
||||||
|
XXHDPI(480), |
||||||
|
XXXHDPI(640); |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun selectByValue(dpi: Int) = when { |
||||||
|
dpi <= LDPI.dpi -> LDPI |
||||||
|
dpi <= MDPI.dpi -> MDPI |
||||||
|
dpi <= HDPI.dpi -> HDPI |
||||||
|
dpi <= XHDPI.dpi -> XHDPI |
||||||
|
dpi <= XXHDPI.dpi -> XXHDPI |
||||||
|
else -> XXXHDPI |
||||||
|
} |
||||||
|
fun selectByDensity(density: Float) = when { |
||||||
|
density <= 0.75 -> LDPI |
||||||
|
density <= 1.0 -> MDPI |
||||||
|
density <= 1.33 -> HDPI |
||||||
|
density <= 2.0 -> XHDPI |
||||||
|
density <= 3.0 -> XXHDPI |
||||||
|
else -> XXXHDPI |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,73 @@ |
|||||||
|
package org.jetbrains.compose.resources |
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme |
||||||
|
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 |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal fun rememberEnvironment(): ResourceEnvironment { |
||||||
|
val composeLocale = Locale.current |
||||||
|
val composeTheme = isSystemInDarkTheme() |
||||||
|
val composeDensity = LocalDensity.current |
||||||
|
|
||||||
|
//cache ResourceEnvironment unless compose environment is changed |
||||||
|
return remember(composeLocale, composeTheme, composeDensity) { |
||||||
|
ResourceEnvironment( |
||||||
|
LanguageQualifier(composeLocale.language), |
||||||
|
RegionQualifier(composeLocale.region), |
||||||
|
ThemeQualifier.selectByValue(composeTheme), |
||||||
|
DensityQualifier.selectByDensity(composeDensity.density) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Provides the resource environment for non-composable access to string resources. |
||||||
|
* It is an expensive operation! Don't use it in composable functions with no cache! |
||||||
|
*/ |
||||||
|
internal expect fun getResourceEnvironment(): ResourceEnvironment |
||||||
|
|
||||||
|
internal fun Resource.getPathByEnvironment(environment: ResourceEnvironment): String { |
||||||
|
//Priority of environments: https://developer.android.com/guide/topics/resources/providing-resources#table2 |
||||||
|
items.toList() |
||||||
|
.filterBy(environment.language) |
||||||
|
.also { if (it.size == 1) return it.first().path } |
||||||
|
.filterBy(environment.region) |
||||||
|
.also { if (it.size == 1) return it.first().path } |
||||||
|
.filterBy(environment.theme) |
||||||
|
.also { if (it.size == 1) return it.first().path } |
||||||
|
.filterBy(environment.density) |
||||||
|
.also { if (it.size == 1) return it.first().path } |
||||||
|
.let { items -> |
||||||
|
if (items.isEmpty()) { |
||||||
|
error("Resource with ID='$id' not found") |
||||||
|
} else { |
||||||
|
error("Resource with ID='$id' has more than one file: ${items.joinToString { it.path }}") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun List<ResourceItem>.filterBy(qualifier: Qualifier): List<ResourceItem> { |
||||||
|
//Android has a slightly different algorithm, |
||||||
|
//but it provides the same result: https://developer.android.com/guide/topics/resources/providing-resources#BestMatch |
||||||
|
|
||||||
|
//filter items with the requested qualifier |
||||||
|
val withQualifier = filter { item -> |
||||||
|
item.qualifiers.any { it == qualifier } |
||||||
|
} |
||||||
|
|
||||||
|
if (withQualifier.isNotEmpty()) return withQualifier |
||||||
|
|
||||||
|
//items with no requested qualifier type (default) |
||||||
|
return filter { item -> |
||||||
|
item.qualifiers.none { it::class == qualifier::class } |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package org.jetbrains.compose.resources |
||||||
|
|
||||||
|
import org.jetbrains.skiko.SystemTheme |
||||||
|
import org.jetbrains.skiko.currentSystemTheme |
||||||
|
import java.awt.Toolkit |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
internal actual fun getResourceEnvironment(): ResourceEnvironment { |
||||||
|
val locale = Locale.getDefault() |
||||||
|
//FIXME: don't use skiko internals |
||||||
|
val isDarkTheme = currentSystemTheme == SystemTheme.DARK |
||||||
|
val dpi = Toolkit.getDefaultToolkit().screenResolution |
||||||
|
return ResourceEnvironment( |
||||||
|
language = LanguageQualifier(locale.language), |
||||||
|
region = RegionQualifier(locale.country), |
||||||
|
theme = ThemeQualifier.selectByValue(isDarkTheme), |
||||||
|
density = DensityQualifier.selectByValue(dpi) |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
package org.jetbrains.compose.resources |
||||||
|
|
||||||
|
import platform.Foundation.* |
||||||
|
import platform.UIKit.UIScreen |
||||||
|
import platform.UIKit.UIUserInterfaceStyle |
||||||
|
|
||||||
|
internal actual fun getResourceEnvironment(): ResourceEnvironment { |
||||||
|
val locale = NSLocale.currentLocale() |
||||||
|
|
||||||
|
val mainScreen = UIScreen.mainScreen |
||||||
|
val isDarkTheme = mainScreen.traitCollection().userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark |
||||||
|
|
||||||
|
//there is no an API to get a physical screen size and calculate a real DPI |
||||||
|
val density = mainScreen.scale.toFloat() |
||||||
|
return ResourceEnvironment( |
||||||
|
language = LanguageQualifier(locale.languageCode), |
||||||
|
region = RegionQualifier(locale.regionCode.orEmpty()), |
||||||
|
theme = ThemeQualifier.selectByValue(isDarkTheme), |
||||||
|
density = DensityQualifier.selectByDensity(density) |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package org.jetbrains.compose.resources |
||||||
|
|
||||||
|
import kotlinx.browser.window |
||||||
|
|
||||||
|
private external class Intl { |
||||||
|
class Locale(locale: String) { |
||||||
|
val language: String |
||||||
|
val region: String |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal actual fun getResourceEnvironment(): ResourceEnvironment { |
||||||
|
val locale = Intl.Locale(window.navigator.language) |
||||||
|
val isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)").matches |
||||||
|
//96 - standard browser DPI https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio |
||||||
|
val dpi: Int = (window.devicePixelRatio * 96).toInt() |
||||||
|
return ResourceEnvironment( |
||||||
|
language = LanguageQualifier(locale.language), |
||||||
|
region = RegionQualifier(locale.region), |
||||||
|
theme = ThemeQualifier.selectByValue(isDarkTheme), |
||||||
|
density = DensityQualifier.selectByValue(dpi) |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
package org.jetbrains.compose.resources |
||||||
|
|
||||||
|
import kotlinx.cinterop.* |
||||||
|
import platform.AppKit.NSScreen |
||||||
|
import platform.CoreGraphics.CGDisplayPixelsWide |
||||||
|
import platform.CoreGraphics.CGDisplayScreenSize |
||||||
|
import platform.Foundation.* |
||||||
|
|
||||||
|
internal actual fun getResourceEnvironment(): ResourceEnvironment { |
||||||
|
val locale = NSLocale.currentLocale() |
||||||
|
val isDarkTheme = NSUserDefaults.standardUserDefaults.stringForKey("AppleInterfaceStyle") == "Dark" |
||||||
|
|
||||||
|
val dpi = NSScreen.mainScreen?.let { screen -> |
||||||
|
val backingScaleFactor = screen.backingScaleFactor |
||||||
|
val screenNumber = interpretObjCPointer<NSNumber>( |
||||||
|
screen.deviceDescription["NSScreenNumber"].objcPtr() |
||||||
|
).unsignedIntValue |
||||||
|
|
||||||
|
val displaySizePX = CGDisplayPixelsWide(screenNumber).toFloat() * backingScaleFactor |
||||||
|
val displaySizeMM = CGDisplayScreenSize(screenNumber).useContents { width } |
||||||
|
|
||||||
|
//1 inch = 25.4 mm |
||||||
|
((displaySizePX / displaySizeMM) * 25.4f).toInt() |
||||||
|
} ?: 0 |
||||||
|
|
||||||
|
return ResourceEnvironment( |
||||||
|
language = LanguageQualifier(locale.languageCode), |
||||||
|
region = RegionQualifier(locale.regionCode.orEmpty()), |
||||||
|
theme = ThemeQualifier.selectByValue(isDarkTheme), |
||||||
|
density = DensityQualifier.selectByValue(dpi) |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package org.jetbrains.compose.resources |
||||||
|
|
||||||
|
import kotlinx.browser.window |
||||||
|
|
||||||
|
private external class Intl { |
||||||
|
class Locale(locale: String) { |
||||||
|
val language: String |
||||||
|
val region: String |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal actual fun getResourceEnvironment(): ResourceEnvironment { |
||||||
|
val locale = Intl.Locale(window.navigator.language) |
||||||
|
val isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)").matches |
||||||
|
//96 - standard browser DPI https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio |
||||||
|
val dpi: Int = (window.devicePixelRatio * 96).toInt() |
||||||
|
return ResourceEnvironment( |
||||||
|
language = LanguageQualifier(locale.language), |
||||||
|
region = RegionQualifier(locale.region), |
||||||
|
theme = ThemeQualifier.selectByValue(isDarkTheme), |
||||||
|
density = DensityQualifier.selectByValue(dpi) |
||||||
|
) |
||||||
|
} |
@ -1,84 +1,123 @@ |
|||||||
package app.group.generated.resources |
package app.group.generated.resources |
||||||
|
|
||||||
|
import org.jetbrains.compose.resources.DrawableResource |
||||||
import org.jetbrains.compose.resources.FontResource |
import org.jetbrains.compose.resources.FontResource |
||||||
import org.jetbrains.compose.resources.ImageResource |
import org.jetbrains.compose.resources.LanguageQualifier |
||||||
|
import org.jetbrains.compose.resources.RegionQualifier |
||||||
import org.jetbrains.compose.resources.ResourceItem |
import org.jetbrains.compose.resources.ResourceItem |
||||||
import org.jetbrains.compose.resources.StringResource |
import org.jetbrains.compose.resources.StringResource |
||||||
|
import org.jetbrains.compose.resources.ThemeQualifier |
||||||
|
|
||||||
internal object Res { |
internal object Res { |
||||||
public object fonts { |
public object drawable { |
||||||
public val emptyfont: FontResource = FontResource( |
public val _3_strange_name: DrawableResource = DrawableResource( |
||||||
"FONT:emptyfont", |
"drawable:_3_strange_name", |
||||||
setOf( |
setOf( |
||||||
ResourceItem(setOf(), "composeRes/fonts/emptyFont.otf"), |
ResourceItem( |
||||||
|
setOf(), |
||||||
|
"composeRes/drawable/3-strange-name.xml" |
||||||
|
), |
||||||
) |
) |
||||||
) |
) |
||||||
} |
|
||||||
|
|
||||||
public object images { |
public val vector: DrawableResource = DrawableResource( |
||||||
public val _3_strange_name: ImageResource = ImageResource( |
"drawable:vector", |
||||||
"IMAGE:_3_strange_name", |
|
||||||
setOf( |
setOf( |
||||||
ResourceItem(setOf(), "composeRes/images/3-strange-name.xml"), |
ResourceItem( |
||||||
|
setOf(LanguageQualifier("au"), RegionQualifier("US"), ), |
||||||
|
"composeRes/drawable-au-rUS/vector.xml" |
||||||
|
), |
||||||
|
ResourceItem( |
||||||
|
setOf(ThemeQualifier.DARK, LanguageQualifier("ge"), ), |
||||||
|
"composeRes/drawable-dark-ge/vector.xml" |
||||||
|
), |
||||||
|
ResourceItem( |
||||||
|
setOf(LanguageQualifier("en"), ), |
||||||
|
"composeRes/drawable-en/vector.xml" |
||||||
|
), |
||||||
|
ResourceItem( |
||||||
|
setOf(), |
||||||
|
"composeRes/drawable/vector.xml" |
||||||
|
), |
||||||
) |
) |
||||||
) |
) |
||||||
|
|
||||||
public val vector: ImageResource = ImageResource( |
public val vector_2: DrawableResource = DrawableResource( |
||||||
"IMAGE:vector", |
"drawable:vector_2", |
||||||
setOf( |
setOf( |
||||||
ResourceItem(setOf("q1", "q2"), "composeRes/images-q1-q2/vector.xml"), |
ResourceItem( |
||||||
ResourceItem(setOf("q1"), "composeRes/images-q1/vector.xml"), |
setOf(), |
||||||
ResourceItem(setOf("q2"), "composeRes/images-q2/vector.xml"), |
"composeRes/drawable/vector_2.xml" |
||||||
ResourceItem(setOf(), "composeRes/images/vector.xml"), |
), |
||||||
) |
) |
||||||
) |
) |
||||||
|
} |
||||||
|
|
||||||
public val vector_2: ImageResource = ImageResource( |
public object font { |
||||||
"IMAGE:vector_2", |
public val emptyfont: FontResource = FontResource( |
||||||
|
"font:emptyfont", |
||||||
setOf( |
setOf( |
||||||
ResourceItem(setOf(), "composeRes/images/vector_2.xml"), |
ResourceItem( |
||||||
|
setOf(), |
||||||
|
"composeRes/font/emptyFont.otf" |
||||||
|
), |
||||||
) |
) |
||||||
) |
) |
||||||
} |
} |
||||||
|
|
||||||
public object strings { |
public object string { |
||||||
public val app_name: StringResource = StringResource( |
public val app_name: StringResource = StringResource( |
||||||
"STRING:app_name", |
"string:app_name", |
||||||
"app_name", |
"app_name", |
||||||
setOf( |
setOf( |
||||||
ResourceItem(setOf(), "composeRes/values/strings.xml"), |
ResourceItem( |
||||||
|
setOf(), |
||||||
|
"composeRes/values/strings.xml" |
||||||
|
), |
||||||
) |
) |
||||||
) |
) |
||||||
|
|
||||||
public val hello: StringResource = StringResource( |
public val hello: StringResource = StringResource( |
||||||
"STRING:hello", |
"string:hello", |
||||||
"hello", |
"hello", |
||||||
setOf( |
setOf( |
||||||
ResourceItem(setOf(), "composeRes/values/strings.xml"), |
ResourceItem( |
||||||
|
setOf(), |
||||||
|
"composeRes/values/strings.xml" |
||||||
|
), |
||||||
) |
) |
||||||
) |
) |
||||||
|
|
||||||
public val multi_line: StringResource = StringResource( |
public val multi_line: StringResource = StringResource( |
||||||
"STRING:multi_line", |
"string:multi_line", |
||||||
"multi_line", |
"multi_line", |
||||||
setOf( |
setOf( |
||||||
ResourceItem(setOf(), "composeRes/values/strings.xml"), |
ResourceItem( |
||||||
|
setOf(), |
||||||
|
"composeRes/values/strings.xml" |
||||||
|
), |
||||||
) |
) |
||||||
) |
) |
||||||
|
|
||||||
public val str_arr: StringResource = StringResource( |
public val str_arr: StringResource = StringResource( |
||||||
"STRING:str_arr", |
"string:str_arr", |
||||||
"str_arr", |
"str_arr", |
||||||
setOf( |
setOf( |
||||||
ResourceItem(setOf(), "composeRes/values/strings.xml"), |
ResourceItem( |
||||||
|
setOf(), |
||||||
|
"composeRes/values/strings.xml" |
||||||
|
), |
||||||
) |
) |
||||||
) |
) |
||||||
|
|
||||||
public val str_template: StringResource = StringResource( |
public val str_template: StringResource = StringResource( |
||||||
"STRING:str_template", |
"string:str_template", |
||||||
"str_template", |
"str_template", |
||||||
setOf( |
setOf( |
||||||
ResourceItem(setOf(), "composeRes/values/strings.xml"), |
ResourceItem( |
||||||
|
setOf(), |
||||||
|
"composeRes/values/strings.xml" |
||||||
|
), |
||||||
) |
) |
||||||
) |
) |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue