refactor: WalletViewModel extract ClipboardViewModel
This commit is contained in:
@@ -4,12 +4,9 @@ import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitcointxoko.gudariwallet.api.LnurlScanResponse
|
||||
import com.bitcointxoko.gudariwallet.data.LnurlCacheRepository
|
||||
import com.bitcointxoko.gudariwallet.data.NodeAliasRepository
|
||||
import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository
|
||||
import com.bitcointxoko.gudariwallet.data.WalletRepository
|
||||
import com.bitcointxoko.gudariwallet.util.SendInputDetector
|
||||
import com.bitcointxoko.gudariwallet.util.SendInputType
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -17,6 +14,7 @@ import kotlinx.coroutines.launch
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.bitcointxoko.gudariwallet.data.BalancePrefsStore
|
||||
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.SendViewModel
|
||||
@@ -26,16 +24,15 @@ private const val TAG = "WalletViewModel"
|
||||
class WalletViewModel(
|
||||
val repo : WalletRepository,
|
||||
val aliasRepo : NodeAliasRepository,
|
||||
val lnurlCache: LnurlCacheRepository,
|
||||
val paymentCache: PaymentCacheRepository,
|
||||
private val balancePrefs: BalancePrefsStore,
|
||||
val balanceVm : BalanceViewModel,
|
||||
val fiatVm : FiatViewModel,
|
||||
val receiveVm : ReceiveViewModel,
|
||||
val sendVm : SendViewModel,
|
||||
val clipboardVm : ClipboardViewModel,
|
||||
val lightningAddress: MutableStateFlow<String?>,
|
||||
) : ViewModel() {
|
||||
|
||||
// ── Balance state ─────────────────────────────────────────────────────────
|
||||
val balanceState: StateFlow<BalanceState> get() = balanceVm.balanceState
|
||||
val balanceHidden: StateFlow<Boolean> get() = fiatVm.balanceHidden
|
||||
val selectedCurrency: StateFlow<String?> get() = fiatVm.selectedCurrency
|
||||
@@ -52,6 +49,7 @@ class WalletViewModel(
|
||||
fun executeWithdraw(k1: String, callback: String, amountSats: Long, memo: String) =
|
||||
receiveVm.executeWithdraw(k1, callback, amountSats, memo)
|
||||
val sendState: StateFlow<SendState> get() = sendVm.sendState
|
||||
fun refreshBalance() = balanceVm.refreshBalance()
|
||||
fun scan(rawInput: String) = sendVm.scan(rawInput)
|
||||
fun payBolt11(bolt11: String) = sendVm.payBolt11(bolt11)
|
||||
fun payLnurl(rawRes: LnurlScanResponse, lnurl: String, amountMsat: Long, comment: String?) =
|
||||
@@ -62,12 +60,9 @@ class WalletViewModel(
|
||||
fun requestPayment(activity: FragmentActivity, subtitle: String, pay: () -> Unit) =
|
||||
sendVm.requestPayment(activity, subtitle, pay)
|
||||
fun resetSendState() = sendVm.resetSendState()
|
||||
|
||||
// ── Other states ──────────────────────────────────────────────────────────
|
||||
private val _lightningAddress = MutableStateFlow<String?>(balancePrefs.lightningAddress)
|
||||
val lightningAddress: StateFlow<String?> = _lightningAddress
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────
|
||||
val clipboardOfferState: StateFlow<ClipboardOfferState> get() = clipboardVm.clipboardOfferState
|
||||
fun checkClipboard(text: String?) = clipboardVm.checkClipboard(text)
|
||||
fun dismissClipboardOffer() = clipboardVm.dismissClipboardOffer()
|
||||
|
||||
init {
|
||||
observePaymentEvents()
|
||||
@@ -91,7 +86,7 @@ class WalletViewModel(
|
||||
.onSuccess { address ->
|
||||
if (address != null) {
|
||||
Log.d(TAG, "LNADDR [FETCH OK ] $address")
|
||||
_lightningAddress.value = address
|
||||
lightningAddress.value = address
|
||||
balancePrefs.setLightningAddress(address)
|
||||
} else {
|
||||
Log.d(TAG, "LNADDR [FETCH OK ] no pay link / username found")
|
||||
@@ -103,47 +98,4 @@ class WalletViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Clipboard offer ───────────────────────────────────────────────────────
|
||||
|
||||
private var lastOfferedClipboard: String? = null
|
||||
|
||||
private val _clipboardOfferState = MutableStateFlow<ClipboardOfferState>(ClipboardOfferState.None)
|
||||
val clipboardOfferState: StateFlow<ClipboardOfferState> = _clipboardOfferState
|
||||
|
||||
fun checkClipboard(text: String?) {
|
||||
Log.d("Clipboard", "checkClipboard called with: $text")
|
||||
val trimmed = text?.trim().takeIf { !it.isNullOrBlank() } ?: return
|
||||
|
||||
// ── Deduplicate: don't re-offer the same string twice ─────────────────
|
||||
if (trimmed == lastOfferedClipboard) return
|
||||
|
||||
// ── Guard: ignore our own invoice ─────────────────────────────────────
|
||||
val ownInvoice = (receiveState.value as? ReceiveState.InvoiceReady)?.bolt11
|
||||
if (ownInvoice != null && trimmed.equals(ownInvoice, ignoreCase = true)) {
|
||||
Log.d("Clipboard", "Skipping own invoice")
|
||||
lastOfferedClipboard = trimmed // still mark as seen so focus re-checks don't re-trigger
|
||||
return
|
||||
}
|
||||
|
||||
// ── Guard: ignore our own lightning address ────────────────────────────
|
||||
val ownAddress = _lightningAddress.value
|
||||
if (ownAddress != null && trimmed.equals(ownAddress, ignoreCase = true)) {
|
||||
Log.d("Clipboard", "Skipping own lightning address")
|
||||
lastOfferedClipboard = trimmed
|
||||
return
|
||||
}
|
||||
|
||||
val type = SendInputDetector.detect(trimmed)
|
||||
if (type == SendInputType.Unknown) return
|
||||
|
||||
lastOfferedClipboard = trimmed
|
||||
_clipboardOfferState.value = ClipboardOfferState.Detected(raw = trimmed, inputType = type)
|
||||
}
|
||||
|
||||
fun dismissClipboardOffer() {
|
||||
_clipboardOfferState.value = ClipboardOfferState.None
|
||||
}
|
||||
|
||||
fun refreshBalance() = balanceVm.refreshBalance()
|
||||
}
|
||||
@@ -13,9 +13,11 @@ import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository
|
||||
import com.bitcointxoko.gudariwallet.security.EncryptedSecretStore
|
||||
import com.bitcointxoko.gudariwallet.service.NodeAliasService
|
||||
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.SendViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class WalletViewModelFactory(private val app: Application) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
@@ -41,6 +43,14 @@ class WalletViewModelFactory(private val app: Application) : ViewModelProvider.F
|
||||
receiveVm.handleWithdrawResponse(raw, lnurl)
|
||||
}
|
||||
)
|
||||
|
||||
val lightningAddress = MutableStateFlow<String?>(balancePrefs.lightningAddress)
|
||||
|
||||
val clipboardVm = ClipboardViewModel(
|
||||
ownInvoice = { (receiveVm.receiveState.value as? ReceiveState.InvoiceReady)?.bolt11 },
|
||||
ownAddress = { lightningAddress.value }
|
||||
)
|
||||
|
||||
balanceVm.onBalanceRefreshed = { fiatVm.onBalanceRefreshed() }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -53,7 +63,9 @@ class WalletViewModelFactory(private val app: Application) : ViewModelProvider.F
|
||||
balanceVm = balanceVm,
|
||||
fiatVm = fiatVm,
|
||||
receiveVm = receiveVm,
|
||||
sendVm = sendVm
|
||||
sendVm = sendVm,
|
||||
clipboardVm = clipboardVm,
|
||||
lightningAddress = lightningAddress
|
||||
) as T
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.bitcointxoko.gudariwallet.ui.clipboard
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.bitcointxoko.gudariwallet.ui.ClipboardOfferState
|
||||
import com.bitcointxoko.gudariwallet.ui.ReceiveState
|
||||
import com.bitcointxoko.gudariwallet.util.SendInputDetector
|
||||
import com.bitcointxoko.gudariwallet.util.SendInputType
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow as SF
|
||||
|
||||
private const val TAG = "ClipboardViewModel"
|
||||
|
||||
class ClipboardViewModel(
|
||||
// Lambdas instead of direct VM references — keeps ClipboardViewModel
|
||||
// independently testable with no peer ViewModel dependencies.
|
||||
private val ownInvoice : () -> String?, // from ReceiveViewModel
|
||||
private val ownAddress : () -> String?, // from WalletViewModel._lightningAddress
|
||||
) : ViewModel() {
|
||||
|
||||
private var lastOfferedClipboard: String? = null
|
||||
|
||||
private val _clipboardOfferState = MutableStateFlow<ClipboardOfferState>(ClipboardOfferState.None)
|
||||
val clipboardOfferState: StateFlow<ClipboardOfferState> = _clipboardOfferState
|
||||
|
||||
fun checkClipboard(text: String?) {
|
||||
Log.d(TAG, "checkClipboard called with: $text")
|
||||
val trimmed = text?.trim().takeIf { !it.isNullOrBlank() } ?: return
|
||||
|
||||
// ── Deduplicate: don't re-offer the same string twice ─────────────────
|
||||
if (trimmed == lastOfferedClipboard) return
|
||||
|
||||
// ── Guard: ignore our own invoice ──────────────────────────────────────
|
||||
val invoice = ownInvoice()
|
||||
if (invoice != null && trimmed.equals(invoice, ignoreCase = true)) {
|
||||
Log.d(TAG, "Skipping own invoice")
|
||||
lastOfferedClipboard = trimmed // mark as seen so re-focus doesn't re-trigger
|
||||
return
|
||||
}
|
||||
|
||||
// ── Guard: ignore our own lightning address ────────────────────────────
|
||||
val address = ownAddress()
|
||||
if (address != null && trimmed.equals(address, ignoreCase = true)) {
|
||||
Log.d(TAG, "Skipping own lightning address")
|
||||
lastOfferedClipboard = trimmed
|
||||
return
|
||||
}
|
||||
|
||||
val type = SendInputDetector.detect(trimmed)
|
||||
if (type == SendInputType.Unknown) return
|
||||
|
||||
lastOfferedClipboard = trimmed
|
||||
_clipboardOfferState.value = ClipboardOfferState.Detected(raw = trimmed, inputType = type)
|
||||
}
|
||||
|
||||
fun dismissClipboardOffer() {
|
||||
_clipboardOfferState.value = ClipboardOfferState.None
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user