previous PR fixes

This commit is contained in:
nrobi144
2026-01-07 07:12:00 +02:00
parent fd83560852
commit a9141763e1
9 changed files with 153 additions and 28 deletions
+10
View File
@@ -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")
}
@@ -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,
)
@@ -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,
)
}
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Login & Auth -->
<string name="login_title">Welcome to Amethyst</string>
<string name="login_subtitle">Sign in to your Nostr account</string>
<string name="login_subtitle_desktop">A Nostr client for desktop</string>
<string name="login_card_title">Login with your Nostr key</string>
<string name="login_card_subtitle">Use nsec for full access or npub for read-only</string>
<string name="login_with_key">Login with Key</string>
<string name="login_button">Login</string>
<string name="login_generate_new">Generate New Key</string>
<string name="login_generate_button">Generate New</string>
<string name="login_key_hint">Enter your private key (nsec) or public key (npub)</string>
<string name="login_key_label">nsec or npub</string>
<string name="login_key_placeholder">nsec1... or npub1...</string>
<string name="login_show_key">Show key</string>
<string name="login_hide_key">Hide key</string>
<!-- New Key Warning -->
<string name="new_key_warning_title">IMPORTANT: Save your keys!</string>
<string name="new_key_warning_message">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!</string>
<string name="new_key_public_label">Public Key (shareable):</string>
<string name="new_key_secret_label">Secret Key (NEVER share this!):</string>
<string name="new_key_continue_button">I've saved my keys, continue</string>
<!-- Common Actions -->
<string name="action_copy">Copy</string>
<string name="action_paste">Paste</string>
<string name="action_cancel">Cancel</string>
<string name="action_ok">OK</string>
<string name="action_save">Save</string>
<string name="action_delete">Delete</string>
<string name="action_share">Share</string>
<!-- Errors -->
<string name="error_invalid_key">Invalid key format. Please check and try again.</string>
<string name="error_network">Network error. Please check your connection.</string>
<string name="error_generic">An error occurred. Please try again.</string>
<!-- Loading & Empty States -->
<string name="action_refresh">Refresh</string>
<string name="action_try_again">Try Again</string>
<string name="feed_empty">Feed is empty</string>
<string name="error_loading_feed">Error loading feed: %s</string>
<!-- Placeholder Screens -->
<string name="screen_search_title">Search</string>
<string name="screen_search_description">Search for users, notes, and hashtags.</string>
<string name="screen_messages_title">Messages</string>
<string name="screen_messages_description">Your encrypted direct messages will appear here.</string>
<string name="screen_notifications_title">Notifications</string>
<string name="screen_notifications_description">Mentions, replies, and reactions will appear here.</string>
</resources>
@@ -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",
)
}
@@ -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<String?>(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 = {},
)
}
@@ -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 = {},
)
}