summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2023-05-15 15:34:24 -0600
committerIván Ávalos <avalos@disroot.org>2023-06-28 19:59:12 -0600
commitf72b427cc7df48f42b99eda5aa6f4f3b66e533a9 (patch)
tree6a106816bce90a2248d39676ec4318f05f027980
parentd99fd893dbe66e64954114bfd2bd6a37f388cc2b (diff)
downloadtaler-android-f72b427cc7df48f42b99eda5aa6f4f3b66e533a9.tar.gz
taler-android-f72b427cc7df48f42b99eda5aa6f4f3b66e533a9.tar.bz2
taler-android-f72b427cc7df48f42b99eda5aa6f4f3b66e533a9.zip
[wallet] Implemented DD37 with the new txActions field
-rw-r--r--wallet/build.gradle2
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt16
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt16
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt11
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt9
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt9
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt9
-rw-r--r--wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt16
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/DeleteTransactionComposable.kt54
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt8
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionDepositFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt72
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt45
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionPaymentFragment.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt15
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionRefundFragment.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionState.kt174
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt16
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt12
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt62
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transitions.kt73
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt104
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt43
-rw-r--r--wallet/src/main/res/drawable/ic_resume.xml5
-rw-r--r--wallet/src/main/res/drawable/ic_retry.xml5
-rw-r--r--wallet/src/main/res/drawable/ic_suspend.xml5
-rw-r--r--wallet/src/main/res/values/strings.xml6
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 185a723..5329577 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 -->