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 }