gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 01/02: [wallet] Implement making deposits (not ful


From: gnunet
Subject: [taler-taler-android] 01/02: [wallet] Implement making deposits (not fully functional)
Date: Thu, 27 Oct 2022 15:38:01 +0200

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

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

commit 725562a48a23ed4d0153f05a5c36f4d17875c90c
Author: Torsten Grote <t@grobox.de>
AuthorDate: Thu Oct 27 10:15:52 2022 -0300

    [wallet] Implement making deposits (not fully functional)
---
 .../src/main/java/net/taler/common/AndroidUtils.kt |  12 +-
 .../java/net/taler/wallet/ReceiveFundsFragment.kt  |   5 +-
 .../java/net/taler/wallet/SendFundsFragment.kt     | 147 ++++++++++--
 .../net/taler/wallet/accounts/KnownBankAccounts.kt |  19 +-
 .../net/taler/wallet/payment/DepositFragment.kt    | 262 +++++++++++++++++++++
 .../java/net/taler/wallet/payment/DepositState.kt  |  44 ++++
 .../net/taler/wallet/payment/PaymentManager.kt     |  97 ++++++--
 .../OutgoingPushFragment.kt}                       |  45 ++--
 .../wallet/peer/OutgoingPushIntroComposable.kt     |  39 +--
 wallet/src/main/res/navigation/nav_graph.xml       |  25 +-
 wallet/src/main/res/values/strings.xml             |  13 +-
 11 files changed, 615 insertions(+), 93 deletions(-)

diff --git 
a/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
index 7dde872..c6d34e9 100644
--- a/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
@@ -97,13 +97,21 @@ fun Context.isOnline(): Boolean {
 }
 
 fun FragmentActivity.showError(mainText: String, detailText: String = "") = 
ErrorBottomSheet
-        .newInstance(mainText, detailText)
-        .show(supportFragmentManager, "ERROR_BOTTOM_SHEET")
+    .newInstance(mainText, detailText)
+    .show(supportFragmentManager, "ERROR_BOTTOM_SHEET")
 
 fun FragmentActivity.showError(@StringRes mainId: Int, detailText: String = 
"") {
     showError(getString(mainId), detailText)
 }
 
