[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-taler-android] branch master updated: [wallet] Open payto:// URIs and hook into deposit to bank account flow,
gnunet <=