taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit 37e9730c4e985d4ed05d6e4f5e62d1ccc870f35c
parent 28cdacb6e6a3a131abb0dc71e188eb62d57dc3bf
Author: Iván Ávalos <avalos@disroot.org>
Date:   Tue, 10 Sep 2024 12:18:19 +0200

[wallet] deposit x-taler-bank host selection

Diffstat:
Mwallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt | 13++++++++++---
Mwallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt | 17++++++++++++-----
Mwallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt | 161+++----------------------------------------------------------------------------
Awallet/src/main/java/net/taler/wallet/deposit/MakeDepositIBAN.kt | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awallet/src/main/java/net/taler/wallet/deposit/MakeDepositTaler.kt | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 272 insertions(+), 163 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt @@ -67,6 +67,7 @@ class DepositFragment : Fragment() { TalerSurface { val state = depositManager.depositState.collectAsStateLifecycleAware() val wireTypes = remember { mutableStateListOf<WireType>() } + val talerBankHostnames = remember { mutableStateListOf<String>() } val coroutine = rememberCoroutineScope() if (amount.currency == CURRENCY_BTC) MakeBitcoinDepositComposable( @@ -79,6 +80,7 @@ class DepositFragment : Fragment() { ) else MakeDepositComposable( state = state.value, supportedWireTypes = wireTypes, + talerBankHostnames = talerBankHostnames, amount = amount.withSpec(spec), presetName = receiverName, presetIban = iban, @@ -89,10 +91,15 @@ class DepositFragment : Fragment() { LaunchedEffect(Unit) { coroutine.launch { - scopeInfo?.let { + scopeInfo?.let { scopeInfo -> depositManager - .getDepositWireTypesForCurrency(it) - ?.let { types -> wireTypes.addAll(types) } + .getDepositWireTypesForCurrency(scopeInfo) + ?.let { result -> + wireTypes.addAll(result.wireTypes) + talerBankHostnames.addAll(result.wireTypeDetails.flatMap { + it.talerBankHostnames + }.distinct()) + } } } } diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt @@ -154,15 +154,15 @@ class DepositManager( return response } - suspend fun getDepositWireTypesForCurrency(scopeInfo: ScopeInfo): List<WireType>? { - var result: List<WireType>? = null + suspend fun getDepositWireTypesForCurrency(scopeInfo: ScopeInfo): GetDepositWireTypesForCurrencyResponse? { + var result: GetDepositWireTypesForCurrencyResponse? = null api.request("getDepositWireTypesForCurrency", GetDepositWireTypesForCurrencyResponse.serializer()) { put("currency", scopeInfo.currency) put("scopeInfo", JSONObject(BackendManager.json.encodeToString(scopeInfo))) }.onError { Log.e(TAG, "Error getDepositWireTypesForCurrency $it") }.onSuccess { - result = it.wireTypes + result = it } return result } @@ -188,6 +188,7 @@ data class CreateDepositGroupResponse( @Serializable data class GetDepositWireTypesForCurrencyResponse( val wireTypes: List<WireType>, + val wireTypeDetails: List<WireTypeDetails>, ) @Serializable @@ -199,4 +200,10 @@ enum class WireType { @SerialName("x-taler-bank") TalerBank, -} -\ No newline at end of file +} + +@Serializable +data class WireTypeDetails( + val paymentTargetType: WireType, + val talerBankHostnames: List<String>, +) +\ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt @@ -24,13 +24,11 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.ScrollableTabRow import androidx.compose.material3.Surface import androidx.compose.material3.Tab import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -39,9 +37,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -58,6 +53,7 @@ import net.taler.wallet.transactions.TransactionAmountComposable fun MakeDepositComposable( state: DepositState, supportedWireTypes: List<WireType>, + talerBankHostnames: List<String>, amount: Amount, presetName: String? = null, presetIban: String? = null, @@ -93,7 +89,7 @@ fun MakeDepositComposable( var ibanName by rememberSaveable { mutableStateOf(presetName ?: "") } var ibanIban by rememberSaveable { mutableStateOf(presetIban ?: "") } var talerName by rememberSaveable { mutableStateOf(presetName ?: "") } - var talerHost by rememberSaveable { mutableStateOf("") } + var talerHost by rememberSaveable { mutableStateOf(talerBankHostnames.firstOrNull() ?: "") } var talerAccount by rememberSaveable { mutableStateOf("") } when(selectedWireType) { @@ -101,7 +97,7 @@ fun MakeDepositComposable( var ibanError by rememberSaveable { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() - MakeDepositIbanForm( + MakeDepositIBAN( name = ibanName, iban = ibanIban, state = state, @@ -118,10 +114,11 @@ fun MakeDepositComposable( ) } - WireType.TalerBank -> MakeDepositTalerBankForm( + WireType.TalerBank -> MakeDepositTaler( name = talerName, host = talerHost, account = talerAccount, + supportedHosts = talerBankHostnames, state = state, onFormEdited = { name, host, account -> talerName = name @@ -232,153 +229,6 @@ fun MakeDepositWireTypeChooser( } } -@Composable -fun MakeDepositIbanForm( - state: DepositState, - name: String, - iban: String, - ibanError: Boolean, - onFormEdited: (name: String, iban: String) -> Unit -) { - val focusRequester = remember { FocusRequester() } - - OutlinedTextField( - modifier = Modifier - .padding(16.dp) - .focusRequester(focusRequester) - .fillMaxWidth(), - value = name, - enabled = !state.showFees, - onValueChange = { input -> - onFormEdited(input, iban) - }, - singleLine = true, - isError = name.isBlank(), - label = { - Text( - stringResource(R.string.send_deposit_name), - color = if (name.isBlank()) { - MaterialTheme.colorScheme.error - } else Color.Unspecified, - ) - } - ) - - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } - - OutlinedTextField( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - value = iban, - singleLine = true, - enabled = !state.showFees, - onValueChange = { input -> - onFormEdited(name, input.uppercase()) - - }, - isError = ibanError, - supportingText = { - if (ibanError) { - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.send_deposit_iban_error), - color = MaterialTheme.colorScheme.error - ) - } - }, - label = { - Text( - text = stringResource(R.string.send_deposit_iban), - color = if (ibanError) { - MaterialTheme.colorScheme.error - } else Color.Unspecified, - ) - } - ) -} - -@Composable -fun MakeDepositTalerBankForm( - state: DepositState, - name: String, - host: String, - account: String, - onFormEdited: (name: String, host: String, account: String) -> Unit -) { - val focusRequester = remember { FocusRequester() } - - OutlinedTextField( - modifier = Modifier - .padding(16.dp) - .focusRequester(focusRequester) - .fillMaxWidth(), - value = name, - enabled = !state.showFees, - onValueChange = { input -> - onFormEdited(input, host, account) - }, - singleLine = true, - isError = name.isBlank(), - label = { - Text( - stringResource(R.string.send_deposit_name), - color = if (name.isBlank()) { - MaterialTheme.colorScheme.error - } else Color.Unspecified, - ) - } - ) - - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } - - OutlinedTextField( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - value = host, - enabled = !state.showFees, - onValueChange = { input -> - onFormEdited(name, input, account) - }, - singleLine = true, - isError = host.isBlank(), - label = { - Text( - stringResource(R.string.send_deposit_host), - color = if (host.isBlank()) { - MaterialTheme.colorScheme.error - } else Color.Unspecified, - ) - } - ) - - OutlinedTextField( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), - value = account, - singleLine = true, - enabled = !state.showFees, - onValueChange = { input -> - onFormEdited(name, host, input) - }, - isError = account.isBlank(), - label = { - Text( - text = stringResource(R.string.send_deposit_account), - color = if (account.isBlank()) { - MaterialTheme.colorScheme.error - } else Color.Unspecified, - ) - } - ) -} - @Preview @Composable fun PreviewMakeDepositComposable() { @@ -390,6 +240,7 @@ fun PreviewMakeDepositComposable() { MakeDepositComposable( state = state, supportedWireTypes = listOf(WireType.TalerBank, WireType.IBAN), + talerBankHostnames = listOf("bank.demo.taler.net", "bank.test.taler.net"), amount = Amount.fromString("TESTKUDOS", "42.23"), validateIban = { true }, onMakeIbanDeposit = { _, _, _ -> }, diff --git a/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositIBAN.kt b/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositIBAN.kt @@ -0,0 +1,101 @@ +/* + * This file is part of GNU Taler + * (C) 2024 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 androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import net.taler.wallet.R + +@Composable +fun MakeDepositIBAN( + state: DepositState, + name: String, + iban: String, + ibanError: Boolean, + onFormEdited: (name: String, iban: String) -> Unit +) { + val focusRequester = remember { FocusRequester() } + + OutlinedTextField( + modifier = Modifier + .padding(16.dp) + .focusRequester(focusRequester) + .fillMaxWidth(), + value = name, + enabled = !state.showFees, + onValueChange = { input -> + onFormEdited(input, iban) + }, + singleLine = true, + isError = name.isBlank(), + label = { + Text( + stringResource(R.string.send_deposit_name), + color = if (name.isBlank()) { + MaterialTheme.colorScheme.error + } else Color.Unspecified, + ) + } + ) + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + + OutlinedTextField( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + value = iban, + singleLine = true, + enabled = !state.showFees, + onValueChange = { input -> + onFormEdited(name, input.uppercase()) + + }, + isError = ibanError, + supportingText = { + if (ibanError) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.send_deposit_iban_error), + color = MaterialTheme.colorScheme.error + ) + } + }, + label = { + Text( + text = stringResource(R.string.send_deposit_iban), + color = if (ibanError) { + MaterialTheme.colorScheme.error + } else Color.Unspecified, + ) + } + ) +} +\ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositTaler.kt b/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositTaler.kt @@ -0,0 +1,141 @@ +/* + * This file is part of GNU Taler + * (C) 2024 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 androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import net.taler.wallet.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MakeDepositTaler( + state: DepositState, + supportedHosts: List<String>, + name: String, + host: String, + account: String, + onFormEdited: (name: String, host: String, account: String) -> Unit +) { + val focusRequester = remember { FocusRequester() } + + OutlinedTextField( + modifier = Modifier + .padding(16.dp) + .focusRequester(focusRequester) + .fillMaxWidth(), + value = name, + enabled = !state.showFees, + onValueChange = { input -> + onFormEdited(input, host, account) + }, + singleLine = true, + isError = name.isBlank(), + label = { + Text( + stringResource(R.string.send_deposit_name), + color = if (name.isBlank()) { + MaterialTheme.colorScheme.error + } else Color.Unspecified, + ) + } + ) + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + + var expanded by remember { mutableStateOf(false) } + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = it }, + ) { + OutlinedTextField( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth() + .menuAnchor(), + readOnly = true, + enabled = !state.showFees, + value = host, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) }, + onValueChange = {}, + label = { + Text( + stringResource(R.string.send_deposit_host), + color = if (host.isBlank()) { + MaterialTheme.colorScheme.error + } else Color.Unspecified, + ) + }, + ) + + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + supportedHosts.forEach { + DropdownMenuItem( + text = { Text(it) }, + onClick = { + onFormEdited(name, it, account) + expanded = false + }, + ) + } + } + } + + OutlinedTextField( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + value = account, + singleLine = true, + enabled = !state.showFees, + onValueChange = { input -> + onFormEdited(name, host, input) + }, + isError = account.isBlank(), + label = { + Text( + text = stringResource(R.string.send_deposit_account), + color = if (account.isBlank()) { + MaterialTheme.colorScheme.error + } else Color.Unspecified, + ) + } + ) +} +\ No newline at end of file