fix: send ui

This commit is contained in:
2026-06-06 14:17:42 +02:00
parent c80edafbd4
commit 0f360067d1
5 changed files with 120 additions and 71 deletions
@@ -126,6 +126,11 @@ val EnHomeStrings = AppStrings(
resolving = "Resolving…",
sendingPayment = "Sending payment…",
// ── SendScreen — paying ───────────────────────────────────────────────────
paymentTakingLong = "This is taking longer than usual",
paymentTakingLongDetail = "Your payment is still on its way. This may be a hold invoice that settles when the recipient is ready. You'll get a notification when it completes — you can safely leave this screen.",
dismissPayment = "Dismiss",
// ── SendScreen — payment sent ─────────────────────────────────────────────
paymentSent = "Payment sent!",
amountSats = { sats -> "$sats sats" },
@@ -126,6 +126,11 @@ val EsHomeStrings = AppStrings(
resolving = "Resolviendo…",
sendingPayment = "Enviando pago…",
// ── SendScreen — paying ───────────────────────────────────────────────────
paymentTakingLong = "Esto está tardando más de lo habitual",
paymentTakingLongDetail = "Tu pago sigue en camino. Es posible que sea una factura hold que se liquidará cuando el destinatario esté listo. Recibirás una notificación cuando se complete — puedes salir de esta pantalla con seguridad.",
dismissPayment = "Salir",
// ── SendScreen — payment sent ─────────────────────────────────────────────
paymentSent = "¡Pago enviado!",
amountSats = { sats -> "$sats sats" },
@@ -126,6 +126,12 @@ val EuStrings = AppStrings(
resolving = "Ebazten…",
sendingPayment = "Ordainketa bidaltzen…",
// ── SendScreen — paying ───────────────────────────────────────────────────
paymentTakingLong = "Ohikoa baino gehiago ari da irauten",
paymentTakingLongDetail = "Zure ordainketa bidean dago oraindik. Baliteke atxikitako faktura bat izatea, hartzaileak prest dagoenean kitatzekoa. Jakinarazpen bat jasoko duzu burutzen denean — pantaila hau segurtasunez utzi dezakezu.",
dismissPayment = "Utzi",
// ── SendScreen — payment sent ─────────────────────────────────────────────
paymentSent = "Ordainketa bidalita!",
amountSats = { sats -> "$sats sat" },
@@ -135,6 +135,11 @@ data class AppStrings(
val resolving : String,
val sendingPayment : String,
// ── SendScreen — paying ───────────────────────────────────────────────────
val paymentTakingLong : String,
val paymentTakingLongDetail: String,
val dismissPayment : String,
// ── SendScreen — payment sent ─────────────────────────────────────────────
val paymentSent : String,
val amountSats : (sats: Long) -> String,
@@ -6,8 +6,10 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.Nfc
import androidx.compose.material.icons.outlined.HourglassTop
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@@ -28,10 +30,13 @@ import com.bitcointxoko.gudariwallet.ui.NfcOfferState
import com.bitcointxoko.gudariwallet.ui.SendState
import com.bitcointxoko.gudariwallet.ui.WalletViewModel
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel
import com.bitcointxoko.gudariwallet.ui.receive.AmountDisplay
import com.bitcointxoko.gudariwallet.util.SendInputDetector
import com.bitcointxoko.gudariwallet.util.feePpm
import com.bitcointxoko.gudariwallet.util.fiatLabel
import com.bitcointxoko.gudariwallet.util.formatFiat
import com.bitcointxoko.gudariwallet.util.satsToFiat
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
@@ -89,25 +94,9 @@ fun SendScreen(
text = strings.send, // ← "Send"
style = MaterialTheme.typography.headlineSmall
)
}
// NFC indicator — only visible in Idle/Error, lights up when offer pending
if (sendState is SendState.Idle || sendState is SendState.Error) {
FilledTonalIconButton(
onClick = { /* passive indicator */ },
modifier = Modifier.size(40.dp)
) {
Icon(
imageVector = Icons.Filled.Nfc,
contentDescription = strings.tapNfcHint, // ← "Tap NFC device to receive invoice"
tint = if (nfcOffer is NfcOfferState.Detected)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
}
}
Spacer(Modifier.height(16.dp))
if (sendState !is SendState.PaymentSent) {
OutlinedTextField(
@@ -206,42 +195,60 @@ fun SendScreen(
}
is SendState.Paying -> {
var slowPayment by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(10_000L)
slowPayment = true
}
Spacer(Modifier.height(16.dp))
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (slowPayment) {
// Slow path — replace spinner with explanatory text
Icon(
imageVector = Icons.Outlined.HourglassTop,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(32.dp)
)
Spacer(Modifier.height(12.dp))
Text(
text = strings.paymentTakingLong, // "This is taking longer than usual"
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center
)
Spacer(Modifier.height(8.dp))
Text(
text = strings.paymentTakingLongDetail,
// "Your payment is still on its way. This may be a hold invoice
// that settles when the recipient is ready. You'll get a
// notification when it completes — you can safely leave this screen."
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
Spacer(Modifier.height(20.dp))
OutlinedButton(
onClick = { vm.resetSendState(); input = "" },
modifier = Modifier.fillMaxWidth()
) {
Text(strings.dismissPayment) // "Dismiss"
}
} else {
// Fast path — normal spinner
CircularProgressIndicator()
Spacer(Modifier.height(8.dp))
Text(strings.sendingPayment) // "Sending payment…"
Text(strings.sendingPayment) // "Sending payment…"
}
}
}
is SendState.PaymentSent -> {
Spacer(Modifier.height(32.dp))
Text(
text = "",
style = MaterialTheme.typography.displayLarge,
color = MaterialTheme.colorScheme.primary
)
Spacer(Modifier.height(8.dp))
Text(
text = strings.paymentSent, // ← "Payment sent!"
style = MaterialTheme.typography.headlineSmall
)
Spacer(Modifier.height(4.dp))
Text(
text = strings.amountSats(state.amountSats), // ← "X sats"
color = MaterialTheme.colorScheme.onSurfaceVariant
)
val fiatAmount = if (fiatRate != null && fiatCurrency != null) {
satsToFiat(state.amountSats, fiatRate)
} else null
if (fiatAmount != null && fiatCurrency != null) {
Text(
text = strings.fiatEquivalent( // ← "≈ X.XX EUR"
formatFiat(fiatAmount, fiatCurrency)
),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Spacer(Modifier.height(4.dp))
val fiatLabel = fiatLabel(state.amountSats, fiatRate, fiatCurrency)
val ppm = if (state.feeSats != null && state.feeSats != 0L) {
feePpm(
amountMsat = state.amountSats * 1_000L,
@@ -256,13 +263,33 @@ fun SendScreen(
strings.routingFee(state.feeSats, ppmSuffix) // ← "Routing fee: X sats (Y ppm)"
}
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Filled.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(72.dp)
)
Spacer(Modifier.height(16.dp))
Text(
text = strings.paymentSent, // ← "Payment sent!"
style = MaterialTheme.typography.headlineSmall
)
Spacer(Modifier.height(8.dp))
AmountDisplay(amountSats = state.amountSats, fiatLabel = fiatLabel)
Spacer(Modifier.height(4.dp))
Text(
text = feeText,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(Modifier.height(32.dp))
OutlinedButton(
Button(
onClick = {
val record = state.pendingRecord
if (record != null) {
@@ -272,12 +299,13 @@ fun SendScreen(
enabled = state.pendingRecord != null,
modifier = Modifier.fillMaxWidth()
) { Text(strings.details) } // ← "Details"
Spacer(Modifier.height(8.dp))
Button(
Spacer(Modifier.height(32.dp))
TextButton(
onClick = { vm.resetSendState(); input = "" },
modifier = Modifier.fillMaxWidth()
) { Text(strings.sendAnother) } // ← "Send Another"
}
}
is SendState.Error -> {
Spacer(Modifier.height(8.dp))