summaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net/taler
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2023-08-18 10:36:04 +0200
committerTorsten Grote <t@grobox.de>2023-09-26 18:30:51 +0200
commitaa1be463c2a79d673f1dd2dd31538649a1cfb83c (patch)
treea510fb0868d635f329c1f5f031fa237bae7695a6 /wallet/src/main/java/net/taler
parentb38d99fd60737a4087948bd3e4ee6b18d756c639 (diff)
downloadtaler-android-aa1be463c2a79d673f1dd2dd31538649a1cfb83c.tar.gz
taler-android-aa1be463c2a79d673f1dd2dd31538649a1cfb83c.tar.bz2
taler-android-aa1be463c2a79d673f1dd2dd31538649a1cfb83c.zip
[wallet] first cleanup of payment template work
the PayTemplateComposable still needs refactoring
Diffstat (limited to 'wallet/src/main/java/net/taler')
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt12
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt228
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt219
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt4
4 files changed, 248 insertions, 215 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
index 243f589..81f3617 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
@@ -49,6 +49,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
+import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@@ -157,7 +158,7 @@ private fun PayToComposable(
CurrencyDropdown(
modifier = Modifier
.fillMaxSize()
- .wrapContentSize(Alignment.Center),
+ .wrapContentSize(Center),
currencies = currencies,
onCurrencyChanged = { c -> currency = c },
)
@@ -190,14 +191,13 @@ private fun PayToComposable(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CurrencyDropdown(
- modifier: Modifier = Modifier,
- initialCurrency: String? = null,
currencies: List<String>,
onCurrencyChanged: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ initialCurrency: String? = null,
) {
- val initialIndex = currencies.indexOf(initialCurrency)
- .let { if (it < 0) null else it }
- var selectedIndex by remember { mutableStateOf(initialIndex ?: 0) }
+ val initialIndex = currencies.indexOf(initialCurrency).let { if (it < 0) 0 else it }
+ var selectedIndex by remember { mutableStateOf(initialIndex) }
var expanded by remember { mutableStateOf(false) }
Box(
modifier = modifier,
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt
new file mode 100644
index 0000000..3279c71
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt
@@ -0,0 +1,228 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2023 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.payment
+
+import android.net.Uri
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Button
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Alignment.Companion.Center
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.asFlow
+import net.taler.common.Amount
+import net.taler.common.AmountParserException
+import net.taler.common.showError
+import net.taler.wallet.AmountResult
+import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
+import net.taler.wallet.deposit.CurrencyDropdown
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun PayTemplateComposable(
+ uri: Uri,
+ currencies: List<String>,
+ fragment: Fragment,
+ model: MainViewModel,
+ onSubmit: (Map<String, String>) -> Unit,
+) {
+ val queryParams = uri.queryParameterNames
+
+ var summary by remember { mutableStateOf(
+ if ("summary" in queryParams)
+ uri.getQueryParameter("summary") else null,
+ ) }
+
+ var amount by remember { mutableStateOf(
+ if ("amount" in queryParams) {
+ val amount = uri.getQueryParameter("amount")!!
+ val parts = amount.split(':')
+ when (parts.size) {
+ 1 -> Amount.fromString(parts[0], "0")
+ 2 -> Amount.fromString(parts[0], parts[1])
+ else -> throw AmountParserException("Invalid Amount Format")
+ }
+ } else null,
+ ) }
+
+ val payStatus by model.paymentManager.payStatus.asFlow()
+ .collectAsState(initial = PayStatus.None)
+
+ // If wallet is empty, there's no way the user can pay something
+ if (payStatus is PayStatus.InsufficientBalance || currencies.isEmpty()) {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Center,
+ ) {
+ Text(
+ text = stringResource(R.string.payment_balance_insufficient),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.error,
+ )
+ }
+ } else when (payStatus) {
+ is PayStatus.None -> {
+ Column(horizontalAlignment = Alignment.End) {
+ if ("summary" in queryParams) {
+ OutlinedTextField(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ value = summary!!,
+ isError = summary!!.isBlank(),
+ onValueChange = { summary = it },
+ singleLine = true,
+ label = { Text(stringResource(R.string.withdraw_manual_ready_subject)) },
+ )
+ }
+
+ if ("amount" in queryParams) {
+ AmountField(
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth(),
+ amount = amount!!,
+ currencies = currencies,
+ onAmountChosen = { amount = it },
+ )
+ }
+
+ Button(
+ modifier = Modifier.padding(16.dp),
+ enabled = summary == null || summary!!.isNotBlank(),
+ onClick = {
+ if (amount != null) {
+ val result = model.createAmount(
+ amount!!.amountStr,
+ amount!!.currency,
+ )
+ when (result) {
+ AmountResult.InsufficientBalance -> {
+ fragment.showError(R.string.payment_balance_insufficient)
+ }
+ AmountResult.InvalidAmount -> {
+ fragment.showError(R.string.receive_amount_invalid)
+ }
+ else -> {
+ onSubmit(
+ mutableMapOf<String, String>().apply {
+ if (summary != null) put("summary", summary!!)
+ if (amount != null) put("amount", amount!!.toJSONString())
+ }
+ )
+ }
+ }
+ }
+ },
+ ) {
+ Text(stringResource(R.string.payment_create_order))
+ }
+ }
+ }
+ is PayStatus.Loading -> {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Center,
+ ) { CircularProgressIndicator() }
+ }
+ is PayStatus.AlreadyPaid -> {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Center,
+ ) {
+ Text(
+ stringResource(R.string.payment_already_paid),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.error,
+ )
+ }
+ }
+ else -> {}
+ }
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+private fun AmountField(
+ modifier: Modifier = Modifier,
+ currencies: List<String>,
+ amount: Amount,
+ onAmountChosen: (Amount) -> Unit,
+) {
+ Row(
+ modifier = modifier,
+ ) {
+ val amountText = if (amount.value == 0L) "" else amount.value.toString()
+ val currency = currencies.find { amount.currency == it } ?: currencies[0]
+ OutlinedTextField(
+ modifier = Modifier
+ .padding(end = 16.dp)
+ .weight(1f),
+ value = amountText,
+ placeholder = { Text("0") },
+ onValueChange = { input ->
+ if (input.isNotBlank()) {
+ onAmountChosen(Amount.fromString(currency, input))
+ } else {
+ onAmountChosen(Amount.zero(currency))
+ }
+ },
+ keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Decimal),
+ singleLine = true,
+ label = { Text(stringResource(R.string.send_amount)) },
+ )
+ CurrencyDropdown(
+ modifier = Modifier.weight(1f),
+ initialCurrency = currency,
+ currencies = currencies,
+ onCurrencyChanged = { c ->
+ onAmountChosen(Amount.fromString(c, amount.amountStr))
+ },
+ )
+ }
+}
+
+// TODO cleanup composable
+//@Preview
+//@Composable
+//fun PayTemplateComposablePreview() {
+// TalerSurface {
+// PayTemplateComposable(Uri.EMPTY, listOf("KUDOS"))
+// }
+//}
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt
index 633ab20..080d319 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt
@@ -21,65 +21,35 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material3.Button
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.asFlow
import androidx.navigation.NavOptions
import androidx.navigation.fragment.findNavController
-import net.taler.common.Amount
-import net.taler.common.AmountParserException
-import net.taler.common.showError
-import net.taler.wallet.AmountResult
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.compose.TalerSurface
-import net.taler.wallet.deposit.CurrencyDropdown
-class PayTemplateFragment: Fragment() {
+class PayTemplateFragment : Fragment() {
+
private val model: MainViewModel by activityViewModels()
- private var uriString: String? = null
- private var uri: Uri? = null
+ private lateinit var uriString: String
+ private lateinit var uri: Uri
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View {
uriString = arguments?.getString("uri") ?: error("no amount passed")
uri = Uri.parse(uriString)
- val currencies = model.getCurrencies()
return ComposeView(requireContext()).apply {
setContent {
TalerSurface {
PayTemplateComposable(
- uri = uri!!,
- currencies = currencies,
+ uri = uri,
+ currencies = model.getCurrencies(),
fragment = this@PayTemplateFragment,
model = model,
onSubmit = { createOrder(it) },
@@ -93,186 +63,21 @@ class PayTemplateFragment: Fragment() {
super.onViewCreated(view, savedInstanceState)
// TODO: this is not ideal, if the template is fixed, the
// user shouldn't even have to go through this fragment.
- if (uri?.queryParameterNames?.isEmpty() == true) {
+ if (uri.queryParameterNames?.isEmpty() == true) {
createOrder(emptyMap())
}
}
private fun createOrder(params: Map<String, String>) {
- uriString ?: return
- model.paymentManager.preparePayForTemplate(uriString!!, params,).invokeOnCompletion {
+ model.paymentManager.preparePayForTemplate(uriString, params).invokeOnCompletion {
+ // TODO maybe better to observe/collect payStatus instead of invokeOnCompletion
+ // and then only reacting to one of the possible payStatus values
if (model.paymentManager.payStatus.value is PayStatus.Prepared) {
val navOptions = NavOptions.Builder()
.setPopUpTo(R.id.nav_main, true)
.build()
- findNavController()
- .navigate(R.id.action_global_promptPayment, null, navOptions)
+ findNavController().navigate(R.id.action_global_promptPayment, null, navOptions)
}
}
}
}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun PayTemplateComposable(
- uri: Uri,
- currencies: List<String>,
- fragment: Fragment,
- model: MainViewModel,
- onSubmit: (Map<String, String>) -> Unit,
-) {
- val queryParams = uri.queryParameterNames
-
- var summary by remember { mutableStateOf(
- if ("summary" in queryParams)
- uri.getQueryParameter("summary") else null,
- ) }
-
- var amount by remember { mutableStateOf(
- if ("amount" in queryParams) {
- val amount = uri.getQueryParameter("amount")!!
- val parts = amount.split(':')
- when (parts.size) {
- 1 -> Amount.fromString(parts[0], "0")
- 2 -> Amount.fromString(parts[0], parts[1])
- else -> throw AmountParserException("Invalid Amount Format")
- }
- } else null,
- ) }
-
- val payStatus by model.paymentManager.payStatus.asFlow()
- .collectAsState(initial = PayStatus.None)
-
- // If wallet is empty, there's no way the user can pay something
- if (payStatus is PayStatus.InsufficientBalance || currencies.isEmpty()) {
- Box(
- modifier = Modifier.fillMaxSize(),
- contentAlignment = Alignment.Center,
- ) {
- Text(
- stringResource(R.string.payment_balance_insufficient),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.error,
- )
- }
- } else when (payStatus) {
- is PayStatus.None -> {
- Column(horizontalAlignment = Alignment.End) {
- if ("summary" in queryParams) {
- OutlinedTextField(
- modifier = Modifier
- .padding(horizontal = 16.dp)
- .fillMaxWidth(),
- value = summary!!,
- isError = summary!!.isBlank(),
- onValueChange = { summary = it },
- singleLine = true,
- label = { Text(stringResource(R.string.withdraw_manual_ready_subject)) },
- )
- }
-
- if ("amount" in queryParams) {
- AmountField(
- modifier = Modifier
- .padding(16.dp)
- .fillMaxWidth(),
- amount = amount!!,
- currencies = currencies,
- onAmountChosen = { amount = it },
- )
- }
-
- Button(
- modifier = Modifier.padding(16.dp),
- enabled = summary == null || summary!!.isNotBlank(),
- onClick = {
- if (amount != null) {
- val result = model.createAmount(
- amount!!.amountStr,
- amount!!.currency,
- )
- when (result) {
- AmountResult.InsufficientBalance -> {
- fragment.showError(R.string.payment_balance_insufficient)
- }
- AmountResult.InvalidAmount -> {
- fragment.showError(R.string.receive_amount_invalid)
- }
- else -> {
- onSubmit(
- mutableMapOf<String, String>().apply {
- if (summary != null) put("summary", summary!!)
- if (amount != null) put("amount", amount!!.toJSONString())
- }
- )
- }
- }
- }
- },
- ) {
- Text(stringResource(R.string.payment_create_order))
- }
- }
- }
- is PayStatus.Loading -> {
- Box(
- modifier = Modifier.fillMaxSize(),
- contentAlignment = Alignment.Center,
- ) { CircularProgressIndicator() }
- }
- is PayStatus.AlreadyPaid -> {
- Box(
- modifier = Modifier.fillMaxSize(),
- contentAlignment = Alignment.Center,
- ) {
- Text(
- stringResource(R.string.payment_already_paid),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.error,
- )
- }
- }
- else -> {}
- }
-}
-
-@Composable
-@OptIn(ExperimentalMaterial3Api::class)
-private fun AmountField(
- modifier: Modifier = Modifier,
- currencies: List<String>,
- amount: Amount,
- onAmountChosen: (Amount) -> Unit,
-) {
- Row(
- modifier = modifier,
- ) {
- val amountText = if (amount.value == 0L) "" else amount.value.toString()
- val currency = currencies.find { amount.currency == it } ?: currencies[0]
- OutlinedTextField(
- modifier = Modifier
- .padding(end = 16.dp)
- .weight(1f),
- value = amountText,
- placeholder = { Text("0") },
- onValueChange = { input ->
- if (input.isNotBlank()) {
- onAmountChosen(Amount.fromString(currency, input))
- } else {
- onAmountChosen(Amount.zero(currency))
- }
- },
- keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Decimal),
- singleLine = true,
- label = { Text(stringResource(R.string.send_amount)) },
- )
- CurrencyDropdown(
- modifier = Modifier.weight(1f),
- initialCurrency = currency,
- currencies = currencies,
- onCurrencyChanged = { c ->
- onAmountChosen(Amount.fromString(c, amount.amountStr))
- },
- )
- }
-} \ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
index c98e0b2..627c05d 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -116,8 +116,8 @@ class PaymentManager(
mPayStatus.value = when (response) {
is PaymentPossibleResponse -> response.toPayStatusPrepared()
is InsufficientBalanceResponse -> InsufficientBalance(
- response.contractTerms,
- response.amountRaw
+ contractTerms = response.contractTerms,
+ amountRaw = response.amountRaw,
)
is AlreadyConfirmedResponse -> AlreadyPaid
}