Merge pull request #2781 from mstrofnone/fix/selectable-error-messages

fix(desktop): make error messages selectable so users can copy them
This commit is contained in:
Vitor Pamplona
2026-05-08 08:12:49 -04:00
committed by GitHub
4 changed files with 52 additions and 26 deletions
@@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -66,6 +67,11 @@ fun LoadingState(
/** /**
* A centered empty state with title, optional description, and optional refresh action. * A centered empty state with title, optional description, and optional refresh action.
*
* The optional `description` is wrapped in a [SelectionContainer] so users can
* select and copy it. `EmptyState` is reused as the in-feed error renderer
* (e.g. "Error loading feed" with the underlying error in `description`), so
* making the description selectable lets users copy error text for reporting.
*/ */
@Composable @Composable
fun EmptyState( fun EmptyState(
@@ -89,11 +95,13 @@ fun EmptyState(
) )
if (description != null) { if (description != null) {
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Text( SelectionContainer {
description, Text(
style = MaterialTheme.typography.bodyMedium, description,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), style = MaterialTheme.typography.bodyMedium,
) color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
)
}
} }
if (onRefresh != null) { if (onRefresh != null) {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
@@ -106,6 +114,9 @@ fun EmptyState(
/** /**
* A centered error state with message and optional retry action. * A centered error state with message and optional retry action.
*
* The error `message` is wrapped in a [SelectionContainer] so users can select
* and copy it — useful for reporting bugs or pasting error text into a search.
*/ */
@Composable @Composable
fun ErrorState( fun ErrorState(
@@ -121,11 +132,13 @@ fun ErrorState(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
Text( SelectionContainer {
message, Text(
style = MaterialTheme.typography.bodyMedium, message,
color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodyMedium,
) color = MaterialTheme.colorScheme.error,
)
}
if (onRetry != null) { if (onRetry != null) {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Button(onClick = onRetry) { Button(onClick = onRetry) {
@@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -251,20 +252,24 @@ fun ComposeNoteDialog(
errorMessage?.let { error -> errorMessage?.let { error ->
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Text( SelectionContainer {
error, Text(
style = MaterialTheme.typography.bodySmall, error,
color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall,
) color = MaterialTheme.colorScheme.error,
)
}
} }
uploadState.error?.let { error -> uploadState.error?.let { error ->
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( SelectionContainer {
"Upload error: $error", Text(
style = MaterialTheme.typography.bodySmall, "Upload error: $error",
color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall,
) color = MaterialTheme.colorScheme.error,
)
}
} }
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.desktop.ui.auth
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -85,7 +86,11 @@ fun KeyInputField(
isError = errorMessage != null, isError = errorMessage != null,
supportingText = supportingText =
errorMessage?.let { errorMessage?.let {
{ Text(it, color = MaterialTheme.colorScheme.error) } {
SelectionContainer {
Text(it, color = MaterialTheme.colorScheme.error)
}
}
}, },
) )
} }
@@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
@@ -254,11 +255,13 @@ private fun NostrConnectContent(
val clipboardManager = LocalClipboard.current val clipboardManager = LocalClipboard.current
if (errorMessage != null) { if (errorMessage != null) {
Text( SelectionContainer {
errorMessage!!, Text(
style = MaterialTheme.typography.bodySmall, errorMessage!!,
color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall,
) color = MaterialTheme.colorScheme.error,
)
}
Spacer(Modifier.height(12.dp)) Spacer(Modifier.height(12.dp))
Button(onClick = { Button(onClick = {
errorMessage = null errorMessage = null