fix: get balance from websocket instead of separate api call

This commit is contained in:
2026-06-04 12:57:59 +02:00
parent ff50cc1b61
commit 5bdc606d1c
3 changed files with 31 additions and 15 deletions
@@ -259,6 +259,7 @@ data class PaymentDetailResponse(
// ── WebSocket payment message ───────────────────────────────────────────────── // ── WebSocket payment message ─────────────────────────────────────────────────
data class WsPaymentMessage( data class WsPaymentMessage(
@SerializedName("wallet_balance") val walletBalance: Long?,
val payment: WsPayment? val payment: WsPayment?
) )
@@ -81,6 +81,7 @@ class WalletNotificationService : Service() {
* Emitted to [paymentEvents] for in-process UI updates. * Emitted to [paymentEvents] for in-process UI updates.
*/ */
data class PaymentEvent( data class PaymentEvent(
val walletBalance: Long?,
val amountSats: Long, val amountSats: Long,
val memo: String?, val memo: String?,
val isOutgoing: Boolean, val isOutgoing: Boolean,
@@ -229,6 +230,7 @@ class WalletNotificationService : Service() {
serviceScope.launch { serviceScope.launch {
_paymentEvents.emit( _paymentEvents.emit(
PaymentEvent( PaymentEvent(
walletBalance = msg.walletBalance,
amountSats = amountSats, amountSats = amountSats,
memo = payment.memo, memo = payment.memo,
isOutgoing = isOutgoing, isOutgoing = isOutgoing,
@@ -245,7 +247,7 @@ class WalletNotificationService : Service() {
checkingId = payment.checkingId, checkingId = payment.checkingId,
paymentHash = payment.paymentHash, paymentHash = payment.paymentHash,
amountMsat = payment.amount, amountMsat = payment.amount,
feeMsat = 0L, // WsPaymentMessage doesn't carry fee feeMsat = payment.fee,
memo = payment.memo, memo = payment.memo,
time = payment.time, time = payment.time,
status = payment.status, status = payment.status,
@@ -25,7 +25,7 @@ class BalanceViewModel(
val balanceState: StateFlow<BalanceState> = _balanceState val balanceState: StateFlow<BalanceState> = _balanceState
// Emits after every successful balance refresh so other ViewModels // Emits after every successful balance refresh so other ViewModels
// (e.g. FiatViewModel) can react without polling. // can react without polling.
private val _balanceRefreshed = MutableSharedFlow<Long>(extraBufferCapacity = 1) private val _balanceRefreshed = MutableSharedFlow<Long>(extraBufferCapacity = 1)
// Emits incoming payment events so ReceiveViewModel can update its state. // Emits incoming payment events so ReceiveViewModel can update its state.
@@ -117,25 +117,38 @@ class BalanceViewModel(
WalletNotificationService.paymentEvents.collect { event -> WalletNotificationService.paymentEvents.collect { event ->
Log.d(TAG, "BALANCE [WS EVENT ] ${if (event.isOutgoing) "SENT" else "RECEIVED"} ${event.amountSats} sats") Log.d(TAG, "BALANCE [WS EVENT ] ${if (event.isOutgoing) "SENT" else "RECEIVED"} ${event.amountSats} sats")
val current = _balanceState.value val newSats = if (event.walletBalance != null) {
if (current is BalanceState.Success) { // Authoritative balance from the server — no delta math needed
val delta = if (event.isOutgoing) -event.amountSats else event.amountSats Log.d(TAG, "BALANCE [WS BALANCE] ${event.walletBalance} sats (from server)")
val newSats = (current.sats + delta).coerceAtLeast(0L) event.walletBalance
Log.d(TAG, "BALANCE [WS DELTA ] ${current.sats} ${if (delta >= 0) "+" else ""}$delta = $newSats sats (optimistic)")
_balanceState.value = current.copy(sats = newSats, isRefreshing = true)
persistBalance(newSats)
} else { } else {
Log.w(TAG, "BALANCE [WS EVENT ] Received event but balance state is ${current::class.simpleName} — skipping delta") // Fallback: server didn't send balance, apply delta optimistically
val current = _balanceState.value
if (current is BalanceState.Success) {
val delta = if (event.isOutgoing) -event.amountSats else event.amountSats
val sats = (current.sats + delta).coerceAtLeast(0L)
Log.d(TAG, "BALANCE [WS DELTA ] ${current.sats} ${if (delta >= 0) "+" else ""}$delta = $sats sats (optimistic fallback)")
sats
} else null
} }
refreshBalance() if (newSats != null) {
val now = System.currentTimeMillis()
_balanceState.value = BalanceState.Success(
sats = newSats,
isRefreshing = false,
lastUpdated = now
)
persistBalance(newSats)
_balanceRefreshed.tryEmit(newSats)
onBalanceRefreshed?.invoke()
}
// refreshBalance() call removed — balance is authoritative from WS
if (!event.isOutgoing) { if (!event.isOutgoing) {
_incomingPayment.tryEmit( _incomingPayment.tryEmit(
IncomingPaymentEvent( IncomingPaymentEvent(amountSats = event.amountSats, memo = event.memo)
amountSats = event.amountSats,
memo = event.memo
)
) )
} }
} }