gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] branch master updated: [wallet] Open payto:// URIs


From: gnunet
Subject: [taler-taler-android] branch master updated: [wallet] Open payto:// URIs and hook into deposit to bank account flow
Date: Wed, 02 Nov 2022 13:01:45 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 865f80d  [wallet] Open payto:// URIs and hook into deposit to bank 
account flow
865f80d is described below

commit 865f80d49a8741de55d27002717d7ca1b42c875f
Author: Torsten Grote <t@grobox.de>
AuthorDate: Wed Nov 2 08:58:45 2022 -0300

    [wallet] Open payto:// URIs and hook into deposit to bank account flow
---
 build.gradle                                       |   2 +-
 wallet/src/main/AndroidManifest.xml                |   1 +
 .../src/main/java/net/taler/wallet/MainActivity.kt |  15 ++
 .../main/java/net/taler/wallet/MainViewModel.kt    |  27 +++
 .../net/taler/wallet/accounts/KnownBankAccounts.kt |   2 +-
 .../wallet/{payment => deposit}/DepositFragment.kt |  24 +-
 .../DepositManager.kt}                             | 107 +--------
 .../wallet/{payment => deposit}/DepositState.kt    |   2 +-
 .../net/taler/wallet/deposit/PayToUriFragment.kt   | 245 +++++++++++++++++++++
 .../net/taler/wallet/payment/PaymentManager.kt     |  79 -------
 wallet/src/main/res/navigation/nav_graph.xml       |  35 ++-
 wallet/src/main/res/values/strings.xml             |   1 +
 12 files changed, 351 insertions(+), 189 deletions(-)

diff --git a/build.gradle b/build.gradle
index 972eceb..98886f9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,7 +11,7 @@ buildscript {
         // check https://android-rebuilds.beuc.net/ for availability of free 
build tools
         build_tools_version = "33.0.0"
         // should debug build types be minified with D8 as well? good for 
catching issues early
-        minify_debug = false // DO NOT MERGE true
+        minify_debug = true
     }
     repositories {
         google()
diff --git a/wallet/src/main/AndroidManifest.xml 
b/wallet/src/main/AndroidManifest.xml
index 68bc321..43ccdd4 100644
--- a/wallet/src/main/AndroidManifest.xml
+++ b/wallet/src/main/AndroidManifest.xml
@@ -48,6 +48,7 @@
             android:name=".MainActivity"
             android:exported="true"
             android:label="@string/app_name"
+            android:launchMode="singleTop"
             android:theme="@style/AppTheme.NoActionBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt 
b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index cb48c30..13fd394 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -32,6 +32,7 @@ import android.view.View.VISIBLE
 import android.widget.TextView
 import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
+import androidx.core.os.bundleOf
 import androidx.core.view.GravityCompat.START
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.Observer
@@ -146,6 +147,13 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
         else super.onBackPressed()
     }
 
