Merge pull request #3250 from vitorpamplona/claude/compassionate-tesla-j373vv

Cashu: avoid flickering invoice dialog during payment polling
This commit is contained in:
Vitor Pamplona
2026-06-17 16:16:48 -04:00
committed by GitHub
3 changed files with 33 additions and 6 deletions
@@ -793,11 +793,21 @@ private fun ReceiveDialog(
Text(stringRes(R.string.cashu_copy_invoice))
}
Spacer(modifier = Modifier.height(4.dp))
// The mint is polled every 3s in the background. We
// keep the invoice on screen and only toggle this
// status line between "waiting" and "checking" so the
// dialog never swaps its whole body mid-poll.
Row(verticalAlignment = Alignment.CenterVertically) {
CircularProgressIndicator(modifier = Modifier.size(14.dp), strokeWidth = 2.dp)
Spacer(modifier = Modifier.width(8.dp))
Text(
stringRes(R.string.cashu_waiting_for_payment),
stringRes(
if (s.checking) {
R.string.cashu_checking_mint
} else {
R.string.cashu_waiting_for_payment
},
),
style = MaterialTheme.typography.bodySmall,
)
}
@@ -62,6 +62,14 @@ sealed class CashuMintFlowState {
val flow: MintQuoteStarted,
val mintUrl: String,
val amountSats: Long,
/**
* True while a background poll is asking the mint whether the
* invoice has been paid. The receive dialog keeps the invoice on
* screen and shows a small inline indicator instead of swapping to
* the [Completing] body, so the routine 3s check no longer flickers
* the whole dialog.
*/
val checking: Boolean = false,
) : CashuMintFlowState()
data object Completing : CashuMintFlowState()
@@ -419,21 +427,29 @@ class CashuWalletViewModel : ViewModel() {
fun checkAndCompleteMint() {
val vm = accountViewModel ?: return
val current = _mintState.value as? CashuMintFlowState.AwaitingPayment ?: return
// Atomic flip to Completing so a concurrent poll (the receive
// Already polling — don't stack a second request.
if (current.checking) return
// Atomic flip to checking=true so a concurrent poll (the receive
// dialog fires this every 3s) can't both reach
// completeMintFromLightning. Without the gate, poll 1 consumed
// the mint quote and poll 2 hit "outputs already signed".
if (!_mintState.compareAndSet(current, CashuMintFlowState.Completing)) return
// the mint quote and poll 2 hit "outputs already signed". We stay
// in AwaitingPayment so the invoice keeps showing — the dialog
// renders an inline "checking the mint" indicator off `checking`
// instead of swapping its whole body, which used to flicker.
if (!_mintState.compareAndSet(current, current.copy(checking = true))) return
vm.launchSigner {
try {
val status = ops.checkMintQuote(current.mintUrl, current.flow.mintQuote.quote)
val paid = status.isSettled()
if (!paid) {
// Roll back to AwaitingPayment so the polling
// LaunchedEffect picks up again on the next tick.
// Clear the checking flag so the polling LaunchedEffect
// picks up again on the next tick, invoice still on screen.
_mintState.value = current
return@launchSigner
}
// Payment confirmed — now it's worth showing the full
// "issuing proofs" body while we finalize the mint.
_mintState.value = CashuMintFlowState.Completing
ops.completeMintFromLightning(current.mintUrl, current.flow.quoteEvent, current.amountSats)
_mintState.value = CashuMintFlowState.Completed(current.amountSats)
} catch (e: Exception) {
+1
View File
@@ -2241,6 +2241,7 @@
<string name="cashu_done">Done</string>
<string name="cashu_requesting_invoice">Asking mint for an invoice…</string>
<string name="cashu_waiting_for_payment">Waiting for the invoice to be paid…</string>
<string name="cashu_checking_mint">Checking the mint…</string>
<string name="cashu_completing_mint">Issuing proofs…</string>
<string name="cashu_paying_invoice">Paying via mint…</string>
<string name="cashu_building_token">Swapping proofs…</string>