taler-android

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

PayToUriScreen.kt (6990B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2026 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.wallet.deposit
     18 
     19 import androidx.compose.foundation.layout.Arrangement
     20 import androidx.compose.foundation.layout.Box
     21 import androidx.compose.foundation.layout.Column
     22 import androidx.compose.foundation.layout.fillMaxWidth
     23 import androidx.compose.foundation.layout.padding
     24 import androidx.compose.foundation.rememberScrollState
     25 import androidx.compose.foundation.verticalScroll
     26 import androidx.compose.material3.Button
     27 import androidx.compose.material3.MaterialTheme
     28 import androidx.compose.material3.Surface
     29 import androidx.compose.material3.Text
     30 import androidx.compose.runtime.Composable
     31 import androidx.compose.runtime.getValue
     32 import androidx.compose.runtime.mutableStateOf
     33 import androidx.compose.runtime.remember
     34 import androidx.compose.runtime.saveable.rememberSaveable
     35 import androidx.compose.runtime.setValue
     36 import androidx.compose.ui.Alignment
     37 import androidx.compose.ui.Modifier
     38 import androidx.compose.ui.platform.LocalFocusManager
     39 import androidx.compose.ui.res.stringResource
     40 import androidx.compose.ui.tooling.preview.Preview
     41 import androidx.compose.ui.unit.dp
     42 import androidx.core.net.toUri
     43 import net.taler.common.Amount
     44 import net.taler.common.CurrencySpecification
     45 import net.taler.wallet.BottomInsetsSpacer
     46 import net.taler.wallet.NavigateCallback
     47 import net.taler.wallet.R
     48 import net.taler.wallet.WalletDestination
     49 import net.taler.wallet.compose.AmountCurrencyField
     50 import net.taler.wallet.compose.GlobalScaffold
     51 import net.taler.wallet.compose.TalerSurface
     52 import net.taler.wallet.main.AmountResult
     53 import net.taler.wallet.main.MainViewModel
     54 
     55 @Composable
     56 fun PayToUriScreen(
     57     model: MainViewModel,
     58     uri: String,
     59     onNavigate: NavigateCallback,
     60     onNavigateBack: () -> Unit,
     61 ) {
     62     val u = uri.toUri()
     63     val receiverName = u.getQueryParameter("receiver-name")?.replace('+', ' ') ?: ""
     64     val iban = u.pathSegments.lastOrNull() ?: ""
     65 
     66     val currencies = model.balanceManager.getCurrencies()
     67 
     68     TalerSurface {
     69         GlobalScaffold(
     70             model = model,
     71             title = { Text(stringResource(R.string.transactions_send_funds)) },
     72             onNavigateBack = onNavigateBack,
     73         ) { paddingValues ->
     74             Box(Modifier.padding(paddingValues)) {
     75                 if (currencies.isEmpty()) {
     76                     Text(
     77                         modifier = Modifier.padding(16.dp),
     78                         text = stringResource(id = R.string.payment_balance_insufficient),
     79                         color = MaterialTheme.colorScheme.error,
     80                     )
     81                 } else if (model.depositManager.isSupportedPayToUri(uri)) {
     82                     PayToComposable(
     83                         currencies = currencies,
     84                         getAmount = model::createAmount,
     85                         onAmountChosen = { amount ->
     86                             onNavigate(
     87                                 WalletDestination.Deposit(
     88                                     amount = amount.toJSONString(),
     89                                     receiverName = receiverName,
     90                                     IBAN = iban
     91                                 ), true,
     92                             )
     93                         },
     94                         getCurrencySpec = model.exchangeManager::getSpecForCurrency,
     95                     )
     96                 } else {
     97                     Text(
     98                         modifier = Modifier.padding(16.dp),
     99                         text = stringResource(id = R.string.uri_invalid),
    100                         color = MaterialTheme.colorScheme.error,
    101                     )
    102                 }
    103             }
    104         }
    105     }
    106 }
    107 
    108 @Composable
    109 private fun PayToComposable(
    110     currencies: List<String>,
    111     getAmount: (String, String) -> AmountResult,
    112     getCurrencySpec: (String) -> CurrencySpecification?,
    113     onAmountChosen: (Amount) -> Unit,
    114 ) {
    115     val scrollState = rememberScrollState()
    116     Column(
    117         modifier = Modifier
    118             .fillMaxWidth()
    119             .padding(vertical = 16.dp)
    120             .verticalScroll(scrollState),
    121         horizontalAlignment = Alignment.CenterHorizontally,
    122         verticalArrangement = Arrangement.spacedBy(16.dp),
    123     ) {
    124         var amount by remember { mutableStateOf(Amount.zero(currencies[0])) }
    125         val currencySpec = remember(amount.currency) { getCurrencySpec(amount.currency) }
    126         var amountError by rememberSaveable { mutableStateOf("") }
    127 
    128         AmountCurrencyField(
    129             modifier = Modifier
    130                 .padding(horizontal = 16.dp)
    131                 .fillMaxWidth(),
    132             amount = amount.withSpec(currencySpec),
    133             currencies = currencies,
    134             readOnly = false,
    135             onAmountChanged = { amount = it },
    136             label = { Text(stringResource(R.string.amount_send)) },
    137             isError = amountError.isNotBlank(),
    138             supportingText = {
    139                 if (amountError.isNotBlank()) {
    140                     Text(amountError)
    141                 }
    142             }
    143         )
    144 
    145         val focusManager = LocalFocusManager.current
    146         val errorStrInvalidAmount = stringResource(id = R.string.amount_invalid)
    147         val errorStrInsufficientBalance = stringResource(id = R.string.payment_balance_insufficient)
    148         Button(
    149             modifier = Modifier.padding(16.dp),
    150             enabled = !amount.isZero(),
    151             onClick = {
    152                 when (val amountResult = getAmount(amount.amountStr, amount.currency)) {
    153                     is AmountResult.Success -> {
    154                         focusManager.clearFocus()
    155                         onAmountChosen(amountResult.amount)
    156                     }
    157                     is AmountResult.InvalidAmount -> amountError = errorStrInvalidAmount
    158                     is AmountResult.InsufficientBalance -> amountError = errorStrInsufficientBalance
    159                 }
    160             },
    161         ) {
    162             Text(text = stringResource(R.string.send_deposit_check_fees_button))
    163         }
    164 
    165         BottomInsetsSpacer()
    166     }
    167 }
    168 
    169 @Preview
    170 @Composable
    171 fun PreviewPayToComposable() {
    172     Surface {
    173         PayToComposable(
    174             currencies = listOf("KUDOS", "TESTKUDOS", "BTCBITCOIN"),
    175             getAmount = { _, _ -> AmountResult.InvalidAmount },
    176             onAmountChosen = {},
    177             getCurrencySpec = { null }
    178         )
    179     }
    180 }