summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2022-11-02 08:58:45 -0300
committerTorsten Grote <t@grobox.de>2022-11-02 08:58:45 -0300
commit865f80d49a8741de55d27002717d7ca1b42c875f (patch)
treea2402ce7c72780510a3c707d803b012e2318d732
parent55624eb33bae14380efe8ca085dc420390b23702 (diff)
downloadtaler-android-865f80d49a8741de55d27002717d7ca1b42c875f.tar.gz
taler-android-865f80d49a8741de55d27002717d7ca1b42c875f.tar.bz2
taler-android-865f80d49a8741de55d27002717d7ca1b42c875f.zip
[wallet] Open payto:// URIs and hook into deposit to bank account flow
-rw-r--r--build.gradle2
-rw-r--r--wallet/src/main/AndroidManifest.xml1
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainActivity.kt15
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainViewModel.kt27
-rw-r--r--wallet/src/main/java/net/taler/wallet/accounts/KnownBankAccounts.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt (renamed from wallet/src/main/java/net/taler/wallet/payment/DepositFragment.kt)24
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt117
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/DepositState.kt (renamed from wallet/src/main/java/net/taler/wallet/payment/DepositState.kt)2
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt245
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt79
-rw-r--r--wallet/src/main/res/navigation/nav_graph.xml35
-rw-r--r--wallet/src/main/res/values/strings.xml1
12 files changed, 459 insertions, 91 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
@@ -142,6 +145,24 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
}
@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 ->
if (balanceItem.currency == amount.currency) {
@@ -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
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/deposit/DepositManager.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt
new file mode 100644
index 0000000..a207691
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.util.Log
+import androidx.annotation.UiThread
+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.wallet.TAG
+import net.taler.wallet.accounts.PaytoUriIban
+import net.taler.wallet.backend.WalletBackendApi
+
+class DepositManager(
+ private val api: WalletBackendApi,
+ private val scope: CoroutineScope,
+) {
+
+ private val mDepositState = MutableStateFlow<DepositState>(DepositState.Start)
+ internal val depositState = mDepositState.asStateFlow()
+
+ 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 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/java/net/taler/wallet/payment/DepositState.kt b/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
@@ -58,6 +58,18 @@
</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"
android:label="Review Tip"
@@ -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>