fix: spinner when loading details from successful send

This commit is contained in:
2026-06-05 19:25:14 +02:00
parent cfde0755cd
commit 946ed8abcb
6 changed files with 104 additions and 15 deletions
@@ -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()
}
@@ -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
}
@@ -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,
@@ -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.
}
}
@@ -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))
@@ -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) }
@@ -75,7 +77,16 @@ class SendViewModel(
_sendState.value = SendState.PaymentSent(
amountSats = amountSats,
feeSats = feeSats,
paymentHash = response.paymentHash
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()
}
@@ -232,7 +243,16 @@ class SendViewModel(
_sendState.value = SendState.PaymentSent(
amountSats = state.amountSats,
feeSats = feeSats,
paymentHash = paymentHash
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,
amountSats = amountSats,
feeSats = feeSats,
paymentHash = paymentHash
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
)
}