gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-android] 02/11: [wallet] Implemented DD37 with the new txAc


From: gnunet
Subject: [taler-taler-android] 02/11: [wallet] Implemented DD37 with the new txActions field
Date: Tue, 11 Jul 2023 16:23:30 +0200

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a commit to branch master
in repository taler-android.

commit 58f5dc13d51eef09463e215af0ac526216531074
Author: Iván Ávalos <avalos@disroot.org>
AuthorDate: Mon May 15 15:34:24 2023 -0600

    [wallet] Implemented DD37 with the new txActions field
---
 wallet/build.gradle                                |   2 +-
 .../wallet/deposit/TransactionDepositComposable.kt |  16 +-
 .../wallet/payment/TransactionPaymentComposable.kt |  16 +-
 .../taler/wallet/peer/TransactionPeerPullCredit.kt |  11 +-
 .../taler/wallet/peer/TransactionPeerPullDebit.kt  |   9 +-
 .../taler/wallet/peer/TransactionPeerPushCredit.kt |   9 +-
 .../taler/wallet/peer/TransactionPeerPushDebit.kt  |   9 +-
 .../wallet/refund/TransactionRefundComposable.kt   |  16 +-
 .../transactions/DeleteTransactionComposable.kt    |  54 -------
 .../wallet/transactions/TransactionAdapter.kt      |   8 +-
 .../transactions/TransactionDepositFragment.kt     |   2 +-
 .../transactions/TransactionDetailFragment.kt      |  72 +++++++--
 .../wallet/transactions/TransactionManager.kt      |  45 +++++-
 .../transactions/TransactionPaymentFragment.kt     |   4 +-
 .../wallet/transactions/TransactionPeerFragment.kt |   6 +-
 .../transactions/TransactionRefreshFragment.kt     |  15 +-
 .../transactions/TransactionRefundFragment.kt      |   4 +-
 .../taler/wallet/transactions/TransactionState.kt  | 174 +++++++++++++++++++++
 .../wallet/transactions/TransactionTipFragment.kt  |  16 +-
 .../transactions/TransactionWithdrawalFragment.kt  |  12 +-
 .../net/taler/wallet/transactions/Transactions.kt  |  62 ++++++--
 .../net/taler/wallet/transactions/Transitions.kt   |  73 ---------
 .../wallet/transactions/TransitionsComposable.kt   | 104 ++++++++++++
 .../withdraw/TransactionWithdrawalComposable.kt    |  43 ++---
 wallet/src/main/res/drawable/ic_resume.xml         |   5 +
 wallet/src/main/res/drawable/ic_retry.xml          |   5 +
 wallet/src/main/res/drawable/ic_suspend.xml        |   5 +
 wallet/src/main/res/values/strings.xml             |   6 +
 28 files changed, 555 insertions(+), 248 deletions(-)

diff --git a/wallet/build.gradle b/wallet/build.gradle
index b1e09ef..c4d71d5 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -19,7 +19,7 @@ plugins {
     id "kotlinx-serialization"
 }
 
-def qtart_version = "0.9.3-dev.8"
+def qtart_version = "0.9.3-dev.10"
 
 static def versionCodeEpoch() {
     return (new Date().getTime() / 1000).toInteger()
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"),
diff --git a/wallet/src/main/res/drawable/ic_resume.xml 
b/wallet/src/main/res/drawable/ic_resume.xml
new file mode 100644
index 0000000..e3fd2e9
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_resume.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#000000"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" 
xmlns:android="http://schemas.android.com/apk/res/android";>
+    <path android:fillColor="@android:color/white" 
android:pathData="M8,5v14l11,-7z"/>
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_retry.xml 
b/wallet/src/main/res/drawable/ic_retry.xml
new file mode 100644
index 0000000..98469ca
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_retry.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#000000"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" 
xmlns:android="http://schemas.android.com/apk/res/android";>
+    <path android:fillColor="@android:color/white" 
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 
-7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 
-5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 
4.22,1.78L13,11h7V4l-2.35,2.35z"/>
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_suspend.xml 
b/wallet/src/main/res/drawable/ic_suspend.xml
new file mode 100644
index 0000000..938bd7f
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_suspend.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#000000"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" 
xmlns:android="http://schemas.android.com/apk/res/android";>
+    <path android:fillColor="@android:color/white" 
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
+</vector>
diff --git a/wallet/src/main/res/values/strings.xml 
b/wallet/src/main/res/values/strings.xml
index 9645196..716d537 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -85,10 +85,16 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="transactions_detail_title">Transaction</string>
     <string name="transactions_detail_title_currency">%s Transactions</string>
     <string name="transactions_delete">Delete</string>
+    <string name="transactions_retry">Retry</string>
+    <string name="transactions_abort">Abort</string>
+    <string name="transactions_suspend">Suspend</string>
+    <string name="transactions_resume">Resume</string>
     <string name="transactions_select_all">Select All</string>
     <string name="transactions_delete_dialog_title">Delete Transaction</string>
     <string name="transactions_delete_dialog_message">Are you sure you want to 
remove this transaction from your wallet?</string>
     <string name="transactions_delete_selected_dialog_message">Are you sure 
you want to remove the selected transactions from your wallet?</string>
+    <string name="transactions_abort_dialog_title">Abort Transaction</string>
+    <string name="transactions_abort_dialog_message">Are you sure you want to 
abort this transaction? Funds still in transit might get lost.</string>
     <string name="transactions_cancel_dialog_message">Are you sure you want to 
cancel this withdrawal? Funds still in transit might get lost.</string>
 
     <!-- Transactions -->

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]