+    override fun onNewIntent(intent: Intent?) {
+        super.onNewIntent(intent)
+        if (intent?.action == ACTION_VIEW) intent.dataString?.let { uri ->
+            handleTalerUri(uri, "intent")
+        }
+    }
+
     override fun onNavigationItemSelected(item: MenuItem): Boolean {
         when (item.itemId) {
             R.id.nav_home -> nav.navigate(R.id.nav_main)
@@ -218,6 +226,13 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
         getTalerAction(uri, 3, MutableLiveData<String>()).observe(this) { u ->
             Log.v(TAG, "found action $u")
 
+            if (u.startsWith("payto://", ignoreCase = true)) {
+                Log.v(TAG, "navigating with paytoUri!")
+                val bundle = bundleOf("uri" to u)
+                nav.navigate(R.id.action_nav_payto_uri, bundle)
+                return@observe
+            }
+
             val normalizedURL = u.lowercase(ROOT)
             val action = normalizedURL.substring(
                 if (normalizedURL.startsWith("taler://", ignoreCase = true)) {
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index e660676..255c28b 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -27,6 +27,7 @@ import androidx.lifecycle.viewModelScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 import net.taler.common.Amount
+import net.taler.common.AmountParserException
 import net.taler.common.Event
 import net.taler.common.assertUiThread
 import net.taler.common.toEvent
@@ -34,6 +35,7 @@ import net.taler.wallet.accounts.AccountManager
 import net.taler.wallet.backend.WalletBackendApi
 import net.taler.wallet.balances.BalanceItem
 import net.taler.wallet.balances.BalanceResponse
+import net.taler.wallet.deposit.DepositManager
 import net.taler.wallet.exchanges.ExchangeManager
 import net.taler.wallet.payment.PaymentManager
 import net.taler.wallet.peer.PeerManager
@@ -100,6 +102,7 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     val peerManager: PeerManager = PeerManager(api, viewModelScope)
     val settingsManager: SettingsManager = 
SettingsManager(app.applicationContext, viewModelScope)
     val accountManager: AccountManager = AccountManager(api, viewModelScope)
+    val depositManager: DepositManager = DepositManager(api, viewModelScope)
 
     private val mTransactionsEvent = MutableLiveData<Event<String>>()
     val transactionsEvent: LiveData<Event<String>> = mTransactionsEvent
@@ -141,6 +144,24 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
         mTransactionsEvent.value = currency.toEvent()
     }
 
+    @UiThread
+    fun getCurrencies(): List<String> {
+        return balances.value?.map { balanceItem ->
+            balanceItem.currency
+        } ?: emptyList()
+    }
+
+    @UiThread
+    fun createAmount(amountText: String, currency: String): AmountResult {
+        val amount = try {
+            Amount.fromString(currency, amountText)
+        } catch (e: AmountParserException) {
+            return AmountResult.InvalidAmount
+        }
+        if (hasSufficientBalance(amount)) return AmountResult.Success(amount)
+        return AmountResult.InsufficientBalance
+    }
+
     @UiThread
     fun hasSufficientBalance(amount: Amount): Boolean {
         balances.value?.forEach { balanceItem ->
@@ -177,3 +198,9 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     }
 
 }
+
+sealed class AmountResult {
+    class Success(val amount: Amount) : AmountResult()
+    object InsufficientBalance : AmountResult()
+    object InvalidAmount : AmountResult()
+}
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 a0ce956..35dd45a 100644
--- a/wallet/src/main/java/net/taler/wallet/accounts/KnownBankAccounts.kt
+++ b/wallet/src/main/java/net/taler/wallet/accounts/KnownBankAccounts.kt
@@ -61,7 +61,7 @@ class PaytoUriIban(
     val paytoUri: String
         get() = Uri.Builder()
             .scheme("payto")
-            .appendEncodedPath("/$targetType")
+            .authority(targetType)
             .apply { if (bic != null) appendPath(bic) }
             .appendPath(iban)
             .apply {
diff --git a/wallet/src/main/java/net/taler/wallet/payment/DepositFragment.kt 
b/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt
similarity index 91%
rename from wallet/src/main/java/net/taler/wallet/payment/DepositFragment.kt
rename to wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt
index add9467..2793e56 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/DepositFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.payment
+package net.taler.wallet.deposit
 
 import android.os.Bundle
 import android.view.LayoutInflater
@@ -59,7 +59,7 @@ import net.taler.wallet.compose.collectAsStateLifecycleAware
 
 class DepositFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
-    private val paymentManager get() = model.paymentManager
+    private val depositManager get() = model.depositManager
 
     override fun onCreateView(
         inflater: LayoutInflater,
@@ -69,15 +69,23 @@ class DepositFragment : Fragment() {
         val amount = arguments?.getString("amount")?.let {
             Amount.fromJSONString(it)
         } ?: error("no amount passed")
+        val receiverName = arguments?.getString("receiverName")
+        val iban = arguments?.getString("IBAN")
+        val bic = arguments?.getString("BIC") ?: ""
 
+        if (receiverName != null && iban != null) {
+            onDepositButtonClicked(amount, receiverName, iban, bic)
+        }
         return ComposeView(requireContext()).apply {
             setContent {
                 MdcTheme {
                     Surface {
-                        val state = 
paymentManager.depositState.collectAsStateLifecycleAware()
+                        val state = 
depositManager.depositState.collectAsStateLifecycleAware()
                         MakeDepositComposable(
                             state = state.value,
                             amount = amount,
+                            presetName = receiverName,
+                            presetIban = iban,
                             onMakeDeposit = 
this@DepositFragment::onDepositButtonClicked,
                         )
                     }
@@ -94,7 +102,7 @@ class DepositFragment : Fragment() {
     override fun onDestroy() {
         super.onDestroy()
         if (!requireActivity().isChangingConfigurations) {
-            paymentManager.resetDepositState()
+            depositManager.resetDepositState()
         }
     }
 
@@ -104,7 +112,7 @@ class DepositFragment : Fragment() {
         iban: String,
         bic: String,
     ) {
-        paymentManager.onDepositButtonClicked(amount, receiverName, iban, bic)
+        depositManager.onDepositButtonClicked(amount, receiverName, iban, bic)
     }
 }
 
@@ -112,6 +120,8 @@ class DepositFragment : Fragment() {
 private fun MakeDepositComposable(
     state: DepositState,
     amount: Amount,
+    presetName: String? = null,
+    presetIban: String? = null,
     onMakeDeposit: (Amount, String, String, String) -> Unit,
 ) {
     val scrollState = rememberScrollState()
@@ -121,8 +131,8 @@ private fun MakeDepositComposable(
             .verticalScroll(scrollState),
         horizontalAlignment = Alignment.CenterHorizontally,
     ) {
-        var name by rememberSaveable { mutableStateOf("") }
-        var iban by rememberSaveable { mutableStateOf("") }
+        var name by rememberSaveable { mutableStateOf(presetName ?: "") }
+        var iban by rememberSaveable { mutableStateOf(presetIban ?: "") }
         var bic by rememberSaveable { mutableStateOf("") }
         val focusRequester = remember { FocusRequester() }
         OutlinedTextField(
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt 
b/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt
similarity index 52%
copy from wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
copy to wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt
index 0af4262..a207691 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt
@@ -1,6 +1,6 @@
 /*
  * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
+ * (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
@@ -14,125 +14,36 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.payment
+package net.taler.wallet.deposit
 
+import android.net.Uri
 import android.util.Log
 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
-import net.taler.wallet.payment.PayStatus.InsufficientBalance
-import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
-import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse
-import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse
 
-val REGEX_PRODUCT_IMAGE = 
Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$")
-
-sealed class PayStatus {
-    object None : PayStatus()
-    object Loading : PayStatus()
-    data class Prepared(
-        val contractTerms: ContractTerms,
-        val proposalId: String,
-        val amountRaw: Amount,
-        val amountEffective: Amount,
-    ) : PayStatus()
-
-    data class InsufficientBalance(
-        val contractTerms: ContractTerms,
-        val amountRaw: Amount,
-    ) : PayStatus()
-
-    // TODO bring user to fulfilment URI
-    object AlreadyPaid : PayStatus()
-    data class Error(val error: String) : PayStatus()
-    data class Success(val currency: String) : PayStatus()
-}
-
-class PaymentManager(
+class DepositManager(
     private val api: WalletBackendApi,
     private val scope: CoroutineScope,
 ) {
 
-    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
-        api.request("preparePayForUri", PreparePayResponse.serializer()) {
-            put("talerPayUri", url)
-        }.onError {
-            handleError("preparePayForUri", it)
-        }.onSuccess { response ->
-            mPayStatus.value = when (response) {
-                is PaymentPossibleResponse -> response.toPayStatusPrepared()
-                is InsufficientBalanceResponse -> InsufficientBalance(
-                    response.contractTerms,
-                    response.amountRaw
-                )
-                is AlreadyConfirmedResponse -> AlreadyPaid
-            }
-        }
-    }
-
-    fun confirmPay(proposalId: String, currency: String) = scope.launch {
-        api.request("confirmPay", ConfirmPayResult.serializer()) {
-            put("proposalId", proposalId)
-        }.onError {
-            handleError("confirmPay", it)
-        }.onSuccess {
-            mPayStatus.postValue(PayStatus.Success(currency))
-        }
-    }
-
-    @UiThread
-    fun abortPay() {
-        val ps = payStatus.value
-        if (ps is PayStatus.Prepared) {
-            abortProposal(ps.proposalId)
-        }
-        resetPayStatus()
-    }
-
-    internal fun abortProposal(proposalId: String) = scope.launch {
-        Log.i(TAG, "aborting proposal")
-        api.request<Unit>("abortProposal") {
-            put("proposalId", proposalId)
-        }.onError {
-            Log.e(TAG, "received error response to abortProposal")
-            handleError("abortProposal", it)
-        }.onSuccess {
-            mPayStatus.postValue(PayStatus.None)
-        }
+    fun isSupportedPayToUri(uriString: String): Boolean {
+        if (!uriString.startsWith("payto://")) return false
+        val u = Uri.parse(uriString)
+        if (!u.authority.equals("iban", ignoreCase = true)) return false
+        return u.pathSegments.size >= 1
     }
 
-    @UiThread
-    fun resetPayStatus() {
-        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 onDepositButtonClicked(amount: Amount, receiverName: String, iban: 
String, bic: String) {
         val paytoUri: String = PaytoUriIban(
diff --git a/wallet/src/main/java/net/taler/wallet/payment/DepositState.kt 
b/wallet/src/main/java/net/taler/wallet/deposit/DepositState.kt
similarity index 97%
rename from wallet/src/main/java/net/taler/wallet/payment/DepositState.kt
rename to wallet/src/main/java/net/taler/wallet/deposit/DepositState.kt
index 8598911..1249155 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/DepositState.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/DepositState.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.payment
+package net.taler.wallet.deposit
 
 import net.taler.common.Amount
 
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt 
b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
new file mode 100644
index 0000000..ac1fd59
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
@@ -0,0 +1,245 @@
+/*
+ * 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.deposit
+
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
+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.DropdownMenu
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.OutlinedTextField
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.material.TextFieldDefaults
+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.platform.ComposeView
+import androidx.compose.ui.platform.LocalFocusManager
+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 androidx.core.os.bundleOf
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.findNavController
+import com.google.android.material.composethemeadapter.MdcTheme
+import net.taler.common.Amount
+import net.taler.wallet.AmountResult
+import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
+
+class PayToUriFragment : Fragment() {
+    private val model: MainViewModel by activityViewModels()
+    private val depositManager get() = model.depositManager
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?,
+    ): View {
+        val uri = arguments?.getString("uri") ?: error("no amount passed")
+
+        val currencies = model.getCurrencies()
+        return ComposeView(requireContext()).apply {
+            setContent {
+                MdcTheme {
+                    Surface {
+                        if (currencies.isEmpty()) Text(
+                            text = stringResource(id = 
R.string.payment_balance_insufficient),
+                            color = colorResource(id = R.color.red),
+                        ) else if (depositManager.isSupportedPayToUri(uri)) 
PayToComposable(
+                            currencies = model.getCurrencies(),
+                            getAmount = model::createAmount,
+                            onAmountChosen = { amount ->
+                                val u = Uri.parse(uri)
+                                val bundle = bundleOf(
+                                    "amount" to amount.toJSONString(),
+                                    "receiverName" to 
u.getQueryParameters("receiver-name")[0],
+                                    "IBAN" to u.pathSegments.last(),
+                                )
+                                findNavController().navigate(
+                                    R.id.action_nav_payto_uri_to_nav_deposit, 
bundle)
+                            },
+                        ) else Text(
+                            text = stringResource(id = R.string.uri_invalid),
+                            color = colorResource(id = R.color.red),
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        activity?.setTitle(R.string.send_deposit_title)
+    }
+
+}
+
+@Composable
+private fun PayToComposable(
+    currencies: List<String>,
+    getAmount: (String, String) -> AmountResult,
+    onAmountChosen: (Amount) -> Unit,
+) {
+    val scrollState = rememberScrollState()
+    Column(
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(vertical = 16.dp)
+            .verticalScroll(scrollState),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.spacedBy(16.dp),
+    ) {
+        var amountText by rememberSaveable { mutableStateOf("") }
+        var amountError by rememberSaveable { mutableStateOf("") }
+        var currency by rememberSaveable { mutableStateOf(currencies[0]) }
+        val focusRequester = remember { FocusRequester() }
+        OutlinedTextField(
+            modifier = Modifier
+                .focusRequester(focusRequester),
+            value = amountText,
+            onValueChange = { input ->
+                amountError = ""
+                amountText = input
+            },
+            keyboardOptions = KeyboardOptions.Default.copy(keyboardType = 
KeyboardType.Decimal),
+            singleLine = true,
+            isError = amountError.isNotBlank(),
+            label = {
+                if (amountError.isBlank()) {
+                    Text(stringResource(R.string.send_amount))
+                } else {
+                    Text(amountError, color = colorResource(R.color.red))
+                }
+            }
+        )
+        CurrencyDropdown(
+            currencies = currencies,
+            onCurrencyChanged = { c -> currency = c },
+        )
+        LaunchedEffect(Unit) {
+            focusRequester.requestFocus()
+        }
+
+        val focusManager = LocalFocusManager.current
+        val errorStrInvalidAmount = stringResource(id = 
R.string.receive_amount_invalid)
+        val errorStrInsufficientBalance = stringResource(id = 
R.string.payment_balance_insufficient)
+        Button(
+            modifier = Modifier.padding(16.dp),
+            enabled = amountText.isNotBlank(),
+            onClick = {
+                when (val amountResult = getAmount(amountText, currency)) {
+                    is AmountResult.Success -> {
+                        focusManager.clearFocus()
+                        onAmountChosen(amountResult.amount)
+                    }
+                    is AmountResult.InvalidAmount -> amountError = 
errorStrInvalidAmount
+                    is AmountResult.InsufficientBalance -> amountError = 
errorStrInsufficientBalance
+                }
+            },
+        ) {
+            Text(text = 
stringResource(R.string.send_deposit_check_fees_button))
+        }
+    }
+}
+
+@Composable
+fun CurrencyDropdown(
+    currencies: List<String>,
+    onCurrencyChanged: (String) -> Unit,
+) {
+    var selectedIndex by remember { mutableStateOf(0) }
+    var expanded by remember { mutableStateOf(false) }
+    Box(
+        modifier = Modifier
+            .fillMaxSize()
+            .wrapContentSize(Alignment.Center),
+    ) {
+        OutlinedTextField(
+            modifier = Modifier
+                .clickable(onClick = { expanded = true }),
+            value = currencies[selectedIndex],
+            onValueChange = { },
+            readOnly = true,
+            enabled = false,
+            textStyle = LocalTextStyle.current.copy( // show text as if not 
disabled
+                color = TextFieldDefaults.outlinedTextFieldColors().textColor(
+                    enabled = true,
+                ).value
+            ),
+            singleLine = true,
+            label = {
+                Text(stringResource(R.string.currency))
+            }
+        )
+        DropdownMenu(
+            expanded = expanded,
+            onDismissRequest = { expanded = false },
+            modifier = Modifier,
+        ) {
+            currencies.forEachIndexed { index, s ->
+                DropdownMenuItem(onClick = {
+                    selectedIndex = index
+                    onCurrencyChanged(currencies[index])
+                    expanded = false
+                }) {
+                    Text(text = s)
+                }
+            }
+        }
+    }
+}
+
+@Preview
+@Composable
+fun PreviewPayToComposable() {
+    Surface {
+        PayToComposable(
+            currencies = listOf("KUDOS", "TESTKUDOS", "BTCBITCOIN"),
+            getAmount = { _, _ -> AmountResult.InvalidAmount },
+            onAmountChosen = {},
+        )
+    }
+}
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 0af4262..53cb259 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -21,14 +21,10 @@ 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
@@ -68,9 +64,6 @@ 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
@@ -131,76 +124,4 @@ class PaymentManager(
         mPayStatus.value = PayStatus.Error(error.userFacingMsg)
     }
 
-    /* Deposits */
-
-    @UiThread
-    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 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,
-                )
-            }
-        }
-    }
-
-    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: Amount,
-    val effectiveDepositAmount: Amount,
-)
-
-@Serializable
-data class CreateDepositGroupResponse(
-    val depositGroupId: String,
-    val transactionId: String,
-)
diff --git a/wallet/src/main/res/navigation/nav_graph.xml 
b/wallet/src/main/res/navigation/nav_graph.xml
index 6feb846..3d253af 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -57,6 +57,18 @@
             app:destination="@id/nav_peer_push" />
     </fragment>
 
+    <fragment
+        android:id="@+id/nav_payto_uri"
+        android:name="net.taler.wallet.deposit.PayToUriFragment"
+        android:label="@string/transactions_send_funds">
+        <argument
+            android:name="uri"
+            app:argType="string" />
+        <action
+            android:id="@+id/action_nav_payto_uri_to_nav_deposit"
+            app:destination="@id/nav_deposit" />
+    </fragment>
+
     <fragment
         android:id="@+id/promptTip"
         android:name="net.taler.wallet.tip.PromptTipFragment"
@@ -133,8 +145,23 @@
 
     <fragment
         android:id="@+id/nav_deposit"
-        android:name="net.taler.wallet.payment.DepositFragment"
-        android:label="@string/send_deposit_title" />
+        android:name="net.taler.wallet.deposit.DepositFragment"
+        android:label="@string/send_deposit_title">
+        <argument
+            android:name="amount"
+            app:argType="string"
+            app:nullable="false" />
+        <argument
+            android:name="IBAN"
+            android:defaultValue="@null"
+            app:argType="string"
+            app:nullable="true" />
+        <argument
+            android:name="receiverName"
+            android:defaultValue="@null"
+            app:argType="string"
+            app:nullable="true" />
+    </fragment>
 
     <fragment
         android:id="@+id/nav_settings_backup"
@@ -356,4 +383,8 @@
         android:id="@+id/action_nav_transactions_detail_deposit"
         app:destination="@id/nav_transactions_detail_deposit" />
 
+    <action
+        android:id="@+id/action_nav_payto_uri"
+        app:destination="@id/nav_payto_uri" />
+
 </navigation>
diff --git a/wallet/src/main/res/values/strings.xml 
b/wallet/src/main/res/values/strings.xml
index 5fba9f1..b34bc24 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -55,6 +55,7 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="search">Search</string>
     <string name="menu">Menu</string>
     <string name="or">or</string>
+    <string name="currency">Currency</string>
 
     <string name="offline">Operation requires internet access. Please ensure 
your internet connection works and try again.</string>
     <string name="error_unsupported_uri">Error: This Taler URI is not 
supported.</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]