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 }