Merge pull request #1751 from vitorpamplona/claude/improve-zapcustomdialog-ui-2Fzic

Redesign Zap Custom Dialog with improved UI/UX
This commit is contained in:
Vitor Pamplona
2026-03-03 10:54:05 -05:00
committed by GitHub
2 changed files with 153 additions and 91 deletions
@@ -24,20 +24,28 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column 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.Row
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.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api 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.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.ZapPaymentHandler 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.components.toasts.multiline.UserBasedErrorMessage
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton 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.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
class ZapOptionViewModel : ViewModel() { class ZapOptionViewModel : ViewModel() {
@@ -107,6 +112,7 @@ class ZapOptionViewModel : ViewModel() {
} }
} }
@OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun ZapCustomDialog( fun ZapCustomDialog(
onZapStarts: () -> Unit, onZapStarts: () -> Unit,
@@ -146,14 +152,11 @@ fun ZapCustomDialog(
), ),
) )
val zapOptions =
remember {
zapTypes.map { TitleExplainer(it.second, it.third) }.toImmutableList()
}
var selectedZapType by var selectedZapType by
remember(accountViewModel) { mutableStateOf(accountViewModel.defaultZapType()) } remember(accountViewModel) { mutableStateOf(accountViewModel.defaultZapType()) }
val presetAmounts = remember(accountViewModel) { accountViewModel.zapAmountChoices() }
Dialog( Dialog(
onDismissRequest = { onClose() }, onDismissRequest = { onClose() },
properties = properties =
@@ -162,110 +165,166 @@ fun ZapCustomDialog(
usePlatformDefaultWidth = false, usePlatformDefaultWidth = false,
), ),
) { ) {
Surface { Surface(
Column(modifier = Modifier.padding(10.dp)) { shape = RoundedCornerShape(16.dp),
modifier = Modifier.padding(horizontal = 16.dp),
) {
Column(
modifier =
Modifier
.padding(16.dp)
.imePadding(),
) {
// Header
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text(
text = stringRes(id = R.string.send_zap),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
)
CloseButton( CloseButton(
onPress = { onPress = {
postViewModel.cancel() postViewModel.cancel()
onClose() onClose()
}, },
) )
}
ZapButton( HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
isActive = postViewModel.canSend() && !baseNote.isDraft(),
// Preset amount quick-select chips
if (presetAmounts.isNotEmpty()) {
FlowRow(
modifier =
Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
onZapStarts() presetAmounts.forEach { amount ->
accountViewModel.zap( SuggestionChip(
baseNote, onClick = {
postViewModel.value()!! * 1000L, postViewModel.customAmount = TextFieldValue(amount.toString())
null, },
postViewModel.customMessage.text, label = { Text("${showAmount(amount.toBigDecimal())}") },
context, )
onError = onError, }
onProgress = onProgress,
onPayViaIntent = onPayViaIntent,
zapType = selectedZapType,
)
onClose()
} }
} }
Row( // Amount input
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp), OutlinedTextField(
horizontalArrangement = Arrangement.SpaceBetween, label = { Text(text = stringRes(id = R.string.amount_in_sats)) },
verticalAlignment = Alignment.CenterVertically, value = postViewModel.customAmount,
) { onValueChange = { postViewModel.customAmount = it },
OutlinedTextField( keyboardOptions =
// stringRes(R.string.new_amount_in_sats KeyboardOptions.Default.copy(
label = { Text(text = stringRes(id = R.string.amount_in_sats)) }, capitalization = KeyboardCapitalization.None,
value = postViewModel.customAmount, keyboardType = KeyboardType.Number,
onValueChange = { postViewModel.customAmount = it }, ),
keyboardOptions = placeholder = {
KeyboardOptions.Default.copy( Text(
capitalization = KeyboardCapitalization.None, text = "100, 1000, 5000",
keyboardType = KeyboardType.Number, color = MaterialTheme.colorScheme.placeholderText,
), )
placeholder = { },
Text( suffix = { Text(text = stringRes(id = R.string.sats)) },
text = "100, 1000, 5000", singleLine = true,
color = MaterialTheme.colorScheme.placeholderText, modifier =
) Modifier
}, .fillMaxWidth()
singleLine = true, .padding(bottom = 8.dp),
modifier = Modifier.padding(end = 5.dp).weight(1f), )
)
TextSpinner( // Zap type label
label = stringRes(id = R.string.zap_type), Text(
placeholder = zapTypes.first { it.first == accountViewModel.defaultZapType() }.second, text = stringRes(id = R.string.zap_type),
options = zapOptions, style = MaterialTheme.typography.labelMedium,
onSelect = { selectedZapType = zapTypes[it].first }, color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f).padding(end = 5.dp), 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( // Message input
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp), OutlinedTextField(
verticalAlignment = Alignment.CenterVertically, label = {
) { when (selectedZapType) {
OutlinedTextField( LnZapEvent.ZapType.PUBLIC, LnZapEvent.ZapType.ANONYMOUS -> {
// stringRes(R.string.new_amount_in_sats Text(text = stringRes(id = R.string.custom_zaps_add_a_message))
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))
}
} }
},
value = postViewModel.customMessage, LnZapEvent.ZapType.PRIVATE -> {
onValueChange = { postViewModel.customMessage = it }, Text(text = stringRes(id = R.string.custom_zaps_add_a_message_private))
keyboardOptions = }
KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.None, LnZapEvent.ZapType.NONZAP -> {
keyboardType = KeyboardType.Text, Text(text = stringRes(id = R.string.custom_zaps_add_a_message_nonzap))
), }
placeholder = { }
Text( },
text = stringRes(id = R.string.custom_zaps_add_a_message_example), value = postViewModel.customMessage,
color = MaterialTheme.colorScheme.placeholderText, onValueChange = { postViewModel.customMessage = it },
) keyboardOptions =
}, KeyboardOptions.Default.copy(
singleLine = true, capitalization = KeyboardCapitalization.Sentences,
modifier = Modifier.padding(end = 5.dp).weight(1f), 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 @Composable
fun ZapButton( fun ZapButton(
isActive: Boolean, isActive: Boolean,
modifier: Modifier = Modifier,
onPost: () -> Unit, onPost: () -> Unit,
) { ) {
Button( Button(
onClick = { if (isActive) onPost() }, onClick = { if (isActive) onPost() },
shape = ButtonBorder, shape = ButtonBorder,
modifier = modifier,
colors = colors =
ButtonDefaults.buttonColors( ButtonDefaults.buttonColors(
containerColor = if (isActive) MaterialTheme.colorScheme.primary else Color.Gray, containerColor = if (isActive) MaterialTheme.colorScheme.primary else Color.Gray,
+1
View File
@@ -479,6 +479,7 @@
<string name="looking_for_event">"Looking for Event %1$s"</string> <string name="looking_for_event">"Looking for Event %1$s"</string>
<string name="send_zap">Send Zap</string>
<string name="custom_zaps_add_a_message">Add a public message</string> <string name="custom_zaps_add_a_message">Add a public message</string>
<string name="custom_zaps_add_a_message_private">Add a private message</string> <string name="custom_zaps_add_a_message_private">Add a private message</string>
<string name="custom_zaps_add_a_message_nonzap">Add an invoice message</string> <string name="custom_zaps_add_a_message_nonzap">Add an invoice message</string>