diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EnStrings.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EnStrings.kt index 550d61c..e6c871f 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EnStrings.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EnStrings.kt @@ -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" }, diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EsStrings.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EsStrings.kt index a752e0a..820783a 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EsStrings.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EsStrings.kt @@ -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" }, diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EuStrings.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EuStrings.kt index ed52863..70574a9 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EuStrings.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EuStrings.kt @@ -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" }, diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/Strings.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/Strings.kt index 297eb3e..69eb3a0 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/Strings.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/Strings.kt @@ -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, diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendScreen.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendScreen.kt index 1154a80..d19f07a 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendScreen.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendScreen.kt @@ -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,26 +94,10 @@ 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( value = input, @@ -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)) - CircularProgressIndicator() - Spacer(Modifier.height(8.dp)) - Text(strings.sendingPayment) // ← "Sending payment…" + 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…" + } + } } 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,27 +263,48 @@ fun SendScreen( strings.routingFee(state.feeSats, ppmSuffix) // ← "Routing fee: X sats (Y ppm)" } } - Text( - text = feeText, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(Modifier.height(32.dp)) - OutlinedButton( - onClick = { - val record = state.pendingRecord - if (record != null) { - onViewDetails(state.paymentHash, record) - } - }, - enabled = state.pendingRecord != null, - modifier = Modifier.fillMaxWidth() - ) { Text(strings.details) } // ← "Details" - Spacer(Modifier.height(8.dp)) - Button( - onClick = { vm.resetSendState(); input = "" }, - modifier = Modifier.fillMaxWidth() - ) { Text(strings.sendAnother) } // ← "Send Another" + + 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)) + Button( + onClick = { + val record = state.pendingRecord + if (record != null) { + onViewDetails(state.paymentHash, record) + } + }, + enabled = state.pendingRecord != null, + modifier = Modifier.fillMaxWidth() + ) { Text(strings.details) } // ← "Details" + Spacer(Modifier.height(32.dp)) + TextButton( + onClick = { vm.resetSendState(); input = "" }, + modifier = Modifier.fillMaxWidth() + ) { Text(strings.sendAnother) } // ← "Send Another" + } } is SendState.Error -> {