199 lines
8.4 KiB
Kotlin
199 lines
8.4 KiB
Kotlin
package com.bitcointxoko.gudariwallet.ui
|
|
|
|
import timber.log.Timber
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import com.bitcointxoko.gudariwallet.api.LnurlScanResponse
|
|
import com.bitcointxoko.gudariwallet.data.NodeAliasRepository
|
|
import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository
|
|
import com.bitcointxoko.gudariwallet.data.WalletRepository
|
|
import kotlinx.coroutines.flow.SharedFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import kotlinx.coroutines.launch
|
|
import com.bitcointxoko.gudariwallet.data.BalancePrefsStore
|
|
import com.bitcointxoko.gudariwallet.data.ContactRepository
|
|
import com.bitcointxoko.gudariwallet.data.LightningAddressStore
|
|
import com.bitcointxoko.gudariwallet.data.NwcCacheRepository
|
|
import com.bitcointxoko.gudariwallet.data.db.ContactEntity
|
|
import com.bitcointxoko.gudariwallet.data.db.PaymentAddressType
|
|
import com.bitcointxoko.gudariwallet.ui.balance.BalanceViewModel
|
|
import com.bitcointxoko.gudariwallet.ui.clipboard.ClipboardViewModel
|
|
import com.bitcointxoko.gudariwallet.ui.fiat.FiatViewModel
|
|
import com.bitcointxoko.gudariwallet.ui.receive.ReceiveViewModel
|
|
import com.bitcointxoko.gudariwallet.ui.send.SendEvent
|
|
import com.bitcointxoko.gudariwallet.ui.send.SendStrings
|
|
import com.bitcointxoko.gudariwallet.ui.send.SendViewModel
|
|
import kotlinx.coroutines.flow.SharingStarted
|
|
import kotlinx.coroutines.flow.map
|
|
import kotlinx.coroutines.flow.stateIn
|
|
|
|
private const val TAG = "WalletViewModel"
|
|
|
|
class WalletViewModel(
|
|
val repo : WalletRepository,
|
|
val aliasRepo : NodeAliasRepository,
|
|
val paymentCache: PaymentCacheRepository,
|
|
val nwcCache : NwcCacheRepository,
|
|
val contactRepo : ContactRepository,
|
|
private val balancePrefs: BalancePrefsStore,
|
|
private val lnAddressStore : LightningAddressStore,
|
|
val balanceVm : BalanceViewModel,
|
|
val fiatVm : FiatViewModel,
|
|
val receiveVm : ReceiveViewModel,
|
|
val sendVm : SendViewModel,
|
|
val clipboardVm : ClipboardViewModel
|
|
) : ViewModel() {
|
|
val balanceState: StateFlow<BalanceState> get() = balanceVm.balanceState
|
|
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
|
|
val receiveState: StateFlow<ReceiveState> get() = receiveVm.receiveState
|
|
val navigateToReceive: SharedFlow<Unit> get() = receiveVm.navigateToReceive
|
|
fun createInvoice(
|
|
amountSats : Long,
|
|
memo : String,
|
|
failedToCreateInvoice : String,
|
|
) = receiveVm.createInvoice(
|
|
amountSats = amountSats,
|
|
memo = memo,
|
|
failedToCreateInvoice = failedToCreateInvoice,
|
|
)
|
|
fun resetReceiveState() = receiveVm.resetReceiveState()
|
|
fun executeWithdraw(
|
|
k1 : String,
|
|
callback : String,
|
|
amountSats : Long,
|
|
memo : String,
|
|
failedToCreateInvoice : String,
|
|
withdrawRejected : String,
|
|
withdrawFailed : String,
|
|
) = receiveVm.executeWithdraw(
|
|
k1 = k1,
|
|
callback = callback,
|
|
amountSats = amountSats,
|
|
memo = memo,
|
|
failedToCreateInvoice = failedToCreateInvoice,
|
|
withdrawRejected = withdrawRejected,
|
|
withdrawFailed = withdrawFailed,
|
|
)
|
|
val sendState: StateFlow<SendState> get() = sendVm.sendState
|
|
fun refreshBalance() = balanceVm.refreshBalance()
|
|
fun scan(rawInput: String, strings: SendStrings) =
|
|
sendVm.scan(rawInput, strings)
|
|
|
|
fun payBolt11(bolt11: String, strings: SendStrings) =
|
|
sendVm.payBolt11(bolt11, strings)
|
|
|
|
fun payLnurl(
|
|
rawRes : LnurlScanResponse,
|
|
lnurl : String,
|
|
amountMsat: Long,
|
|
comment : String?,
|
|
strings : SendStrings,
|
|
) = sendVm.payLnurl(rawRes, lnurl, amountMsat, comment, strings)
|
|
|
|
fun fetchLnurlInvoice(
|
|
rawRes : LnurlScanResponse,
|
|
lnurl : String,
|
|
domain : String,
|
|
amountMsat: Long,
|
|
comment : String?,
|
|
strings : SendStrings,
|
|
) = sendVm.fetchLnurlInvoice(rawRes, lnurl, domain, amountMsat, comment, strings)
|
|
|
|
fun payLnurlInvoice(state: SendState.LnurlInvoiceReady, strings: SendStrings) =
|
|
sendVm.payLnurlInvoice(state, strings)
|
|
|
|
fun onAuthFailed(message: String, strings: SendStrings) {
|
|
sendVm.onAuthFailed(message, strings)
|
|
}
|
|
fun resetSendState() = sendVm.resetSendState()
|
|
|
|
val sendEvents: SharedFlow<SendEvent> = sendVm.events
|
|
|
|
fun handleWithdrawScanned(raw: LnurlScanResponse, lnurl: String) {
|
|
receiveVm.handleWithdrawResponse(raw, lnurl)
|
|
}
|
|
|
|
val lightningAddress: StateFlow<String?> = lnAddressStore.lightningAddressFlow
|
|
.stateIn(
|
|
scope = viewModelScope,
|
|
started = SharingStarted.Eagerly,
|
|
initialValue = null
|
|
)
|
|
|
|
val clipboardOfferState: StateFlow<ClipboardOfferState> get() = clipboardVm.clipboardOfferState
|
|
fun checkClipboard(text: String?) = clipboardVm.checkClipboard(text)
|
|
fun dismissClipboardOffer() = clipboardVm.dismissClipboardOffer()
|
|
|
|
val contactForCurrentLnurl: StateFlow<ContactEntity?> =
|
|
sendVm.sendState
|
|
.map { state ->
|
|
val lnurl = (state as? SendState.LnurlReady)?.lnurl
|
|
?: return@map null
|
|
contactRepo.findByLnAddress(lnurl)
|
|
?: contactRepo.findByLnurl(lnurl)
|
|
}
|
|
.stateIn(
|
|
scope = viewModelScope,
|
|
started = SharingStarted.WhileSubscribed(5_000),
|
|
initialValue = null
|
|
)
|
|
|
|
fun saveContactFromSend(name: String, addressType: PaymentAddressType?, address: String?) {
|
|
viewModelScope.launch {
|
|
val lnAddress = address?.takeIf { addressType == PaymentAddressType.LIGHTNING_ADDRESS }
|
|
val lnUrl = address?.takeIf { addressType == PaymentAddressType.LNURL }
|
|
contactRepo.createContact(
|
|
localAlias = name,
|
|
lnAddress = lnAddress,
|
|
lnUrl = lnUrl
|
|
)
|
|
}
|
|
}
|
|
|
|
init {
|
|
observePaymentEvents()
|
|
fetchLightningAddress()
|
|
viewModelScope.launch { aliasRepo.warmUp() }
|
|
}
|
|
|
|
// ── WebSocket event collection ────────────────────────────────────────────
|
|
private fun observePaymentEvents() {
|
|
viewModelScope.launch {
|
|
balanceVm.incomingPayment.collect { event ->
|
|
receiveVm.onIncomingPayment(event)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun fetchLightningAddress() {
|
|
// lightningAddressFlow already emits the cached DataStore value immediately
|
|
// via SharingStarted.Eagerly — this coroutine only does the network refresh.
|
|
viewModelScope.launch {
|
|
if (!lnAddressStore.isCacheStale()) {
|
|
Timber.d("LNADDR [TTL ] cache is fresh — skipping network fetch")
|
|
return@launch
|
|
}
|
|
runCatching { repo.getLightningAddress() }
|
|
.onSuccess { address ->
|
|
if (address != null) {
|
|
Timber.d("LNADDR [FETCH OK ] fetched from API: \"$address\" — updating cache")
|
|
lnAddressStore.setLightningAddress(address)
|
|
// StateFlow updates automatically via the DataStore flow
|
|
} else {
|
|
Timber.d("LNADDR [FETCH OK ] fetched from API: no pay link / username configured")
|
|
}
|
|
}
|
|
.onFailure { e ->
|
|
Timber.w("LNADDR [FETCH ERR] API call failed (${e.message}) — falling back to cached value")
|
|
// Nothing to do — DataStore flow already holds the last good value
|
|
}
|
|
}
|
|
}
|
|
} |