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…", resolving = "Resolving…",
sendingPayment = "Sending payment…", 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 ───────────────────────────────────────────── // ── SendScreen — payment sent ─────────────────────────────────────────────
paymentSent = "Payment sent!", paymentSent = "Payment sent!",
amountSats = { sats -> "$sats sats" }, amountSats = { sats -> "$sats sats" },
@@ -126,6 +126,11 @@ val EsHomeStrings = AppStrings(
resolving = "Resolviendo…", resolving = "Resolviendo…",
sendingPayment = "Enviando pago…", 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 ───────────────────────────────────────────── // ── SendScreen — payment sent ─────────────────────────────────────────────
paymentSent = "¡Pago enviado!", paymentSent = "¡Pago enviado!",
amountSats = { sats -> "$sats sats" }, amountSats = { sats -> "$sats sats" },
@@ -126,6 +126,12 @@ val EuStrings = AppStrings(
resolving = "Ebazten…", resolving = "Ebazten…",
sendingPayment = "Ordainketa bidaltzen…", 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 ───────────────────────────────────────────── // ── SendScreen — payment sent ─────────────────────────────────────────────
paymentSent = "Ordainketa bidalita!", paymentSent = "Ordainketa bidalita!",
amountSats = { sats -> "$sats sat" }, amountSats = { sats -> "$sats sat" },
@@ -135,6 +135,11 @@ data class AppStrings(
val resolving : String, val resolving : String,
val sendingPayment : String, val sendingPayment : String,
// ── SendScreen — paying ───────────────────────────────────────────────────
val paymentTakingLong : String,
val paymentTakingLongDetail: String,
val dismissPayment : String,
// ── SendScreen — payment sent ───────────────────────────────────────────── // ── SendScreen — payment sent ─────────────────────────────────────────────
val paymentSent : String, val paymentSent : String,
val amountSats : (sats: Long) -> 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.text.KeyboardActions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons 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.ContentPaste
import androidx.compose.material.icons.filled.Nfc import androidx.compose.material.icons.filled.Nfc
import androidx.compose.material.icons.outlined.HourglassTop
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment 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.SendState
import com.bitcointxoko.gudariwallet.ui.WalletViewModel import com.bitcointxoko.gudariwallet.ui.WalletViewModel
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel 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.SendInputDetector
import com.bitcointxoko.gudariwallet.util.feePpm import com.bitcointxoko.gudariwallet.util.feePpm
import com.bitcointxoko.gudariwallet.util.fiatLabel
import com.bitcointxoko.gudariwallet.util.formatFiat import com.bitcointxoko.gudariwallet.util.formatFiat
import com.bitcointxoko.gudariwallet.util.satsToFiat import com.bitcointxoko.gudariwallet.util.satsToFiat
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
@@ -89,25 +94,9 @@ fun SendScreen(
text = strings.send, // ← "Send" text = strings.send, // ← "Send"
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall
) )
}
// NFC indicator — only visible in Idle/Error, lights up when offer pending Spacer(Modifier.height(16.dp))
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)
)
}
}
}
if (sendState !is SendState.PaymentSent) { if (sendState !is SendState.PaymentSent) {
OutlinedTextField( OutlinedTextField(
@@ -206,42 +195,60 @@ fun SendScreen(
} }
is SendState.Paying -> { is SendState.Paying -> {
var slowPayment by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(10_000L)
slowPayment = true
}
Spacer(Modifier.height(16.dp)) 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() CircularProgressIndicator()
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Text(strings.sendingPayment) // "Sending payment…" Text(strings.sendingPayment) // "Sending payment…"
}
}
} }
is SendState.PaymentSent -> { is SendState.PaymentSent -> {
Spacer(Modifier.height(32.dp)) val fiatLabel = fiatLabel(state.amountSats, fiatRate, fiatCurrency)
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 ppm = if (state.feeSats != null && state.feeSats != 0L) { val ppm = if (state.feeSats != null && state.feeSats != 0L) {
feePpm( feePpm(
amountMsat = state.amountSats * 1_000L, amountMsat = state.amountSats * 1_000L,
@@ -256,13 +263,33 @@ fun SendScreen(
strings.routingFee(state.feeSats, ppmSuffix) // ← "Routing fee: X sats (Y ppm)" 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(
text = feeText, text = feeText,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
Spacer(Modifier.height(32.dp)) Spacer(Modifier.height(32.dp))
OutlinedButton( Button(
onClick = { onClick = {
val record = state.pendingRecord val record = state.pendingRecord
if (record != null) { if (record != null) {
@@ -272,12 +299,13 @@ fun SendScreen(
enabled = state.pendingRecord != null, enabled = state.pendingRecord != null,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { Text(strings.details) } // ← "Details" ) { Text(strings.details) } // ← "Details"
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(32.dp))
Button( TextButton(
onClick = { vm.resetSendState(); input = "" }, onClick = { vm.resetSendState(); input = "" },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { Text(strings.sendAnother) } // ← "Send Another" ) { Text(strings.sendAnother) } // ← "Send Another"
} }
}
is SendState.Error -> { is SendState.Error -> {
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))