summaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net/taler/wallet/transactions
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2023-05-15 15:34:24 -0600
committerTorsten Grote <t@grobox.de>2023-07-11 10:31:26 -0300
commit58f5dc13d51eef09463e215af0ac526216531074 (patch)
treeb8685fc4f3fee8888d6e7923ac7c6f9c59574eea /wallet/src/main/java/net/taler/wallet/transactions
parent255094c5432244ca214055cc9406a31d5b561b5f (diff)
downloadtaler-android-58f5dc13d51eef09463e215af0ac526216531074.tar.gz
taler-android-58f5dc13d51eef09463e215af0ac526216531074.tar.bz2
taler-android-58f5dc13d51eef09463e215af0ac526216531074.zip
[wallet] Implemented DD37 with the new txActions field
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/transactions')
-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
15 files changed, 460 insertions, 191 deletions
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")
+ },
+ )
+ }
+ }
+}