commit 26a9121fc7bb75fd3c730cf60b2cde5380843105
parent 9b5703efbb32e40ed0a5ba2f0dd08be2cf45aef3
Author: Iván Ávalos <avalos@disroot.org>
Date: Thu, 7 Nov 2024 13:56:23 +0100
[wallet] Fixes and improvements for all withdrawal flows
Diffstat:
5 files changed, 96 insertions(+), 91 deletions(-)
diff --git a/wallet/src/main/java/net/taler/wallet/HandleUriFragment.kt b/wallet/src/main/java/net/taler/wallet/HandleUriFragment.kt
@@ -35,7 +35,6 @@ import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import net.taler.common.isOnline
import net.taler.common.showError
import net.taler.wallet.compose.LoadingScreen
@@ -128,7 +127,10 @@ class HandleUriFragment: Fragment() {
}
action.startsWith("withdraw-exchange/", ignoreCase = true) -> {
- prepareManualWithdrawal(u2)
+ Log.v(TAG, "navigating!")
+ val args = bundleOf("withdrawExchangeUri" to u2)
+ model.withdrawManager.resetWithdrawal()
+ findNavController().navigate(R.id.action_handleUri_to_promptWithdraw, args)
}
action.startsWith("refund/", ignoreCase = true) -> {
@@ -225,35 +227,6 @@ class HandleUriFragment: Fragment() {
return actionFound
}
- private fun prepareManualWithdrawal(uri: String) {
- model.showProgressBar.value = true
- lifecycleScope.launch(Dispatchers.IO) {
- val response = model.withdrawManager.prepareManualWithdrawal(uri)
- if (response == null) withContext(Dispatchers.Main) {
- model.showProgressBar.value = false
- findNavController().navigate(R.id.errorFragment)
- } else {
- val exchange =
- model.exchangeManager.findExchangeByUrl(response.exchangeBaseUrl)
- if (exchange == null) withContext(Dispatchers.Main) {
- model.showProgressBar.value = false
- showError(R.string.exchange_add_error)
- findNavController().navigateUp()
- } else {
- model.exchangeManager.withdrawalExchange = exchange
- withContext(Dispatchers.Main) {
- model.showProgressBar.value = false
- val args = bundleOf(
- "exchangeBaseUrl" to response.exchangeBaseUrl,
- "amount" to response.amount?.toJSONString(),
- )
- findNavController().navigate(R.id.promptWithdraw, args)
- }
- }
- }
- }
- }
-
private fun onRefundResponse(status: RefundStatus) {
model.showProgressBar.value = false
when (status) {
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -84,6 +84,7 @@ class PromptWithdrawFragment: Fragment() {
savedInstanceState: Bundle?
) = ComposeView(requireContext()).apply {
val withdrawUri = arguments?.getString("withdrawUri")
+ val withdrawExchangeUri = arguments?.getString("withdrawExchangeUri")
val exchangeBaseUrl = arguments?.getString("exchangeBaseUrl")
val amount = arguments?.getString("amount")?.let { Amount.fromJSONString(it) }
editableCurrency = arguments?.getBoolean("editableCurrency") ?: true
@@ -134,7 +135,11 @@ class PromptWithdrawFragment: Fragment() {
selectExchange()
},
onSelectAmount = { amount ->
- withdrawManager.getWithdrawalDetails(amount = amount, loading = false)
+ withdrawManager.getWithdrawalDetails(
+ amount = amount,
+ // only show loading screen when switching currencies
+ loading = amount.currency != status.currency,
+ )
},
onTosReview = {
// TODO: rewrite ToS review screen in compose
@@ -151,16 +156,22 @@ class PromptWithdrawFragment: Fragment() {
}
}
- LaunchedEffect(exchange?.exchangeBaseUrl) {
- if (withdrawUri != null) {
- // get withdrawal details for taler:// URI
- withdrawManager.getWithdrawalDetails(withdrawUri, loading = true)
- } else {
- withdrawManager.getWithdrawalDetails(
- amount = amount ?: Amount.zero(defaultCurrency),
- exchangeBaseUrl = exchange?.exchangeBaseUrl ?: exchangeBaseUrl,
- loading = true,
- )
+ LaunchedEffect(status.status) {
+ if (status.status == None) {
+ if (withdrawUri != null) {
+ // get withdrawal details for taler://withdraw URI
+ withdrawManager.getWithdrawalDetails(withdrawUri, loading = true)
+ } else if (withdrawExchangeUri != null) {
+ // get withdrawal details for taler://withdraw-exchange URI
+ withdrawManager.prepareManualWithdrawal(withdrawExchangeUri)
+ } else {
+ // get withdrawal details for available data
+ withdrawManager.getWithdrawalDetails(
+ amount = amount ?: Amount.zero(defaultCurrency),
+ exchangeBaseUrl = exchangeBaseUrl,
+ loading = true,
+ )
+ }
}
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -240,12 +240,6 @@ class WithdrawManager(
_withdrawTestStatus.value = TestWithdrawStatus.None
}
- fun setWithdrawalExchange(exchangeBaseUrl: String) {
- _withdrawStatus.update { value ->
- value.copy(exchangeBaseUrl = exchangeBaseUrl)
- }
- }
-
fun getWithdrawalDetails(
uri: String,
loading: Boolean = true,
@@ -253,7 +247,7 @@ class WithdrawManager(
_withdrawStatus.update {
WithdrawStatus(
talerWithdrawUri = uri,
- status = Loading,
+ status = if (loading) Loading else Updating,
)
}
@@ -285,72 +279,88 @@ class WithdrawManager(
fun getWithdrawalDetails(
amount: Amount? = null,
exchangeBaseUrl: String? = null,
- uriInfo: WithdrawalDetailsForUri? = null,
loading: Boolean = true,
) = scope.launch {
val status = _withdrawStatus.getAndUpdate { value ->
value.copy(status = if (loading) Loading else Updating)
}
- val a = amount
- // reset amount to zero when exchangeBaseUrl changes but amount is not set
- ?: exchangeBaseUrl?.let { url -> exchangeManager.findExchangeByUrl(url)?.currency?.let { Amount.zero(it) } }
- ?: status.uriInfo?.amount
- ?: status.amountInfo?.amountRaw
- ?: error("no amount for withdrawal")
-
- val exchange = if (exchangeBaseUrl == null && amount?.currency != status.currency) {
- // find exchange from currency in absence of exchangeBaseUrl
- amount?.currency?.let { exchangeManager.findExchange(it) }
+ val ex: ExchangeItem
+ val am: Amount?
+
+ // use cases:
+ if (amount != null && exchangeBaseUrl != null) {
+ // 1. user sets both to null state
+ // => they are processed as-is
+ ex = exchangeManager.findExchangeByUrl(exchangeBaseUrl)
+ ?: error("could not resolve exchange")
+ am = amount
+ } else if (amount != null) {
+ // 2a user updates amount
+ // => amount is updated
+ // => exchange URL is recycled (unless currency changes)
+ // 2b. user sets amount to null state
+ // => exchange URL is calculated from amount
+ ex = if (status.exchangeBaseUrl != null
+ && status.currency == amount.currency) {
+ exchangeManager.findExchangeByUrl(status.exchangeBaseUrl)
+ ?: error("could not resolve exchange")
+ } else {
+ exchangeManager.findExchange(amount.currency)
+ ?: error("could not resolve exchange")
+ }
+ am = amount
+ } else if (exchangeBaseUrl != null) {
+ // 3a. user updates exchange URL
+ // => amount is reset to zero (always)
+ // => exchangeURL is updated
+ // 3b. user sets exchange URL to null state
+ // => amount is calculated from exchange URL
+ ex = exchangeManager.findExchangeByUrl(exchangeBaseUrl)
+ ?: error("could not resolve exchange")
+ am = ex.currency
+ ?.let { Amount.zero(it) }
+ ?: error("could not resolve currency")
} else {
- exchangeBaseUrl?.let { exchangeManager.findExchangeByUrl(it) }
- ?: status.exchangeBaseUrl?.let { exchangeManager.findExchangeByUrl(it) }
- ?: amount?.currency?.let { exchangeManager.findExchange(it) }
- } ?: error("no exchange for withdrawal")
+ error("no parameters specified")
+ }
api.request("getWithdrawalDetailsForAmount", WithdrawalDetailsForAmount.serializer()) {
- put("exchangeBaseUrl", exchange.exchangeBaseUrl)
- put("amount", a.toJSONString())
+ put("exchangeBaseUrl", ex.exchangeBaseUrl)
+ put("amount", am.toJSONString())
}.onError { error ->
handleError("getWithdrawalDetailsForAmount", error)
}.onSuccess { details ->
scope.launch {
- if (exchange.tosStatus == ExchangeTosStatus.Accepted) {
- _withdrawStatus.update { value ->
- value.copy(
- status = InfoReceived,
- exchangeBaseUrl = exchange.exchangeBaseUrl,
- uriInfo = uriInfo ?: value.uriInfo,
- amountInfo = details,
- currency = details.amountRaw.currency,
- )
- }
- } else {
- _withdrawStatus.update { value ->
- value.copy(
- status = TosReviewRequired,
- amountInfo = details,
- currency = details.amountRaw.currency,
- exchangeBaseUrl = exchange.exchangeBaseUrl,
- )
- }
+ _withdrawStatus.update { value ->
+ value.copy(
+ status = if (ex.tosStatus != ExchangeTosStatus.Accepted) {
+ TosReviewRequired
+ } else {
+ InfoReceived
+ },
+ exchangeBaseUrl = ex.exchangeBaseUrl,
+ amountInfo = details,
+ currency = details.amountRaw.currency,
+ )
}
}
}
}
- @WorkerThread
- suspend fun prepareManualWithdrawal(uri: String): WithdrawExchangeResponse? {
+ @UiThread
+ fun prepareManualWithdrawal(uri: String) = scope.launch {
_withdrawStatus.value = WithdrawStatus(status = Loading)
- var response: WithdrawExchangeResponse? = null
api.request("prepareWithdrawExchange", WithdrawExchangeResponse.serializer()) {
put("talerUri", uri)
}.onError {
handleError("prepareWithdrawExchange", it)
}.onSuccess {
- response = it
+ getWithdrawalDetails(
+ amount = it.amount,
+ exchangeBaseUrl = it.exchangeBaseUrl,
+ )
}
- return response
}
@UiThread
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawalShowInfo.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawalShowInfo.kt
@@ -116,7 +116,14 @@ fun WithdrawalShowInfo(
amount = selectedAmount.withSpec(spec),
currencies = currencies,
editableCurrency = editableCurrency,
- onAmountChanged = { selectedAmount = it },
+ onAmountChanged = { amount ->
+ selectedAmount = if (amount.currency != status.currency) {
+ // if amount changes, reset to zero!
+ Amount.zero(amount.currency)
+ } else {
+ amount
+ }
+ },
label = { Text(stringResource(R.string.amount_withdraw)) },
isError = selectedAmount.isZero() || maxAmount != null && selectedAmount > maxAmount,
supportingText = {
diff --git a/wallet/src/main/res/navigation/nav_graph.xml b/wallet/src/main/res/navigation/nav_graph.xml
@@ -258,6 +258,10 @@
app:nullable="true"
app:argType="string" />
<argument
+ android:name="withdrawExchangeUri"
+ app:nullable="true"
+ app:argType="string" />
+ <argument
android:name="editableCurrency"
android:defaultValue="true"
app:argType="boolean" />