diff --git a/images/compose-create-first/compose-resources-project-structure.png b/images/compose-create-first/compose-resources-project-structure.png index 6c197cba..290a1003 100644 Binary files a/images/compose-create-first/compose-resources-project-structure.png and b/images/compose-create-first/compose-resources-project-structure.png differ diff --git a/images/compose-create-first/first-compose-project-on-android-ios-2.png b/images/compose-create-first/first-compose-project-on-android-ios-2.png index 4d2ae04f..156afa83 100644 Binary files a/images/compose-create-first/first-compose-project-on-android-ios-2.png and b/images/compose-create-first/first-compose-project-on-android-ios-2.png differ diff --git a/topics/compose-onboard/compose-multiplatform-explore-composables.md b/topics/compose-onboard/compose-multiplatform-explore-composables.md index c34b5efa..bd1f20c1 100644 --- a/topics/compose-onboard/compose-multiplatform-explore-composables.md +++ b/topics/compose-onboard/compose-multiplatform-explore-composables.md @@ -30,7 +30,7 @@ fun App() { } AnimatedVisibility(showContent) { Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - Image(painterResource("compose-multiplatform.xml"), null) + Image(painterResource(Res.drawable.compose_multiplatform), null) Text("Compose: $greeting") } } diff --git a/topics/compose-onboard/compose-multiplatform-modify-project.md b/topics/compose-onboard/compose-multiplatform-modify-project.md index 7ef3c7ae..1c70ec61 100644 --- a/topics/compose-onboard/compose-multiplatform-modify-project.md +++ b/topics/compose-onboard/compose-multiplatform-modify-project.md @@ -80,7 +80,7 @@ To use this library: } AnimatedVisibility(showContent) { Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - Image(painterResource("compose-multiplatform.xml"), null) + Image(painterResource(Res.drawable.compose_multiplatform), null) Text("Compose: $greeting") } } @@ -100,6 +100,10 @@ and desktop: ![First Compose Multiplatform app on desktop](first-compose-project-on-desktop-2.png){width=400} +> You can find this state of the project in our [GitHub repository](https://github.com/kotlin-hands-on/get-started-with-cm/tree/main/ComposeDemoStage1). +> +{type="tip"} + ## Next step In the next part of the tutorial, you'll learn new Compose Multiplatform concepts and create your own application from diff --git a/topics/compose-onboard/compose-multiplatform-new-project.md b/topics/compose-onboard/compose-multiplatform-new-project.md index 70bbbed5..066dc58e 100644 --- a/topics/compose-onboard/compose-multiplatform-new-project.md +++ b/topics/compose-onboard/compose-multiplatform-new-project.md @@ -28,24 +28,24 @@ To get started, implement a new `App` composable: composable: ```kotlin - @Composable - fun App() { - MaterialTheme { - var timeAtLocation by remember { mutableStateOf("No location selected") } - Column { - Text(timeAtLocation) - Button(onClick = { timeAtLocation = "13:30" }) { - Text("Show Time At Location") - } - } - } - } - ``` - - * The layout is a column containing two composables. The first is a `Text` composable, and the second is a `Button`. - * The two composables are linked by a single shared state, namely the `timeAtLocation` property. The `Text` - composable is an observer of this state. - * The `Button` composable changes the state using the `onClick` event handler. + @Composable + fun App() { + MaterialTheme { + var timeAtLocation by remember { mutableStateOf("No location selected") } + Column { + Text(timeAtLocation) + Button(onClick = { timeAtLocation = "13:30" }) { + Text("Show Time At Location") + } + } + } + } + ``` + + * The layout is a column containing two composables. The first is a `Text` composable, and the second is a `Button`. + * The two composables are linked by a single shared state, namely the `timeAtLocation` property. The `Text` + composable is an observer of this state. + * The `Button` composable changes the state using the `onClick` event handler. 2. Run the application on Android and iOS: @@ -209,6 +209,10 @@ time message could be rendered more prominently. ![Improved style of the Compose Multiplatform app on desktop](first-compose-project-on-desktop-7.png){width=350} +> You can find this state of the project in our [GitHub repository](https://github.com/kotlin-hands-on/get-started-with-cm/tree/main/ComposeDemoStage2). +> +{type="tip"} + ## Refactor the design The application works, but it's susceptible to users' typos. For example, if the user enters "Franse" instead of "France", @@ -290,12 +294,16 @@ list. ![The country list in the Compose Multiplatform app on desktop](first-compose-project-on-desktop-8.png){width=350} +> You can find this state of the project in our [GitHub repository](https://github.com/kotlin-hands-on/get-started-with-cm/tree/main/ComposeDemoStage3). +> +{type="tip"} + > You can further improve the design using a dependency injection framework, such as [Koin](https://insert-koin.io/), > to build and inject the table of locations. If the data is stored externally, > you can use the [Ktor](https://ktor.io/docs/create-client.html) library to fetch it over the network or > the [SQLDelight](https://github.com/cashapp/sqldelight) library to fetch it from a database. > -{type="tip"} +{type="note"} ## Introduce images @@ -314,44 +322,41 @@ code to load and display them: are [Japan](https://flagcdn.com/w320/jp.png), [France](https://flagcdn.com/w320/fr.png), [Mexico](https://flagcdn.com/w320/mx.png), [Indonesia](https://flagcdn.com/w320/id.png), and [Egypt](https://flagcdn.com/w320/eg.png). -2. Move the images to `src/commonMain/resources` so that the same flags are available on all platforms: +2. Move the images to the `composeApp/src/commonMain/composeResources/drawable` directory so that the same flags are available on all platforms: ![Compose Multiplatform resources project structure](compose-resources-project-structure.png){width=300} - > Each platform-specific source set can also have its own resources folder. This allows you to use different images on different - > platforms, if needed. - > - {type="tip"} - 3. Change the codebase to support images: ```kotlin - data class Country(val name: String, val zone: TimeZone, val image: String) - + @OptIn(ExperimentalResourceApi::class) + data class Country(val name: String, val zone: TimeZone, val image: DrawableResource) + fun currentTimeAt(location: String, zone: TimeZone): String { fun LocalTime.formatted() = "$hour:$minute:$second" - + val time = Clock.System.now() val localTime = time.toLocalDateTime(zone).time - + return "The time in $location is ${localTime.formatted()}" } - - fun countries() = listOf( - Country("Japan", TimeZone.of("Asia/Tokyo"), "jp.png"), - Country("France", TimeZone.of("Europe/Paris"), "fr.png"), - Country("Mexico", TimeZone.of("America/Mexico_City"), "mx.png"), - Country("Indonesia", TimeZone.of("Asia/Jakarta"), "id.png"), - Country("Egypt", TimeZone.of("Africa/Cairo"), "eg.png") + + @OptIn(ExperimentalResourceApi::class) + val defaultCountries = listOf( + Country("Japan", TimeZone.of("Asia/Tokyo"), Res.drawable.jp), + Country("France", TimeZone.of("Europe/Paris"), Res.drawable.fr), + Country("Mexico", TimeZone.of("America/Mexico_City"), Res.drawable.mx), + Country("Indonesia", TimeZone.of("Asia/Jakarta"), Res.drawable.id), + Country("Egypt", TimeZone.of("Africa/Cairo"), Res.drawable.eg) ) - + @OptIn(ExperimentalResourceApi::class) @Composable - fun App(countries: List = countries()) { + fun App(countries: List = defaultCountries) { MaterialTheme { var showCountries by remember { mutableStateOf(false) } var timeAtLocation by remember { mutableStateOf("No location selected") } - + Column(modifier = Modifier.padding(20.dp)) { Text( timeAtLocation, @@ -383,7 +388,7 @@ code to load and display them: } } } - + Button(modifier = Modifier.padding(start = 20.dp, top = 10.dp), onClick = { showCountries = !showCountries }) { Text("Select Location") @@ -405,6 +410,10 @@ code to load and display them: ![The country flags in the Compose Multiplatform app on desktop](first-compose-project-on-desktop-9.png){width=350} +> You can find this state of the project in our [GitHub repository](https://github.com/kotlin-hands-on/get-started-with-cm/tree/main/ComposeDemoStage4). +> +{type="tip"} + ## What's next We encourage you to explore multiplatform development further and try out more projects: diff --git a/topics/compose/compose-images-resources.md b/topics/compose/compose-images-resources.md index f299bcd0..4fa607ad 100644 --- a/topics/compose/compose-images-resources.md +++ b/topics/compose/compose-images-resources.md @@ -1,11 +1,7 @@ [//]: # (title: Images and resources) -> This page describes the new revamped version of the resource library available since Compose Multiplatform 1.6.0-beta01. -> -{type="note"} - -Compose Multiplatform provides a special library and a Gradle plugin support for accessing resources in common code -across all supported platforms. Resources are static content, such as images, fonts, and strings, that you can use from +Compose Multiplatform provides a special library and Gradle plugin support for accessing resources in common code +across all supported platforms. Resources are static content, such as images, fonts, and strings, which you can use from your application. > The library is [Experimental](supported-platforms.md#core-kotlin-multiplatform-technology-stability-levels). @@ -15,8 +11,8 @@ your application. When working with resources in Compose Multiplatform, consider the current conditions: -* Almost all resources are read synchronously in the caller thread. The only exceptions are raw files, - plus all the resources on the JS platform which are read asynchronously. +* Almost all resources are read synchronously in the caller thread. The only exceptions are raw files + and all of the resources on the JS platform that are read asynchronously. * Reading big raw files, like long videos, as a stream is not supported yet. Use separate files on the user device and read them with the file system API, for example, the [kotlinx-io](https://github.com/Kotlin/kotlinx-io) library. * Multimodule projects are not supported yet. The JetBrains team is working on adding this functionality in future releases. @@ -49,7 +45,7 @@ To access resources in your multiplatform projects: ![Compose resources project structure](compose-resources-structure.png){width=250} -3. Organize the `composeResources` directory structure following these rules: +3. Organize the `composeResources` directory structure according to these rules: * Images should be in the `drawable` directory. * Fonts should be in the `font` directory. @@ -59,20 +55,20 @@ To access resources in your multiplatform projects: ## Qualifiers Sometimes, the same resource should be presented in different ways depending on the environment, such as locale, -screen density, or an interface theme. For example, you might need to localize texts for different languages or adjust +screen density, or interface theme. For example, you might need to localize texts for different languages or adjust images for the dark theme. For that, the library provides special qualifiers. All resource types (except for raw files in the `files` directory) support qualifiers. Apply qualifiers to directory -names using a dash: +names using a hyphen: ![Qualifiers in compose resources](compose-resources-qualifiers.png){width=250} -The library supports (in the order of priority) [language](#language-and-regional-qualifiers), [theme](#theme-qualifier), -and [density](#density-qualifier) qualifiers. +The library supports (in the order of priority) the following qualifiers: [language](#language-and-regional-qualifiers), +[theme](#theme-qualifier), and [density](#density-qualifier). * Different types of qualifiers can be applied together. For example, "drawable-en-rUS-mdpi-dark" is an image for the English language in the United States region, suitable for 160 DPI screens in the dark theme. -* If a resource with the requested qualifier doesn't exist, a default resource without a qualifier is used instead. +* If a resource with the requested qualifier doesn't exist, the default resource without a qualifier is used instead. ### Language and regional qualifiers @@ -100,16 +96,16 @@ You can use the following density qualifiers: * "xxhdpi" − 480 DPI, 3x density * "xxxhdpi" − 640dpi, 4x density -The resource is selected depending on a screen density defined in the system. +The resource is selected depending on the screen density defined in the system. ## Resource usage -After importing a project, a special `Res` class that provides access to resources is generated. +After importing a project, a special `Res` class is generated which provides access to resources. To manually generate the `Res` class, run the `generateComposeResClass` Gradle task. ### Images -You can access drawable resources as simple images, rasterized images, and XML vectors: +You can access drawable resources as simple images, rasterized images, or XML vectors: * To access drawable resources as `Painter` images, use the `painterResource()` function: @@ -117,11 +113,11 @@ You can access drawable resources as simple images, rasterized images, and XML v @Composable fun painterResource(resource: DrawableResource): Painter {...} ``` - + The `painterResource()` function takes a resource path and returns a `Painter` value. The function works synchronously - on all targets, except for web. For the web target, it returns an empty `Painter` for the first recomposition that is + on all targets except for web. For the web target, it returns an empty `Painter` for the first recomposition that is replaced with the loaded image in subsequent recompositions. - + * `painterResource()` loads either a `BitmapPainter` for rasterized image formats, such as `.png`, `.jpg`, `.bmp`, `.webp`, or a `VectorPainter` for the Android XML vector drawable format. * XML vector drawables have the same format as [Android](https://developer.android.com/reference/android/graphics/drawable/VectorDrawable), @@ -163,7 +159,7 @@ Store all string resources in the `strings.xml` file in the `composeResources/va A static accessor is generated for each item in the `strings.xml` file. -To get string resources as a `String`: +To get string resources as a `String`, use the following code: @@ -186,7 +182,7 @@ Text(stringResource(Res.string.app_name)) ``` - + ```kotlin suspend fun getString(resource: StringResource): String @@ -208,8 +204,8 @@ coroutineScope.launch { You can use special symbols in string resources: - -* `\n` — for a new line + +* `\n` — for a new line * `\t` — for a tab symbol * `\uXXXX` — for a specific Unicode character @@ -220,7 +216,7 @@ Currently, arguments have basic support in string resources: ```XML - Hello, %1$s! You have %2$d new messages. + Hello, %1$s! You have %2$d new messages. ``` @@ -262,7 +258,7 @@ suspend fun readBytes(path: String): ByteArray You can place raw files in the `composeResources/files` directory and create any hierarchy inside it. -For example, to access raw files: +For example, to access raw files, use the following code: @@ -278,7 +274,7 @@ Text(bytes.decodeToString()) ``` - + ```kotlin coroutineScope.launch { @@ -291,7 +287,7 @@ coroutineScope.launch { ### Remote files -Only files that are a part of the application are considered as resources. +Only files that are part of the application are considered resources. You can also load remote files from the internet using their URL. To load remote files, use special libraries: diff --git a/topics/tools/fleet.md b/topics/tools/fleet.md index 9485ff3d..15aa8b49 100644 --- a/topics/tools/fleet.md +++ b/topics/tools/fleet.md @@ -192,7 +192,7 @@ Since the `ContentView.swift` file is already open, you can explore Fleet suppor } AnimatedVisibility(showContent) { Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - Image(painterResource("compose-multiplatform.xml"), null) + Image(painterResource(Res.drawable.compose_multiplatform), null) Text("Compose: $greeting") } }