From c0f692865a3e44007ab39ab602b1b5bd3ac9b4f1 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Fri, 21 Jul 2023 11:19:01 +0300 Subject: [PATCH] For #305 - Use Compose Multiplatform to show the UI from :shared on Android Added moko-resources as the community preferred approach to using strings in similar scenarios. Added the materialIconsExtended dependency for :shared instead of reusing the vector images shown previously from :app since those vectors are not in a format moko-resources supports at this moment (though it support SVGs). While this is a big package R8 will ensure unused resources are stripped from the generated builds at compile time. --- .../touchlab/kampkit/android/MainActivity.kt | 2 +- .../main/res/drawable/ic_favorite_24px.xml | 9 ----- .../res/drawable/ic_favorite_border_24px.xml | 9 ----- build.gradle.kts | 3 ++ gradle/libs.versions.toml | 8 ++++ shared/build.gradle.kts | 16 ++++++++ .../co/touchlab/kampkit/utils/Strings.kt | 24 ++++++++++++ .../co/touchlab/kampkit}/ui/Composables.kt | 38 +++++++------------ .../co/touchlab/kampkit/utils/Strings.kt | 7 ++++ .../commonMain/resources/MR/base/strings.xml | 7 ++++ .../co/touchlab/kampkit/utils/Strings.kt | 13 +++++++ 11 files changed, 92 insertions(+), 44 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_favorite_24px.xml delete mode 100644 app/src/main/res/drawable/ic_favorite_border_24px.xml create mode 100644 shared/src/androidMain/kotlin/co/touchlab/kampkit/utils/Strings.kt rename {app/src/main/kotlin/co/touchlab/kampkit/android => shared/src/commonMain/kotlin/co/touchlab/kampkit}/ui/Composables.kt (83%) create mode 100644 shared/src/commonMain/kotlin/co/touchlab/kampkit/utils/Strings.kt create mode 100644 shared/src/commonMain/resources/MR/base/strings.xml create mode 100644 shared/src/iosMain/kotlin/co/touchlab/kampkit/utils/Strings.kt diff --git a/app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt b/app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt index b9f8402d..91a9d0fd 100644 --- a/app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt +++ b/app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt @@ -4,7 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.isSystemInDarkTheme -import co.touchlab.kampkit.android.ui.MainScreen +import co.touchlab.kampkit.ui.MainScreen import co.touchlab.kampkit.ui.theme.KaMPKitTheme import co.touchlab.kampkit.injectLogger import co.touchlab.kampkit.models.BreedViewModel diff --git a/app/src/main/res/drawable/ic_favorite_24px.xml b/app/src/main/res/drawable/ic_favorite_24px.xml deleted file mode 100644 index ce351f43..00000000 --- a/app/src/main/res/drawable/ic_favorite_24px.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_favorite_border_24px.xml b/app/src/main/res/drawable/ic_favorite_border_24px.xml deleted file mode 100644 index e6646709..00000000 --- a/app/src/main/res/drawable/ic_favorite_border_24px.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/build.gradle.kts b/build.gradle.kts index 673ba272..b71bca0a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.gradleVersions) alias(libs.plugins.ktlint) apply false + alias(libs.plugins.moko.resources) apply false kotlin("multiplatform") version libs.versions.kotlin.get() apply false kotlin("plugin.serialization") version libs.versions.kotlin.get() apply false @@ -18,6 +19,7 @@ allprojects { mavenCentral() maven("https://androidx.dev/storage/compose-compiler/repository/") maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/") + gradlePluginPortal() // for moko } } @@ -25,6 +27,7 @@ subprojects { // TODO libs doesn't resolve if we do this // apply(plugin = libs.plugins.ktlint.get().pluginId) apply(plugin = "org.jlleitschuh.gradle.ktlint") + apply(plugin = "dev.icerock.mobile.multiplatform-resources") configure { enableExperimentalRules.set(true) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d50d86d6..695c9c94 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,6 +36,9 @@ multiplatformSettings = "1.0.0" turbine = "1.0.0" sqlDelight = "2.0.0-rc01" +moko-resources = "0.23.0" +moko-graphics = "0.9.0" + [libraries] android-desugaring = { module = "com.android.tools:desugar_jdk_libs", version.ref = "android-desugaring" } androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } @@ -87,9 +90,14 @@ touchlab-kermit-simple = { module = "co.touchlab:kermit-simple", version.ref = " turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +moko-resources = { module = "dev.icerock.moko:resources", version.ref = "moko-resources" } +moko-resources-compose = { module = "dev.icerock.moko:resources-compose", version.ref = "moko-resources" } +moko-graphics = { module = "dev.icerock.moko:graphics", version.ref = "moko-graphics" } + [plugins] ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-gradle" } gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradle-versions" } +moko-resources = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko-resources" } [bundles] app-ui = [ diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index d6ee7bf7..6447a7c1 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -49,7 +49,9 @@ kotlin { dependencies { implementation(compose.runtime) implementation(compose.foundation) + implementation(compose.material) // for PullRefreshIndicator implementation(compose.material3) + implementation(compose.materialIconsExtended) implementation(libs.koin.core) implementation(libs.coroutines.core) implementation(libs.sqlDelight.coroutinesExt) @@ -57,6 +59,8 @@ kotlin { implementation(libs.multiplatformSettings.common) implementation(libs.kotlinx.dateTime) api(libs.touchlab.kermit) + api(libs.moko.resources) + api(libs.moko.resources.compose) } } val commonTest by getting { @@ -65,6 +69,8 @@ kotlin { } } val androidMain by getting { + // Below line adds a temporary workaround for https://github.com/icerockdev/moko-resources/issues/531 + kotlin.srcDirs("build/generated/moko/androidMain/src") dependencies { implementation(libs.compose.activity) implementation(libs.androidx.lifecycle.viewmodel) @@ -78,6 +84,7 @@ kotlin { } } val iosMain by getting { + resources.srcDirs("build/generated/moko/iosMain/src") dependencies { implementation(libs.sqlDelight.native) implementation(libs.ktor.client.ios) @@ -86,6 +93,7 @@ kotlin { } val iosTest by getting val iosSimulatorArm64Main by getting { + resources.srcDirs("build/generated/moko/iosSimulatorArm64Main/src") dependsOn(iosMain) } val iosSimulatorArm64Test by getting { @@ -105,6 +113,8 @@ kotlin { isStatic = false // SwiftUI preview requires dynamic framework linkerOpts("-lsqlite3") export(libs.touchlab.kermit.simple) + export(libs.moko.resources) + export(libs.moko.graphics) } ios.deploymentTarget = "14.0" podfile = project.file("../ios/Podfile") @@ -123,3 +133,9 @@ sqldelight { packageName.set("co.touchlab.kampkit.db") } } + +multiplatformResources { + multiplatformResourcesPackage = "co.touchlab.kampkit" // required + // multiplatformResourcesSourceSet = "iosSimulatorArm64Main" + multiplatformResourcesClassName = "MR" // optional, default MR +} diff --git a/shared/src/androidMain/kotlin/co/touchlab/kampkit/utils/Strings.kt b/shared/src/androidMain/kotlin/co/touchlab/kampkit/utils/Strings.kt new file mode 100644 index 00000000..8d69d97d --- /dev/null +++ b/shared/src/androidMain/kotlin/co/touchlab/kampkit/utils/Strings.kt @@ -0,0 +1,24 @@ +package co.touchlab.kampkit.utils + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.desc.Resource +import dev.icerock.moko.resources.desc.StringDesc +import dev.icerock.moko.resources.format + +actual class Strings(private val context: Context) { + actual fun get(id: StringResource, args: List): String { + return when (args.isEmpty()) { + true -> StringDesc.Resource(id).toString(context) + false -> id.format(*args.toTypedArray()).toString(context) + } + } +} + +/** + * Get a string existing in the shared module. + */ +@Composable +fun getString(id: StringResource, vararg args: Any) = Strings(LocalContext.current).get(id, args.toList()) diff --git a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/Composables.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/Composables.kt similarity index 83% rename from app/src/main/kotlin/co/touchlab/kampkit/android/ui/Composables.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/Composables.kt index 002c2624..7f03c87b 100644 --- a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/Composables.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/Composables.kt @@ -1,4 +1,4 @@ -package co.touchlab.kampkit.android.ui +package co.touchlab.kampkit.ui import androidx.compose.animation.Crossfade import androidx.compose.animation.core.FastOutSlowInEasing @@ -20,25 +20,26 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import co.touchlab.kampkit.android.R +import co.touchlab.kampkit.MR import co.touchlab.kampkit.db.Breed import co.touchlab.kampkit.models.BreedViewModel import co.touchlab.kampkit.models.BreedViewState import co.touchlab.kermit.Logger +import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.launch @Composable @@ -46,7 +47,7 @@ fun MainScreen( viewModel: BreedViewModel, log: Logger ) { - val dogsState by viewModel.breedState.collectAsStateWithLifecycle() + val dogsState by viewModel.breedState.collectAsState() val scope = rememberCoroutineScope() MainScreenContent( @@ -106,7 +107,7 @@ fun Empty() { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text(stringResource(R.string.empty_breeds)) + Text(text = stringResource(MR.strings.empty_breeds)) } } @@ -166,27 +167,14 @@ fun FavoriteIcon(breed: Breed) { ) { fav -> if (fav) { Image( - painter = painterResource(id = R.drawable.ic_favorite_border_24px), - contentDescription = stringResource(R.string.favorite_breed, breed.name) + imageVector = Icons.Default.FavoriteBorder, + contentDescription = stringResource(MR.strings.favorite_breed, breed.name) ) } else { Image( - painter = painterResource(id = R.drawable.ic_favorite_24px), - contentDescription = stringResource(R.string.unfavorite_breed, breed.name) + imageVector = Icons.Default.Favorite, + contentDescription = stringResource(MR.strings.unfavorite_breed, breed.name) ) } } } - -@Preview -@Composable -fun MainScreenContentPreview_Success() { - MainScreenContent( - dogsState = BreedViewState( - breeds = listOf( - Breed(0, "appenzeller", false), - Breed(1, "australian", true) - ) - ) - ) -} diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/utils/Strings.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/utils/Strings.kt new file mode 100644 index 00000000..ed868d2f --- /dev/null +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/utils/Strings.kt @@ -0,0 +1,7 @@ +package co.touchlab.kampkit.utils + +import dev.icerock.moko.resources.StringResource + +expect class Strings { + fun get(id: StringResource, args: List): String +} diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml new file mode 100644 index 00000000..5ac1e5a9 --- /dev/null +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -0,0 +1,7 @@ + + + KaMP Kit + Favorite %1$s + Unfavorite %1$s + Sorry, no doggos found + diff --git a/shared/src/iosMain/kotlin/co/touchlab/kampkit/utils/Strings.kt b/shared/src/iosMain/kotlin/co/touchlab/kampkit/utils/Strings.kt new file mode 100644 index 00000000..1502f5cd --- /dev/null +++ b/shared/src/iosMain/kotlin/co/touchlab/kampkit/utils/Strings.kt @@ -0,0 +1,13 @@ +package co.touchlab.kampkit.utils + +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.desc.Resource +import dev.icerock.moko.resources.desc.StringDesc +import dev.icerock.moko.resources.format + +actual class Strings { + actual fun get(id: StringResource, args: List) = when (args.isEmpty()) { + true -> StringDesc.Resource(id).localized() + false -> id.format(*args.toTypedArray()).localized() + } +}