From 6f80232c32d1323621d990cc559c15933385e0fa Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Mar 2026 15:46:19 +0000 Subject: [PATCH] feat: improve ZapCustomDialog UI with chips and cleaner layout - Add dialog title "Send Zap" with close button in header - Add preset amount SuggestionChips from user's saved zap amounts - Replace TextSpinner dropdown with FilterChip row for zap type selection - Make amount field full-width with "sats" suffix - Move ZapButton to bottom as full-width call-to-action - Add imePadding for keyboard avoidance - Add RoundedCornerShape(16dp) to dialog Surface - Add HorizontalDivider separating header from content - Change message keyboard capitalization to Sentences - Add send_zap string resource https://claude.ai/code/session_017gXfNSaqGshRth2x5tdTKz --- .../amethyst/ui/note/ZapCustomDialog.kt | 243 +++++++++++------- amethyst/src/main/res/values/strings.xml | 1 + 2 files changed, 153 insertions(+), 91 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index 0725f9dca..3e17859e1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -24,20 +24,28 @@ import android.content.Context import android.content.Intent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold +import androidx.compose.material3.SuggestionChip import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -68,8 +76,6 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.ZapPaymentHandler -import com.vitorpamplona.amethyst.ui.components.TextSpinner -import com.vitorpamplona.amethyst.ui.components.TitleExplainer import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton @@ -85,7 +91,6 @@ import com.vitorpamplona.amethyst.ui.theme.ZeroPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CancellationException class ZapOptionViewModel : ViewModel() { @@ -107,6 +112,7 @@ class ZapOptionViewModel : ViewModel() { } } +@OptIn(ExperimentalLayoutApi::class) @Composable fun ZapCustomDialog( onZapStarts: () -> Unit, @@ -146,14 +152,11 @@ fun ZapCustomDialog( ), ) - val zapOptions = - remember { - zapTypes.map { TitleExplainer(it.second, it.third) }.toImmutableList() - } - var selectedZapType by remember(accountViewModel) { mutableStateOf(accountViewModel.defaultZapType()) } + val presetAmounts = remember(accountViewModel) { accountViewModel.zapAmountChoices() } + Dialog( onDismissRequest = { onClose() }, properties = @@ -162,110 +165,166 @@ fun ZapCustomDialog( usePlatformDefaultWidth = false, ), ) { - Surface { - Column(modifier = Modifier.padding(10.dp)) { + Surface( + shape = RoundedCornerShape(16.dp), + modifier = Modifier.padding(horizontal = 16.dp), + ) { + Column( + modifier = + Modifier + .padding(16.dp) + .imePadding(), + ) { + // Header Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { + Text( + text = stringRes(id = R.string.send_zap), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + ) CloseButton( onPress = { postViewModel.cancel() onClose() }, ) + } - ZapButton( - isActive = postViewModel.canSend() && !baseNote.isDraft(), + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + + // Preset amount quick-select chips + if (presetAmounts.isNotEmpty()) { + FlowRow( + modifier = + Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - onZapStarts() - accountViewModel.zap( - baseNote, - postViewModel.value()!! * 1000L, - null, - postViewModel.customMessage.text, - context, - onError = onError, - onProgress = onProgress, - onPayViaIntent = onPayViaIntent, - zapType = selectedZapType, - ) - onClose() + presetAmounts.forEach { amount -> + SuggestionChip( + onClick = { + postViewModel.customAmount = TextFieldValue(amount.toString()) + }, + label = { Text("⚡ ${showAmount(amount.toBigDecimal())}") }, + ) + } } } - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedTextField( - // stringRes(R.string.new_amount_in_sats - label = { Text(text = stringRes(id = R.string.amount_in_sats)) }, - value = postViewModel.customAmount, - onValueChange = { postViewModel.customAmount = it }, - keyboardOptions = - KeyboardOptions.Default.copy( - capitalization = KeyboardCapitalization.None, - keyboardType = KeyboardType.Number, - ), - placeholder = { - Text( - text = "100, 1000, 5000", - color = MaterialTheme.colorScheme.placeholderText, - ) - }, - singleLine = true, - modifier = Modifier.padding(end = 5.dp).weight(1f), - ) + // Amount input + OutlinedTextField( + label = { Text(text = stringRes(id = R.string.amount_in_sats)) }, + value = postViewModel.customAmount, + onValueChange = { postViewModel.customAmount = it }, + keyboardOptions = + KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Number, + ), + placeholder = { + Text( + text = "100, 1000, 5000", + color = MaterialTheme.colorScheme.placeholderText, + ) + }, + suffix = { Text(text = stringRes(id = R.string.sats)) }, + singleLine = true, + modifier = + Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + ) - TextSpinner( - label = stringRes(id = R.string.zap_type), - placeholder = zapTypes.first { it.first == accountViewModel.defaultZapType() }.second, - options = zapOptions, - onSelect = { selectedZapType = zapTypes[it].first }, - modifier = Modifier.weight(1f).padding(end = 5.dp), - ) + // Zap type label + Text( + text = stringRes(id = R.string.zap_type), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(bottom = 4.dp), + ) + + // Zap type selection chips + FlowRow( + modifier = + Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + zapTypes.forEach { (type, label, _) -> + FilterChip( + selected = selectedZapType == type, + onClick = { selectedZapType = type }, + label = { Text(label) }, + colors = + FilterChipDefaults.filterChipColors( + selectedContainerColor = MaterialTheme.colorScheme.primary, + selectedLabelColor = MaterialTheme.colorScheme.onPrimary, + ), + ) + } } - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedTextField( - // stringRes(R.string.new_amount_in_sats - label = { - when (selectedZapType) { - LnZapEvent.ZapType.PUBLIC, LnZapEvent.ZapType.ANONYMOUS -> { - Text(text = stringRes(id = R.string.custom_zaps_add_a_message)) - } - - LnZapEvent.ZapType.PRIVATE -> { - Text(text = stringRes(id = R.string.custom_zaps_add_a_message_private)) - } - - LnZapEvent.ZapType.NONZAP -> { - Text(text = stringRes(id = R.string.custom_zaps_add_a_message_nonzap)) - } + // Message input + OutlinedTextField( + label = { + when (selectedZapType) { + LnZapEvent.ZapType.PUBLIC, LnZapEvent.ZapType.ANONYMOUS -> { + Text(text = stringRes(id = R.string.custom_zaps_add_a_message)) } - }, - value = postViewModel.customMessage, - onValueChange = { postViewModel.customMessage = it }, - keyboardOptions = - KeyboardOptions.Default.copy( - capitalization = KeyboardCapitalization.None, - keyboardType = KeyboardType.Text, - ), - placeholder = { - Text( - text = stringRes(id = R.string.custom_zaps_add_a_message_example), - color = MaterialTheme.colorScheme.placeholderText, - ) - }, - singleLine = true, - modifier = Modifier.padding(end = 5.dp).weight(1f), + + LnZapEvent.ZapType.PRIVATE -> { + Text(text = stringRes(id = R.string.custom_zaps_add_a_message_private)) + } + + LnZapEvent.ZapType.NONZAP -> { + Text(text = stringRes(id = R.string.custom_zaps_add_a_message_nonzap)) + } + } + }, + value = postViewModel.customMessage, + onValueChange = { postViewModel.customMessage = it }, + keyboardOptions = + KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.Sentences, + keyboardType = KeyboardType.Text, + ), + placeholder = { + Text( + text = stringRes(id = R.string.custom_zaps_add_a_message_example), + color = MaterialTheme.colorScheme.placeholderText, + ) + }, + singleLine = true, + modifier = + Modifier + .fillMaxWidth() + .padding(bottom = 12.dp), + ) + + // Full-width send button + ZapButton( + isActive = postViewModel.canSend() && !baseNote.isDraft(), + modifier = Modifier.fillMaxWidth(), + ) { + onZapStarts() + accountViewModel.zap( + baseNote, + postViewModel.value()!! * 1000L, + null, + postViewModel.customMessage.text, + context, + onError = onError, + onProgress = onProgress, + onPayViaIntent = onPayViaIntent, + zapType = selectedZapType, ) + onClose() } } } @@ -275,11 +334,13 @@ fun ZapCustomDialog( @Composable fun ZapButton( isActive: Boolean, + modifier: Modifier = Modifier, onPost: () -> Unit, ) { Button( onClick = { if (isActive) onPost() }, shape = ButtonBorder, + modifier = modifier, colors = ButtonDefaults.buttonColors( containerColor = if (isActive) MaterialTheme.colorScheme.primary else Color.Gray, diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 771a576c6..76146bd36 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -479,6 +479,7 @@ "Looking for Event %1$s" + Send Zap Add a public message Add a private message Add an invoice message