+fun Fragment.showError(mainText: String, detailText: String = "") = 
ErrorBottomSheet
+    .newInstance(mainText, detailText)
+    .show(parentFragmentManager, "ERROR_BOTTOM_SHEET")
+
+fun Fragment.showError(@StringRes mainId: Int, detailText: String = "") {
+    showError(getString(mainId), detailText)
+}
+
 fun Fragment.startActivitySafe(intent: Intent) {
     try {
         startActivity(intent)
diff --git a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt
index 31228a4..cf01e59 100644
--- a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt
@@ -23,8 +23,10 @@ import android.view.ViewGroup
 import android.widget.Toast
 import android.widget.Toast.LENGTH_LONG
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.text.KeyboardOptions
@@ -167,6 +169,7 @@ private fun ReceiveFundsIntro(
             Button(
                 modifier = Modifier
                     .padding(end = 16.dp)
+                    .height(IntrinsicSize.Max)
                     .weight(1f),
                 onClick = {
                     val amount = getAmount(currency, text)
@@ -176,7 +179,7 @@ private fun ReceiveFundsIntro(
                 Text(text = stringResource(R.string.receive_withdraw))
             }
             Button(
-                modifier = Modifier.weight(1f),
+                modifier = Modifier.weight(1f).height(IntrinsicSize.Max),
                 onClick = {
                     val amount = getAmount(currency, text)
                     if (amount == null) isError = true
diff --git a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
index 290c91b..60d98a5 100644
--- a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
@@ -20,21 +20,42 @@ 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.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.OutlinedTextField
 import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.core.os.bundleOf
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.navigation.findNavController
+import androidx.navigation.fragment.findNavController
 import com.google.android.material.composethemeadapter.MdcTheme
 import net.taler.common.Amount
-import net.taler.wallet.compose.collectAsStateLifecycleAware
-import net.taler.wallet.peer.OutgoingIntro
-import net.taler.wallet.peer.OutgoingPushIntroComposable
-import net.taler.wallet.peer.OutgoingPushResultComposable
 
 class SendFundsFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
-    private val transactionManager get() = model.transactionManager
     private val peerManager get() = model.peerManager
 
     override fun onCreateView(
@@ -44,16 +65,11 @@ class SendFundsFragment : Fragment() {
         setContent {
             MdcTheme {
                 Surface {
-                    val state = 
peerManager.pushState.collectAsStateLifecycleAware()
-                    if (state.value is OutgoingIntro) {
-                        val currency = transactionManager.selectedCurrency
-                            ?: error("No currency selected")
-                        OutgoingPushIntroComposable(currency, 
this@SendFundsFragment::onSend)
-                    } else {
-                        OutgoingPushResultComposable(state.value) {
-                            findNavController().popBackStack()
-                        }
-                    }
+                    SendFundsIntro(
+                        model.transactionManager.selectedCurrency ?: error("No 
currency selected"),
+                        this@SendFundsFragment::onDeposit,
+                        this@SendFundsFragment::onPeerPush,
+                    )
                 }
             }
         }
@@ -69,7 +85,102 @@ class SendFundsFragment : Fragment() {
         if (!requireActivity().isChangingConfigurations) 
peerManager.resetPushPayment()
     }
 
-    private fun onSend(amount: Amount, summary: String) {
-        peerManager.initiatePeerPushPayment(amount, summary)
+    fun onDeposit(amount: Amount) {
+        val bundle = bundleOf("amount" to amount.toJSONString())
+        findNavController().navigate(R.id.action_sendFunds_to_nav_deposit, 
bundle)
+    }
+
+    fun onPeerPush(amount: Amount) {
+        val bundle = bundleOf("amount" to amount.toJSONString())
+        findNavController().navigate(R.id.action_sendFunds_to_nav_peer_push, 
bundle)
+    }
+}
+
+@Composable
+private fun SendFundsIntro(
+    currency: String,
+    onDeposit: (Amount) -> Unit,
+    onPeerPush: (Amount) -> Unit,
+) {
+    val scrollState = rememberScrollState()
+    Column(
+        modifier = Modifier
+            .fillMaxWidth()
+            .verticalScroll(scrollState),
+    ) {
+        var text by rememberSaveable { mutableStateOf("") }
+        var isError by rememberSaveable { mutableStateOf(false) }
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            modifier = Modifier
+                .padding(16.dp),
+        ) {
+            OutlinedTextField(
+                modifier = Modifier
+                    .weight(1f)
+                    .padding(end = 16.dp),
+                value = text,
+                keyboardOptions = KeyboardOptions.Default.copy(keyboardType = 
KeyboardType.Decimal),
+                onValueChange = { input ->
+                    isError = false
+                    text = input.filter { it.isDigit() || it == '.' }
+                },
+                isError = isError,
+                label = {
+                    if (isError) {
+                        Text(
+                            stringResource(R.string.receive_amount_invalid),
+                            color = Color.Red,
+                        )
+                    } else {
+                        Text(stringResource(R.string.send_amount))
+                    }
+                }
+            )
+            Text(
+                modifier = Modifier,
+                text = currency,
+                softWrap = false,
+                style = MaterialTheme.typography.h6,
+            )
+        }
+        Text(
+            modifier = Modifier.padding(horizontal = 16.dp),
+            text = stringResource(R.string.send_intro),
+            style = MaterialTheme.typography.h6,
+        )
+        Row(modifier = Modifier.padding(16.dp)) {
+            Button(
+                modifier = Modifier
+                    .padding(end = 16.dp)
+                    .weight(1f),
+                onClick = {
+                    val amount = getAmount(currency, text)
+                    if (amount == null) isError = true
+                    else onDeposit(amount)
+                }) {
+                Text(text = stringResource(R.string.send_deposit))
+            }
+            Button(
+                modifier = Modifier
+                    .height(IntrinsicSize.Max)
+                    .weight(1f),
+                onClick = {
+                    val amount = getAmount(currency, text)
+                    if (amount == null) isError = true
+                    else onPeerPush(amount)
+                },
+            ) {
+                Text(text = stringResource(R.string.send_peer))
+            }
+        }
+    }
+}
+
+@Preview
+@Composable
+fun PreviewSendFundsIntro() {
+    Surface {
+        SendFundsIntro("TESTKUDOS", {}) {}
     }
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/accounts/KnownBankAccounts.kt 
b/wallet/src/main/java/net/taler/wallet/accounts/KnownBankAccounts.kt
index 0dcb18e..a0ce956 100644
--- a/wallet/src/main/java/net/taler/wallet/accounts/KnownBankAccounts.kt
+++ b/wallet/src/main/java/net/taler/wallet/accounts/KnownBankAccounts.kt
@@ -16,6 +16,7 @@
 
 package net.taler.wallet.accounts
 
+import android.net.Uri
 import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
@@ -48,14 +49,28 @@ sealed class PaytoUri(
 
 @Serializable
 @SerialName("iban")
-class PaytoUriIBAN(
+class PaytoUriIban(
     val iban: String,
+    val bic: String? = "SANDBOXX",
     override val targetPath: String,
     override val params: Map<String, String>,
 ) : PaytoUri(
     isKnown = true,
     targetType = "iban",
-)
+) {
+    val paytoUri: String
+        get() = Uri.Builder()
+            .scheme("payto")
+            .appendEncodedPath("/$targetType")
+            .apply { if (bic != null) appendPath(bic) }
+            .appendPath(iban)
+            .apply {
+                params.forEach { (key, value) ->
+                    appendQueryParameter(key, value)
+                }
+            }
+            .build().toString()
+}
 
 @Serializable
 @SerialName("x-taler-bank")
diff --git a/wallet/src/main/java/net/taler/wallet/payment/DepositFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/DepositFragment.kt
new file mode 100644
index 0000000..add9467
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/DepositFragment.kt
@@ -0,0 +1,262 @@
+/*
+ * 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.payment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Column
+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.OutlinedTextField
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import com.google.android.material.composethemeadapter.MdcTheme
+import net.taler.common.Amount
+import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
+import net.taler.wallet.compose.collectAsStateLifecycleAware
+
+class DepositFragment : Fragment() {
+    private val model: MainViewModel by activityViewModels()
+    private val paymentManager get() = model.paymentManager
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?,
+    ): View {
+        val amount = arguments?.getString("amount")?.let {
+            Amount.fromJSONString(it)
+        } ?: error("no amount passed")
+
+        return ComposeView(requireContext()).apply {
+            setContent {
+                MdcTheme {
+                    Surface {
+                        val state = 
paymentManager.depositState.collectAsStateLifecycleAware()
+                        MakeDepositComposable(
+                            state = state.value,
+                            amount = amount,
+                            onMakeDeposit = 
this@DepositFragment::onDepositButtonClicked,
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        activity?.setTitle(R.string.send_deposit_title)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        if (!requireActivity().isChangingConfigurations) {
+            paymentManager.resetDepositState()
+        }
+    }
+
+    private fun onDepositButtonClicked(
+        amount: Amount,
+        receiverName: String,
+        iban: String,
+        bic: String,
+    ) {
+        paymentManager.onDepositButtonClicked(amount, receiverName, iban, bic)
+    }
+}
+
+@Composable
+private fun MakeDepositComposable(
+    state: DepositState,
+    amount: Amount,
+    onMakeDeposit: (Amount, String, String, String) -> Unit,
+) {
+    val scrollState = rememberScrollState()
+    Column(
+        modifier = Modifier
+            .fillMaxWidth()
+            .verticalScroll(scrollState),
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        var name by rememberSaveable { mutableStateOf("") }
+        var iban by rememberSaveable { mutableStateOf("") }
+        var bic by rememberSaveable { mutableStateOf("") }
+        val focusRequester = remember { FocusRequester() }
+        OutlinedTextField(
+            modifier = Modifier
+                .padding(16.dp)
+                .focusRequester(focusRequester),
+            value = name,
+            enabled = !state.showFees,
+            onValueChange = { input ->
+                name = input
+            },
+            isError = name.isBlank(),
+            label = {
+                Text(
+                    stringResource(R.string.send_deposit_name),
+                    color = if (name.isBlank()) {
+                        colorResource(R.color.red)
+                    } else Color.Unspecified,
+                )
+            }
+        )
+        LaunchedEffect(Unit) {
+            focusRequester.requestFocus()
+        }
+        OutlinedTextField(
+            modifier = Modifier
+                .padding(16.dp),
+            value = iban,
+            enabled = !state.showFees,
+            onValueChange = { input ->
+                iban = input
+            },
+            isError = iban.isBlank(),
+            label = {
+                Text(
+                    text = stringResource(R.string.send_deposit_iban),
+                    color = if (iban.isBlank()) {
+                        colorResource(R.color.red)
+                    } else Color.Unspecified,
+                )
+            }
+        )
+        OutlinedTextField(
+            modifier = Modifier
+                .padding(16.dp),
+            value = bic,
+            enabled = !state.showFees,
+            onValueChange = { input ->
+                bic = input
+            },
+            label = {
+                Text(
+                    text = stringResource(R.string.send_deposit_bic),
+                )
+            }
+        )
+        Text(
+            modifier = Modifier.padding(horizontal = 16.dp),
+            text = stringResource(id = R.string.amount_chosen),
+        )
+        Text(
+            modifier = Modifier.padding(16.dp),
+            fontSize = 24.sp,
+            color = colorResource(R.color.green),
+            text = amount.toString(),
+        )
+        AnimatedVisibility(visible = state.showFees) {
+            Column(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalAlignment = Alignment.CenterHorizontally,
+            ) {
+                val effectiveAmount = state.effectiveDepositAmount
+                val fee = amount - (effectiveAmount ?: 
Amount.zero(amount.currency))
+                Text(
+                    modifier = Modifier.padding(horizontal = 16.dp),
+                    text = stringResource(id = R.string.withdraw_fees),
+                )
+                Text(
+                    modifier = Modifier.padding(16.dp),
+                    fontSize = 24.sp,
+                    color = colorResource(if (fee.isZero()) R.color.green else 
R.color.red),
+                    text = if (fee.isZero()) {
+                        fee.toString()
+                    } else {
+                        stringResource(R.string.amount_negative, 
fee.toString())
+                    },
+                )
+                Text(
+                    modifier = Modifier.padding(horizontal = 16.dp),
+                    text = stringResource(id = 
R.string.send_deposit_amount_effective),
+                )
+                Text(
+                    modifier = Modifier.padding(16.dp),
+                    fontSize = 24.sp,
+                    color = colorResource(R.color.green),
+                    text = effectiveAmount.toString(),
+                )
+            }
+        }
+        AnimatedVisibility(visible = state is DepositState.Error) {
+            Text(
+                modifier = Modifier.padding(16.dp),
+                fontSize = 18.sp,
+                color = colorResource(R.color.red),
+                text = (state as? DepositState.Error)?.msg ?: "",
+            )
+        }
+        val focusManager = LocalFocusManager.current
+        Button(
+            modifier = Modifier.padding(16.dp),
+            enabled = iban.isNotBlank(),
+            onClick = {
+                focusManager.clearFocus()
+                onMakeDeposit(amount, name, iban, bic)
+            },
+        ) {
+            Text(text = stringResource(
+                if (state.showFees) R.string.send_deposit_create_button
+                else R.string.send_deposit_check_fees_button
+            ))
+        }
+    }
+}
+
+@Preview
+@Composable
+fun PreviewMakeDepositComposable() {
+    Surface {
+        val state = DepositState.FeesChecked(
+            effectiveDepositAmount = Amount.fromDouble("TESTKUDOS", 42.00),
+        )
+        MakeDepositComposable(
+            state = state,
+            amount = Amount.fromDouble("TESTKUDOS", 42.23)) { _, _, _, _ ->
+        }
+    }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/payment/DepositState.kt 
b/wallet/src/main/java/net/taler/wallet/payment/DepositState.kt
new file mode 100644
index 0000000..8598911
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/DepositState.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.payment
+
+import net.taler.common.Amount
+
+sealed class DepositState {
+
+    open val showFees: Boolean = false
+    open val effectiveDepositAmount: Amount? = null
+
+    object Start : DepositState()
+    object CheckingFees : DepositState()
+    class FeesChecked(
+        override val effectiveDepositAmount: Amount,
+    ) : DepositState() {
+        override val showFees = true
+    }
+
+    class MakingDeposit(
+        override val effectiveDepositAmount: Amount,
+    ) : DepositState() {
+        override val showFees = true
+    }
+
+    object Success : DepositState()
+
+    class Error(val msg: String) : DepositState()
+
+}
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 dfa14c2..74740ca 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -21,11 +21,14 @@ import androidx.annotation.UiThread
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 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.accounts.PaytoUriIban
 import net.taler.wallet.backend.TalerErrorInfo
 import net.taler.wallet.backend.WalletBackendApi
 import net.taler.wallet.payment.PayStatus.AlreadyPaid
@@ -43,12 +46,12 @@ sealed class PayStatus {
         val contractTerms: ContractTerms,
         val proposalId: String,
         val amountRaw: Amount,
-        val amountEffective: Amount
+        val amountEffective: Amount,
     ) : PayStatus()
 
     data class InsufficientBalance(
         val contractTerms: ContractTerms,
-        val amountRaw: Amount
+        val amountRaw: Amount,
     ) : PayStatus()
 
     // TODO bring user to fulfilment URI
@@ -65,6 +68,9 @@ class PaymentManager(
     private val mPayStatus = MutableLiveData<PayStatus>(PayStatus.None)
     internal val payStatus: LiveData<PayStatus> = mPayStatus
 
+    private val mDepositState = 
MutableStateFlow<DepositState>(DepositState.Start)
+    internal val depositState = mDepositState.asStateFlow()
+
     @UiThread
     fun preparePay(url: String) = scope.launch {
         mPayStatus.value = PayStatus.Loading
@@ -120,28 +126,91 @@ class PaymentManager(
         mPayStatus.value = PayStatus.None
     }
 
+    private fun handleError(operation: String, error: TalerErrorInfo) {
+        Log.e(TAG, "got $operation error result $error")
+        mPayStatus.value = PayStatus.Error(error.userFacingMsg)
+    }
+
+    /* Deposits */
+
     @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")
+    fun onDepositButtonClicked(amount: Amount, receiverName: String, iban: 
String, bic: String) {
+        val paytoUri: String = PaytoUriIban(
+            iban = iban,
+            bic = bic,
+            targetPath = "",
+            params = mapOf("receiver-name" to receiverName),
+        ).paytoUri
+
+        if (depositState.value.showFees) {
+            val effectiveDepositAmount = 
depositState.value.effectiveDepositAmount
+                ?: Amount.zero(amount.currency)
+            makeDeposit(paytoUri, amount, effectiveDepositAmount)
+        } else {
+            prepareDeposit(paytoUri, amount)
         }
     }
 
-    private fun handleError(operation: String, error: TalerErrorInfo) {
-        Log.e(TAG, "got $operation error result $error")
-        mPayStatus.value = PayStatus.Error(error.userFacingMsg)
+    private fun prepareDeposit(paytoUri: String, amount: Amount) {
+        mDepositState.value = DepositState.CheckingFees
+        scope.launch {
+            api.request("prepareDeposit", PrepareDepositResponse.serializer()) 
{
+                put("depositPaytoUri", paytoUri)
+                put("amount", amount.toJSONString())
+            }.onError {
+                Log.e(TAG, "Error prepareDeposit $it")
+                mDepositState.value = DepositState.Error(it.userFacingMsg)
+            }.onSuccess {
+                mDepositState.value = DepositState.FeesChecked(
+                    effectiveDepositAmount = it.effectiveDepositAmount.amount,
+                )
+            }
+        }
     }
 
+    private fun makeDeposit(
+        paytoUri: String,
+        amount: Amount,
+        effectiveDepositAmount: Amount,
+    ) {
+        mDepositState.value = 
DepositState.MakingDeposit(effectiveDepositAmount)
+        scope.launch {
+            api.request("createDepositGroup", 
CreateDepositGroupResponse.serializer()) {
+                put("depositPaytoUri", paytoUri)
+                put("amount", amount.toJSONString())
+            }.onError {
+                Log.e(TAG, "Error createDepositGroup $it")
+                mDepositState.value = DepositState.Error(it.userFacingMsg)
+            }.onSuccess {
+                mDepositState.value = DepositState.Success
+            }
+        }
+    }
+
+    @UiThread
+    fun resetDepositState() {
+        mDepositState.value = DepositState.Start
+    }
 }
 
+@Serializable
+data class PrepareDepositResponse(
+    val totalDepositCost: AmountJson,
+    val effectiveDepositAmount: AmountJson,
+)
+
 @Serializable
 data class CreateDepositGroupResponse(
     val depositGroupId: String,
     val transactionId: String,
 )
+
+@Serializable
+@Deprecated("no idea why this is now in the API")
+data class AmountJson(
+    val currency: String,
+    val value: Long,
+    val fraction: Int,
+) {
+    val amount = Amount(currency, value, fraction)
+}
diff --git a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
similarity index 62%
copy from wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
copy to wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
index 290c91b..ae0ef10 100644
--- a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet
+package net.taler.wallet.peer
 
 import android.os.Bundle
 import android.view.LayoutInflater
@@ -27,31 +27,36 @@ import androidx.fragment.app.activityViewModels
 import androidx.navigation.findNavController
 import com.google.android.material.composethemeadapter.MdcTheme
 import net.taler.common.Amount
+import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
 import net.taler.wallet.compose.collectAsStateLifecycleAware
-import net.taler.wallet.peer.OutgoingIntro
-import net.taler.wallet.peer.OutgoingPushIntroComposable
-import net.taler.wallet.peer.OutgoingPushResultComposable
 
-class SendFundsFragment : Fragment() {
+class OutgoingPushFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
-    private val transactionManager get() = model.transactionManager
     private val peerManager get() = model.peerManager
 
     override fun onCreateView(
-        inflater: LayoutInflater, container: ViewGroup?,
+        inflater: LayoutInflater,
+        container: ViewGroup?,
         savedInstanceState: Bundle?,
-    ): View = ComposeView(requireContext()).apply {
-        setContent {
-            MdcTheme {
-                Surface {
-                    val state = 
peerManager.pushState.collectAsStateLifecycleAware()
-                    if (state.value is OutgoingIntro) {
-                        val currency = transactionManager.selectedCurrency
-                            ?: error("No currency selected")
-                        OutgoingPushIntroComposable(currency, 
this@SendFundsFragment::onSend)
-                    } else {
-                        OutgoingPushResultComposable(state.value) {
-                            findNavController().popBackStack()
+    ): View {
+        val amount = arguments?.getString("amount")?.let {
+            Amount.fromJSONString(it)
+        } ?: error("no amount passed")
+        return ComposeView(requireContext()).apply {
+            setContent {
+                MdcTheme {
+                    Surface {
+                        val state = 
peerManager.pushState.collectAsStateLifecycleAware()
+                        if (state.value is OutgoingIntro) {
+                            OutgoingPushIntroComposable(
+                                amount = amount,
+                                onSend = this@OutgoingPushFragment::onSend,
+                            )
+                        } else {
+                            OutgoingPushResultComposable(state.value) {
+                                findNavController().popBackStack()
+                            }
                         }
                     }
                 }
@@ -61,7 +66,7 @@ class SendFundsFragment : Fragment() {
 
     override fun onStart() {
         super.onStart()
-        activity?.setTitle(R.string.transactions_send_funds)
+        activity?.setTitle(R.string.receive_peer_title)
     }
 
     override fun onDestroy() {
diff --git 
a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt 
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
index 72c8862..1964ebd 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
@@ -21,7 +21,6 @@ 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.text.KeyboardOptions
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Button
 import androidx.compose.material.MaterialTheme
@@ -39,16 +38,14 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import net.taler.common.Amount
 import net.taler.wallet.R
-import net.taler.wallet.getAmount
 
 @Composable
 fun OutgoingPushIntroComposable(
-    currency: String,
+    amount: Amount,
     onSend: (amount: Amount, summary: String) -> Unit,
 ) {
     val scrollState = rememberScrollState()
@@ -58,38 +55,14 @@ fun OutgoingPushIntroComposable(
             .verticalScroll(scrollState),
         horizontalAlignment = CenterHorizontally,
     ) {
-        var amountText by rememberSaveable { mutableStateOf("") }
-        var isError by rememberSaveable { mutableStateOf(false) }
         Row(
             verticalAlignment = Alignment.CenterVertically,
             modifier = Modifier
                 .padding(16.dp),
         ) {
-            OutlinedTextField(
-                modifier = Modifier
-                    .weight(1f)
-                    .padding(end = 16.dp),
-                value = amountText,
-                keyboardOptions = KeyboardOptions.Default.copy(keyboardType = 
KeyboardType.Decimal),
-                onValueChange = { input ->
-                    isError = false
-                    amountText = input.filter { it.isDigit() || it == '.' }
-                },
-                isError = isError,
-                label = {
-                    if (isError) {
-                        Text(
-                            stringResource(R.string.receive_amount_invalid),
-                            color = Color.Red,
-                        )
-                    } else {
-                        Text(stringResource(R.string.send_peer_amount))
-                    }
-                }
-            )
             Text(
                 modifier = Modifier,
-                text = currency,
+                text = amount.toString(),
                 softWrap = false,
                 style = MaterialTheme.typography.h6,
             )
@@ -118,11 +91,9 @@ fun OutgoingPushIntroComposable(
         )
         Button(
             modifier = Modifier.padding(16.dp),
-            enabled = subject.isNotBlank() && amountText.isNotBlank(),
+            enabled = subject.isNotBlank(),
             onClick = {
-                val amount = getAmount(currency, amountText)
-                if (amount == null) isError = true
-                else onSend(amount, subject)
+                onSend(amount, subject)
             },
         ) {
             Text(text = stringResource(R.string.send_peer_create_button))
@@ -134,6 +105,6 @@ fun OutgoingPushIntroComposable(
 @Composable
 fun PeerPushIntroComposablePreview() {
     Surface {
-        OutgoingPushIntroComposable("TESTKUDOS") { _, _ -> }
+        OutgoingPushIntroComposable(Amount.fromDouble("TESTKUDOS", 42.23)) { 
_, _ -> }
     }
 }
diff --git a/wallet/src/main/res/navigation/nav_graph.xml 
b/wallet/src/main/res/navigation/nav_graph.xml
index 96ca49f..6feb846 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -48,7 +48,14 @@
     <fragment
         android:id="@+id/sendFunds"
         android:name="net.taler.wallet.SendFundsFragment"
-        android:label="@string/transactions_send_funds" />
+        android:label="@string/transactions_send_funds">
+        <action
+            android:id="@+id/action_sendFunds_to_nav_deposit"
+            app:destination="@id/nav_deposit" />
+        <action
+            android:id="@+id/action_sendFunds_to_nav_peer_push"
+            app:destination="@id/nav_peer_push" />
+    </fragment>
 
     <fragment
         android:id="@+id/promptTip"
@@ -124,6 +131,11 @@
             app:popUpTo="@id/nav_main" />
     </fragment>
 
+    <fragment
+        android:id="@+id/nav_deposit"
+        android:name="net.taler.wallet.payment.DepositFragment"
+        android:label="@string/send_deposit_title" />
+
     <fragment
         android:id="@+id/nav_settings_backup"
         android:name="net.taler.wallet.settings.BackupSettingsFragment"
@@ -140,6 +152,17 @@
             app:nullable="true" />
     </fragment>
 
+    <fragment
+        android:id="@+id/nav_peer_push"
+        android:name="net.taler.wallet.peer.OutgoingPushFragment"
+        android:label="@string/send_peer_title">
+        <argument
+            android:name="amount"
+            android:defaultValue="@null"
+            app:argType="string"
+            app:nullable="true" />
+    </fragment>
+
     <fragment
         android:id="@+id/promptPullPayment"
         android:name="net.taler.wallet.peer.IncomingPullPaymentFragment"
diff --git a/wallet/src/main/res/values/strings.xml 
b/wallet/src/main/res/values/strings.xml
index f72b345..2b81894 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -125,7 +125,18 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="receive_peer_invoice_instruction">Let the payer scan this QR 
code to pay:</string>
     <string name="receive_peer_invoice_uri">Alternatively, copy and send this 
URI:</string>
 
-    <string name="send_peer_amount">Amount to send</string>
+    <string name="send_amount">Amount to send</string>
+    <string name="send_intro">Choose where to send money to:</string>
+    <string name="send_deposit">To a bank account</string>
+    <string name="send_deposit_title">Deposit to a bank account</string>
+    <string name="send_deposit_iban">IBAN</string>
+    <string name="send_deposit_bic">BIC/SWIFT</string>
+    <string name="send_deposit_name">Account holder</string>
+    <string name="send_deposit_check_fees_button">Check fees</string>
+    <string name="send_deposit_amount_effective">Effective Amount</string>
+    <string name="send_deposit_create_button">Make deposit</string>
+    <string name="send_peer">To another wallet</string>
+    <string name="send_peer_title">Send money to another wallet</string>
     <string name="send_peer_create_button">Send funds now</string>
     <string name="send_peer_warning">Warning: Funds will leave the wallet 
immediately.</string>
     <string name="send_peer_payment_instruction">Let the payee scan this QR 
code to receive:</string>

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



reply via email to

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