refactor(commons): migrate from Moko to Compose Multiplatform Resources

Remove Moko Resources dependency that blocks AGP 9.0 migration.
Migrate to built-in Compose Multiplatform Resources which is compatible
with the new KMP Android plugin.

- Remove moko-resources plugin and libraries from commons module
- Move strings.xml from moko-resources/ to composeResources/values/
- Update imports from SharedRes.strings.* to Res.string.*
- Add compose.components.resources dependency to desktopApp
- Configure resource package as com.vitorpamplona.amethyst.commons.resources

Closes #1675

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-01-25 13:29:55 +02:00
parent 8ebba994e8
commit 71e68dc35b
10 changed files with 68 additions and 47 deletions
+6 -7
View File
@@ -5,7 +5,6 @@ plugins {
alias(libs.plugins.androidLibrary)
alias(libs.plugins.jetbrainsComposeCompiler)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.mokoResources)
}
android {
@@ -81,9 +80,8 @@ kotlin {
// Immutable collections
api(libs.kotlinx.collections.immutable)
// Moko Resources for KMP string resources
api(libs.moko.resources)
api(libs.moko.resources.compose)
// Compose Multiplatform Resources
implementation(compose.components.resources)
}
}
@@ -141,7 +139,8 @@ kotlin {
}
}
multiplatformResources {
resourcesPackage.set("com.vitorpamplona.amethyst.commons")
resourcesClassName.set("SharedRes")
compose.resources {
publicResClass = true
packageOfResClass = "com.vitorpamplona.amethyst.commons.resources"
generateResClass = always
}
@@ -34,8 +34,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.SharedRes
import dev.icerock.moko.resources.compose.stringResource
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.action_refresh
import com.vitorpamplona.amethyst.commons.resources.action_try_again
import com.vitorpamplona.amethyst.commons.resources.error_loading_feed
import com.vitorpamplona.amethyst.commons.resources.feed_empty
import org.jetbrains.compose.resources.stringResource
/**
* A centered loading state with a progress indicator and message.
@@ -71,7 +75,7 @@ fun EmptyState(
onRefresh: (() -> Unit)? = null,
refreshLabel: String? = null,
) {
val actualRefreshLabel = refreshLabel ?: stringResource(SharedRes.strings.action_refresh)
val actualRefreshLabel = refreshLabel ?: stringResource(Res.string.action_refresh)
Column(
modifier = modifier.fillMaxSize(),
@@ -110,7 +114,7 @@ fun ErrorState(
onRetry: (() -> Unit)? = null,
retryLabel: String? = null,
) {
val actualRetryLabel = retryLabel ?: stringResource(SharedRes.strings.action_try_again)
val actualRetryLabel = retryLabel ?: stringResource(Res.string.action_try_again)
Column(
modifier = modifier.fillMaxSize(),
@@ -140,7 +144,7 @@ fun FeedEmptyState(
title: String? = null,
onRefresh: (() -> Unit)? = null,
) {
val actualTitle = title ?: stringResource(SharedRes.strings.feed_empty)
val actualTitle = title ?: stringResource(Res.string.feed_empty)
EmptyState(
title = actualTitle,
@@ -158,7 +162,7 @@ fun FeedErrorState(
modifier: Modifier = Modifier,
onRetry: (() -> Unit)? = null,
) {
val formattedMessage = stringResource(SharedRes.strings.error_loading_feed).format(errorMessage)
val formattedMessage = stringResource(Res.string.error_loading_feed).format(errorMessage)
ErrorState(
message = formattedMessage,
@@ -28,8 +28,14 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.SharedRes
import dev.icerock.moko.resources.compose.stringResource
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.screen_messages_description
import com.vitorpamplona.amethyst.commons.resources.screen_messages_title
import com.vitorpamplona.amethyst.commons.resources.screen_notifications_description
import com.vitorpamplona.amethyst.commons.resources.screen_notifications_title
import com.vitorpamplona.amethyst.commons.resources.screen_search_description
import com.vitorpamplona.amethyst.commons.resources.screen_search_title
import org.jetbrains.compose.resources.stringResource
/**
* Generic placeholder screen with title and description.
@@ -60,8 +66,8 @@ fun PlaceholderScreen(
@Composable
fun SearchPlaceholder(modifier: Modifier = Modifier) {
PlaceholderScreen(
title = stringResource(SharedRes.strings.screen_search_title),
description = stringResource(SharedRes.strings.screen_search_description),
title = stringResource(Res.string.screen_search_title),
description = stringResource(Res.string.screen_search_description),
modifier = modifier,
)
}
@@ -72,8 +78,8 @@ fun SearchPlaceholder(modifier: Modifier = Modifier) {
@Composable
fun MessagesPlaceholder(modifier: Modifier = Modifier) {
PlaceholderScreen(
title = stringResource(SharedRes.strings.screen_messages_title),
description = stringResource(SharedRes.strings.screen_messages_description),
title = stringResource(Res.string.screen_messages_title),
description = stringResource(Res.string.screen_messages_description),
modifier = modifier,
)
}
@@ -84,8 +90,8 @@ fun MessagesPlaceholder(modifier: Modifier = Modifier) {
@Composable
fun NotificationsPlaceholder(modifier: Modifier = Modifier) {
PlaceholderScreen(
title = stringResource(SharedRes.strings.screen_notifications_title),
description = stringResource(SharedRes.strings.screen_notifications_description),
title = stringResource(Res.string.screen_notifications_title),
description = stringResource(Res.string.screen_notifications_description),
modifier = modifier,
)
}
+1
View File
@@ -25,6 +25,7 @@ dependencies {
implementation(compose.desktop.currentOs)
implementation(compose.material3)
implementation(compose.materialIconsExtended)
implementation(compose.components.resources)
// Quartz Nostr library (will use JVM target)
implementation(project(":quartz"))
@@ -37,14 +37,16 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.SharedRes
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.login_subtitle_desktop
import com.vitorpamplona.amethyst.commons.resources.login_title
import com.vitorpamplona.amethyst.desktop.account.AccountManager
import com.vitorpamplona.amethyst.desktop.account.AccountState
import com.vitorpamplona.amethyst.desktop.ui.auth.LoginCard
import com.vitorpamplona.amethyst.desktop.ui.auth.NewKeyWarningCard
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
@Composable
fun LoginScreen(
@@ -61,7 +63,7 @@ fun LoginScreen(
verticalArrangement = Arrangement.Center,
) {
Text(
stringResource(SharedRes.strings.login_title),
stringResource(Res.string.login_title),
style = MaterialTheme.typography.headlineLarge,
color = MaterialTheme.colorScheme.onBackground,
)
@@ -69,7 +71,7 @@ fun LoginScreen(
Spacer(Modifier.height(8.dp))
Text(
stringResource(SharedRes.strings.login_subtitle_desktop),
stringResource(Res.string.login_subtitle_desktop),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -42,8 +42,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.SharedRes
import dev.icerock.moko.resources.compose.stringResource
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.login_hide_key
import com.vitorpamplona.amethyst.commons.resources.login_key_label
import com.vitorpamplona.amethyst.commons.resources.login_key_placeholder
import com.vitorpamplona.amethyst.commons.resources.login_show_key
import org.jetbrains.compose.resources.stringResource
/**
* Text field for entering Nostr keys (nsec or npub) with visibility toggle.
@@ -53,8 +57,8 @@ fun KeyInputField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
label: String = stringResource(SharedRes.strings.login_key_label),
placeholder: String = stringResource(SharedRes.strings.login_key_placeholder),
label: String = stringResource(Res.string.login_key_label),
placeholder: String = stringResource(Res.string.login_key_placeholder),
errorMessage: String? = null,
) {
var showKey by remember { mutableStateOf(false) }
@@ -76,7 +80,7 @@ fun KeyInputField(
IconButton(onClick = { showKey = !showKey }) {
Icon(
if (showKey) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = if (showKey) stringResource(SharedRes.strings.login_hide_key) else stringResource(SharedRes.strings.login_show_key),
contentDescription = if (showKey) stringResource(Res.string.login_hide_key) else stringResource(Res.string.login_show_key),
)
}
},
@@ -44,8 +44,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.SharedRes
import dev.icerock.moko.resources.compose.stringResource
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.login_button
import com.vitorpamplona.amethyst.commons.resources.login_card_subtitle
import com.vitorpamplona.amethyst.commons.resources.login_card_title
import com.vitorpamplona.amethyst.commons.resources.login_generate_button
import org.jetbrains.compose.resources.stringResource
/**
* Login card with Nostr key input field and action buttons.
@@ -63,8 +67,8 @@ fun LoginCard(
onGenerateNew: () -> Unit,
modifier: Modifier = Modifier,
cardWidth: Dp = 400.dp,
title: String = stringResource(SharedRes.strings.login_card_title),
subtitle: String = stringResource(SharedRes.strings.login_card_subtitle),
title: String = stringResource(Res.string.login_card_title),
subtitle: String = stringResource(Res.string.login_card_subtitle),
) {
var keyInput by remember { mutableStateOf("") }
var errorMessage by remember { mutableStateOf<String?>(null) }
@@ -121,14 +125,14 @@ fun LoginCard(
modifier = Modifier.weight(1f),
enabled = keyInput.isNotBlank(),
) {
Text(stringResource(SharedRes.strings.login_button))
Text(stringResource(Res.string.login_button))
}
OutlinedButton(
onClick = onGenerateNew,
modifier = Modifier.weight(1f),
) {
Text(stringResource(SharedRes.strings.login_generate_button))
Text(stringResource(Res.string.login_generate_button))
}
}
}
@@ -37,8 +37,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.SharedRes
import dev.icerock.moko.resources.compose.stringResource
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.new_key_continue_button
import com.vitorpamplona.amethyst.commons.resources.new_key_public_label
import com.vitorpamplona.amethyst.commons.resources.new_key_secret_label
import com.vitorpamplona.amethyst.commons.resources.new_key_warning_message
import com.vitorpamplona.amethyst.commons.resources.new_key_warning_title
import org.jetbrains.compose.resources.stringResource
/**
* Warning card displayed after generating a new Nostr key pair.
@@ -69,7 +74,7 @@ fun NewKeyWarningCard(
modifier = Modifier.padding(24.dp),
) {
Text(
stringResource(SharedRes.strings.new_key_warning_title),
stringResource(Res.string.new_key_warning_title),
style = MaterialTheme.typography.titleMedium,
color = Color.Red,
)
@@ -77,7 +82,7 @@ fun NewKeyWarningCard(
Spacer(Modifier.height(16.dp))
Text(
stringResource(SharedRes.strings.new_key_warning_message),
stringResource(Res.string.new_key_warning_message),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
)
@@ -85,7 +90,7 @@ fun NewKeyWarningCard(
Spacer(Modifier.height(16.dp))
Text(
stringResource(SharedRes.strings.new_key_public_label),
stringResource(Res.string.new_key_public_label),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -95,7 +100,7 @@ fun NewKeyWarningCard(
nsec?.let { secretKey ->
Text(
stringResource(SharedRes.strings.new_key_secret_label),
stringResource(Res.string.new_key_secret_label),
style = MaterialTheme.typography.labelMedium,
color = Color.Red,
)
@@ -108,7 +113,7 @@ fun NewKeyWarningCard(
onClick = onContinue,
modifier = Modifier.fillMaxWidth(),
) {
Text(stringResource(SharedRes.strings.new_key_continue_button))
Text(stringResource(Res.string.new_key_continue_button))
}
}
}
-4
View File
@@ -40,7 +40,6 @@ lightcompressor-enhanced = "1.6.0"
markdown = "f92ef49c9d"
media3 = "1.9.0"
mockk = "1.14.7"
mokoResources = "0.25.2"
kotlinx-coroutines-test = "1.10.2"
navigationCompose = "2.9.6"
okhttp = "5.3.2"
@@ -137,8 +136,6 @@ markdown-ui = { group = "com.github.vitorpamplona.compose-richtext", name = "ric
markdown-ui-material3 = { group = "com.github.vitorpamplona.compose-richtext", name = "richtext-ui-material3", version.ref = "markdown" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" }
moko-resources = { group = "dev.icerock.moko", name = "resources", version.ref = "mokoResources" }
moko-resources-compose = { group = "dev.icerock.moko", name = "resources-compose", version.ref = "mokoResources" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx-coroutines-test"}
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttpCoroutines = { group = "com.squareup.okhttp3", name = "okhttp-coroutines", version.ref = "okhttp" }
@@ -178,4 +175,3 @@ androidKotlinMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.lib
vanniktech-mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
stability-analyzer = { id = "com.github.skydoves.compose.stability.analyzer", version = "0.6.6" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
mokoResources = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "mokoResources" }