From a9141763e19d4e34c4f466c3297e53181a03f9f1 Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Wed, 7 Jan 2026 07:12:00 +0200 Subject: [PATCH] previous PR fixes --- commons/build.gradle.kts | 10 ++++ .../commons/ui/components/LoadingState.kt | 24 ++++++--- .../commons/ui/screens/PlaceholderScreens.kt | 14 ++--- .../moko-resources/base/strings.xml | 53 +++++++++++++++++++ .../amethyst/commons/ui/auth/KeyInputField.kt | 26 +++++++-- .../amethyst/commons/ui/auth/LoginCard.kt | 20 +++++-- .../commons/ui/auth/NewKeyWarningCard.kt | 24 ++++++--- .../amethyst/desktop/ui/LoginScreen.kt | 6 ++- gradle/libs.versions.toml | 4 ++ 9 files changed, 153 insertions(+), 28 deletions(-) create mode 100644 commons/src/commonMain/moko-resources/base/strings.xml diff --git a/commons/build.gradle.kts b/commons/build.gradle.kts index d93e36172..427ed28b4 100644 --- a/commons/build.gradle.kts +++ b/commons/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.jetbrainsComposeCompiler) alias(libs.plugins.composeMultiplatform) + alias(libs.plugins.mokoResources) } android { @@ -75,6 +76,10 @@ kotlin { // Immutable collections api(libs.kotlinx.collections.immutable) + + // Moko Resources for KMP string resources + api(libs.moko.resources) + api(libs.moko.resources.compose) } } @@ -124,3 +129,8 @@ kotlin { } } } + +multiplatformResources { + resourcesPackage.set("com.vitorpamplona.amethyst.commons") + resourcesClassName.set("SharedRes") +} diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/LoadingState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/LoadingState.kt index b549466dd..2ed9eb625 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/LoadingState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/LoadingState.kt @@ -34,6 +34,8 @@ 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 /** * A centered loading state with a progress indicator and message. @@ -67,8 +69,10 @@ fun EmptyState( modifier: Modifier = Modifier, description: String? = null, onRefresh: (() -> Unit)? = null, - refreshLabel: String = "Refresh", + refreshLabel: String? = null, ) { + val actualRefreshLabel = refreshLabel ?: stringResource(SharedRes.strings.action_refresh) + Column( modifier = modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, @@ -90,7 +94,7 @@ fun EmptyState( if (onRefresh != null) { Spacer(Modifier.height(16.dp)) OutlinedButton(onClick = onRefresh) { - Text(refreshLabel) + Text(actualRefreshLabel) } } } @@ -104,8 +108,10 @@ fun ErrorState( message: String, modifier: Modifier = Modifier, onRetry: (() -> Unit)? = null, - retryLabel: String = "Try Again", + retryLabel: String? = null, ) { + val actualRetryLabel = retryLabel ?: stringResource(SharedRes.strings.action_try_again) + Column( modifier = modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, @@ -119,7 +125,7 @@ fun ErrorState( if (onRetry != null) { Spacer(Modifier.height(16.dp)) Button(onClick = onRetry) { - Text(retryLabel) + Text(actualRetryLabel) } } } @@ -131,11 +137,13 @@ fun ErrorState( @Composable fun FeedEmptyState( modifier: Modifier = Modifier, - title: String = "Feed is empty", + title: String? = null, onRefresh: (() -> Unit)? = null, ) { + val actualTitle = title ?: stringResource(SharedRes.strings.feed_empty) + EmptyState( - title = title, + title = actualTitle, modifier = modifier, onRefresh = onRefresh, ) @@ -150,8 +158,10 @@ fun FeedErrorState( modifier: Modifier = Modifier, onRetry: (() -> Unit)? = null, ) { + val formattedMessage = stringResource(SharedRes.strings.error_loading_feed).format(errorMessage) + ErrorState( - message = "Error loading feed: $errorMessage", + message = formattedMessage, modifier = modifier, onRetry = onRetry, ) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screens/PlaceholderScreens.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screens/PlaceholderScreens.kt index 4048a5c02..3a30ba107 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screens/PlaceholderScreens.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screens/PlaceholderScreens.kt @@ -28,6 +28,8 @@ 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 /** * Generic placeholder screen with title and description. @@ -58,8 +60,8 @@ fun PlaceholderScreen( @Composable fun SearchPlaceholder(modifier: Modifier = Modifier) { PlaceholderScreen( - title = "Search", - description = "Search for users, notes, and hashtags.", + title = stringResource(SharedRes.strings.screen_search_title), + description = stringResource(SharedRes.strings.screen_search_description), modifier = modifier, ) } @@ -70,8 +72,8 @@ fun SearchPlaceholder(modifier: Modifier = Modifier) { @Composable fun MessagesPlaceholder(modifier: Modifier = Modifier) { PlaceholderScreen( - title = "Messages", - description = "Your encrypted direct messages will appear here.", + title = stringResource(SharedRes.strings.screen_messages_title), + description = stringResource(SharedRes.strings.screen_messages_description), modifier = modifier, ) } @@ -82,8 +84,8 @@ fun MessagesPlaceholder(modifier: Modifier = Modifier) { @Composable fun NotificationsPlaceholder(modifier: Modifier = Modifier) { PlaceholderScreen( - title = "Notifications", - description = "Mentions, replies, and reactions will appear here.", + title = stringResource(SharedRes.strings.screen_notifications_title), + description = stringResource(SharedRes.strings.screen_notifications_description), modifier = modifier, ) } diff --git a/commons/src/commonMain/moko-resources/base/strings.xml b/commons/src/commonMain/moko-resources/base/strings.xml new file mode 100644 index 000000000..7403080ba --- /dev/null +++ b/commons/src/commonMain/moko-resources/base/strings.xml @@ -0,0 +1,53 @@ + + + + Welcome to Amethyst + Sign in to your Nostr account + A Nostr client for desktop + Login with your Nostr key + Use nsec for full access or npub for read-only + Login with Key + Login + Generate New Key + Generate New + Enter your private key (nsec) or public key (npub) + nsec or npub + nsec1... or npub1... + Show key + Hide key + + + IMPORTANT: Save your keys! + Your secret key (nsec) is the ONLY way to access your account. If you lose it, your account is gone forever. Save it somewhere safe! + Public Key (shareable): + Secret Key (NEVER share this!): + I've saved my keys, continue + + + Copy + Paste + Cancel + OK + Save + Delete + Share + + + Invalid key format. Please check and try again. + Network error. Please check your connection. + An error occurred. Please try again. + + + Refresh + Try Again + Feed is empty + Error loading feed: %s + + + Search + Search for users, notes, and hashtags. + Messages + Your encrypted direct messages will appear here. + Notifications + Mentions, replies, and reactions will appear here. + diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/KeyInputField.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/KeyInputField.kt index e27629c9f..21e0c4b94 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/KeyInputField.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/KeyInputField.kt @@ -41,6 +41,9 @@ 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 org.jetbrains.compose.ui.tooling.preview.Preview /** * Text field for entering Nostr keys (nsec or npub) with visibility toggle. @@ -50,8 +53,8 @@ fun KeyInputField( value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, - label: String = "nsec or npub", - placeholder: String = "nsec1... or npub1...", + label: String = stringResource(SharedRes.strings.login_key_label), + placeholder: String = stringResource(SharedRes.strings.login_key_placeholder), errorMessage: String? = null, ) { var showKey by remember { mutableStateOf(false) } @@ -73,7 +76,7 @@ fun KeyInputField( IconButton(onClick = { showKey = !showKey }) { Icon( if (showKey) Icons.Default.VisibilityOff else Icons.Default.Visibility, - contentDescription = if (showKey) "Hide key" else "Show key", + contentDescription = if (showKey) stringResource(SharedRes.strings.login_hide_key) else stringResource(SharedRes.strings.login_show_key), ) } }, @@ -108,3 +111,20 @@ fun SelectableKeyText( ) } } + +@Preview +@Composable +fun KeyInputFieldPreview() { + KeyInputField( + value = "nsec1example1234567890", + onValueChange = {}, + ) +} + +@Preview +@Composable +fun SelectableKeyTextPreview() { + SelectableKeyText( + key = "npub1example1234567890abcdefghijklmnopqrstuvwxyz1234567890", + ) +} diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/LoginCard.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/LoginCard.kt index a80912b91..f97e9179b 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/LoginCard.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/LoginCard.kt @@ -43,6 +43,9 @@ 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 org.jetbrains.compose.ui.tooling.preview.Preview /** * Login card with Nostr key input field and action buttons. @@ -60,8 +63,8 @@ fun LoginCard( onGenerateNew: () -> Unit, modifier: Modifier = Modifier, cardWidth: Dp = 400.dp, - title: String = "Login with your Nostr key", - subtitle: String = "Use nsec for full access or npub for read-only", + title: String = stringResource(SharedRes.strings.login_card_title), + subtitle: String = stringResource(SharedRes.strings.login_card_subtitle), ) { var keyInput by remember { mutableStateOf("") } var errorMessage by remember { mutableStateOf(null) } @@ -118,16 +121,25 @@ fun LoginCard( modifier = Modifier.weight(1f), enabled = keyInput.isNotBlank(), ) { - Text("Login") + Text(stringResource(SharedRes.strings.login_button)) } OutlinedButton( onClick = onGenerateNew, modifier = Modifier.weight(1f), ) { - Text("Generate New") + Text(stringResource(SharedRes.strings.login_generate_button)) } } } } } + +@Preview +@Composable +fun LoginCardPreview() { + LoginCard( + onLogin = { Result.success(Unit) }, + onGenerateNew = {}, + ) +} diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/NewKeyWarningCard.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/NewKeyWarningCard.kt index 4fa976aa9..9952ee4fb 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/NewKeyWarningCard.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/auth/NewKeyWarningCard.kt @@ -36,6 +36,9 @@ 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 org.jetbrains.compose.ui.tooling.preview.Preview /** * Warning card displayed after generating a new Nostr key pair. @@ -66,7 +69,7 @@ fun NewKeyWarningCard( modifier = Modifier.padding(24.dp), ) { Text( - "IMPORTANT: Save your keys!", + stringResource(SharedRes.strings.new_key_warning_title), style = MaterialTheme.typography.titleMedium, color = Color.Red, ) @@ -74,8 +77,7 @@ fun NewKeyWarningCard( Spacer(Modifier.height(16.dp)) Text( - "Your secret key (nsec) is the ONLY way to access your account. " + - "If you lose it, your account is gone forever. Save it somewhere safe!", + stringResource(SharedRes.strings.new_key_warning_message), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface, ) @@ -83,7 +85,7 @@ fun NewKeyWarningCard( Spacer(Modifier.height(16.dp)) Text( - "Public Key (shareable):", + stringResource(SharedRes.strings.new_key_public_label), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -93,7 +95,7 @@ fun NewKeyWarningCard( nsec?.let { secretKey -> Text( - "Secret Key (NEVER share this!):", + stringResource(SharedRes.strings.new_key_secret_label), style = MaterialTheme.typography.labelMedium, color = Color.Red, ) @@ -106,8 +108,18 @@ fun NewKeyWarningCard( onClick = onContinue, modifier = Modifier.fillMaxWidth(), ) { - Text("I've saved my keys, continue") + Text(stringResource(SharedRes.strings.new_key_continue_button)) } } } } + +@Preview +@Composable +fun NewKeyWarningCardPreview() { + NewKeyWarningCard( + npub = "npub1example1234567890abcdefghijklmnopqrstuvwxyz", + nsec = "nsec1example1234567890abcdefghijklmnopqrstuvwxyz", + onContinue = {}, + ) +} diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/LoginScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/LoginScreen.kt index f194564ae..be84b6375 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/LoginScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/LoginScreen.kt @@ -37,10 +37,12 @@ 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.account.AccountManager import com.vitorpamplona.amethyst.commons.account.AccountState import com.vitorpamplona.amethyst.commons.ui.auth.LoginCard import com.vitorpamplona.amethyst.commons.ui.auth.NewKeyWarningCard +import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -59,7 +61,7 @@ fun LoginScreen( verticalArrangement = Arrangement.Center, ) { Text( - "Welcome to Amethyst", + stringResource(SharedRes.strings.login_title), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onBackground, ) @@ -67,7 +69,7 @@ fun LoginScreen( Spacer(Modifier.height(8.dp)) Text( - "A Nostr client for desktop", + stringResource(SharedRes.strings.login_subtitle_desktop), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0918278b9..5717cae99 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,7 @@ 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" @@ -135,6 +136,8 @@ 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" } @@ -173,3 +176,4 @@ 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.1" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } +mokoResources = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "mokoResources" }