summaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net/taler/wallet/deposit
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/deposit')
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt22
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt41
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/DepositState.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt41
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt137
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt85
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt68
7 files changed, 223 insertions, 175 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt
index c4b302f..20acee1 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt
@@ -32,10 +32,13 @@ import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.compose.TalerSurface
import net.taler.wallet.compose.collectAsStateLifecycleAware
+import net.taler.wallet.showError
class DepositFragment : Fragment() {
private val model: MainViewModel by activityViewModels()
private val depositManager get() = model.depositManager
+ private val balanceManager get() = model.balanceManager
+ private val transactionManager get() = model.transactionManager
override fun onCreateView(
inflater: LayoutInflater,
@@ -45,12 +48,12 @@ class DepositFragment : Fragment() {
val amount = arguments?.getString("amount")?.let {
Amount.fromJSONString(it)
} ?: error("no amount passed")
+ val scopeInfo = transactionManager.selectedScope
+ val spec = scopeInfo?.let { balanceManager.getSpecForScopeInfo(it) }
val receiverName = arguments?.getString("receiverName")
val iban = arguments?.getString("IBAN")
- val bic = arguments?.getString("BIC") ?: ""
-
if (receiverName != null && iban != null) {
- onDepositButtonClicked(amount, receiverName, iban, bic)
+ onDepositButtonClicked(amount, receiverName, iban)
}
return ComposeView(requireContext()).apply {
setContent {
@@ -58,14 +61,14 @@ class DepositFragment : Fragment() {
val state = depositManager.depositState.collectAsStateLifecycleAware()
if (amount.currency == CURRENCY_BTC) MakeBitcoinDepositComposable(
state = state.value,
- amount = amount,
+ amount = amount.withSpec(spec),
bitcoinAddress = null,
onMakeDeposit = { amount, bitcoinAddress ->
depositManager.onDepositButtonClicked(amount, bitcoinAddress)
},
) else MakeDepositComposable(
state = state.value,
- amount = amount,
+ amount = amount.withSpec(spec),
presetName = receiverName,
presetIban = iban,
onMakeDeposit = this@DepositFragment::onDepositButtonClicked,
@@ -80,7 +83,11 @@ class DepositFragment : Fragment() {
lifecycleScope.launchWhenStarted {
depositManager.depositState.collect { state ->
if (state is DepositState.Error) {
- showError(state.msg)
+ if (model.devMode.value == false) {
+ showError(state.error.userFacingMsg)
+ } else {
+ showError(state.error)
+ }
} else if (state is DepositState.Success) {
findNavController().navigate(R.id.action_nav_deposit_to_nav_main)
}
@@ -104,8 +111,7 @@ class DepositFragment : Fragment() {
amount: Amount,
receiverName: String,
iban: String,
- bic: String,
) {
- depositManager.onDepositButtonClicked(amount, receiverName, iban, bic)
+ depositManager.onDepositButtonClicked(amount, receiverName, iban)
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt
index 0f10b5f..0075f95 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt
@@ -46,10 +46,36 @@ class DepositManager(
}
@UiThread
- fun onDepositButtonClicked(amount: Amount, receiverName: String, iban: String, bic: String) {
+ fun onDepositButtonClicked(amount: Amount, receiverName: String, iban: String) {
+ if (depositState.value is DepositState.FeesChecked) {
+ // fees already checked, so IBAN was validated, can make deposit directly
+ makeIbanDeposit(amount, receiverName, iban)
+ } else {
+ // validate IBAN first
+ mDepositState.value = DepositState.CheckingFees
+ scope.launch {
+ api.request("validateIban", ValidateIbanResponse.serializer()) {
+ put("iban", iban)
+ }.onError {
+ Log.e(TAG, "Error validateIban $it")
+ mDepositState.value = DepositState.Error(it)
+ }.onSuccess { response ->
+ if (response.valid) {
+ // only prepare/make deposit, if IBAN is valid
+ makeIbanDeposit(amount, receiverName, iban)
+ } else {
+ mDepositState.value = DepositState.IbanInvalid
+ }
+ }
+ }
+ }
+ }
+
+ @UiThread
+ private fun makeIbanDeposit(amount: Amount, receiverName: String, iban: String) {
val paytoUri: String = PaytoUriIban(
iban = iban,
- bic = bic,
+ bic = null,
targetPath = "",
params = mapOf("receiver-name" to receiverName),
).paytoUri
@@ -66,7 +92,7 @@ class DepositManager(
}
private fun makeDeposit(amount: Amount, uri: String) {
- if (depositState.value.showFees) makeDeposit(
+ if (depositState.value is DepositState.FeesChecked) makeDeposit(
paytoUri = uri,
amount = amount,
totalDepositCost = depositState.value.totalDepositCost
@@ -86,7 +112,7 @@ class DepositManager(
put("amount", amount.toJSONString())
}.onError {
Log.e(TAG, "Error prepareDeposit $it")
- mDepositState.value = DepositState.Error(it.userFacingMsg)
+ mDepositState.value = DepositState.Error(it)
}.onSuccess {
mDepositState.value = DepositState.FeesChecked(
totalDepositCost = it.totalDepositCost,
@@ -112,7 +138,7 @@ class DepositManager(
put("amount", amount.toJSONString())
}.onError {
Log.e(TAG, "Error createDepositGroup $it")
- mDepositState.value = DepositState.Error(it.userFacingMsg)
+ mDepositState.value = DepositState.Error(it)
}.onSuccess {
mDepositState.value = DepositState.Success
}
@@ -126,6 +152,11 @@ class DepositManager(
}
@Serializable
+data class ValidateIbanResponse(
+ val valid: Boolean,
+)
+
+@Serializable
data class PrepareDepositResponse(
val totalDepositCost: Amount,
val effectiveDepositAmount: Amount,
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositState.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositState.kt
index 918d74c..168378f 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/DepositState.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/DepositState.kt
@@ -17,6 +17,7 @@
package net.taler.wallet.deposit
import net.taler.common.Amount
+import net.taler.wallet.backend.TalerErrorInfo
sealed class DepositState {
@@ -26,6 +27,7 @@ sealed class DepositState {
object Start : DepositState()
object CheckingFees : DepositState()
+ object IbanInvalid : DepositState()
class FeesChecked(
override val totalDepositCost: Amount,
override val effectiveDepositAmount: Amount,
@@ -42,6 +44,6 @@ sealed class DepositState {
object Success : DepositState()
- class Error(val msg: String) : DepositState()
+ class Error(val error: TalerErrorInfo) : DepositState()
}
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt
index 1ff3423..d356051 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt
@@ -22,10 +22,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.Button
-import androidx.compose.material.OutlinedTextField
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -39,7 +40,6 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -71,6 +71,7 @@ fun MakeBitcoinDepositComposable(
.padding(16.dp)
.focusRequester(focusRequester),
value = address,
+ singleLine = true,
enabled = !state.showFees,
onValueChange = { input ->
address = input
@@ -80,7 +81,7 @@ fun MakeBitcoinDepositComposable(
Text(
stringResource(R.string.send_deposit_bitcoin_address),
color = if (address.isBlank()) {
- colorResource(R.color.red)
+ MaterialTheme.colorScheme.error
} else Color.Unspecified,
)
}
@@ -90,7 +91,7 @@ fun MakeBitcoinDepositComposable(
}
val amountTitle = if (state.effectiveDepositAmount == null) {
R.string.amount_chosen
- } else R.string.send_deposit_amount_effective
+ } else R.string.amount_effective
TransactionAmountComposable(
label = stringResource(id = amountTitle),
amount = state.effectiveDepositAmount ?: amount,
@@ -103,14 +104,16 @@ fun MakeBitcoinDepositComposable(
) {
val totalAmount = state.totalDepositCost ?: amount
val effectiveAmount = state.effectiveDepositAmount ?: Amount.zero(amount.currency)
- val fee = totalAmount - effectiveAmount
+ if (totalAmount > effectiveAmount) {
+ val fee = totalAmount - effectiveAmount
+ TransactionAmountComposable(
+ label = stringResource(id = R.string.amount_fee),
+ amount = fee,
+ amountType = AmountType.Negative,
+ )
+ }
TransactionAmountComposable(
- label = stringResource(id = R.string.withdraw_fees),
- amount = fee,
- amountType = AmountType.Negative,
- )
- TransactionAmountComposable(
- label = stringResource(id = R.string.send_amount),
+ label = stringResource(id = R.string.amount_send),
amount = totalAmount,
amountType = AmountType.Positive,
)
@@ -120,8 +123,8 @@ fun MakeBitcoinDepositComposable(
Text(
modifier = Modifier.padding(16.dp),
fontSize = 18.sp,
- color = colorResource(R.color.red),
- text = (state as? DepositState.Error)?.msg ?: "",
+ color = MaterialTheme.colorScheme.error,
+ text = (state as? DepositState.Error)?.error?.userFacingMsg ?: "",
)
}
val focusManager = LocalFocusManager.current
@@ -147,12 +150,12 @@ fun MakeBitcoinDepositComposable(
fun PreviewMakeBitcoinDepositComposable() {
Surface {
val state = DepositState.FeesChecked(
- effectiveDepositAmount = Amount.fromDouble(CURRENCY_BTC, 42.00),
- totalDepositCost = Amount.fromDouble(CURRENCY_BTC, 42.23),
+ effectiveDepositAmount = Amount.fromString(CURRENCY_BTC, "42.00"),
+ totalDepositCost = Amount.fromString(CURRENCY_BTC, "42.23"),
)
MakeBitcoinDepositComposable(
state = state,
- amount = Amount.fromDouble(CURRENCY_BTC, 42.23)) { _, _ ->
+ amount = Amount.fromString(CURRENCY_BTC, "42.23")) { _, _ ->
}
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt
index 8b5cac1..2f9fd88 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt
@@ -22,10 +22,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.Button
-import androidx.compose.material.OutlinedTextField
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -39,13 +40,15 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import net.taler.common.Amount
import net.taler.wallet.R
+import net.taler.wallet.transactions.AmountType.Negative
+import net.taler.wallet.transactions.AmountType.Positive
+import net.taler.wallet.transactions.TransactionAmountComposable
@Composable
fun MakeDepositComposable(
@@ -53,7 +56,7 @@ fun MakeDepositComposable(
amount: Amount,
presetName: String? = null,
presetIban: String? = null,
- onMakeDeposit: (Amount, String, String, String) -> Unit,
+ onMakeDeposit: (Amount, String, String) -> Unit,
) {
val scrollState = rememberScrollState()
Column(
@@ -64,23 +67,24 @@ fun MakeDepositComposable(
) {
var name by rememberSaveable { mutableStateOf(presetName ?: "") }
var iban by rememberSaveable { mutableStateOf(presetIban ?: "") }
- var bic by rememberSaveable { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
OutlinedTextField(
modifier = Modifier
.padding(16.dp)
- .focusRequester(focusRequester),
+ .focusRequester(focusRequester)
+ .fillMaxWidth(),
value = name,
enabled = !state.showFees,
onValueChange = { input ->
name = input
},
+ singleLine = true,
isError = name.isBlank(),
label = {
Text(
stringResource(R.string.send_deposit_name),
color = if (name.isBlank()) {
- colorResource(R.color.red)
+ MaterialTheme.colorScheme.error
} else Color.Unspecified,
)
}
@@ -88,53 +92,40 @@ fun MakeDepositComposable(
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
+ val ibanError = state is DepositState.IbanInvalid
OutlinedTextField(
modifier = Modifier
- .padding(16.dp),
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
value = iban,
+ singleLine = true,
enabled = !state.showFees,
onValueChange = { input ->
- iban = input
+ iban = input.uppercase()
+ },
+ isError = ibanError,
+ supportingText = {
+ if (ibanError) {
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(R.string.send_deposit_iban_error),
+ color = MaterialTheme.colorScheme.error
+ )
+ }
},
- isError = iban.isBlank(),
label = {
Text(
text = stringResource(R.string.send_deposit_iban),
- color = if (iban.isBlank()) {
- colorResource(R.color.red)
+ color = if (ibanError) {
+ MaterialTheme.colorScheme.error
} else Color.Unspecified,
)
}
)
- OutlinedTextField(
- modifier = Modifier
- .padding(16.dp),
- value = bic,
- enabled = !state.showFees,
- onValueChange = { input ->
- bic = input
- },
- label = {
- Text(
- text = stringResource(R.string.send_deposit_bic),
- )
- }
- )
- val amountTitle = if (state.effectiveDepositAmount == null) {
- R.string.amount_chosen
- } else R.string.send_deposit_amount_effective
- Text(
- modifier = Modifier.padding(horizontal = 16.dp),
- text = stringResource(id = amountTitle),
- )
- val shownAmount = if (state.effectiveDepositAmount == null) amount else {
- state.effectiveDepositAmount
- }
- Text(
- modifier = Modifier.padding(16.dp),
- fontSize = 24.sp,
- color = colorResource(R.color.green),
- text = shownAmount.toString(),
+ TransactionAmountComposable(
+ label = stringResource(R.string.amount_chosen),
+ amount = amount,
+ amountType = Positive,
)
AnimatedVisibility(visible = state.showFees) {
Column(
@@ -143,30 +134,20 @@ fun MakeDepositComposable(
) {
val totalAmount = state.totalDepositCost ?: amount
val effectiveAmount = state.effectiveDepositAmount ?: Amount.zero(amount.currency)
- val fee = totalAmount - effectiveAmount
- Text(
- modifier = Modifier.padding(horizontal = 16.dp),
- text = stringResource(id = R.string.withdraw_fees),
- )
- Text(
- modifier = Modifier.padding(16.dp),
- fontSize = 24.sp,
- color = colorResource(if (fee.isZero()) R.color.green else R.color.red),
- text = if (fee.isZero()) {
- fee.toString()
- } else {
- stringResource(R.string.amount_negative, fee.toString())
- },
- )
- Text(
- modifier = Modifier.padding(horizontal = 16.dp),
- text = stringResource(id = R.string.send_amount),
- )
- Text(
- modifier = Modifier.padding(16.dp),
- fontSize = 24.sp,
- color = colorResource(R.color.green),
- text = totalAmount.toString(),
+ if (totalAmount > effectiveAmount) {
+ val fee = totalAmount - effectiveAmount
+
+ TransactionAmountComposable(
+ label = stringResource(R.string.amount_fee),
+ amount = fee.withSpec(amount.spec),
+ amountType = Negative,
+ )
+ }
+
+ TransactionAmountComposable(
+ label = stringResource(R.string.amount_send),
+ amount = effectiveAmount.withSpec(amount.spec),
+ amountType = Positive,
)
}
}
@@ -174,8 +155,8 @@ fun MakeDepositComposable(
Text(
modifier = Modifier.padding(16.dp),
fontSize = 18.sp,
- color = colorResource(R.color.red),
- text = (state as? DepositState.Error)?.msg ?: "",
+ color = MaterialTheme.colorScheme.error,
+ text = (state as? DepositState.Error)?.error?.userFacingMsg ?: "",
)
}
val focusManager = LocalFocusManager.current
@@ -184,13 +165,15 @@ fun MakeDepositComposable(
enabled = iban.isNotBlank(),
onClick = {
focusManager.clearFocus()
- onMakeDeposit(amount, name, iban, bic)
+ onMakeDeposit(amount, name, iban)
},
) {
- Text(text = stringResource(
- if (state.showFees) R.string.send_deposit_create_button
- else R.string.send_deposit_check_fees_button
- ))
+ Text(
+ text = stringResource(
+ if (state is DepositState.FeesChecked) R.string.send_deposit_create_button
+ else R.string.send_deposit_check_fees_button
+ )
+ )
}
}
}
@@ -200,12 +183,12 @@ fun MakeDepositComposable(
fun PreviewMakeDepositComposable() {
Surface {
val state = DepositState.FeesChecked(
- effectiveDepositAmount = Amount.fromDouble("TESTKUDOS", 42.00),
- totalDepositCost = Amount.fromDouble("TESTKUDOS", 42.23),
+ effectiveDepositAmount = Amount.fromString("TESTKUDOS", "42.00"),
+ totalDepositCost = Amount.fromString("TESTKUDOS", "42.23"),
)
MakeDepositComposable(
state = state,
- amount = Amount.fromDouble("TESTKUDOS", 42.23)) { _, _, _, _ ->
+ amount = Amount.fromString("TESTKUDOS", "42.23")) { _, _, _ ->
}
}
}
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 47e8cfb..0dd3abd 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
@@ -30,16 +30,15 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.Button
-import androidx.compose.material.DropdownMenu
-import androidx.compose.material.DropdownMenuItem
-import androidx.compose.material.LocalTextStyle
-import androidx.compose.material.OutlinedTextField
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.material.TextFieldDefaults
+import androidx.compose.material3.Button
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -48,14 +47,13 @@ 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
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
@@ -66,6 +64,7 @@ import net.taler.common.Amount
import net.taler.wallet.AmountResult
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
+import net.taler.wallet.compose.AmountInputField
import net.taler.wallet.compose.TalerSurface
class PayToUriFragment : Fragment() {
@@ -85,9 +84,9 @@ class PayToUriFragment : Fragment() {
TalerSurface {
if (currencies.isEmpty()) Text(
text = stringResource(id = R.string.payment_balance_insufficient),
- color = colorResource(id = R.color.red),
+ color = MaterialTheme.colorScheme.error,
) else if (depositManager.isSupportedPayToUri(uri)) PayToComposable(
- currencies = model.getCurrencies(),
+ currencies = currencies,
getAmount = model::createAmount,
onAmountChosen = { amount ->
val u = Uri.parse(uri)
@@ -101,7 +100,7 @@ class PayToUriFragment : Fragment() {
},
) else Text(
text = stringResource(id = R.string.uri_invalid),
- color = colorResource(id = R.color.red),
+ color = MaterialTheme.colorScheme.error,
)
}
}
@@ -130,30 +129,27 @@ private fun PayToComposable(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
- var amountText by rememberSaveable { mutableStateOf("") }
+ var amountText by rememberSaveable { mutableStateOf("0") }
var amountError by rememberSaveable { mutableStateOf("") }
var currency by rememberSaveable { mutableStateOf(currencies[0]) }
val focusRequester = remember { FocusRequester() }
- OutlinedTextField(
- modifier = Modifier
- .focusRequester(focusRequester),
+ AmountInputField(
+ modifier = Modifier.focusRequester(focusRequester),
value = amountText,
onValueChange = { input ->
amountError = ""
amountText = input
},
- keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Decimal),
- singleLine = true,
+ label = { Text(stringResource(R.string.amount_send)) },
+ supportingText = {
+ if (amountError.isNotBlank()) Text(amountError)
+ },
isError = amountError.isNotBlank(),
- label = {
- if (amountError.isBlank()) {
- Text(stringResource(R.string.send_amount))
- } else {
- Text(amountError, color = colorResource(R.color.red))
- }
- }
)
CurrencyDropdown(
+ modifier = Modifier
+ .fillMaxSize()
+ .wrapContentSize(Center),
currencies = currencies,
onCurrencyChanged = { c -> currency = c },
)
@@ -162,7 +158,7 @@ private fun PayToComposable(
}
val focusManager = LocalFocusManager.current
- val errorStrInvalidAmount = stringResource(id = R.string.receive_amount_invalid)
+ val errorStrInvalidAmount = stringResource(id = R.string.amount_invalid)
val errorStrInsufficientBalance = stringResource(id = R.string.payment_balance_insufficient)
Button(
modifier = Modifier.padding(16.dp),
@@ -187,25 +183,25 @@ private fun PayToComposable(
fun CurrencyDropdown(
currencies: List<String>,
onCurrencyChanged: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ initialCurrency: String? = null,
+ readOnly: Boolean = false,
) {
- var selectedIndex by remember { mutableStateOf(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
- .fillMaxSize()
- .wrapContentSize(Alignment.Center),
+ modifier = modifier,
) {
OutlinedTextField(
modifier = Modifier
- .clickable(onClick = { expanded = true }),
+ .clickable(onClick = { if (!readOnly) expanded = true }),
value = currencies[selectedIndex],
onValueChange = { },
readOnly = true,
enabled = false,
textStyle = LocalTextStyle.current.copy( // show text as if not disabled
- color = TextFieldDefaults.outlinedTextFieldColors().textColor(
- enabled = true,
- ).value
+ color = MaterialTheme.colorScheme.onSurfaceVariant
),
singleLine = true,
label = {
@@ -218,13 +214,16 @@ fun CurrencyDropdown(
modifier = Modifier,
) {
currencies.forEachIndexed { index, s ->
- DropdownMenuItem(onClick = {
- selectedIndex = index
- onCurrencyChanged(currencies[index])
- expanded = false
- }) {
- Text(text = s)
- }
+ DropdownMenuItem(
+ text = {
+ Text(text = s)
+ },
+ onClick = {
+ selectedIndex = index
+ onCurrencyChanged(currencies[index])
+ expanded = false
+ }
+ )
}
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt
index 7c5c7a2..11264a1 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt
@@ -21,9 +21,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -32,16 +32,31 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.taler.common.Amount
+import net.taler.common.CurrencySpecification
import net.taler.common.Timestamp
import net.taler.common.toAbsoluteTime
import net.taler.wallet.R
+import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
+import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.transactions.AmountType
-import net.taler.wallet.transactions.DeleteTransactionComposable
+import net.taler.wallet.transactions.ErrorTransactionButton
+import net.taler.wallet.transactions.TransactionAction
+import net.taler.wallet.transactions.TransactionAction.Abort
+import net.taler.wallet.transactions.TransactionAction.Retry
+import net.taler.wallet.transactions.TransactionAction.Suspend
import net.taler.wallet.transactions.TransactionAmountComposable
import net.taler.wallet.transactions.TransactionDeposit
+import net.taler.wallet.transactions.TransactionMajorState.Pending
+import net.taler.wallet.transactions.TransactionState
+import net.taler.wallet.transactions.TransitionsComposable
@Composable
-fun TransactionDepositComposable(t: TransactionDeposit, onDelete: () -> Unit) {
+fun TransactionDepositComposable(
+ t: TransactionDeposit,
+ devMode: Boolean,
+ spec: CurrencySpecification?,
+ onTransition: (t: TransactionAction) -> Unit,
+) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
@@ -53,27 +68,34 @@ fun TransactionDepositComposable(t: TransactionDeposit, onDelete: () -> Unit) {
Text(
modifier = Modifier.padding(16.dp),
text = t.timestamp.ms.toAbsoluteTime(context).toString(),
- style = MaterialTheme.typography.body1,
- )
- TransactionAmountComposable(
- label = stringResource(id = R.string.transaction_paid),
- amount = t.amountEffective,
- amountType = AmountType.Negative,
+ style = MaterialTheme.typography.bodyLarge,
)
+
TransactionAmountComposable(
- label = stringResource(id = R.string.transaction_order_total),
- amount = t.amountRaw,
+ label = stringResource(id = R.string.amount_chosen),
+ amount = t.amountRaw.withSpec(spec),
amountType = AmountType.Neutral,
)
- val fee = t.amountEffective - t.amountRaw
- if (!fee.isZero()) {
+
+ if (t.amountEffective > t.amountRaw) {
+ val fee = t.amountEffective - t.amountRaw
TransactionAmountComposable(
- label = stringResource(id = R.string.withdraw_fees),
- amount = fee,
+ label = stringResource(id = R.string.amount_fee),
+ amount = fee.withSpec(spec),
amountType = AmountType.Negative,
)
}
- DeleteTransactionComposable(onDelete)
+
+ TransactionAmountComposable(
+ label = stringResource(id = R.string.amount_sent),
+ amount = t.amountEffective.withSpec(spec),
+ amountType = AmountType.Negative,
+ )
+
+ TransitionsComposable(t, devMode, onTransition)
+ if (devMode && t.error != null) {
+ ErrorTransactionButton(error = t.error)
+ }
}
}
@@ -83,13 +105,15 @@ fun TransactionDepositComposablePreview() {
val t = TransactionDeposit(
transactionId = "transactionId",
timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000),
- pending = true,
+ txState = TransactionState(Pending),
+ txActions = listOf(Retry, Suspend, Abort),
depositGroupId = "fooBar",
- amountRaw = Amount.fromDouble("TESTKUDOS", 42.1337),
- amountEffective = Amount.fromDouble("TESTKUDOS", 42.23),
+ amountRaw = Amount.fromString("TESTKUDOS", "42.1337"),
+ amountEffective = Amount.fromString("TESTKUDOS", "42.23"),
targetPaytoUri = "https://exchange.example.org/peer/pull/credit",
+ error = TalerErrorInfo(code = EXCHANGE_GENERIC_KYC_REQUIRED),
)
Surface {
- TransactionDepositComposable(t) {}
+ TransactionDepositComposable(t, true, null) {}
}
}