fix: send ui
This commit is contained in:
@@ -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,26 +94,10 @@ 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
|
|
||||||
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) {
|
if (sendState !is SendState.PaymentSent) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = input,
|
value = input,
|
||||||
@@ -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))
|
||||||
CircularProgressIndicator()
|
Column(
|
||||||
Spacer(Modifier.height(8.dp))
|
modifier = Modifier.fillMaxWidth(),
|
||||||
Text(strings.sendingPayment) // ← "Sending payment…"
|
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 -> {
|
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,27 +263,48 @@ fun SendScreen(
|
|||||||
strings.routingFee(state.feeSats, ppmSuffix) // ← "Routing fee: X sats (Y ppm)"
|
strings.routingFee(state.feeSats, ppmSuffix) // ← "Routing fee: X sats (Y ppm)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(
|
|
||||||
text = feeText,
|
Column(
|
||||||
style = MaterialTheme.typography.bodySmall,
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
)
|
verticalArrangement = Arrangement.Center
|
||||||
Spacer(Modifier.height(32.dp))
|
) {
|
||||||
OutlinedButton(
|
Icon(
|
||||||
onClick = {
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
val record = state.pendingRecord
|
contentDescription = null,
|
||||||
if (record != null) {
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
onViewDetails(state.paymentHash, record)
|
modifier = Modifier.size(72.dp)
|
||||||
}
|
)
|
||||||
},
|
Spacer(Modifier.height(16.dp))
|
||||||
enabled = state.pendingRecord != null,
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth()
|
text = strings.paymentSent, // ← "Payment sent!"
|
||||||
) { Text(strings.details) } // ← "Details"
|
style = MaterialTheme.typography.headlineSmall
|
||||||
Spacer(Modifier.height(8.dp))
|
)
|
||||||
Button(
|
Spacer(Modifier.height(8.dp))
|
||||||
onClick = { vm.resetSendState(); input = "" },
|
AmountDisplay(amountSats = state.amountSats, fiatLabel = fiatLabel)
|
||||||
modifier = Modifier.fillMaxWidth()
|
Spacer(Modifier.height(4.dp))
|
||||||
) { Text(strings.sendAnother) } // ← "Send Another"
|
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 -> {
|
is SendState.Error -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user