commit 4a72da387532c044c684e1678ba72d1252ca2f79
parent 01cfba831d1a03f0c29a012b5a7df600b671ddbb
Author: Iván Ávalos <avalos@disroot.org>
Date: Wed, 28 May 2025 16:08:57 +0200
[wallet] unify confirmation buttons
Diffstat:
3 files changed, 359 insertions(+), 321 deletions(-)
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositAmountComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositAmountComposable.kt
@@ -18,6 +18,7 @@ package net.taler.wallet.deposit
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
@@ -45,6 +46,8 @@ import net.taler.wallet.R
import net.taler.wallet.accounts.BankAccountRow
import net.taler.wallet.accounts.KnownBankAccountInfo
import net.taler.wallet.compose.AmountCurrencyField
+import net.taler.wallet.compose.BottomButtonBox
+import net.taler.wallet.systemBarsPaddingBottom
import net.taler.wallet.transactions.AmountType.Negative
import net.taler.wallet.transactions.AmountType.Positive
import net.taler.wallet.transactions.TransactionAmountComposable
@@ -70,13 +73,10 @@ fun DepositAmountComposable(
return
}
- val scrollState = rememberScrollState()
Column(
- modifier = Modifier
- .fillMaxWidth()
- .verticalScroll(scrollState)
+ Modifier
+ .fillMaxSize()
.imePadding(),
- horizontalAlignment = CenterHorizontally,
) {
var checkResult by remember { mutableStateOf<CheckDepositResult>(CheckDepositResult.None()) }
// TODO: use scopeInfo instead of currency
@@ -85,107 +85,114 @@ fun DepositAmountComposable(
var amount by remember(state.maxDepositable) { mutableStateOf(Amount.zero(currencies.first())) }
val spec = remember(amount) { getCurrencySpec(amount.currency) }
- amount.useDebounce {
- if (!amount.isZero()) {
- checkResult = checkDeposit(amount)
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .verticalScroll(rememberScrollState())
+ .fillMaxWidth(),
+ horizontalAlignment = CenterHorizontally,
+ ) {
+
+ amount.useDebounce {
+ if (!amount.isZero()) {
+ checkResult = checkDeposit(amount)
+ }
}
- }
- BankAccountRow(
- account = state.account,
- showMenu = false,
- )
+ BankAccountRow(
+ account = state.account,
+ showMenu = false,
+ )
- HorizontalDivider(
- modifier = Modifier.padding(bottom = 16.dp),
- )
+ HorizontalDivider(
+ modifier = Modifier.padding(bottom = 16.dp),
+ )
- AnimatedVisibility(checkResult.maxDepositAmountRaw != null) {
- checkResult.maxDepositAmountRaw?.let {
- Text(
- modifier = Modifier.padding(
- start = 16.dp,
- end = 16.dp,
- bottom = 16.dp,
- ),
- text = if (checkResult.maxDepositAmountEffective == it) {
- stringResource(
- R.string.amount_available_transfer,
- it.withSpec(spec),
- )
- } else {
- stringResource(
- R.string.amount_available_transfer_fees,
- it.withSpec(spec),
- )
- },
- )
- }
- }
-
- AmountCurrencyField(
- modifier = Modifier
- .padding(horizontal = 16.dp)
- .fillMaxWidth(),
- amount = amount.withSpec(spec),
- onAmountChanged = { amount = it },
- editableCurrency = true,
- currencies = currencies,
- isError = checkResult !is CheckDepositResult.Success,
- label = { Text(stringResource(R.string.amount_deposit)) },
- supportingText = {
- val res = checkResult
- if (res is CheckDepositResult.InsufficientBalance && res.maxAmountEffective != null) {
+ AnimatedVisibility(checkResult.maxDepositAmountRaw != null) {
+ checkResult.maxDepositAmountRaw?.let {
Text(
- stringResource(
- R.string.payment_balance_insufficient_max,
- res.maxAmountEffective.withSpec(spec),
- )
+ modifier = Modifier.padding(
+ start = 16.dp,
+ end = 16.dp,
+ bottom = 16.dp,
+ ),
+ text = if (checkResult.maxDepositAmountEffective == it) {
+ stringResource(
+ R.string.amount_available_transfer,
+ it.withSpec(spec),
+ )
+ } else {
+ stringResource(
+ R.string.amount_available_transfer_fees,
+ it.withSpec(spec),
+ )
+ },
)
}
}
- )
- AnimatedVisibility(visible = checkResult is CheckDepositResult.Success) {
- val res = checkResult as? CheckDepositResult.Success ?: return@AnimatedVisibility
+ AmountCurrencyField(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ amount = amount.withSpec(spec),
+ onAmountChanged = { amount = it },
+ editableCurrency = true,
+ currencies = currencies,
+ isError = checkResult !is CheckDepositResult.Success,
+ label = { Text(stringResource(R.string.amount_deposit)) },
+ supportingText = {
+ val res = checkResult
+ if (res is CheckDepositResult.InsufficientBalance) {
+ Text(stringResource(R.string.payment_balance_insufficient))
+ }
+ }
+ )
- Column(
- modifier = Modifier.fillMaxWidth(),
- horizontalAlignment = CenterHorizontally,
- ) {
- val totalAmount = res.totalDepositCost
- val effectiveAmount = res.effectiveDepositAmount
- if (totalAmount > effectiveAmount) {
- val fee = totalAmount - effectiveAmount
+ AnimatedVisibility(visible = checkResult is CheckDepositResult.Success) {
+ val res = checkResult as? CheckDepositResult.Success ?: return@AnimatedVisibility
- TransactionAmountComposable(
- label = stringResource(R.string.amount_fee),
- amount = fee.withSpec(amount.spec),
- amountType = Negative,
- )
- }
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = CenterHorizontally,
+ ) {
+ val totalAmount = res.totalDepositCost
+ val effectiveAmount = res.effectiveDepositAmount
+ 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,
- )
+ TransactionAmountComposable(
+ label = stringResource(R.string.amount_send),
+ amount = effectiveAmount.withSpec(amount.spec),
+ amountType = Positive,
+ )
+ }
+ }
}
- }
- val focusManager = LocalFocusManager.current
- Button(
- modifier = Modifier.padding(16.dp),
- enabled = checkResult is CheckDepositResult.Success,
- onClick = {
- focusManager.clearFocus()
- onMakeDeposit(amount)
- },
- ) {
- Text(stringResource(R.string.send_deposit_create_button))
+ BottomInsetsSpacer()
}
- BottomInsetsSpacer()
+ BottomButtonBox(Modifier.fillMaxWidth()) {
+ val focusManager = LocalFocusManager.current
+ Button(
+ modifier = Modifier
+ .systemBarsPaddingBottom(),
+ enabled = checkResult is CheckDepositResult.Success,
+ onClick = {
+ focusManager.clearFocus()
+ onMakeDeposit(amount)
+ },
+ ) {
+ Text(stringResource(R.string.send_deposit_create_button))
+ }
+ }
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -43,7 +44,6 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.serialization.json.JsonPrimitive
@@ -57,8 +57,10 @@ import net.taler.wallet.balances.ScopeInfo
import net.taler.wallet.cleanExchange
import net.taler.wallet.compose.AmountScope
import net.taler.wallet.compose.AmountScopeField
+import net.taler.wallet.compose.BottomButtonBox
import net.taler.wallet.compose.TalerSurface
import net.taler.wallet.exchanges.ExchangeTosStatus
+import net.taler.wallet.systemBarsPaddingBottom
import net.taler.wallet.transactions.TransactionInfoComposable
import net.taler.wallet.useDebounce
import kotlin.random.Random
@@ -111,129 +113,139 @@ fun OutgoingPullIntroComposable(
onCreateInvoice: (amount: AmountScope, subject: String, hours: Long, exchangeBaseUrl: String) -> Unit,
onTosAccept: (exchangeBaseUrl: String) -> Unit,
) {
- val scrollState = rememberScrollState()
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp)
- .verticalScroll(scrollState),
- horizontalAlignment = CenterHorizontally,
- ) {
- var subject by rememberSaveable { mutableStateOf("") }
- var amount by remember {
- val scope = defaultScope ?: scopes[0]
- val currency = scope.currency
- mutableStateOf(AmountScope(Amount.zero(currency), scope))
- }
- val selectedSpec = remember(amount.scope) { getCurrencySpec(amount.scope) }
- var checkResult by remember { mutableStateOf<CheckPeerPullCreditResult?>(null) }
+ var subject by rememberSaveable { mutableStateOf("") }
+ var amount by remember {
+ val scope = defaultScope ?: scopes[0]
+ val currency = scope.currency
+ mutableStateOf(AmountScope(Amount.zero(currency), scope))
+ }
+ val selectedSpec = remember(amount.scope) { getCurrencySpec(amount.scope) }
+ var checkResult by remember { mutableStateOf<CheckPeerPullCreditResult?>(null) }
+ val res = checkResult
- amount.useDebounce {
- checkResult = checkPeerPullCredit(it)
- }
+ var option by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY) }
+ var hours by rememberSaveable { mutableLongStateOf(DEFAULT_EXPIRY.hours) }
- LaunchedEffect(Unit) {
- checkResult = checkPeerPullCredit(amount)
- }
-
- AmountScopeField(
- modifier = Modifier
- .padding(bottom = 16.dp)
- .fillMaxWidth(),
- amount = amount.copy(amount = amount.amount.withSpec(selectedSpec)),
- scopes = scopes,
- readOnly = false,
- onAmountChanged = { amount = it },
- isError = amount.amount.isZero(),
- label = { Text(stringResource(R.string.amount_receive)) },
- )
+ amount.useDebounce {
+ checkResult = checkPeerPullCredit(it)
+ }
- OutlinedTextField(
- modifier = Modifier.fillMaxWidth(),
- singleLine = true,
- value = subject,
- onValueChange = { input ->
- if (input.length <= MAX_LENGTH_SUBJECT)
- subject = input.replace('\n', ' ')
- },
- isError = subject.isBlank(),
- label = {
- Text(
- stringResource(R.string.send_peer_purpose),
- color = if (subject.isBlank()) {
- MaterialTheme.colorScheme.error
- } else Color.Unspecified,
- )
- }
- )
+ LaunchedEffect(Unit) {
+ checkResult = checkPeerPullCredit(amount)
+ }
- Text(
+ Column(
+ Modifier
+ .fillMaxSize()
+ .imePadding(),
+ ) {
+ Column(
modifier = Modifier
.fillMaxWidth()
- .padding(top = 5.dp),
- color = if (subject.isBlank()) MaterialTheme.colorScheme.error else Color.Unspecified,
- text = stringResource(R.string.char_count, subject.length, MAX_LENGTH_SUBJECT),
- textAlign = TextAlign.End,
- )
+ .weight(1f)
+ .verticalScroll(rememberScrollState()),
+ horizontalAlignment = CenterHorizontally,
+ ) {
+ AmountScopeField(
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth(),
+ amount = amount.copy(amount = amount.amount.withSpec(selectedSpec)),
+ scopes = scopes,
+ readOnly = false,
+ onAmountChanged = { amount = it },
+ isError = amount.amount.isZero(),
+ label = { Text(stringResource(R.string.amount_receive)) },
+ )
+
+ OutlinedTextField(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ singleLine = true,
+ value = subject,
+ onValueChange = { input ->
+ if (input.length <= MAX_LENGTH_SUBJECT)
+ subject = input.replace('\n', ' ')
+ },
+ isError = subject.isBlank(),
+ label = {
+ Text(
+ stringResource(R.string.send_peer_purpose),
+ color = if (subject.isBlank()) {
+ MaterialTheme.colorScheme.error
+ } else Color.Unspecified,
+ )
+ },
+ supportingText = {
+ Text(stringResource(R.string.char_count, subject.length, MAX_LENGTH_SUBJECT))
+ },
+ )
- val res = checkResult
- if (res != null) {
- if (res.amountEffective > res.amountRaw) {
- val fee = res.amountEffective - res.amountRaw
- Text(
- modifier = Modifier.padding(vertical = 16.dp),
- text = stringResource(id = R.string.payment_fee, fee.withSpec(selectedSpec)),
- softWrap = false,
- color = MaterialTheme.colorScheme.error,
+ if (res != null) {
+ if (res.amountEffective > res.amountRaw) {
+ val fee = res.amountEffective - res.amountRaw
+ Text(
+ modifier = Modifier.padding(vertical = 16.dp),
+ text = stringResource(
+ id = R.string.payment_fee,
+ fee.withSpec(selectedSpec)
+ ),
+ softWrap = false,
+ color = MaterialTheme.colorScheme.error,
+ )
+ }
+ }
+
+ checkResult?.exchangeBaseUrl?.let { exchangeBaseUrl ->
+ TransactionInfoComposable(
+ label = stringResource(id = R.string.withdraw_exchange),
+ info = cleanExchange(exchangeBaseUrl),
)
}
- }
- checkResult?.exchangeBaseUrl?.let { exchangeBaseUrl ->
- TransactionInfoComposable(
- label = stringResource(id = R.string.withdraw_exchange),
- info = cleanExchange(exchangeBaseUrl),
+ Text(
+ modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
+ text = stringResource(R.string.send_peer_expiration_period),
+ style = MaterialTheme.typography.bodyMedium,
)
- }
- Text(
- modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
- text = stringResource(R.string.send_peer_expiration_period),
- style = MaterialTheme.typography.bodyMedium,
- )
+ ExpirationComposable(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .padding(top = 8.dp, bottom = 16.dp),
+ option = option,
+ hours = hours,
+ onOptionChange = { option = it }
+ ) { hours = it }
- var option by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY) }
- var hours by rememberSaveable { mutableLongStateOf(DEFAULT_EXPIRY.hours) }
- ExpirationComposable(
- modifier = Modifier.padding(top = 8.dp, bottom = 16.dp),
- option = option,
- hours = hours,
- onOptionChange = { option = it }
- ) { hours = it }
+ BottomInsetsSpacer()
+ }
- Button(
- modifier = Modifier.padding(16.dp),
- enabled = subject.isNotBlank() && res != null,
- onClick = {
- val ex = res?.exchangeBaseUrl ?: error("clickable without exchange")
- if (res.tosStatus == ExchangeTosStatus.Accepted) {
- onCreateInvoice(
- amount,
- subject,
- hours,
- ex
- )
- } else onTosAccept(ex)
- },
- ) {
- if (checkResult != null && checkResult?.tosStatus != ExchangeTosStatus.Accepted) {
- Text(text = stringResource(R.string.exchange_tos_accept))
- } else {
- Text(text = stringResource(R.string.receive_peer_create_button))
+ BottomButtonBox(Modifier.fillMaxWidth()) {
+ Button(
+ modifier = Modifier
+ .systemBarsPaddingBottom(),
+ enabled = subject.isNotBlank() && res != null,
+ onClick = {
+ val ex = res?.exchangeBaseUrl ?: error("clickable without exchange")
+ if (res.tosStatus == ExchangeTosStatus.Accepted) {
+ onCreateInvoice(
+ amount,
+ subject,
+ hours,
+ ex
+ )
+ } else onTosAccept(ex)
+ },
+ ) {
+ if (checkResult != null && checkResult?.tosStatus != ExchangeTosStatus.Accepted) {
+ Text(text = stringResource(R.string.exchange_tos_accept))
+ } else {
+ Text(text = stringResource(R.string.receive_peer_create_button))
+ }
}
}
-
- BottomInsetsSpacer()
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt
@@ -18,7 +18,9 @@ package net.taler.wallet.peer
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -51,12 +53,14 @@ import net.taler.wallet.balances.ScopeInfo
import net.taler.wallet.cleanExchange
import net.taler.wallet.compose.AmountScope
import net.taler.wallet.compose.AmountScopeField
+import net.taler.wallet.compose.BottomButtonBox
import net.taler.wallet.compose.TalerSurface
import net.taler.wallet.exchanges.ExchangeTosStatus
import net.taler.wallet.payment.stringResId
import net.taler.wallet.peer.CheckFeeResult.InsufficientBalance
import net.taler.wallet.peer.CheckFeeResult.None
import net.taler.wallet.peer.CheckFeeResult.Success
+import net.taler.wallet.systemBarsPaddingBottom
import net.taler.wallet.transactions.TransactionInfoComposable
import net.taler.wallet.useDebounce
import kotlin.random.Random
@@ -92,146 +96,161 @@ fun OutgoingPushIntroComposable(
getFees: suspend (amount: AmountScope) -> CheckFeeResult?,
onSend: (amount: AmountScope, summary: String, hours: Long) -> Unit,
) {
- val scrollState = rememberScrollState()
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp)
- .verticalScroll(scrollState),
- horizontalAlignment = CenterHorizontally,
- ) {
- var amount by remember {
- val scope = defaultScope ?: scopes[0]
- val currency = scope.currency
- mutableStateOf(AmountScope(Amount.zero(currency), scope))
- }
- val selectedSpec = remember(amount.scope) { getCurrencySpec(amount.scope) }
- var feeResult by remember { mutableStateOf<CheckFeeResult>(None()) }
+ var amount by remember {
+ val scope = defaultScope ?: scopes[0]
+ val currency = scope.currency
+ mutableStateOf(AmountScope(Amount.zero(currency), scope))
+ }
+ val selectedSpec = remember(amount.scope) { getCurrencySpec(amount.scope) }
+ var feeResult by remember { mutableStateOf<CheckFeeResult>(None()) }
+ var subject by rememberSaveable { mutableStateOf("") }
- amount.useDebounce {
- feeResult = getFees(it) ?: None()
- }
+ var option by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY) }
+ var hours by rememberSaveable { mutableLongStateOf(DEFAULT_EXPIRY.hours) }
- LaunchedEffect(Unit) {
- feeResult = getFees(amount) ?: None()
- }
+ amount.useDebounce {
+ feeResult = getFees(it) ?: None()
+ }
- AnimatedVisibility(feeResult.maxDepositAmountRaw != null) {
- feeResult.maxDepositAmountRaw?.let {
- Text(
- modifier = Modifier.padding(
- start = 16.dp,
- end = 16.dp,
- bottom = 16.dp,
- ),
- text = if (feeResult.maxDepositAmountEffective == it) {
- stringResource(
- R.string.amount_available_transfer,
- it.withSpec(selectedSpec),
- )
- } else {
- stringResource(
- R.string.amount_available_transfer_fees,
- it.withSpec(selectedSpec),
- )
- },
- )
+ LaunchedEffect(Unit) {
+ feeResult = getFees(amount) ?: None()
+ }
+
+ Column(
+ Modifier
+ .fillMaxSize()
+ .imePadding(),
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .verticalScroll(rememberScrollState()),
+ horizontalAlignment = CenterHorizontally,
+ ) {
+ AnimatedVisibility(feeResult.maxDepositAmountRaw != null) {
+ feeResult.maxDepositAmountRaw?.let {
+ Text(
+ modifier = Modifier.padding(16.dp),
+ text = if (feeResult.maxDepositAmountEffective == it) {
+ stringResource(
+ R.string.amount_available_transfer,
+ it.withSpec(selectedSpec),
+ )
+ } else {
+ stringResource(
+ R.string.amount_available_transfer_fees,
+ it.withSpec(selectedSpec),
+ )
+ },
+ )
+ }
}
- }
- AmountScopeField(
- modifier = Modifier.fillMaxWidth(),
- amount = amount.copy(amount = amount.amount.withSpec(selectedSpec)),
- scopes = scopes,
- readOnly = false,
- onAmountChanged = { amount = it },
- label = { Text(stringResource(R.string.amount_send)) },
- isError = amount.amount.isZero() || feeResult is InsufficientBalance,
- supportingText = {
- when (val res = feeResult) {
- is Success -> if (res.amountEffective > res.amountRaw) {
- val fee = res.amountEffective - res.amountRaw
- Text(
- text = stringResource(
- id = R.string.payment_fee,
- fee.withSpec(selectedSpec)
- ),
- softWrap = false,
- color = MaterialTheme.colorScheme.error,
- )
- }
+ AmountScopeField(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ amount = amount.copy(amount = amount.amount.withSpec(selectedSpec)),
+ scopes = scopes,
+ readOnly = false,
+ onAmountChanged = { amount = it },
+ label = { Text(stringResource(R.string.amount_send)) },
+ isError = amount.amount.isZero() || feeResult is InsufficientBalance,
+ supportingText = {
+ when (val res = feeResult) {
+ is Success -> if (res.amountEffective > res.amountRaw) {
+ val fee = res.amountEffective - res.amountRaw
+ Text(
+ text = stringResource(
+ id = R.string.payment_fee,
+ fee.withSpec(selectedSpec)
+ ),
+ softWrap = false,
+ color = MaterialTheme.colorScheme.error,
+ )
+ }
- is InsufficientBalance -> {
- Text(stringResource(res.causeHint?.stringResId()
- ?: R.string.payment_balance_insufficient))
- }
+ is InsufficientBalance -> {
+ Text(
+ stringResource(
+ res.causeHint?.stringResId()
+ ?: R.string.payment_balance_insufficient
+ )
+ )
+ }
- else -> {}
+ else -> {}
+ }
}
- }
- )
+ )
- var subject by rememberSaveable { mutableStateOf("") }
- OutlinedTextField(
- modifier = Modifier
- .fillMaxWidth(),
- singleLine = true,
- value = subject,
- onValueChange = { input ->
- if (input.length <= MAX_LENGTH_SUBJECT)
- subject = input.replace('\n', ' ')
- },
- isError = subject.isBlank(),
- label = {
- Text(
- stringResource(R.string.send_peer_purpose),
- color = if (subject.isBlank()) {
- MaterialTheme.colorScheme.error
- } else Color.Unspecified,
- )
- },
- supportingText = {
- Text(stringResource(R.string.char_count, subject.length, MAX_LENGTH_SUBJECT))
- },
- )
+ OutlinedTextField(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ singleLine = true,
+ value = subject,
+ onValueChange = { input ->
+ if (input.length <= MAX_LENGTH_SUBJECT)
+ subject = input.replace('\n', ' ')
+ },
+ isError = subject.isBlank(),
+ label = {
+ Text(
+ stringResource(R.string.send_peer_purpose),
+ color = if (subject.isBlank()) {
+ MaterialTheme.colorScheme.error
+ } else Color.Unspecified,
+ )
+ },
+ supportingText = {
+ Text(stringResource(R.string.char_count, subject.length, MAX_LENGTH_SUBJECT))
+ },
+ )
- Text(
- modifier = Modifier.padding(top = 8.dp, start = 16.dp, end = 16.dp),
- text = stringResource(R.string.send_peer_expiration_period),
- style = MaterialTheme.typography.bodyMedium,
- )
+ Text(
+ modifier = Modifier.padding(top = 8.dp, start = 16.dp, end = 16.dp),
+ text = stringResource(R.string.send_peer_expiration_period),
+ style = MaterialTheme.typography.bodyMedium,
+ )
- var option by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY) }
- var hours by rememberSaveable { mutableLongStateOf(DEFAULT_EXPIRY.hours) }
- ExpirationComposable(
- modifier = Modifier.padding(vertical = 8.dp),
- option = option,
- hours = hours,
- onOptionChange = { option = it }
- ) { hours = it }
+ ExpirationComposable(
+ modifier = Modifier.padding(
+ vertical = 8.dp,
+ horizontal = 16.dp,
+ ),
+ option = option,
+ hours = hours,
+ onOptionChange = { option = it }
+ ) { hours = it }
- AnimatedVisibility(feeResult is Success) {
- (feeResult as? Success)?.let {
- Column(
- modifier = Modifier.padding(bottom = 8.dp),
- horizontalAlignment = CenterHorizontally,
- ) {
- TransactionInfoComposable(
- label = stringResource(id = R.string.withdraw_exchange),
- info = cleanExchange(it.exchangeBaseUrl),
- )
+ AnimatedVisibility(feeResult is Success) {
+ (feeResult as? Success)?.let {
+ Column(
+ modifier = Modifier.padding(bottom = 8.dp),
+ horizontalAlignment = CenterHorizontally,
+ ) {
+ TransactionInfoComposable(
+ label = stringResource(id = R.string.withdraw_exchange),
+ info = cleanExchange(it.exchangeBaseUrl),
+ )
+ }
}
}
- }
- Button(
- enabled = feeResult is Success && subject.isNotBlank(),
- onClick = { onSend(amount, subject, hours) },
- ) {
- Text(text = stringResource(R.string.send_peer_create_button))
+ BottomInsetsSpacer()
}
- BottomInsetsSpacer()
+ BottomButtonBox(Modifier.fillMaxWidth()) {
+ Button(
+ modifier = Modifier.systemBarsPaddingBottom(),
+ enabled = feeResult is Success && subject.isNotBlank(),
+ onClick = { onSend(amount, subject, hours) },
+ ) {
+ Text(text = stringResource(R.string.send_peer_create_button))
+ }
+ }
}
}