taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

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:
Mwallet/src/main/java/net/taler/wallet/HandleUriFragment.kt | 35++++-------------------------------
Mwallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt | 33++++++++++++++++++++++-----------
Mwallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt | 106+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mwallet/src/main/java/net/taler/wallet/withdraw/WithdrawalShowInfo.kt | 9++++++++-
Mwallet/src/main/res/navigation/nav_graph.xml | 4++++
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" />