refactor: clean up state in SendViewModel
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package com.bitcointxoko.gudariwallet.ui.send
|
package com.bitcointxoko.gudariwallet.ui.send
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.bitcointxoko.gudariwallet.api.LnurlScanResponse
|
import com.bitcointxoko.gudariwallet.api.LnurlScanResponse
|
||||||
@@ -11,7 +10,6 @@ import com.bitcointxoko.gudariwallet.data.LnurlCacheRepository
|
|||||||
import com.bitcointxoko.gudariwallet.data.NodeAliasRepository
|
import com.bitcointxoko.gudariwallet.data.NodeAliasRepository
|
||||||
import com.bitcointxoko.gudariwallet.data.WalletRepository
|
import com.bitcointxoko.gudariwallet.data.WalletRepository
|
||||||
import com.bitcointxoko.gudariwallet.ui.SendState
|
import com.bitcointxoko.gudariwallet.ui.SendState
|
||||||
import com.bitcointxoko.gudariwallet.util.BiometricHelper
|
|
||||||
import com.bitcointxoko.gudariwallet.util.Bip21Parser
|
import com.bitcointxoko.gudariwallet.util.Bip21Parser
|
||||||
import com.bitcointxoko.gudariwallet.util.LnurlMetadataParser
|
import com.bitcointxoko.gudariwallet.util.LnurlMetadataParser
|
||||||
import com.bitcointxoko.gudariwallet.util.RouteHintAnalyzer
|
import com.bitcointxoko.gudariwallet.util.RouteHintAnalyzer
|
||||||
@@ -24,6 +22,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
private const val TAG = "SendViewModel"
|
private const val TAG = "SendViewModel"
|
||||||
@@ -76,7 +75,6 @@ class SendViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Entry point ──────────────────────────────────────────────────────────
|
// ── Entry point ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fun scan(rawInput: String, strings: SendStrings) {
|
fun scan(rawInput: String, strings: SendStrings) {
|
||||||
val normalized = SendInputDetector.normalize(rawInput)
|
val normalized = SendInputDetector.normalize(rawInput)
|
||||||
val type = SendInputDetector.detect(rawInput)
|
val type = SendInputDetector.detect(rawInput)
|
||||||
@@ -98,22 +96,24 @@ class SendViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Payment ──────────────────────────────────────────────────────────────
|
// ── Payment ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fun payBolt11(bolt11: String, strings: SendStrings) {
|
fun payBolt11(bolt11: String, strings: SendStrings) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val decoded = _sendState.value as? SendState.Bolt11Decoded
|
val decoded = _sendState.value as? SendState.Bolt11Decoded
|
||||||
val amountSats = decoded?.amountSats ?: 0L
|
if (decoded == null) {
|
||||||
|
Log.w(TAG, "SEND [PAY BOLT11] called in unexpected state: ${_sendState.value::class.simpleName}")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
_sendState.value = SendState.Paying
|
_sendState.value = SendState.Paying
|
||||||
Log.d(TAG, "SEND [PAY BOLT11 START] Sending invoice to server")
|
Log.d(TAG, "SEND [PAY BOLT11 START] Sending invoice to server")
|
||||||
runCatching { repo.payBolt11(bolt11) }
|
runCatching { repo.payBolt11(bolt11) }
|
||||||
.onSuccess { response ->
|
.onSuccess { response ->
|
||||||
handlePaymentResult(
|
handlePaymentResult(
|
||||||
paymentHash = response.paymentHash,
|
paymentHash = response.paymentHash,
|
||||||
amountSats,
|
amountSats = decoded.amountSats,
|
||||||
bolt11 = decoded?.bolt11,
|
bolt11 = decoded.bolt11,
|
||||||
memo = decoded?.memo,
|
memo = decoded.memo,
|
||||||
pubkey = decoded?.payee,
|
pubkey = decoded.payee,
|
||||||
alias = decoded?.nodeAlias,
|
alias = decoded.nodeAlias,
|
||||||
strings
|
strings
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,6 @@ class SendViewModel(
|
|||||||
_events.tryEmit(SendEvent.PaymentCompleted)
|
_events.tryEmit(SendEvent.PaymentCompleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun payLnurl(
|
fun payLnurl(
|
||||||
rawRes : LnurlScanResponse,
|
rawRes : LnurlScanResponse,
|
||||||
lnurl : String,
|
lnurl : String,
|
||||||
@@ -192,7 +191,17 @@ class SendViewModel(
|
|||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_sendState.value = SendState.Paying
|
_sendState.value = SendState.Paying
|
||||||
|
executeLnurlPayment(rawRes, lnurl, amountMsat, comment, strings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun executeLnurlPayment(
|
||||||
|
rawRes : LnurlScanResponse,
|
||||||
|
lnurl : String,
|
||||||
|
amountMsat: Long,
|
||||||
|
comment : String?,
|
||||||
|
strings : SendStrings,
|
||||||
|
) {
|
||||||
val result = payer.pay(rawRes, lnurl, amountMsat, comment)
|
val result = payer.pay(rawRes, lnurl, amountMsat, comment)
|
||||||
|
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
@@ -214,8 +223,6 @@ class SendViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun fetchLnurlInvoice(
|
fun fetchLnurlInvoice(
|
||||||
rawRes : LnurlScanResponse,
|
rawRes : LnurlScanResponse,
|
||||||
@@ -278,7 +285,6 @@ class SendViewModel(
|
|||||||
resolveAliasesInBackground(
|
resolveAliasesInBackground(
|
||||||
payee = decoded.payee,
|
payee = decoded.payee,
|
||||||
routeHints = decoded.routeHints,
|
routeHints = decoded.routeHints,
|
||||||
readState = { _sendState.value },
|
|
||||||
copyAlias = { state, alias ->
|
copyAlias = { state, alias ->
|
||||||
(state as? SendState.LnurlInvoiceReady)
|
(state as? SendState.LnurlInvoiceReady)
|
||||||
?.takeIf { it.payee == decoded.payee }
|
?.takeIf { it.payee == decoded.payee }
|
||||||
@@ -295,9 +301,7 @@ class SendViewModel(
|
|||||||
fun payLnurlInvoice(state: SendState.LnurlInvoiceReady, strings: SendStrings) {
|
fun payLnurlInvoice(state: SendState.LnurlInvoiceReady, strings: SendStrings) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_sendState.value = SendState.Paying
|
_sendState.value = SendState.Paying
|
||||||
|
|
||||||
val directResult = runCatching { repo.payBolt11(state.bolt11) }
|
val directResult = runCatching { repo.payBolt11(state.bolt11) }
|
||||||
|
|
||||||
if (directResult.isSuccess) {
|
if (directResult.isSuccess) {
|
||||||
val paymentHash = directResult.getOrThrow().paymentHash
|
val paymentHash = directResult.getOrThrow().paymentHash
|
||||||
handlePaymentResult(
|
handlePaymentResult(
|
||||||
@@ -311,17 +315,9 @@ class SendViewModel(
|
|||||||
)
|
)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.w(TAG, "LNURL [PAY INVOICE DIRECT FAIL] " +
|
Log.w(TAG, "LNURL [PAY INVOICE DIRECT FAIL] " +
|
||||||
"${directResult.exceptionOrNull()?.message} — falling back to payLnurl")
|
"${directResult.exceptionOrNull()?.message} — falling back to payLnurl")
|
||||||
|
executeLnurlPayment(state.rawRes, state.lnurl, state.amountMsat, state.comment, strings)
|
||||||
payLnurl(
|
|
||||||
rawRes = state.rawRes,
|
|
||||||
lnurl = state.lnurl,
|
|
||||||
amountMsat = state.amountMsat,
|
|
||||||
comment = state.comment,
|
|
||||||
strings = strings,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,10 +354,7 @@ class SendViewModel(
|
|||||||
.onSuccess { raw ->
|
.onSuccess { raw ->
|
||||||
Log.d(TAG, "LUD17 [RESPONSE] tag=${raw.tag}")
|
Log.d(TAG, "LUD17 [RESPONSE] tag=${raw.tag}")
|
||||||
when (raw.tag) {
|
when (raw.tag) {
|
||||||
"withdrawRequest" -> {
|
"withdrawRequest" -> {emitWithdrawScanned(raw, httpsUrl)}
|
||||||
_sendState.value = SendState.Idle
|
|
||||||
_events.tryEmit(SendEvent.WithdrawScanned(raw, httpsUrl))
|
|
||||||
}
|
|
||||||
"payRequest" -> {
|
"payRequest" -> {
|
||||||
lnurlCache.put(httpsUrl, raw)
|
lnurlCache.put(httpsUrl, raw)
|
||||||
_sendState.value = buildLnurlReadyState(raw, httpsUrl)
|
_sendState.value = buildLnurlReadyState(raw, httpsUrl)
|
||||||
@@ -405,7 +398,6 @@ class SendViewModel(
|
|||||||
resolveAliasesInBackground(
|
resolveAliasesInBackground(
|
||||||
payee = decoded.payee,
|
payee = decoded.payee,
|
||||||
routeHints = decoded.routeHints,
|
routeHints = decoded.routeHints,
|
||||||
readState = { _sendState.value },
|
|
||||||
copyAlias = { state, alias ->
|
copyAlias = { state, alias ->
|
||||||
(state as? SendState.Bolt11Decoded)
|
(state as? SendState.Bolt11Decoded)
|
||||||
?.takeIf { it.payee == decoded.payee }
|
?.takeIf { it.payee == decoded.payee }
|
||||||
@@ -429,18 +421,11 @@ class SendViewModel(
|
|||||||
private fun handleLnurlScan(normalized: String, strings: SendStrings) {
|
private fun handleLnurlScan(normalized: String, strings: SendStrings) {
|
||||||
_sendState.value = SendState.Scanning
|
_sendState.value = SendState.Scanning
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val result = scanner.scan(normalized)
|
val (raw, fromCache) = scanner.scan(normalized).getOrElse { e ->
|
||||||
if (result.isFailure) {
|
_sendState.value = SendState.Error(parseApiError(e, strings.couldNotResolveAddress))
|
||||||
_sendState.value = SendState.Error(
|
|
||||||
parseApiError(
|
|
||||||
result.exceptionOrNull() ?: Exception("Scan failed"),
|
|
||||||
strings.couldNotResolveAddress
|
|
||||||
) )
|
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val (raw, fromCache) = result.getOrThrow()
|
|
||||||
|
|
||||||
val protocolError = lnurlProtocolError(raw)
|
val protocolError = lnurlProtocolError(raw)
|
||||||
if (protocolError != null) {
|
if (protocolError != null) {
|
||||||
Log.w(TAG, "LNURL [SCAN PROTO ERR] $protocolError")
|
Log.w(TAG, "LNURL [SCAN PROTO ERR] $protocolError")
|
||||||
@@ -452,7 +437,7 @@ class SendViewModel(
|
|||||||
|
|
||||||
when (raw.tag) {
|
when (raw.tag) {
|
||||||
"payRequest" -> _sendState.value = buildLnurlReadyState(raw, normalized)
|
"payRequest" -> _sendState.value = buildLnurlReadyState(raw, normalized)
|
||||||
"withdrawRequest" -> { _sendState.value = SendState.Idle; _events.tryEmit(SendEvent.WithdrawScanned(raw, normalized)) }
|
"withdrawRequest" -> emitWithdrawScanned(raw, normalized)
|
||||||
null -> _sendState.value = SendState.Error(strings.emptyResponse)
|
null -> _sendState.value = SendState.Error(strings.emptyResponse)
|
||||||
else -> _sendState.value = SendState.Error(strings.unsupportedType(raw.tag))
|
else -> _sendState.value = SendState.Error(strings.unsupportedType(raw.tag))
|
||||||
}
|
}
|
||||||
@@ -461,6 +446,10 @@ class SendViewModel(
|
|||||||
|
|
||||||
// ── Private helpers ──────────────────────────────────────────────────────
|
// ── Private helpers ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private fun emitWithdrawScanned(raw: LnurlScanResponse, lnurl: String) {
|
||||||
|
_events.tryEmit(SendEvent.WithdrawScanned(raw, lnurl))
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildLnurlReadyState(raw: LnurlScanResponse, lnurl: String): SendState.LnurlReady {
|
private fun buildLnurlReadyState(raw: LnurlScanResponse, lnurl: String): SendState.LnurlReady {
|
||||||
return SendState.LnurlReady(
|
return SendState.LnurlReady(
|
||||||
callback = raw.callback ?: "",
|
callback = raw.callback ?: "",
|
||||||
@@ -477,22 +466,19 @@ class SendViewModel(
|
|||||||
private fun resolveAliasesInBackground(
|
private fun resolveAliasesInBackground(
|
||||||
payee : String?,
|
payee : String?,
|
||||||
routeHints : List<RouteHintHop>,
|
routeHints : List<RouteHintHop>,
|
||||||
readState : () -> SendState,
|
|
||||||
copyAlias : (SendState, String) -> SendState?,
|
copyAlias : (SendState, String) -> SendState?,
|
||||||
copyHint : (SendState, String, String) -> SendState?
|
copyHint : (SendState, String, String) -> SendState?
|
||||||
) {
|
) {
|
||||||
payee?.let { pubkey ->
|
payee?.let { pubkey ->
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val alias = aliasRepo.resolve(pubkey) ?: return@launch
|
val alias = aliasRepo.resolve(pubkey) ?: return@launch
|
||||||
val current = readState()
|
_sendState.update { copyAlias(it, alias) ?: it }
|
||||||
copyAlias(current, alias)?.let { _sendState.value = it }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
routeHints.map { it.publicKey }.distinct().forEach { pubkey ->
|
routeHints.map { it.publicKey }.distinct().forEach { pubkey ->
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val alias = aliasRepo.resolve(pubkey) ?: return@launch
|
val alias = aliasRepo.resolve(pubkey) ?: return@launch
|
||||||
val current = readState()
|
_sendState.update { copyHint(it, pubkey, alias) ?: it }
|
||||||
copyHint(current, pubkey, alias)?.let { _sendState.value = it }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user