From b38d99fd60737a4087948bd3e4ee6b18d756c639 Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Wed, 16 Aug 2023 16:03:45 +0200 Subject: [wallet] Improved templates UX and PoS confirmation codes --- .../taler/wallet/payment/PayTemplateFragment.kt | 152 ++++++++++++++------- .../net/taler/wallet/payment/PaymentManager.kt | 1 + .../wallet/payment/TransactionPaymentComposable.kt | 6 + .../net/taler/wallet/transactions/Transactions.kt | 1 + 4 files changed, 110 insertions(+), 50 deletions(-) (limited to 'wallet/src/main/java/net/taler') diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt index ab6dada..633ab20 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt @@ -21,16 +21,21 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -43,6 +48,8 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.asFlow +import androidx.navigation.NavOptions import androidx.navigation.fragment.findNavController import net.taler.common.Amount import net.taler.common.AmountParserException @@ -84,15 +91,22 @@ class PayTemplateFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + // TODO: this is not ideal, if the template is fixed, the + // user shouldn't even have to go through this fragment. if (uri?.queryParameterNames?.isEmpty() == true) { createOrder(emptyMap()) } } - fun createOrder(params: Map) { - uriString?.let { - model.paymentManager.preparePayForTemplate(it, params,).invokeOnCompletion { - findNavController().navigate(R.id.action_global_promptPayment) + private fun createOrder(params: Map) { + uriString ?: return + model.paymentManager.preparePayForTemplate(uriString!!, params,).invokeOnCompletion { + if (model.paymentManager.payStatus.value is PayStatus.Prepared) { + val navOptions = NavOptions.Builder() + .setPopUpTo(R.id.nav_main, true) + .build() + findNavController() + .navigate(R.id.action_global_promptPayment, null, navOptions) } } } @@ -126,61 +140,99 @@ fun PayTemplateComposable( } else null, ) } - Column(horizontalAlignment = Alignment.End) { - if ("summary" in queryParams) { - OutlinedTextField( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - value = summary!!, - isError = summary!!.isBlank(), - onValueChange = { summary = it }, - singleLine = true, - label = { Text(stringResource(R.string.withdraw_manual_ready_subject)) }, - ) - } + val payStatus by model.paymentManager.payStatus.asFlow() + .collectAsState(initial = PayStatus.None) - if ("amount" in queryParams) { - AmountField( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), - amount = amount!!, - currencies = currencies, - onAmountChosen = { amount = it }, + // If wallet is empty, there's no way the user can pay something + if (payStatus is PayStatus.InsufficientBalance || currencies.isEmpty()) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + Text( + stringResource(R.string.payment_balance_insufficient), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.error, ) } + } else when (payStatus) { + is PayStatus.None -> { + Column(horizontalAlignment = Alignment.End) { + if ("summary" in queryParams) { + OutlinedTextField( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + value = summary!!, + isError = summary!!.isBlank(), + onValueChange = { summary = it }, + singleLine = true, + label = { Text(stringResource(R.string.withdraw_manual_ready_subject)) }, + ) + } - Button( - modifier = Modifier.padding(16.dp), - enabled = summary == null || summary!!.isNotBlank(), - onClick = { - if (amount != null) { - val result = model.createAmount( - amount!!.amountStr, - amount!!.currency, + if ("amount" in queryParams) { + AmountField( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + amount = amount!!, + currencies = currencies, + onAmountChosen = { amount = it }, ) - when (result) { - AmountResult.InsufficientBalance -> { - fragment.showError(R.string.payment_balance_insufficient) - } - AmountResult.InvalidAmount -> { - fragment.showError(R.string.receive_amount_invalid) - } - else -> { - onSubmit( - mutableMapOf().apply { - if (summary != null) put("summary", summary!!) - if (amount != null) put("amount", amount!!.toJSONString()) - } + } + + Button( + modifier = Modifier.padding(16.dp), + enabled = summary == null || summary!!.isNotBlank(), + onClick = { + if (amount != null) { + val result = model.createAmount( + amount!!.amountStr, + amount!!.currency, ) + when (result) { + AmountResult.InsufficientBalance -> { + fragment.showError(R.string.payment_balance_insufficient) + } + AmountResult.InvalidAmount -> { + fragment.showError(R.string.receive_amount_invalid) + } + else -> { + onSubmit( + mutableMapOf().apply { + if (summary != null) put("summary", summary!!) + if (amount != null) put("amount", amount!!.toJSONString()) + } + ) + } + } } - } + }, + ) { + Text(stringResource(R.string.payment_create_order)) } - }, - ) { - Text(stringResource(R.string.payment_create_order)) + } + } + is PayStatus.Loading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { CircularProgressIndicator() } + } + is PayStatus.AlreadyPaid -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + Text( + stringResource(R.string.payment_already_paid), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.error, + ) + } } + else -> {} } } diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt index 538f2e1..c98e0b2 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt @@ -104,6 +104,7 @@ class PaymentManager( } fun preparePayForTemplate(url: String, params: Map) = scope.launch { + mPayStatus.value = PayStatus.Loading api.request("preparePayForTemplate", PreparePayResponse.serializer()) { put("talerPayTemplateUri", url) put("templateParams", JSONObject().apply { diff --git a/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt index e6a65d1..c08bc76 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt @@ -88,6 +88,12 @@ fun TransactionPaymentComposable( amount = t.amountEffective - t.amountRaw, amountType = AmountType.Negative, ) + if (t.posConfirmation != null) { + TransactionInfoComposable( + label = stringResource(id = R.string.payment_confirmation_code), + info = t.posConfirmation, + ) + } PurchaseDetails(info = t.info) { onFulfill(t.info.fulfillmentUrl ?: "") } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt index c6be73a..536d433 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt @@ -218,6 +218,7 @@ class TransactionPayment( override val error: TalerErrorInfo? = null, override val amountRaw: Amount, override val amountEffective: Amount, + val posConfirmation: String? = null, ) : Transaction() { override val icon = R.drawable.ic_cash_usd_outline override val detailPageNav = R.id.action_nav_transactions_detail_payment -- cgit v1.2.3