From baca5eceb08055d35ef96d6d6586fbba55511d40 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 23 Sep 2022 15:05:38 -0300 Subject: [wallet] Implement beginning of deposits --- wallet/src/main/AndroidManifest.xml | 1 + .../src/main/java/net/taler/wallet/MainActivity.kt | 7 +- .../main/java/net/taler/wallet/UriInputFragment.kt | 3 +- .../net/taler/wallet/payment/PaymentManager.kt | 22 ++++- .../transactions/TransactionDepositFragment.kt | 105 +++++++++++++++++++++ .../wallet/transactions/TransactionManager.kt | 2 +- .../net/taler/wallet/transactions/Transactions.kt | 40 ++++++-- wallet/src/main/res/navigation/nav_graph.xml | 10 ++ wallet/src/main/res/values/strings.xml | 1 + 9 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 wallet/src/main/java/net/taler/wallet/transactions/TransactionDepositFragment.kt diff --git a/wallet/src/main/AndroidManifest.xml b/wallet/src/main/AndroidManifest.xml index 69670c3..68bc321 100644 --- a/wallet/src/main/AndroidManifest.xml +++ b/wallet/src/main/AndroidManifest.xml @@ -63,6 +63,7 @@ + diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt b/wallet/src/main/java/net/taler/wallet/MainActivity.kt index 5597564..cb48c30 100644 --- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt +++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt @@ -203,9 +203,6 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, } } } else { - if (!scheme.startsWith("taler", ignoreCase = true)) { - return actionFound - } actionFound.postValue(uri.toString()) } @@ -225,8 +222,8 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, val action = normalizedURL.substring( if (normalizedURL.startsWith("taler://", ignoreCase = true)) { "taler://".length - } else if (normalizedURL.startsWith("taler+http://", - ignoreCase = true) && model.devMode.value == true + } else if (normalizedURL.startsWith("taler+http://", ignoreCase = true) && + model.devMode.value == true ) { "taler+http://".length } else { diff --git a/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt b/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt index 8ad1fb7..00ec404 100644 --- a/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt @@ -59,7 +59,8 @@ class UriInputFragment : Fragment() { } } ui.okButton.setOnClickListener { - if (ui.uriView.text?.startsWith("taler://", ignoreCase = true) == true) { + if (ui.uriView.text?.startsWith("taler://", ignoreCase = true) == true || + ui.uriView.text?.startsWith("payto://", ignoreCase = true) == true) { ui.uriLayout.error = null val i = Intent(ACTION_VIEW, Uri.parse(ui.uriView.text.toString())) startActivity(i) diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt index ae091df..b8918c1 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt @@ -22,11 +22,12 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable import net.taler.common.Amount import net.taler.common.ContractTerms import net.taler.wallet.TAG -import net.taler.wallet.backend.WalletBackendApi import net.taler.wallet.backend.TalerErrorInfo +import net.taler.wallet.backend.WalletBackendApi import net.taler.wallet.payment.PayStatus.AlreadyPaid import net.taler.wallet.payment.PayStatus.InsufficientBalance import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse @@ -119,9 +120,28 @@ class PaymentManager( mPayStatus.value = PayStatus.None } + @UiThread + fun makeDeposit(url: String, amount: Amount) = scope.launch { + // TODO + api.request("createDepositGroup", CreateDepositGroupResponse.serializer()) { + put("depositPaytoUri", url) + put("amount", amount.toJSONString()) + }.onError { + Log.e(TAG, "Error createDepositGroup $it") + }.onSuccess { + Log.e(TAG, "createDepositGroup $it") + } + } + private fun handleError(operation: String, error: TalerErrorInfo) { Log.e(TAG, "got $operation error result $error") mPayStatus.value = PayStatus.Error(error.userFacingMsg) } } + +@Serializable +data class CreateDepositGroupResponse( + val depositGroupId: String, + val transactionId: String, +) diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDepositFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDepositFragment.kt new file mode 100644 index 0000000..f721090 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDepositFragment.kt @@ -0,0 +1,105 @@ +/* + * 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 + */ + +package net.taler.wallet.transactions + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +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.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.google.android.material.composethemeadapter.MdcTheme +import net.taler.common.toAbsoluteTime +import net.taler.wallet.R + +class TransactionDepositFragment : TransactionDetailFragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View = ComposeView(requireContext()).apply { + setContent { + MdcTheme { + Surface { + val t = transaction ?: error("No transaction") + TransactionDepositComposable(t as TransactionDeposit) { + onDeleteButtonClicked(t) + } + } + } + } + } +} + +@Composable +fun TransactionDepositComposable(t: TransactionDeposit, onDelete: () -> Unit) { + val scrollState = rememberScrollState() + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(scrollState), + horizontalAlignment = CenterHorizontally, + ) { + val context = LocalContext.current + Text( + modifier = Modifier.padding(16.dp), + text = t.timestamp.ms.toAbsoluteTime(context).toString(), + style = MaterialTheme.typography.body1, + ) + // TODO + Button( + modifier = Modifier.padding(16.dp), + colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.red)), + onClick = onDelete, + ) { + Row(verticalAlignment = CenterVertically) { + Icon( + painter = painterResource(id = R.drawable.ic_delete), + contentDescription = null, + tint = Color.White, + ) + Text( + modifier = Modifier.padding(start = 8.dp), + text = stringResource(R.string.transactions_delete), + color = Color.White, + ) + } + } + } +} 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 d1020e2..bbae22b 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt @@ -89,7 +89,7 @@ class TransactionManager( } } - fun deleteTransaction(transactionId: String) = scope.launch { + fun deleteTransaction(transactionId: String) = scope.launch { api.request("deleteTransaction") { put("transactionId", transactionId) }.onError { 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 97ac5ea..dcb524e 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt @@ -75,7 +75,7 @@ class TransactionWithdrawal( val withdrawalDetails: WithdrawalDetails, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, - override val amountEffective: Amount + override val amountEffective: Amount, ) : Transaction() { override val icon = R.drawable.transaction_withdrawal @@ -102,7 +102,7 @@ sealed class WithdrawalDetails { * * Already contains the amount and message. */ - val exchangePaytoUris: List + val exchangePaytoUris: List, ) : WithdrawalDetails() @Serializable @@ -133,7 +133,7 @@ class TransactionPayment( val status: PaymentStatus, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, - override val amountEffective: Amount + override val amountEffective: Amount, ) : Transaction() { override val icon = R.drawable.ic_cash_usd_outline override val detailPageNav = R.id.action_nav_transactions_detail_payment @@ -192,7 +192,7 @@ class TransactionRefund( val amountInvalid: Amount? = null, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, - override val amountEffective: Amount + override val amountEffective: Amount, ) : Transaction() { override val icon = R.drawable.transaction_refund override val detailPageNav = R.id.action_nav_transactions_detail_refund @@ -216,7 +216,7 @@ class TransactionTip( val merchantBaseUrl: String, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, - override val amountEffective: Amount + override val amountEffective: Amount, ) : Transaction() { override val icon = R.drawable.transaction_tip_accepted // TODO different when declined override val detailPageNav = 0 @@ -239,7 +239,7 @@ class TransactionRefresh( val exchangeBaseUrl: String, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, - override val amountEffective: Amount + override val amountEffective: Amount, ) : Transaction() { override val icon = R.drawable.transaction_refresh override val detailPageNav = R.id.action_nav_transactions_detail_refresh @@ -253,6 +253,30 @@ class TransactionRefresh( override val generalTitleRes = R.string.transaction_refresh } +@Serializable +@SerialName("deposit") +class TransactionDeposit( + override val transactionId: String, + override val timestamp: Timestamp, + override val pending: Boolean, + override val error: TalerErrorInfo? = null, + override val amountRaw: Amount, + override val amountEffective: Amount, + val targetPaytoUri: String, + val depositGroupId: String, +) : Transaction() { + override val icon = R.drawable.ic_cash_usd_outline + override val detailPageNav = R.id.action_nav_transactions_detail_deposit + + @Transient + override val amountType = AmountType.Negative + override fun getTitle(context: Context): String { + return context.getString(R.string.transaction_deposit) + } + + override val generalTitleRes = R.string.transaction_deposit +} + @Serializable data class PeerInfoShort( val expiration: Timestamp? = null, @@ -282,6 +306,7 @@ class TransactionPeerPullDebit( override fun getTitle(context: Context): String { return context.getString(R.string.transaction_peer_pull_debit) } + override val generalTitleRes = R.string.transaction_peer_pull_debit } @@ -309,6 +334,7 @@ class TransactionPeerPullCredit( override fun getTitle(context: Context): String { return context.getString(R.string.transaction_peer_pull_credit) } + override val generalTitleRes = R.string.transaction_peer_pull_credit } @@ -337,6 +363,7 @@ class TransactionPeerPushDebit( override fun getTitle(context: Context): String { return context.getString(R.string.transaction_peer_push_debit) } + override val generalTitleRes = R.string.payment_title } @@ -363,5 +390,6 @@ class TransactionPeerPushCredit( override fun getTitle(context: Context): String { return context.getString(R.string.transaction_peer_push_credit) } + override val generalTitleRes = R.string.transaction_peer_push_credit } diff --git a/wallet/src/main/res/navigation/nav_graph.xml b/wallet/src/main/res/navigation/nav_graph.xml index f9060c5..96ca49f 100644 --- a/wallet/src/main/res/navigation/nav_graph.xml +++ b/wallet/src/main/res/navigation/nav_graph.xml @@ -198,6 +198,12 @@ android:label="@string/transactions_detail_title" tools:layout="@layout/fragment_transaction_withdrawal" /> + + + + diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml index 52700b8..6d5f554 100644 --- a/wallet/src/main/res/values/strings.xml +++ b/wallet/src/main/res/values/strings.xml @@ -96,6 +96,7 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card Refund from %s PENDING Coin expiry change fee + Deposit Push payment Invoice Invoice paid -- cgit v1.2.3