taler-android

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

PayTemplateOrderComposable.kt (7354B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2023 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.payment
     18 
     19 import androidx.compose.foundation.layout.Column
     20 import androidx.compose.foundation.layout.fillMaxWidth
     21 import androidx.compose.foundation.layout.padding
     22 import androidx.compose.material3.Button
     23 import androidx.compose.material3.OutlinedTextField
     24 import androidx.compose.material3.Text
     25 import androidx.compose.runtime.Composable
     26 import androidx.compose.runtime.LaunchedEffect
     27 import androidx.compose.runtime.getValue
     28 import androidx.compose.runtime.mutableStateOf
     29 import androidx.compose.runtime.remember
     30 import androidx.compose.runtime.setValue
     31 import androidx.compose.ui.Alignment.Companion.End
     32 import androidx.compose.ui.Modifier
     33 import androidx.compose.ui.focus.FocusRequester
     34 import androidx.compose.ui.focus.focusRequester
     35 import androidx.compose.ui.focus.onFocusChanged
     36 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
     37 import androidx.compose.ui.res.stringResource
     38 import androidx.compose.ui.tooling.preview.Preview
     39 import androidx.compose.ui.unit.dp
     40 import net.taler.common.Amount
     41 import net.taler.common.CurrencySpecification
     42 import net.taler.common.RelativeTime
     43 import net.taler.wallet.main.AmountResult
     44 import net.taler.wallet.BottomInsetsSpacer
     45 import net.taler.wallet.R
     46 import net.taler.wallet.compose.AmountCurrencyField
     47 import net.taler.wallet.compose.TalerSurface
     48 
     49 @Composable
     50 fun PayTemplateOrderComposable(
     51     usableCurrencies: List<String>, // non-empty intersection between the stored currencies and the ones supported by the merchant
     52     templateDetails: WalletTemplateDetails,
     53     onCreateAmount: (String, String) -> AmountResult,
     54     getCurrencySpec: (String) -> CurrencySpecification?,
     55     onError: (msg: String) -> Unit,
     56     onSubmit: (params: TemplateParams) -> Unit,
     57 ) {
     58     val defaultSummary = templateDetails.defaultSummary
     59     val defaultAmount = templateDetails.defaultAmount
     60     val defaultCurrency = templateDetails.defaultCurrency
     61 
     62     val summaryFocusRequester = remember { FocusRequester() }
     63     val keyboardController = LocalSoftwareKeyboardController.current
     64 
     65     var summary by remember { mutableStateOf(defaultSummary ?: "") }
     66     var amount by remember {
     67         val currency = defaultCurrency ?: usableCurrencies[0]
     68         mutableStateOf(defaultAmount?.withCurrency(currency) ?: Amount.zero(currency))
     69     }
     70     val currencySpec = remember(amount.currency) {
     71         getCurrencySpec(amount.currency)
     72     }
     73 
     74     val balanceInsufficientError = stringResource(R.string.payment_balance_insufficient)
     75     val amountInvalidError = stringResource(R.string.amount_invalid)
     76 
     77     Column(horizontalAlignment = End) {
     78         OutlinedTextField(
     79             modifier = Modifier
     80                 .padding(horizontal = 16.dp)
     81                 .fillMaxWidth()
     82                 .focusRequester(summaryFocusRequester)
     83                 .onFocusChanged {
     84                     if (it.isFocused) {
     85                         keyboardController?.show()
     86                     }
     87                 },
     88             value = summary,
     89             isError = templateDetails.isSummaryEditable() && summary.isBlank(),
     90             onValueChange = { summary = it },
     91             readOnly = !templateDetails.isSummaryEditable(),
     92             label = { Text(stringResource(R.string.withdraw_manual_ready_subject)) },
     93         )
     94 
     95         if (templateDetails.isAmountEditable()) AmountCurrencyField(
     96             modifier = Modifier
     97                 .padding(16.dp)
     98                 .fillMaxWidth(),
     99             amount = amount.withSpec(currencySpec),
    100             currencies = usableCurrencies,
    101             onAmountChanged = { amount = it },
    102             label = { Text(stringResource(R.string.amount_send)) },
    103         )
    104 
    105         Button(
    106             modifier = Modifier.padding(16.dp),
    107             enabled = !templateDetails.isSummaryEditable() || summary.isNotBlank(),
    108             onClick = {
    109                 when (val res = onCreateAmount(amount.amountStr, amount.currency)) {
    110                     is AmountResult.InsufficientBalance -> onError(balanceInsufficientError)
    111                     is AmountResult.InvalidAmount -> onError(amountInvalidError)
    112                     // NOTE: it is important to nullify non-editable values!
    113                     is AmountResult.Success -> onSubmit(TemplateParams(
    114                         summary = if (templateDetails.isSummaryEditable()) summary else null,
    115                         amount = if(templateDetails.isAmountEditable()) res.amount else null,
    116                     ))
    117                 }
    118             },
    119         ) {
    120             Text(stringResource(R.string.payment_create_order))
    121         }
    122 
    123         BottomInsetsSpacer()
    124     }
    125 
    126     LaunchedEffect(Unit) {
    127         if (templateDetails.isSummaryEditable()
    128             && templateDetails.defaultSummary == null) {
    129             summaryFocusRequester.requestFocus()
    130         }
    131     }
    132 }
    133 
    134 val defaultTemplateDetails = WalletTemplateDetails(
    135     templateContract = TemplateContractDetails(
    136         templateType = TemplateType.FixedOrder,
    137         minimumAge = 18,
    138         payDuration = RelativeTime.forever(),
    139     ),
    140     editableDefaults = TemplateContractDetailsDefaults(
    141         summary = "Donation",
    142         amount = Amount.fromJSONString("KUDOS:10.0"),
    143     ),
    144 )
    145 
    146 @Preview
    147 @Composable
    148 fun PayTemplateDefaultPreview() {
    149     TalerSurface {
    150         PayTemplateOrderComposable(
    151             templateDetails = defaultTemplateDetails,
    152             usableCurrencies = listOf("KUDOS", "ARS"),
    153             onCreateAmount = { text, currency ->
    154                 AmountResult.Success(amount = Amount.fromString(currency, text))
    155             },
    156             onSubmit = { _ -> },
    157             onError = { },
    158             getCurrencySpec = { null },
    159         )
    160     }
    161 }
    162 
    163 @Preview
    164 @Composable
    165 fun PayTemplateFixedAmountPreview() {
    166     TalerSurface {
    167         PayTemplateOrderComposable(
    168             templateDetails = defaultTemplateDetails,
    169             usableCurrencies = listOf("KUDOS", "ARS"),
    170             onCreateAmount = { text, currency ->
    171                 AmountResult.Success(amount = Amount.fromString(currency, text))
    172             },
    173             onSubmit = { _ -> },
    174             onError = { },
    175             getCurrencySpec = { null },
    176         )
    177     }
    178 }
    179 
    180 @Preview
    181 @Composable
    182 fun PayTemplateBlankSubjectPreview() {
    183     TalerSurface {
    184         PayTemplateOrderComposable(
    185             templateDetails = defaultTemplateDetails,
    186             usableCurrencies = listOf("KUDOS", "ARS"),
    187             onCreateAmount = { text, currency ->
    188                 AmountResult.Success(amount = Amount.fromString(currency, text))
    189             },
    190             onSubmit = { _ -> },
    191             onError = { },
    192             getCurrencySpec = { null },
    193         )
    194     }
    195 }