From 946ed8abcb3f4a0b92e4078e4bed1122583c744d Mon Sep 17 00:00:00 2001 From: rasputin Date: Fri, 5 Jun 2026 19:25:14 +0200 Subject: [PATCH] fix: spinner when loading details from successful send --- .../bitcointxoko/gudariwallet/ui/UiStates.kt | 3 +- .../gudariwallet/ui/WalletScreen.kt | 4 +- .../ui/history/HistoryViewModel.kt | 4 + .../ui/history/PaymentSyncManager.kt | 16 ++++ .../gudariwallet/ui/send/SendScreen.kt | 11 ++- .../gudariwallet/ui/send/SendViewModel.kt | 81 ++++++++++++++++--- 6 files changed, 104 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/UiStates.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/UiStates.kt index 73a8b36..cde5604 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/UiStates.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/UiStates.kt @@ -91,7 +91,8 @@ sealed class SendState { data class PaymentSent( val amountSats : Long, val feeSats : Long? = null, - val paymentHash: String + val paymentHash: String, + val pendingRecord: PaymentRecord? = null ) : SendState() data class Error(val message: String) : SendState() } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt index 8c79ab0..8b3c2e9 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt @@ -297,8 +297,8 @@ fun WalletScreen( SendScreen( vm = vm, nfcVm = nfcVm, - onViewDetails = { checkingId -> - historyVm.refresh() + onViewDetails = { checkingId, record -> + historyVm.primePayment(record) navController.navigate("payment_detail/$checkingId") { launchSingleTop = true } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt index 931a15a..4304796 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt @@ -39,6 +39,10 @@ class HistoryViewModel( scope = viewModelScope, onNewPayments = { payments -> enricher.enrich(payments) } ) + // ── Optimistic insert (called after a successful send) ───────────────────── + fun primePayment(record: PaymentRecord) { + syncManager.primePayment(record) + } private val enricher: PaymentEnricher = PaymentEnricher( repo = repo, aliasRepo = aliasRepo, diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentSyncManager.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentSyncManager.kt index c279491..5b76af6 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentSyncManager.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentSyncManager.kt @@ -192,4 +192,20 @@ class PaymentSyncManager( ) } } + + // ── Optimistic insert ────────────────────────────────────────────────────── + + fun primePayment(record: PaymentRecord) { + val current = mutableState.value + if (current is HistoryState.Success) { + val already = current.payments.any { it.checkingId == record.checkingId } + if (!already) { + mutableState.value = current.copy( + payments = listOf(record) + current.payments + ) + } + } + // If state is Loading/Error (e.g. first launch), the next syncNewPayments() + // will fetch the real record from the API — no action needed here. + } } 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 3ec8fb7..c8266b1 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 @@ -21,6 +21,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.fragment.app.FragmentActivity import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.bitcointxoko.gudariwallet.api.PaymentRecord import com.bitcointxoko.gudariwallet.ui.BalanceState import com.bitcointxoko.gudariwallet.ui.NfcOfferState import com.bitcointxoko.gudariwallet.ui.SendState @@ -36,7 +37,7 @@ import kotlinx.coroutines.launch fun SendScreen( vm : WalletViewModel, nfcVm : NfcViewModel, - onViewDetails: (paymentHash: String) -> Unit = {} + onViewDetails: (paymentHash: String, record: PaymentRecord) -> Unit = { _, _ -> } ) { val sendState : SendState by vm.sendState.collectAsStateWithLifecycle() var input by remember { mutableStateOf("") } @@ -244,7 +245,13 @@ fun SendScreen( ) Spacer(Modifier.height(32.dp)) OutlinedButton( - onClick = { onViewDetails(state.paymentHash) }, + onClick = { + val record = state.pendingRecord + if (record != null) { + onViewDetails(state.paymentHash, record) + } + }, + enabled = state.pendingRecord != null, modifier = Modifier.fillMaxWidth() ) { Text("Details") } Spacer(Modifier.height(8.dp)) diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendViewModel.kt index 425aa21..73c695d 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendViewModel.kt @@ -5,6 +5,7 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bitcointxoko.gudariwallet.api.LnurlScanResponse +import com.bitcointxoko.gudariwallet.api.PaymentRecord import com.bitcointxoko.gudariwallet.api.RouteHintHop import com.bitcointxoko.gudariwallet.data.LnurlCacheRepository import com.bitcointxoko.gudariwallet.data.NodeAliasRepository @@ -65,6 +66,7 @@ class SendViewModel( fun payBolt11(bolt11: String) { viewModelScope.launch { + val decoded = _sendState.value as? SendState.Bolt11Decoded val amountSats = (_sendState.value as? SendState.Bolt11Decoded)?.amountSats ?: 0L _sendState.value = SendState.Paying runCatching { repo.payBolt11(bolt11) } @@ -73,9 +75,18 @@ class SendViewModel( repo.getPaymentDetail(response.paymentHash).details?.feeSat }.getOrNull() _sendState.value = SendState.PaymentSent( - amountSats = amountSats, - feeSats = feeSats, - paymentHash = response.paymentHash + amountSats = amountSats, + feeSats = feeSats, + paymentHash = response.paymentHash, + pendingRecord = buildPendingRecord( + paymentHash = response.paymentHash, + amountSats = amountSats, + feeSats = feeSats, + bolt11 = decoded?.bolt11, + memo = decoded?.memo, + pubkey = decoded?.payee, + alias = decoded?.nodeAlias + ) ) balanceVm.refreshBalance() } @@ -230,9 +241,18 @@ class SendViewModel( repo.getPaymentDetail(paymentHash).details?.feeSat }.getOrNull() _sendState.value = SendState.PaymentSent( - amountSats = state.amountSats, - feeSats = feeSats, - paymentHash = paymentHash + amountSats = state.amountSats, + feeSats = feeSats, + paymentHash = paymentHash, + pendingRecord = buildPendingRecord( + paymentHash = paymentHash, + amountSats = state.amountSats, + feeSats = feeSats, + bolt11 = state.bolt11, + memo = state.memo, + pubkey = state.payee, + alias = state.nodeAlias + ) ) balanceVm.refreshBalance() return@launch @@ -454,14 +474,31 @@ class SendViewModel( ) } - private suspend fun recordPaymentSuccess(paymentHash: String, amountMsat: Long) { + private suspend fun recordPaymentSuccess( + paymentHash: String, + amountMsat: Long, + bolt11 : String? = null, + memo : String? = null, + pubkey : String? = null, + alias : String? = null + ) { val feeSats = runCatching { repo.getPaymentDetail(paymentHash).details?.feeSat }.getOrNull() + val amountSats = amountMsat / WalletConstants.MSAT_PER_SAT _sendState.value = SendState.PaymentSent( - amountSats = amountMsat / WalletConstants.MSAT_PER_SAT, - feeSats = feeSats, - paymentHash = paymentHash + amountSats = amountSats, + feeSats = feeSats, + paymentHash = paymentHash, + pendingRecord = buildPendingRecord( + paymentHash = paymentHash, + amountSats = amountSats, + feeSats = feeSats, + bolt11 = bolt11, + memo = memo, + pubkey = pubkey, + alias = alias + ) ) balanceVm.refreshBalance() } @@ -551,4 +588,28 @@ class SendViewModel( key to value }.toMap() } + + private fun buildPendingRecord( + paymentHash: String, + amountSats : Long, + feeSats : Long?, + bolt11 : String?, + memo : String?, + pubkey : String?, + alias : String? + ): PaymentRecord = PaymentRecord( + paymentHash = paymentHash, + checkingId = paymentHash, + amountMsat = -(amountSats * 1_000L), // negative = outgoing + feeMsat = (feeSats ?: 0L) * 1_000L, + memo = memo, + time = (System.currentTimeMillis() / 1_000L).toString(), + status = "complete", + bolt11 = bolt11, + pending = false, + preimage = null, + extra = null, + pubkey = pubkey, + alias = alias + ) } \ No newline at end of file