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 }