refactor: WalletViewModel extract FiatViewModel

This commit is contained in:
2026-06-01 20:57:14 +02:00
parent 933681c4b2
commit bba41033e5
4 changed files with 139 additions and 96 deletions
@@ -13,22 +13,18 @@ import com.bitcointxoko.gudariwallet.util.RouteHintAnalyzer
import com.bitcointxoko.gudariwallet.util.SendInputDetector
import com.bitcointxoko.gudariwallet.util.SendInputType
import com.bitcointxoko.gudariwallet.util.WalletConstants
import com.bitcointxoko.gudariwallet.util.satsToFiat
import com.bitcointxoko.gudariwallet.util.parseApiError
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.SharingStarted
import com.bitcointxoko.gudariwallet.util.lnurlProtocolError
import androidx.fragment.app.FragmentActivity
import com.bitcointxoko.gudariwallet.api.RouteHintHop
import com.bitcointxoko.gudariwallet.data.BalancePrefsStore
import com.bitcointxoko.gudariwallet.data.FiatRateCache
import com.bitcointxoko.gudariwallet.ui.balance.BalanceViewModel
import com.bitcointxoko.gudariwallet.ui.fiat.FiatViewModel
import com.bitcointxoko.gudariwallet.util.BiometricHelper
private const val TAG = "WalletViewModel"
@@ -40,60 +36,22 @@ class WalletViewModel(
val paymentCache: PaymentCacheRepository,
private val balancePrefs: BalancePrefsStore,
val balanceVm : BalanceViewModel,
val fiatVm : FiatViewModel
) : ViewModel() {
// ── Balance state ─────────────────────────────────────────────────────────
val balanceState: StateFlow<BalanceState> get() = balanceVm.balanceState
// ── Balance visible state ─────────────────────────────────────────────────
private val _balanceHidden = MutableStateFlow(balancePrefs.isBalanceHidden)
val balanceHidden: StateFlow<Boolean> = _balanceHidden
fun toggleBalanceVisibility() {
val next = !_balanceHidden.value
_balanceHidden.value = next
balancePrefs.setBalanceHidden(next)
}
// ── Fiat cache ────────────────────────────────────────────────────────────
private val fiatCache = FiatRateCache(balancePrefs)
val fiatRate: Double?
get() = fiatCache.currentRate
val fiatCurrency: String? get() = _selectedCurrency.value
val fiatSatsPerUnit: StateFlow<Double?> = balanceVm.balanceState
.mapNotNull { state ->
(state as? BalanceState.Success)?.let { s ->
if (s.fiatAmount != null && s.sats > 0)
s.sats.toDouble() / s.fiatAmount
else null
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = null
)
private val _selectedCurrency = MutableStateFlow(balancePrefs.selectedCurrency)
val selectedCurrency: StateFlow<String?> = _selectedCurrency
fun setSelectedCurrency(currency: String?) {
if (currency == _selectedCurrency.value) return
_selectedCurrency.value = currency
balancePrefs.setSelectedCurrency(currency)
fiatCache.invalidateRate()
Log.d(TAG, "FIAT [CURRENCY ] changed to ${currency ?: "none"} — cache invalidated")
if (currency != null) {
viewModelScope.launch { fetchAndApplyFiatRate() }
} else {
balanceVm.applyFiat(null, null) // ← fix
}
}
companion object {
private const val RATE_TTL_MS = 5 * 60 * 1_000L // 5 minutes
}
val balanceHidden: StateFlow<Boolean> get() = fiatVm.balanceHidden
val selectedCurrency: StateFlow<String?> get() = fiatVm.selectedCurrency
val fiatSatsPerUnit: StateFlow<Double?> get() = fiatVm.fiatSatsPerUnit
fun toggleBalanceVisibility() = fiatVm.toggleBalanceVisibility()
fun setSelectedCurrency(c: String?) = fiatVm.setSelectedCurrency(c)
suspend fun getSupportedCurrencies() = fiatVm.getSupportedCurrencies()
val fiatRate: Double? get() = fiatVm.fiatRate
val fiatCurrency: String? get() = fiatVm.fiatCurrency
// ── Other states ──────────────────────────────────────────────────────────
private val _receiveState = MutableStateFlow<ReceiveState>(ReceiveState.Idle)
val receiveState: StateFlow<ReceiveState> = _receiveState
private val _sendState = MutableStateFlow<SendState>(SendState.Idle)
@@ -110,48 +68,7 @@ class WalletViewModel(
fetchLightningAddress()
}
// ── Fiat rate fetch + apply ───────────────────────────────────────────────
private suspend fun fetchAndApplyFiatRate() {
val currency = _selectedCurrency.value ?: return
val rate: Double = fiatCache.getRate(currency, RATE_TTL_MS)
?: run {
runCatching { repo.getFiatRate(currency) }
.getOrElse { e ->
Log.w(TAG, "FIAT [RATE ERROR ] $currency${e.message}")
Log.w(TAG, "FIAT [RATE MISS ] $currency — no fallback available, suppressing fiat display")
return
}
.also { fresh -> fiatCache.putRate(currency, fresh) }
}
// ← fix: delegate to balanceVm instead of writing to _balanceState
val currentSats = (balanceVm.balanceState.value as? BalanceState.Success)?.sats ?: return
val fiat = satsToFiat(currentSats, rate)
Log.d(TAG, "FIAT [APPLY ] $currentSats sats ÷ $rate sats/$currency = $fiat $currency")
balanceVm.applyFiat(fiat, currency)
}
// ── Currency list ────────────────────────────────────────────────────────
suspend fun getSupportedCurrencies(): List<String> {
fiatCache.getCurrencies()?.let { return it }
// Cache miss (memory + disk) — fetch from network
return fetchAndCacheCurrencies()
}
private suspend fun fetchAndCacheCurrencies(): List<String> {
return runCatching { repo.getSupportedCurrencies() }
.getOrElse { e ->
Log.w(TAG, "FIAT [CURRENCIES] network fetch failed — ${e.message}")
emptyList()
}
.also { list -> fiatCache.putCurrencies(list) }
}
// ── WebSocket event collection ────────────────────────────────────────────
private fun observePaymentEvents() {
viewModelScope.launch {
balanceVm.incomingPayment.collect { event ->