diff options
author | Iván Ávalos <avalos@disroot.org> | 2023-05-15 15:34:24 -0600 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2023-07-11 10:31:26 -0300 |
commit | 58f5dc13d51eef09463e215af0ac526216531074 (patch) | |
tree | b8685fc4f3fee8888d6e7923ac7c6f9c59574eea /wallet/src/main/java | |
parent | 255094c5432244ca214055cc9406a31d5b561b5f (diff) | |
download | taler-android-58f5dc13d51eef09463e215af0ac526216531074.tar.gz taler-android-58f5dc13d51eef09463e215af0ac526216531074.tar.bz2 taler-android-58f5dc13d51eef09463e215af0ac526216531074.zip |
[wallet] Implemented DD37 with the new txActions field
Diffstat (limited to 'wallet/src/main/java')
23 files changed, 533 insertions, 247 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt index 3b5bcdc..82bf121 100644 --- a/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt @@ -38,14 +38,19 @@ import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.transactions.AmountType -import net.taler.wallet.transactions.DeleteTransactionComposable import net.taler.wallet.transactions.ErrorTransactionButton -import net.taler.wallet.transactions.ExtendedStatus.Pending +import net.taler.wallet.transactions.TransactionAction +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend import net.taler.wallet.transactions.TransactionAmountComposable import net.taler.wallet.transactions.TransactionDeposit +import net.taler.wallet.transactions.TransactionMajorState.Pending +import net.taler.wallet.transactions.TransactionState +import net.taler.wallet.transactions.TransitionsComposable @Composable -fun TransactionDepositComposable(t: TransactionDeposit, devMode: Boolean?, onDelete: () -> Unit) { +fun TransactionDepositComposable(t: TransactionDeposit, devMode: Boolean?, onTransition: (t: TransactionAction) -> Unit) { val scrollState = rememberScrollState() Column( modifier = Modifier @@ -77,7 +82,7 @@ fun TransactionDepositComposable(t: TransactionDeposit, devMode: Boolean?, onDel amountType = AmountType.Negative, ) } - DeleteTransactionComposable(onDelete) + TransitionsComposable(t, onTransition) if (devMode == true && t.error != null) { ErrorTransactionButton(error = t.error) } @@ -90,7 +95,8 @@ fun TransactionDepositComposablePreview() { val t = TransactionDeposit( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), depositGroupId = "fooBar", amountRaw = Amount.fromString("TESTKUDOS", "42.1337"), amountEffective = Amount.fromString("TESTKUDOS", "42.23"), 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 276e521..a3f18d7 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt @@ -39,22 +39,27 @@ import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.compose.TalerSurface import net.taler.wallet.transactions.AmountType -import net.taler.wallet.transactions.DeleteTransactionComposable import net.taler.wallet.transactions.ErrorTransactionButton -import net.taler.wallet.transactions.ExtendedStatus import net.taler.wallet.transactions.PaymentStatus +import net.taler.wallet.transactions.TransactionAction +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend import net.taler.wallet.transactions.TransactionAmountComposable import net.taler.wallet.transactions.TransactionInfo import net.taler.wallet.transactions.TransactionInfoComposable import net.taler.wallet.transactions.TransactionLinkComposable +import net.taler.wallet.transactions.TransactionMajorState.Pending import net.taler.wallet.transactions.TransactionPayment +import net.taler.wallet.transactions.TransactionState +import net.taler.wallet.transactions.TransitionsComposable @Composable fun TransactionPaymentComposable( t: TransactionPayment, devMode: Boolean, onFulfill: (url: String) -> Unit, - onDelete: () -> Unit, + onTransition: (t: TransactionAction) -> Unit, ) { val scrollState = rememberScrollState() Column( @@ -87,7 +92,7 @@ fun TransactionPaymentComposable( PurchaseDetails(info = t.info) { onFulfill(t.info.fulfillmentUrl ?: "") } - DeleteTransactionComposable(onDelete) + TransitionsComposable(t, onTransition) if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } @@ -133,7 +138,8 @@ fun TransactionPaymentComposablePreview() { val t = TransactionPayment( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = ExtendedStatus.Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), info = TransactionInfo( orderId = "123", merchant = ContractMerchant(name = "Taler"), diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt index 74e9f6c..fe847b3 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt @@ -33,12 +33,16 @@ import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.compose.QrCodeUriComposable import net.taler.wallet.transactions.AmountType -import net.taler.wallet.transactions.ExtendedStatus.Pending import net.taler.wallet.transactions.PeerInfoShort +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend import net.taler.wallet.transactions.TransactionAmountComposable import net.taler.wallet.transactions.TransactionInfoComposable +import net.taler.wallet.transactions.TransactionMajorState.Pending import net.taler.wallet.transactions.TransactionPeerComposable import net.taler.wallet.transactions.TransactionPeerPullCredit +import net.taler.wallet.transactions.TransactionState @Composable fun ColumnScope.TransactionPeerPullCreditComposable(t: TransactionPeerPullCredit) { @@ -64,7 +68,7 @@ fun ColumnScope.TransactionPeerPullCreditComposable(t: TransactionPeerPullCredit label = stringResource(id = R.string.send_peer_purpose), info = t.info.summary ?: "", ) - if (t.extendedStatus == Pending) { + if (t.txState.major == Pending) { QrCodeUriComposable( talerUri = t.talerUri, clipBoardLabel = "Invoice", @@ -85,7 +89,8 @@ fun TransactionPeerPullCreditPreview() { val t = TransactionPeerPullCredit( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), exchangeBaseUrl = "https://exchange.example.org/", amountRaw = Amount.fromString("TESTKUDOS", "42.23"), amountEffective = Amount.fromString("TESTKUDOS", "42.1337"), diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt index 5ed9c6c..aa12a8e 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt @@ -26,12 +26,16 @@ import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.transactions.AmountType -import net.taler.wallet.transactions.ExtendedStatus.Pending import net.taler.wallet.transactions.PeerInfoShort +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend import net.taler.wallet.transactions.TransactionAmountComposable import net.taler.wallet.transactions.TransactionInfoComposable +import net.taler.wallet.transactions.TransactionMajorState.Pending import net.taler.wallet.transactions.TransactionPeerComposable import net.taler.wallet.transactions.TransactionPeerPullDebit +import net.taler.wallet.transactions.TransactionState @Composable fun TransactionPeerPullDebitComposable(t: TransactionPeerPullDebit) { @@ -65,7 +69,8 @@ fun TransactionPeerPullDebitPreview() { val t = TransactionPeerPullDebit( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), exchangeBaseUrl = "https://exchange.example.org/", amountRaw = Amount.fromString("TESTKUDOS", "42.1337"), amountEffective = Amount.fromString("TESTKUDOS", "42.23"), diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt index 8344d7a..2c1c24c 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt @@ -26,12 +26,16 @@ import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.transactions.AmountType -import net.taler.wallet.transactions.ExtendedStatus.Pending import net.taler.wallet.transactions.PeerInfoShort +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend import net.taler.wallet.transactions.TransactionAmountComposable import net.taler.wallet.transactions.TransactionInfoComposable +import net.taler.wallet.transactions.TransactionMajorState.Pending import net.taler.wallet.transactions.TransactionPeerComposable import net.taler.wallet.transactions.TransactionPeerPushCredit +import net.taler.wallet.transactions.TransactionState @Composable fun TransactionPeerPushCreditComposable(t: TransactionPeerPushCredit) { @@ -65,7 +69,8 @@ fun TransactionPeerPushCreditPreview() { val t = TransactionPeerPushCredit( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), exchangeBaseUrl = "https://exchange.example.org/", amountRaw = Amount.fromString("TESTKUDOS", "42.23"), amountEffective = Amount.fromString("TESTKUDOS", "42.1337"), diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt index 8f16746..796f7fc 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt @@ -33,12 +33,16 @@ import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.compose.QrCodeUriComposable import net.taler.wallet.transactions.AmountType -import net.taler.wallet.transactions.ExtendedStatus.Pending import net.taler.wallet.transactions.PeerInfoShort +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend import net.taler.wallet.transactions.TransactionAmountComposable import net.taler.wallet.transactions.TransactionInfoComposable +import net.taler.wallet.transactions.TransactionMajorState.Pending import net.taler.wallet.transactions.TransactionPeerComposable import net.taler.wallet.transactions.TransactionPeerPushDebit +import net.taler.wallet.transactions.TransactionState @Composable fun ColumnScope.TransactionPeerPushDebitComposable(t: TransactionPeerPushDebit) { @@ -83,7 +87,8 @@ fun TransactionPeerPushDebitPreview() { val t = TransactionPeerPushDebit( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), exchangeBaseUrl = "https://exchange.example.org/", amountRaw = Amount.fromString("TESTKUDOS", "42.1337"), amountEffective = Amount.fromString("TESTKUDOS", "42.23"), diff --git a/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt b/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt index 307e3e2..c160dec 100644 --- a/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt @@ -40,19 +40,24 @@ import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.compose.TalerSurface import net.taler.wallet.payment.PurchaseDetails import net.taler.wallet.transactions.AmountType -import net.taler.wallet.transactions.DeleteTransactionComposable import net.taler.wallet.transactions.ErrorTransactionButton -import net.taler.wallet.transactions.ExtendedStatus +import net.taler.wallet.transactions.TransactionAction +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend import net.taler.wallet.transactions.TransactionAmountComposable import net.taler.wallet.transactions.TransactionInfo +import net.taler.wallet.transactions.TransactionMajorState.Pending import net.taler.wallet.transactions.TransactionRefund +import net.taler.wallet.transactions.TransactionState +import net.taler.wallet.transactions.TransitionsComposable @Composable fun TransactionRefundComposable( t: TransactionRefund, devMode: Boolean, onFulfill: (url: String) -> Unit, - onDelete: () -> Unit, + onTransition: (t: TransactionAction) -> Unit, ) { val scrollState = rememberScrollState() Column( @@ -85,7 +90,7 @@ fun TransactionRefundComposable( PurchaseDetails(info = t.info) { onFulfill(t.info.fulfillmentUrl ?: "") } - DeleteTransactionComposable(onDelete) + TransitionsComposable(t, onTransition) if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } @@ -98,7 +103,8 @@ fun TransactionRefundComposablePreview() { val t = TransactionRefund( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = ExtendedStatus.Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), info = TransactionInfo( orderId = "123", merchant = ContractMerchant(name = "Taler"), diff --git a/wallet/src/main/java/net/taler/wallet/transactions/DeleteTransactionComposable.kt b/wallet/src/main/java/net/taler/wallet/transactions/DeleteTransactionComposable.kt deleted file mode 100644 index 75ec599..0000000 --- a/wallet/src/main/java/net/taler/wallet/transactions/DeleteTransactionComposable.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of GNU Taler - * (C) 2022 Taler Systems S.A. - * - * GNU Taler is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3, or (at your option) any later version. - * - * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -package net.taler.wallet.transactions - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import net.taler.wallet.R - -@Composable -fun DeleteTransactionComposable(onDelete: () -> Unit) { - Button( - modifier = Modifier.padding(16.dp), - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), - onClick = onDelete, - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - painter = painterResource(id = R.drawable.ic_delete), - contentDescription = null, - tint = MaterialTheme.colorScheme.onError, - ) - Text( - modifier = Modifier.padding(start = 8.dp), - text = stringResource(R.string.transactions_delete), - color = MaterialTheme.colorScheme.onError, - ) - } - } -} diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt index 69c1a8a..dd46a92 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt @@ -35,8 +35,8 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder import net.taler.common.exhaustive import net.taler.common.toRelativeTime import net.taler.wallet.R -import net.taler.wallet.transactions.ExtendedStatus.Pending import net.taler.wallet.transactions.TransactionAdapter.TransactionViewHolder +import net.taler.wallet.transactions.TransactionMajorState.Pending internal class TransactionAdapter( private val listener: OnTransactionClickListener @@ -98,7 +98,7 @@ internal class TransactionAdapter( bindExtraInfo(transaction) time.text = transaction.timestamp.ms.toRelativeTime(context) bindAmount(transaction) - pendingView.visibility = if (transaction.extendedStatus == Pending) VISIBLE else GONE + pendingView.visibility = if (transaction.txState.major == Pending) VISIBLE else GONE val bgColor = getColor(context, if (selected) R.color.selectedBackground else android.R.color.transparent) @@ -129,11 +129,11 @@ internal class TransactionAdapter( when (transaction.amountType) { AmountType.Positive -> { amount.text = context.getString(R.string.amount_positive, amountStr) - amount.setTextColor(if (transaction.extendedStatus == Pending) amountColor else green) + amount.setTextColor(if (transaction.txState.major == Pending) amountColor else green) } AmountType.Negative -> { amount.text = context.getString(R.string.amount_negative, amountStr) - amount.setTextColor(if (transaction.extendedStatus == Pending) amountColor else red) + amount.setTextColor(if (transaction.txState.major == Pending) amountColor else red) } AmountType.Neutral -> { amount.text = amountStr diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDepositFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDepositFragment.kt index 3fd37ce..2077f3e 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDepositFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDepositFragment.kt @@ -36,7 +36,7 @@ class TransactionDepositFragment : TransactionDetailFragment() { TalerSurface { val t = transactionManager.selectedTransaction.observeAsState().value if (t is TransactionDeposit) TransactionDepositComposable(t, devMode.value) { - onDeleteButtonClicked(t) + onTransitionButton(t, it) } } } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt index 678bed2..1a709b0 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt @@ -20,13 +20,13 @@ import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem -import androidx.annotation.StringRes import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import com.google.android.material.dialog.MaterialAlertDialogBuilder import net.taler.wallet.MainViewModel import net.taler.wallet.R +import net.taler.wallet.transactions.TransactionAction.* abstract class TransactionDetailFragment : Fragment() { @@ -63,27 +63,50 @@ abstract class TransactionDetailFragment : Fragment() { } } - @StringRes - protected open val deleteDialogTitle = R.string.transactions_delete + private fun dialogTitle(t: TransactionAction): Int? = when (t) { + Delete -> R.string.transactions_delete_dialog_title + Abort -> R.string.transactions_abort_dialog_title + else -> null + } - @StringRes - protected open val deleteDialogMessage = R.string.transactions_delete_dialog_message + private fun dialogMessage(t: TransactionAction): Int? = when (t) { + Delete -> R.string.transactions_delete_dialog_message + Abort -> R.string.transactions_abort_dialog_message + else -> null + } - @StringRes - protected open val deleteDialogButton = R.string.transactions_delete + private fun dialogButton(t: TransactionAction): Int? = when (t) { + Delete -> R.string.transactions_delete + Abort -> R.string.transactions_abort + else -> null + } - protected fun onDeleteButtonClicked(t: Transaction) { - MaterialAlertDialogBuilder(requireContext(), R.style.MaterialAlertDialog_Material3) - .setTitle(deleteDialogTitle) - .setMessage(deleteDialogMessage) - .setNeutralButton(R.string.cancel) { dialog, _ -> - dialog.cancel() + protected fun onTransitionButton(t: Transaction, tt: TransactionAction) { + when (tt) { + Delete, Abort -> { + MaterialAlertDialogBuilder(requireContext(), R.style.MaterialAlertDialog_Material3) + .setTitle(dialogTitle(tt)!!) + .setMessage(dialogMessage(tt)!!) + .setNeutralButton(R.string.cancel) { dialog, _ -> + dialog.cancel() + } + .setNegativeButton(dialogButton(tt)!!) { dialog, _ -> + when (tt) { + Delete -> deleteTransaction(t) + Abort -> abortTransaction(t) + else -> {} + } + dialog.dismiss() + } + .show() } - .setNegativeButton(deleteDialogButton) { dialog, _ -> - deleteTransaction(t) - dialog.dismiss() + else -> when (tt) { + Retry -> retryTransaction(t) + Suspend -> suspendTransaction(t) + Resume -> resumeTransaction(t) + else -> {} } - .show() + } } private fun deleteTransaction(t: Transaction) { @@ -91,4 +114,19 @@ abstract class TransactionDetailFragment : Fragment() { findNavController().popBackStack() } + private fun retryTransaction(t: Transaction) { + transactionManager.retryTransaction(t.transactionId) + } + + private fun abortTransaction(t: Transaction) { + transactionManager.abortTransaction(t.transactionId) + } + + private fun suspendTransaction(t: Transaction) { + transactionManager.suspendTransaction(t.transactionId) + } + + private fun resumeTransaction(t: Transaction) { + transactionManager.resumeTransaction(t.transactionId) + } } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt index ed4c4da..dfe25ad 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt @@ -26,7 +26,7 @@ import kotlinx.coroutines.launch import net.taler.wallet.TAG import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.backend.WalletBackendApi -import net.taler.wallet.transactions.ExtendedStatus.Pending +import net.taler.wallet.transactions.TransactionMajorState.Pending import java.util.LinkedList sealed class TransactionsResult { @@ -79,7 +79,7 @@ class TransactionManager( val transactions = LinkedList(result.transactions) // TODO remove when fixed in wallet-core val comparator = compareBy<Transaction>( - { it.extendedStatus == Pending }, + { it.txState.major == Pending }, { it.timestamp.ms }, { it.transactionId } ) @@ -138,7 +138,48 @@ class TransactionManager( } } + fun retryTransaction(transactionId: String) = scope.launch { + api.request<Unit>("retryTransaction") { + put("transactionId", transactionId) + }.onError { + Log.e(TAG, "Error retryTransaction $it") + }.onSuccess { + loadTransactions() + } + } + + fun abortTransaction(transactionId: String) = scope.launch { + api.request<Unit>("abortTransaction") { + put("transactionId", transactionId) + }.onError { + Log.e(TAG, "Error abortTransaction $it") + }.onSuccess { + loadTransactions() + } + } + + fun suspendTransaction(transactionId: String) = scope.launch { + api.request<Unit>("suspendTransaction") { + put("transactionId", transactionId) + }.onError { + Log.e(TAG, "Error suspendTransaction $it") + }.onSuccess { + loadTransactions() + } + } + + fun resumeTransaction(transactionId: String) = scope.launch { + api.request<Unit>("resumeTransaction") { + put("transactionId", transactionId) + }.onError { + Log.e(TAG, "Error resumeTransaction $it") + }.onSuccess { + loadTransactions() + } + } + fun deleteTransactions(transactionIds: List<String>) { + // TODO: do NOT delete non-deletable transactions transactionIds.forEach { id -> deleteTransaction(id) } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionPaymentFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPaymentFragment.kt index e9eb5b8..19a38fe 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionPaymentFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPaymentFragment.kt @@ -41,8 +41,8 @@ class TransactionPaymentFragment : TransactionDetailFragment() { onFulfill = { url -> launchInAppBrowser(requireContext(), url) }, - onDelete = { - onDeleteButtonClicked(t) + onTransition = { + onTransitionButton(t, it) } ) } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt index 297c937..8e8bcaf 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt @@ -57,7 +57,7 @@ class TransactionPeerFragment : TransactionDetailFragment() { TalerSurface { val t = transactionManager.selectedTransaction.observeAsState(null).value if (t != null) TransactionPeerComposable(t, devMode.value) { - onDeleteButtonClicked(t) + onTransitionButton(t, it) } } } @@ -65,7 +65,7 @@ class TransactionPeerFragment : TransactionDetailFragment() { } @Composable -fun TransactionPeerComposable(t: Transaction, devMode: Boolean?, onDelete: () -> Unit) { +fun TransactionPeerComposable(t: Transaction, devMode: Boolean?, onTransition: (t: TransactionAction) -> Unit) { val scrollState = rememberScrollState() Column( modifier = Modifier @@ -86,7 +86,7 @@ fun TransactionPeerComposable(t: Transaction, devMode: Boolean?, onDelete: () -> is TransactionPeerPushDebit -> TransactionPeerPushDebitComposable(t) else -> error("unexpected transaction: ${t::class.simpleName}") } - DeleteTransactionComposable(onDelete) + TransitionsComposable(t, onTransition) if (devMode == true && t.error != null) { ErrorTransactionButton(error = t.error!!) } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt index ca3f39b..79aca76 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt @@ -44,6 +44,10 @@ import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.compose.TalerSurface +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend +import net.taler.wallet.transactions.TransactionMajorState.Pending class TransactionRefreshFragment : TransactionDetailFragment() { @@ -57,7 +61,7 @@ class TransactionRefreshFragment : TransactionDetailFragment() { val t = transactionManager.selectedTransaction.observeAsState().value val devMode = devMode.observeAsState().value ?: false if (t is TransactionRefresh) TransactionRefreshComposable(t, devMode) { - onDeleteButtonClicked(t) + onTransitionButton(t, it) } } } @@ -68,7 +72,7 @@ class TransactionRefreshFragment : TransactionDetailFragment() { private fun TransactionRefreshComposable( t: TransactionRefresh, devMode: Boolean, - onDelete: () -> Unit, + onTransition: (t: TransactionAction) -> Unit, ) { val scrollState = rememberScrollState() Column( @@ -88,7 +92,9 @@ private fun TransactionRefreshComposable( amount = t.amountEffective, amountType = AmountType.Negative, ) - DeleteTransactionComposable(onDelete) + t.txActions.forEach { + TransitionComposable(it, onTransition) + } if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } @@ -101,7 +107,8 @@ private fun TransactionRefreshComposablePreview() { val t = TransactionRefresh( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = ExtendedStatus.Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), amountRaw = Amount.fromString("TESTKUDOS", "42.23"), amountEffective = Amount.fromString("TESTKUDOS", "42.1337"), error = TalerErrorInfo(code = TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED), diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefundFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefundFragment.kt index 61c0364..bf026b2 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefundFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefundFragment.kt @@ -41,8 +41,8 @@ class TransactionRefundFragment : TransactionDetailFragment() { onFulfill = { url -> launchInAppBrowser(requireContext(), url) }, - onDelete = { - onDeleteButtonClicked(t) + onTransition = { + onTransitionButton(t, it) } ) } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionState.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionState.kt new file mode 100644 index 0000000..64e23c9 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionState.kt @@ -0,0 +1,174 @@ +/* + * This file is part of GNU Taler + * (C) 2023 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.transactions + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class TransactionState( + val major: TransactionMajorState, + val minor: TransactionMinorState? = null, +) { + override fun equals(other: Any?): Boolean { + return if (other is TransactionState) + // if other.minor is null, then ignore minor in comparison + major == other.major && (other.minor == null || minor == other.minor) + else false + } + + override fun hashCode(): Int { + var result = major.hashCode() + result = 31 * result + (minor?.hashCode() ?: 0) + return result + } +} + +@Serializable +enum class TransactionMajorState { + @SerialName("none") + None, + + @SerialName("pending") + Pending, + + @SerialName("done") + Done, + + @SerialName("aborting") + Aborting, + + @SerialName("aborted") + Aborted, + + @SerialName("suspended") + Suspended, + + @SerialName("dialog") + Dialog, + + @SerialName("suspended-aborting") + SuspendedAborting, + + @SerialName("failed") + Failed, + + @SerialName("deleted") + Deleted, + + @SerialName("unknown") + Unknown; +} + +@Serializable +enum class TransactionMinorState { + @SerialName("unknown") + Unknown, + + @SerialName("deposit") + Deposit, + + @SerialName("kyc") + KycRequired, + + @SerialName("aml") + AmlRequired, + + @SerialName("merge-kyc") + MergeKycRequired, + + @SerialName("track") + Track, + + @SerialName("submit-payment") + SubmitPayment, + + @SerialName("rebind-session") + RebindSession, + + @SerialName("refresh") + Refresh, + + @SerialName("pickup") + Pickup, + + @SerialName("auto-refund") + AutoRefund, + + @SerialName("user") + User, + + @SerialName("bank") + Bank, + + @SerialName("exchange") + Exchange, + + @SerialName("claim-proposal") + ClaimProposal, + + @SerialName("check-refund") + CheckRefund, + + @SerialName("create-purse") + CreatePurse, + + @SerialName("delete-purse") + DeletePurse, + + @SerialName("ready") + Ready, + + @SerialName("merge") + Merge, + + @SerialName("repurchase") + Repurchase, + + @SerialName("bank-register-reserve") + BankRegisterReserve, + + @SerialName("bank-confirm-transfer") + BankConfirmTransfer, + + @SerialName("withdraw-coins") + WithdrawCoins, + + @SerialName("exchange-wait-reserve") + ExchangeWaitReserve, + + @SerialName("aborting-bank") + AbortingBank, + + @SerialName("refused") + Refused, + + @SerialName("withdraw") + Withdraw, + + @SerialName("merchant-order-proposed") + MerchantOrderProposed, + + @SerialName("proposed") + Proposed, + + @SerialName("refund-available") + RefundAvailable, + + @SerialName("accept-refund") + AcceptRefund +} diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt index b2db0bb..f7e3e9e 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt @@ -44,7 +44,10 @@ import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.compose.TalerSurface -import net.taler.wallet.transactions.ExtendedStatus.Pending +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend +import net.taler.wallet.transactions.TransactionMajorState.Pending class TransactionTipFragment : TransactionDetailFragment() { @@ -57,7 +60,7 @@ class TransactionTipFragment : TransactionDetailFragment() { TalerSurface { val t = transactionManager.selectedTransaction.observeAsState(null).value if (t is TransactionTip) TransactionTipComposable(t, devMode.value) { - onDeleteButtonClicked(t) + onTransitionButton(t, it) } } } @@ -65,7 +68,7 @@ class TransactionTipFragment : TransactionDetailFragment() { } @Composable -fun TransactionTipComposable(t: TransactionTip, devMode: Boolean?, onDelete: () -> Unit) { +fun TransactionTipComposable(t: TransactionTip, devMode: Boolean?, onTransition: (t: TransactionAction) -> Unit) { val scrollState = rememberScrollState() Column( modifier = Modifier @@ -102,7 +105,9 @@ fun TransactionTipComposable(t: TransactionTip, devMode: Boolean?, onDelete: () label = stringResource(id = R.string.tip_merchant_url), info = t.merchantBaseUrl, ) - DeleteTransactionComposable(onDelete) + t.txActions.forEach { + TransitionComposable(it, onTransition) + } if (devMode == true && t.error != null) { ErrorTransactionButton(error = t.error) } @@ -115,7 +120,8 @@ fun TransactionTipPreview() { val t = TransactionTip( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), merchantBaseUrl = "https://merchant.example.org/", amountRaw = Amount.fromString("TESTKUDOS", "42.23"), amountEffective = Amount.fromString("TESTKUDOS", "42.1337"), diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt index 7a85522..f23bc13 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt @@ -38,16 +38,6 @@ class TransactionWithdrawalFragment : TransactionDetailFragment(), ActionListene private val model: MainViewModel by activityViewModels() private val withdrawManager by lazy { model.withdrawManager } - private val isPending get() = transactionManager.selectedTransaction.value?.extendedStatus == ExtendedStatus.Pending - - override val deleteDialogTitle: Int - get() = if (isPending) R.string.cancel else super.deleteDialogTitle - override val deleteDialogMessage: Int - get() = if (isPending) R.string.transactions_cancel_dialog_message - else super.deleteDialogMessage - override val deleteDialogButton: Int - get() = if (isPending) R.string.ok else super.deleteDialogButton - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -62,7 +52,7 @@ class TransactionWithdrawalFragment : TransactionDetailFragment(), ActionListene devMode = devMode, actionListener = this@TransactionWithdrawalFragment, ) { - onDeleteButtonClicked(t) + onTransitionButton(t, it) } } } 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 6e00b4f..cb917db 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt @@ -42,6 +42,8 @@ import net.taler.wallet.TAG import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.cleanExchange +import net.taler.wallet.transactions.TransactionMajorState.Failed +import net.taler.wallet.transactions.TransactionMajorState.Pending import net.taler.wallet.transactions.WithdrawalDetails.ManualTransfer import net.taler.wallet.transactions.WithdrawalDetails.TalerBankIntegrationApi import java.util.UUID @@ -97,7 +99,8 @@ class TransactionSerializer : KSerializer<Transaction> { sealed class Transaction { abstract val transactionId: String abstract val timestamp: Timestamp - abstract val extendedStatus: ExtendedStatus + abstract val txState: TransactionState + abstract val txActions: List<TransactionAction> abstract val error: TalerErrorInfo? abstract val amountRaw: Amount abstract val amountEffective: Amount @@ -140,6 +143,28 @@ enum class ExtendedStatus { Deleted; } +@Serializable +enum class TransactionAction { + // Common States + @SerialName("delete") + Delete, + + @SerialName("suspend") + Suspend, + + @SerialName("resume") + Resume, + + @SerialName("abort") + Abort, + + @SerialName("fail") + Fail, + + @SerialName("retry") + Retry, +} + sealed class AmountType { object Positive : AmountType() object Negative : AmountType() @@ -151,7 +176,8 @@ sealed class AmountType { class TransactionWithdrawal( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, val exchangeBaseUrl: String, val withdrawalDetails: WithdrawalDetails, override val error: TalerErrorInfo? = null, @@ -167,7 +193,7 @@ class TransactionWithdrawal( override fun getTitle(context: Context) = cleanExchange(exchangeBaseUrl) override val generalTitleRes = R.string.withdraw_title val confirmed: Boolean - get() = extendedStatus != ExtendedStatus.Pending && ( + get() = txState.major != Pending && ( (withdrawalDetails is TalerBankIntegrationApi && withdrawalDetails.confirmed) || withdrawalDetails is ManualTransfer ) @@ -209,7 +235,8 @@ sealed class WithdrawalDetails { class TransactionPayment( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, val info: TransactionInfo, val status: PaymentStatus, override val error: TalerErrorInfo? = null, @@ -264,7 +291,8 @@ enum class PaymentStatus { class TransactionRefund( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, val refundedTransactionId: String, val info: TransactionInfo, /** @@ -292,7 +320,8 @@ class TransactionRefund( class TransactionTip( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, val merchantBaseUrl: String, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, @@ -315,7 +344,8 @@ class TransactionTip( class TransactionRefresh( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, override val amountEffective: Amount, @@ -337,7 +367,8 @@ class TransactionRefresh( class TransactionDeposit( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, override val amountEffective: Amount, @@ -370,7 +401,8 @@ data class PeerInfoShort( class TransactionPeerPullDebit( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, val exchangeBaseUrl: String, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, @@ -397,7 +429,8 @@ class TransactionPeerPullDebit( class TransactionPeerPullCredit( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, val exchangeBaseUrl: String, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, @@ -425,7 +458,8 @@ class TransactionPeerPullCredit( class TransactionPeerPushDebit( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, val exchangeBaseUrl: String, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, @@ -454,7 +488,8 @@ class TransactionPeerPushDebit( class TransactionPeerPushCredit( override val transactionId: String, override val timestamp: Timestamp, - override val extendedStatus: ExtendedStatus, + override val txState: TransactionState, + override val txActions: List<TransactionAction>, val exchangeBaseUrl: String, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, @@ -481,7 +516,8 @@ class DummyTransaction( override val timestamp: Timestamp, override val error: TalerErrorInfo, ) : Transaction() { - override val extendedStatus: ExtendedStatus = ExtendedStatus.Failed + override val txState: TransactionState = TransactionState(Failed) + override val txActions: List<TransactionAction> = listOf(TransactionAction.Delete) override val amountRaw: Amount = Amount.zero("TESTKUDOS") override val amountEffective: Amount = Amount.zero("TESTKUDOS") override val icon: Int = R.drawable.ic_bug_report diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transitions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transitions.kt deleted file mode 100644 index 31aa655..0000000 --- a/wallet/src/main/java/net/taler/wallet/transactions/Transitions.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * This file is part of GNU Taler - * (C) 2023 Taler Systems S.A. - * - * GNU Taler is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3, or (at your option) any later version. - * - * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -package net.taler.wallet.transactions - -/** - * Based on “DD 37: Wallet Transaction Lifecycle” - * - * TODO: implement sub-states (pending in wallet-core) - * TODO: implement sub-state specific transitions - */ - -enum class Transition { - // Common States - Delete, - Retry, - Abort, - Suspend, - Resume, - AbortForce, - - // Payment to Merchant - PayAccept, - Expired, - CheckRefund, - PayReplay, - - // Tip - AcceptTip, - - // Peer Pull Debit - ConfirmPay, -} - -fun Transaction.canPerform(t: Transition): Boolean { - return when (t) { - Transition.Delete -> extendedStatus in arrayOf( - ExtendedStatus.Done, - ExtendedStatus.Aborted, - ExtendedStatus.Failed, - ) - Transition.Retry -> extendedStatus in arrayOf( - ExtendedStatus.Pending, - ExtendedStatus.Aborting, - ) - Transition.Abort -> extendedStatus in arrayOf( - ExtendedStatus.Pending, - ) - Transition.Suspend -> extendedStatus in arrayOf( - ExtendedStatus.Pending, - ) - Transition.Resume -> extendedStatus in arrayOf( - ExtendedStatus.Suspended, - ) - Transition.AbortForce -> extendedStatus in arrayOf( - ExtendedStatus.Aborting, - ) - else -> false - } -}
\ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt new file mode 100644 index 0000000..e66de47 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt @@ -0,0 +1,104 @@ +/* + * This file is part of GNU Taler + * (C) 2023 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.transactions + +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import net.taler.wallet.R +import net.taler.wallet.transactions.TransactionAction.* + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun TransitionsComposable(t: Transaction, onTransition: (t: TransactionAction) -> Unit) { + FlowRow { + t.txActions.forEach { + TransitionComposable(it, onTransition) + } + } +} + +@Composable +fun TransitionComposable(t: TransactionAction, onClick: (t: TransactionAction) -> Unit) { + // TODO: handle more transitions! + if (t !in arrayOf(Delete, Retry, Abort, Resume, Suspend)) return + Button( + modifier = Modifier.padding(16.dp), + colors = ButtonDefaults.buttonColors(containerColor = when(t) { + Delete -> MaterialTheme.colorScheme.error + Retry -> MaterialTheme.colorScheme.primary + Abort -> MaterialTheme.colorScheme.error + Resume -> MaterialTheme.colorScheme.primary + Suspend -> MaterialTheme.colorScheme.primary + else -> error("Unsupported") + }), + onClick = { onClick(t) }, + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + painter = when (t) { + Delete -> painterResource(id = R.drawable.ic_delete) + Retry -> painterResource(id = R.drawable.ic_retry) + Abort -> painterResource(id = R.drawable.ic_cancel) + Resume -> painterResource(id = R.drawable.ic_resume) + Suspend -> painterResource(id = R.drawable.ic_suspend) + else -> error("Unsupported") + }, + contentDescription = null, + tint = when (t) { + Delete -> MaterialTheme.colorScheme.onError + Retry -> MaterialTheme.colorScheme.onPrimary + Abort -> MaterialTheme.colorScheme.onError + Resume -> MaterialTheme.colorScheme.onPrimary + Suspend -> MaterialTheme.colorScheme.onPrimary + else -> error("Unsupported") + }, + ) + Text( + modifier = Modifier.padding(start = 8.dp), + text = when (t) { + Delete -> stringResource(R.string.transactions_delete) + Retry -> stringResource(R.string.transactions_retry) + Abort -> stringResource(R.string.transactions_abort) + Resume -> stringResource(R.string.transactions_resume) + Suspend -> stringResource(R.string.transactions_suspend) + else -> error("Unsupported") + }, + color = when (t) { + Delete -> MaterialTheme.colorScheme.onError + Retry -> MaterialTheme.colorScheme.onPrimary + Abort -> MaterialTheme.colorScheme.onError + Resume -> MaterialTheme.colorScheme.onPrimary + Suspend -> MaterialTheme.colorScheme.onPrimary + else -> error("Unsupported") + }, + ) + } + } +} diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt index 3996ec1..1dff2ae 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt @@ -17,23 +17,17 @@ package net.taler.wallet.withdraw import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment -import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -47,13 +41,18 @@ import net.taler.wallet.cleanExchange import net.taler.wallet.transactions.ActionButton import net.taler.wallet.transactions.ActionListener import net.taler.wallet.transactions.AmountType -import net.taler.wallet.transactions.DeleteTransactionComposable import net.taler.wallet.transactions.ErrorTransactionButton -import net.taler.wallet.transactions.ExtendedStatus import net.taler.wallet.transactions.Transaction +import net.taler.wallet.transactions.TransactionAction +import net.taler.wallet.transactions.TransactionAction.Abort +import net.taler.wallet.transactions.TransactionAction.Retry +import net.taler.wallet.transactions.TransactionAction.Suspend import net.taler.wallet.transactions.TransactionAmountComposable import net.taler.wallet.transactions.TransactionInfoComposable +import net.taler.wallet.transactions.TransactionMajorState.Pending +import net.taler.wallet.transactions.TransactionState import net.taler.wallet.transactions.TransactionWithdrawal +import net.taler.wallet.transactions.TransitionsComposable import net.taler.wallet.transactions.WithdrawalDetails.ManualTransfer @Composable @@ -61,7 +60,7 @@ fun TransactionWithdrawalComposable( t: TransactionWithdrawal, devMode: Boolean, actionListener: ActionListener, - onDelete: () -> Unit, + onTransition: (t: TransactionAction) -> Unit, ) { val scrollState = rememberScrollState() Column( @@ -96,28 +95,7 @@ fun TransactionWithdrawalComposable( label = stringResource(id = R.string.withdraw_exchange), info = cleanExchange(t.exchangeBaseUrl), ) - if (t.extendedStatus == ExtendedStatus.Pending) { - Button( - modifier = Modifier.padding(16.dp), - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), - onClick = onDelete, - ) { - Row(verticalAlignment = CenterVertically) { - Icon( - painter = painterResource(id = R.drawable.ic_cancel), - contentDescription = null, - tint = MaterialTheme.colorScheme.onError, - ) - Text( - modifier = Modifier.padding(start = 8.dp), - text = stringResource(R.string.cancel), - color = MaterialTheme.colorScheme.onError, - ) - } - } - } else { - DeleteTransactionComposable(onDelete) - } + TransitionsComposable(t, onTransition) if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } @@ -130,7 +108,8 @@ fun TransactionWithdrawalComposablePreview() { val t = TransactionWithdrawal( transactionId = "transactionId", timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000), - extendedStatus = ExtendedStatus.Pending, + txState = TransactionState(Pending), + txActions = listOf(Retry, Suspend, Abort), exchangeBaseUrl = "https://exchange.demo.taler.net/", withdrawalDetails = ManualTransfer(exchangePaytoUris = emptyList()), amountRaw = Amount.fromString("TESTKUDOS", "42.23"), |