taler-android

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

PayToUriFragment.kt (7281B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2022 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 android.os.Bundle
     20 import android.view.LayoutInflater
     21 import android.view.View
     22 import android.view.ViewGroup
     23 import androidx.compose.foundation.layout.Arrangement
     24 import androidx.compose.foundation.layout.Column
     25 import androidx.compose.foundation.layout.fillMaxWidth
     26 import androidx.compose.foundation.layout.padding
     27 import androidx.compose.foundation.rememberScrollState
     28 import androidx.compose.foundation.verticalScroll
     29 import androidx.compose.material3.Button
     30 import androidx.compose.material3.MaterialTheme
     31 import androidx.compose.material3.Surface
     32 import androidx.compose.material3.Text
     33 import androidx.compose.runtime.Composable
     34 import androidx.compose.runtime.getValue
     35 import androidx.compose.runtime.mutableStateOf
     36 import androidx.compose.runtime.remember
     37 import androidx.compose.runtime.saveable.rememberSaveable
     38 import androidx.compose.runtime.setValue
     39 import androidx.compose.ui.Alignment
     40 import androidx.compose.ui.Modifier
     41 import androidx.compose.ui.platform.ComposeView
     42 import androidx.compose.ui.platform.LocalFocusManager
     43 import androidx.compose.ui.res.stringResource
     44 import androidx.compose.ui.tooling.preview.Preview
     45 import androidx.compose.ui.unit.dp
     46 import androidx.core.os.bundleOf
     47 import androidx.fragment.app.Fragment
     48 import androidx.fragment.app.activityViewModels
     49 import androidx.navigation.fragment.findNavController
     50 import net.taler.common.Amount
     51 import net.taler.common.CurrencySpecification
     52 import net.taler.wallet.main.AmountResult
     53 import net.taler.wallet.BottomInsetsSpacer
     54 import net.taler.wallet.main.MainViewModel
     55 import net.taler.wallet.R
     56 import net.taler.wallet.compose.AmountCurrencyField
     57 import net.taler.wallet.compose.TalerSurface
     58 import androidx.core.net.toUri
     59 
     60 class PayToUriFragment : Fragment() {
     61     private val model: MainViewModel by activityViewModels()
     62     private val depositManager get() = model.depositManager
     63     private val balanceManager get() = model.balanceManager
     64     private val exchangeManager get() = model.exchangeManager
     65 
     66     override fun onCreateView(
     67         inflater: LayoutInflater,
     68         container: ViewGroup?,
     69         savedInstanceState: Bundle?,
     70     ): View {
     71         val uri = arguments?.getString("uri") ?: error("no amount passed")
     72         val u = uri.toUri()
     73         val receiverName = u.getQueryParameter("receiver-name")
     74             ?.replace('+', ' ') ?: ""
     75         val iban = u.pathSegments.last() ?: ""
     76 
     77         val currencies = balanceManager.getCurrencies()
     78         return ComposeView(requireContext()).apply {
     79             setContent {
     80                 TalerSurface {
     81                     if (currencies.isEmpty()) Text(
     82                         text = stringResource(id = R.string.payment_balance_insufficient),
     83                         color = MaterialTheme.colorScheme.error,
     84                     ) else if (depositManager.isSupportedPayToUri(uri)) PayToComposable(
     85                         currencies = currencies,
     86                         getAmount = model::createAmount,
     87                         onAmountChosen = { amount ->
     88                             val bundle = bundleOf(
     89                                 "amount" to amount.toJSONString(),
     90                                 "receiverName" to receiverName,
     91                                 "IBAN" to iban,
     92                             )
     93                             findNavController().navigate(
     94                                 R.id.action_global_deposit, bundle)
     95                         },
     96                         getCurrencySpec = exchangeManager::getSpecForCurrency,
     97                     ) else Text(
     98                         text = stringResource(id = R.string.uri_invalid),
     99                         color = MaterialTheme.colorScheme.error,
    100                     )
    101                 }
    102             }
    103         }
    104     }
    105 
    106     override fun onStart() {
    107         super.onStart()
    108         activity?.setTitle(R.string.send_deposit_title)
    109     }
    110 
    111 }
    112 
    113 @Composable
    114 private fun PayToComposable(
    115     currencies: List<String>,
    116     getAmount: (String, String) -> AmountResult,
    117     getCurrencySpec: (String) -> CurrencySpecification?,
    118     onAmountChosen: (Amount) -> Unit,
    119 ) {
    120     val scrollState = rememberScrollState()
    121     Column(
    122         modifier = Modifier
    123             .fillMaxWidth()
    124             .padding(vertical = 16.dp)
    125             .verticalScroll(scrollState),
    126         horizontalAlignment = Alignment.CenterHorizontally,
    127         verticalArrangement = Arrangement.spacedBy(16.dp),
    128     ) {
    129         var amount by remember { mutableStateOf(Amount.zero(currencies[0])) }
    130         val currencySpec = remember(amount.currency) { getCurrencySpec(amount.currency) }
    131         var amountError by rememberSaveable { mutableStateOf("") }
    132 
    133         AmountCurrencyField(
    134             modifier = Modifier
    135                 .padding(horizontal = 16.dp)
    136                 .fillMaxWidth(),
    137             amount = amount.withSpec(currencySpec),
    138             currencies = currencies,
    139             readOnly = false,
    140             onAmountChanged = { amount = it },
    141             label = { Text(stringResource(R.string.amount_send)) },
    142             isError = amountError.isNotBlank(),
    143             supportingText = {
    144                 if (amountError.isNotBlank()) {
    145                     Text(amountError)
    146                 }
    147             }
    148         )
    149 
    150         val focusManager = LocalFocusManager.current
    151         val errorStrInvalidAmount = stringResource(id = R.string.amount_invalid)
    152         val errorStrInsufficientBalance = stringResource(id = R.string.payment_balance_insufficient)
    153         Button(
    154             modifier = Modifier.padding(16.dp),
    155             enabled = !amount.isZero(),
    156             onClick = {
    157                 when (val amountResult = getAmount(amount.amountStr, amount.currency)) {
    158                     is AmountResult.Success -> {
    159                         focusManager.clearFocus()
    160                         onAmountChosen(amountResult.amount)
    161                     }
    162                     is AmountResult.InvalidAmount -> amountError = errorStrInvalidAmount
    163                     is AmountResult.InsufficientBalance -> amountError = errorStrInsufficientBalance
    164                 }
    165             },
    166         ) {
    167             Text(text = stringResource(R.string.send_deposit_check_fees_button))
    168         }
    169 
    170         BottomInsetsSpacer()
    171     }
    172 }
    173 
    174 @Preview
    175 @Composable
    176 fun PreviewPayToComposable() {
    177     Surface {
    178         PayToComposable(
    179             currencies = listOf("KUDOS", "TESTKUDOS", "BTCBITCOIN"),
    180             getAmount = { _, _ -> AmountResult.InvalidAmount },
    181             onAmountChosen = {},
    182             getCurrencySpec = { null }
    183         )
    184     }
    185 }