From 817227806b83bdfa7987f43c83971ca8d17d5b96 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 26 May 2026 21:41:41 +0000 Subject: [PATCH] fix(cashu): replace auto-popup with a tappable pending-quote banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: every entry to the wallet screen re-opened the Receive dialog if any pending kind:7374 mint quote existed. So a user who got an invoice and then navigated away — for any reason, even without dismissing — would be greeted by the same invoice dialog the next time they opened the wallet. Worse: even after they'd paid and the mint had issued proofs, the brief window before the kind:7374 NIP-09 delete propagated would cause the dialog to pop again. Fix: drop the LaunchedEffect(pendingQuotes) auto-resume; replace with a non-modal banner card just under the BalanceCard that shows "N pending invoices · Tap to resume". The user opts in by tapping it. The underlying CashuWalletState.pendingQuotes flow + the viewModel.resumeMintQuote() VM method are unchanged — only the trigger surface moves from "auto" to "user-initiated". playDebug + fdroidDebug compile clean; 24/24 jvm tests still pass. --- .../loggedIn/wallet/CashuWalletScreen.kt | 89 +++++++++++++++++-- amethyst/src/main/res/values/strings.xml | 7 ++ 2 files changed, 87 insertions(+), 9 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt index c0fc8c4422..75f7a8aedb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -107,15 +108,11 @@ fun CashuWalletScreen( var sendTokenOpen by remember { mutableStateOf(false) } var redeemOpen by remember { mutableStateOf(false) } - // If the user has an unfinished kind:7374 quote, surface it the next time - // they open the wallet so the in-flight invoice isn't lost. Auto-resumes - // the most recent quote on first composition. - LaunchedEffect(pendingQuotes) { - if (!receiveOpen && pendingQuotes.isNotEmpty()) { - viewModel.resumeMintQuote(pendingQuotes.first()) - receiveOpen = true - } - } + // pendingQuotes drives a non-modal banner in the wallet body (see + // PendingQuoteBanner below). Tapping the banner is what opens the + // Receive dialog with the stored quote pre-loaded — we do NOT auto-pop + // the dialog on every entry to the screen, which would re-surface + // dismissed quotes every time the user navigates back. Scaffold( topBar = { @@ -147,11 +144,18 @@ fun CashuWalletScreen( balanceSats = balanceSats, mints = mints, history = history, + pendingQuoteCount = pendingQuotes.size, onReceive = { receiveOpen = true }, onSendLn = { sendLnOpen = true }, onSendToken = { sendTokenOpen = true }, onRedeem = { redeemOpen = true }, onRecommendMint = { viewModel.recommendMint(it) }, + onResumePendingQuote = { + pendingQuotes.firstOrNull()?.let { + viewModel.resumeMintQuote(it) + receiveOpen = true + } + }, ) // NIP-60 wallets are portable across clients — show a "looking @@ -275,11 +279,13 @@ private fun CashuWalletContent( balanceSats: Long, mints: List, history: List, + pendingQuoteCount: Int, onReceive: () -> Unit, onSendLn: () -> Unit, onSendToken: () -> Unit, onRedeem: () -> Unit, onRecommendMint: (String) -> Unit, + onResumePendingQuote: () -> Unit, ) { LazyColumn( modifier = @@ -293,6 +299,10 @@ private fun CashuWalletContent( BalanceCard(balanceSats) } + if (pendingQuoteCount > 0) { + item { PendingQuoteBanner(count = pendingQuoteCount, onResume = onResumePendingQuote) } + } + item { ActionRow( onReceive = onReceive, @@ -332,6 +342,67 @@ private fun CashuWalletContent( } } +/** + * Banner that surfaces unfinished mint quotes — tappable to resume the + * receive flow with the stored invoice. Driven by + * [com.vitorpamplona.amethyst.model.nip60Cashu.CashuWalletState.pendingQuotes]. + * + * Replaces the earlier auto-popup behaviour which re-surfaced the Receive + * dialog on every entry to the screen. + */ +@Composable +private fun PendingQuoteBanner( + count: Int, + onResume: () -> Unit, +) { + Card( + modifier = + Modifier + .fillMaxWidth() + .clickable(onClick = onResume), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer), + ) { + Row( + modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + symbol = MaterialSymbols.Bolt, + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.onSecondaryContainer, + ) + Spacer(modifier = Modifier.width(10.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = + androidx.compose.ui.res + .pluralStringResource( + R.plurals.cashu_pending_quotes_title, + count, + count, + ), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) + Text( + text = stringRes(R.string.cashu_pending_quotes_subtitle), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.8f), + ) + } + Text( + text = stringRes(R.string.cashu_pending_quotes_resume), + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) + } + } +} + @Composable private fun BalanceCard(balanceSats: Long) { val formatted = diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 33a6b27858..43467adf71 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -2857,6 +2857,13 @@ %1$d mints + + %1$d pending invoice + %1$d pending invoices + + Tap to resume and check status. + Resume + <%1$s Connecting Downloading