secant-android-wallet/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt

784 lines
28 KiB
Kotlin
Raw Normal View History

@file:Suppress("TooManyFunctions")
package co.electriccoin.zcash.ui.screen.send.view
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.ExperimentalFoundationApi
2024-04-23 04:29:12 -07:00
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
2024-04-23 04:29:12 -07:00
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
2024-04-23 04:29:12 -07:00
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
2024-04-23 04:29:12 -07:00
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
2023-02-17 03:05:23 -08:00
import cash.z.ecc.android.sdk.model.Memo
import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.Zatoshi
2023-02-17 03:05:23 -08:00
import cash.z.ecc.android.sdk.model.ZecSend
import cash.z.ecc.android.sdk.model.ZecSendExt
import cash.z.ecc.android.sdk.type.AddressType
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import cash.z.ecc.sdk.type.ZcashCurrency
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.compose.BalanceState
import co.electriccoin.zcash.ui.common.compose.BalanceWidget
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.model.canSpend
import co.electriccoin.zcash.ui.common.model.spendableBalance
import co.electriccoin.zcash.ui.common.test.CommonTag
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
import co.electriccoin.zcash.ui.design.component.AppAlertDialog
import co.electriccoin.zcash.ui.design.component.Body
import co.electriccoin.zcash.ui.design.component.BodySmall
import co.electriccoin.zcash.ui.design.component.FormTextField
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.Small
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.BalanceStateFixture
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.send.SendTag
import co.electriccoin.zcash.ui.screen.send.model.AmountState
import co.electriccoin.zcash.ui.screen.send.model.MemoState
import co.electriccoin.zcash.ui.screen.send.model.RecipientAddressState
import co.electriccoin.zcash.ui.screen.send.model.SendStage
2024-04-23 04:29:12 -07:00
import kotlinx.coroutines.launch
import java.util.Locale
@Composable
@Preview("SendForm")
private fun PreviewSendForm() {
ZcashTheme(forceDarkMode = false) {
GradientSurface {
Send(
sendStage = SendStage.Form,
onCreateZecSend = {},
focusManager = LocalFocusManager.current,
onBack = {},
onSettings = {},
onQrScannerOpen = {},
goBalances = {},
hasCameraFeature = true,
recipientAddressState = RecipientAddressState("invalid_address", AddressType.Invalid()),
onRecipientAddressChange = {},
setAmountState = {},
amountState = AmountState.Valid(ZatoshiFixture.ZATOSHI_LONG.toString(), ZatoshiFixture.new()),
setMemoState = {},
memoState = MemoState.new("Test message"),
walletRestoringState = WalletRestoringState.NONE,
walletSnapshot = WalletSnapshotFixture.new(),
balanceState = BalanceStateFixture.new()
)
}
}
}
// TODO [#1260]: Cover Send screens UI with tests
// TODO [#1260]: https://github.com/Electric-Coin-Company/zashi-android/issues/1260
@Suppress("LongParameterList")
@Composable
fun Send(
balanceState: BalanceState,
sendStage: SendStage,
onCreateZecSend: (ZecSend) -> Unit,
focusManager: FocusManager,
onBack: () -> Unit,
onSettings: () -> Unit,
onQrScannerOpen: () -> Unit,
goBalances: () -> Unit,
hasCameraFeature: Boolean,
recipientAddressState: RecipientAddressState,
onRecipientAddressChange: (String) -> Unit,
setAmountState: (AmountState) -> Unit,
amountState: AmountState,
setMemoState: (MemoState) -> Unit,
memoState: MemoState,
walletRestoringState: WalletRestoringState,
walletSnapshot: WalletSnapshot,
) {
Scaffold(topBar = {
SendTopAppBar(
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
onSettings = onSettings
)
}) { paddingValues ->
SendMainContent(
balanceState = balanceState,
walletSnapshot = walletSnapshot,
onBack = onBack,
focusManager = focusManager,
sendStage = sendStage,
onCreateZecSend = onCreateZecSend,
recipientAddressState = recipientAddressState,
onRecipientAddressChange = onRecipientAddressChange,
amountState = amountState,
setAmountState = setAmountState,
memoState = memoState,
setMemoState = setMemoState,
onQrScannerOpen = onQrScannerOpen,
goBalances = goBalances,
hasCameraFeature = hasCameraFeature,
modifier =
Modifier
.padding(
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingHuge,
start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
end = ZcashTheme.dimens.screenHorizontalSpacingRegular
)
)
}
}
@Composable
private fun SendTopAppBar(
onSettings: () -> Unit,
showRestoring: Boolean
) {
SmallTopAppBar(
restoringLabel =
if (showRestoring) {
stringResource(id = R.string.restoring_wallet_label)
} else {
null
},
titleText = stringResource(id = R.string.send_stage_send_title),
hamburgerMenuActions = {
IconButton(
onClick = onSettings,
modifier = Modifier.testTag(CommonTag.SETTINGS_TOP_BAR_BUTTON)
) {
Icon(
painter = painterResource(id = co.electriccoin.zcash.ui.design.R.drawable.hamburger_menu_icon),
contentDescription = stringResource(id = R.string.settings_menu_content_description)
)
}
},
)
}
@Suppress("LongParameterList")
@Composable
private fun SendMainContent(
balanceState: BalanceState,
walletSnapshot: WalletSnapshot,
focusManager: FocusManager,
onBack: () -> Unit,
goBalances: () -> Unit,
onCreateZecSend: (ZecSend) -> Unit,
sendStage: SendStage,
onQrScannerOpen: () -> Unit,
recipientAddressState: RecipientAddressState,
onRecipientAddressChange: (String) -> Unit,
hasCameraFeature: Boolean,
amountState: AmountState,
setAmountState: (AmountState) -> Unit,
memoState: MemoState,
setMemoState: (MemoState) -> Unit,
modifier: Modifier = Modifier,
) {
// For now, we merge [SendStage.Form] and [SendStage.Proposing] into one stage. We could eventually display a
// loader if calling the Proposal API takes longer than expected
SendForm(
balanceState = balanceState,
walletSnapshot = walletSnapshot,
recipientAddressState = recipientAddressState,
onRecipientAddressChange = onRecipientAddressChange,
amountState = amountState,
setAmountState = setAmountState,
memoState = memoState,
setMemoState = setMemoState,
onCreateZecSend = onCreateZecSend,
focusManager = focusManager,
onQrScannerOpen = onQrScannerOpen,
goBalances = goBalances,
hasCameraFeature = hasCameraFeature,
modifier = modifier
)
if (sendStage is SendStage.SendFailure) {
SendFailure(
reason = sendStage.error,
onDone = onBack
)
}
}
const val DEFAULT_LESS_THAN_FEE = 100_000L
// TODO [#217]: Need to handle changing of Locale after user input, but before submitting the button.
// TODO [#217]: https://github.com/Electric-Coin-Company/zashi-android/issues/217
// TODO [#1257]: Send.Form TextFields not persisted on a configuration change when the underlying ViewPager is on the
// Balances page
// TODO [#1257]: https://github.com/Electric-Coin-Company/zashi-android/issues/1257
@Suppress("LongMethod", "LongParameterList")
@Composable
private fun SendForm(
balanceState: BalanceState,
walletSnapshot: WalletSnapshot,
focusManager: FocusManager,
recipientAddressState: RecipientAddressState,
onRecipientAddressChange: (String) -> Unit,
amountState: AmountState,
setAmountState: (AmountState) -> Unit,
memoState: MemoState,
setMemoState: (MemoState) -> Unit,
onCreateZecSend: (ZecSend) -> Unit,
onQrScannerOpen: () -> Unit,
goBalances: () -> Unit,
hasCameraFeature: Boolean,
modifier: Modifier = Modifier,
) {
// TODO [#1171]: Remove default MonetarySeparators locale
// TODO [#1171]: https://github.com/Electric-Coin-Company/zashi-android/issues/1171
val monetarySeparators = MonetarySeparators.current(Locale.US)
val scrollState = rememberScrollState()
2024-04-23 04:29:12 -07:00
val (scrollToFeePixels, setScrollToFeePixels) = rememberSaveable { mutableIntStateOf(0) }
Column(
modifier =
Modifier
.fillMaxHeight()
.verticalScroll(scrollState)
.then(modifier),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
BalanceWidget(
balanceState = balanceState,
isReferenceToBalances = true,
onReferenceClick = goBalances
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
// TODO [#1256]: Consider Send.Form TextFields scrolling
// TODO [#1256]: https://github.com/Electric-Coin-Company/zashi-android/issues/1256
SendFormAddressTextField(
focusManager = focusManager,
hasCameraFeature = hasCameraFeature,
onQrScannerOpen = onQrScannerOpen,
recipientAddressState = recipientAddressState,
setRecipientAddress = onRecipientAddressChange
)
Spacer(Modifier.size(ZcashTheme.dimens.spacingDefault))
SendFormAmountTextField(
amountSate = amountState,
focusManager = focusManager,
imeAction =
if (recipientAddressState.type == AddressType.Transparent) {
ImeAction.Done
} else {
ImeAction.Next
},
isTransparentRecipient = recipientAddressState.type?.let { it == AddressType.Transparent } ?: false,
monetarySeparators = monetarySeparators,
setAmountState = setAmountState,
walletSnapshot = walletSnapshot,
)
Spacer(Modifier.size(ZcashTheme.dimens.spacingDefault))
SendFormMemoTextField(
memoState = memoState,
setMemoState = setMemoState,
focusManager = focusManager,
isMemoFieldAvailable = (
recipientAddressState.address.isEmpty() ||
recipientAddressState.type is AddressType.Invalid ||
(
recipientAddressState.type is AddressType.Valid &&
recipientAddressState.type !is AddressType.Transparent
)
),
2024-04-23 04:29:12 -07:00
scrollState = scrollState,
scrollTo = scrollToFeePixels
)
Spacer(
modifier =
Modifier
.fillMaxHeight()
.weight(MINIMAL_WEIGHT)
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
2024-04-23 04:29:12 -07:00
SendButton(
amountState = amountState,
memoState = memoState,
monetarySeparators = monetarySeparators,
onCreateZecSend = onCreateZecSend,
recipientAddressState = recipientAddressState,
walletSnapshot = walletSnapshot,
setScrollToFeePixels = setScrollToFeePixels
)
}
}
@Composable
@Suppress("LongParameterList")
fun SendButton(
amountState: AmountState,
memoState: MemoState,
monetarySeparators: MonetarySeparators,
onCreateZecSend: (ZecSend) -> Unit,
recipientAddressState: RecipientAddressState,
2024-04-23 04:29:12 -07:00
setScrollToFeePixels: (Int) -> Unit,
walletSnapshot: WalletSnapshot,
) {
val context = LocalContext.current
// Common conditions continuously checked for validity
val sendButtonEnabled =
recipientAddressState.type !is AddressType.Invalid &&
recipientAddressState.address.isNotEmpty() &&
amountState is AmountState.Valid &&
amountState.value.isNotBlank() &&
walletSnapshot.canSpend(amountState.zatoshi) &&
// A valid memo is necessary only for non-transparent recipient
(recipientAddressState.type == AddressType.Transparent || memoState is MemoState.Correct)
Column(
modifier = Modifier.padding(horizontal = ZcashTheme.dimens.screenHorizontalSpacingRegular),
horizontalAlignment = Alignment.CenterHorizontally
) {
PrimaryButton(
onClick = {
// SDK side validations
val zecSendValidation =
ZecSendExt.new(
context = context,
destinationString = recipientAddressState.address,
zecString = amountState.value,
// Take memo for a valid non-transparent receiver only
memoString =
if (recipientAddressState.type == AddressType.Transparent) {
""
} else {
memoState.text
},
monetarySeparators = monetarySeparators
)
when (zecSendValidation) {
is ZecSendExt.ZecSendValidation.Valid -> onCreateZecSend(zecSendValidation.zecSend)
is ZecSendExt.ZecSendValidation.Invalid -> {
// We do not expect this validation to fail, so logging is enough here
// An error popup could be reasonable here as well
Twig.warn { "Send failed with: ${zecSendValidation.validationErrors}" }
}
}
},
text = stringResource(id = R.string.send_create),
enabled = sendButtonEnabled,
modifier =
Modifier
.testTag(SendTag.SEND_FORM_BUTTON)
.fillMaxWidth()
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
BodySmall(
text =
stringResource(
id = R.string.send_fee,
// TODO [#1047]: Representing Zatoshi amount
// TODO [#1047]: https://github.com/Electric-Coin-Company/zashi-android/issues/1047
Zatoshi(DEFAULT_LESS_THAN_FEE).convertZatoshiToZecString(maxDecimals = 3)
),
2024-04-23 04:29:12 -07:00
textFontWeight = FontWeight.SemiBold,
modifier =
Modifier.onGloballyPositioned {
setScrollToFeePixels(it.positionInRoot().y.toInt())
}
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Suppress("LongMethod")
@Composable
fun SendFormAddressTextField(
focusManager: FocusManager,
hasCameraFeature: Boolean,
onQrScannerOpen: () -> Unit,
recipientAddressState: RecipientAddressState,
setRecipientAddress: (String) -> Unit,
) {
val bringIntoViewRequester = remember { BringIntoViewRequester() }
Column(
modifier =
Modifier
// Animate error show/hide
.animateContentSize()
// Scroll TextField above ime keyboard
.bringIntoViewRequester(bringIntoViewRequester)
) {
Small(text = stringResource(id = R.string.send_address_label))
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
val recipientAddressValue = recipientAddressState.address
val recipientAddressError =
if (
recipientAddressValue.isNotEmpty() &&
recipientAddressState.type is AddressType.Invalid
) {
stringResource(id = R.string.send_address_invalid)
} else {
null
}
FormTextField(
value = recipientAddressValue,
onValueChange = {
setRecipientAddress(it)
},
modifier =
Modifier
.fillMaxWidth(),
error = recipientAddressError,
placeholder = {
Text(
text = stringResource(id = R.string.send_address_hint),
style = ZcashTheme.extendedTypography.textFieldHint,
color = ZcashTheme.colors.textFieldHint
)
},
trailingIcon =
if (hasCameraFeature) {
{
IconButton(
onClick = onQrScannerOpen,
content = {
Icon(
painter = painterResource(id = R.drawable.qr_code_icon),
contentDescription = stringResource(R.string.send_scan_content_description)
)
}
)
}
} else {
null
},
keyboardOptions =
KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next
),
keyboardActions =
KeyboardActions(
onNext = {
focusManager.moveFocus(FocusDirection.Down)
}
),
bringIntoViewRequester = bringIntoViewRequester,
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Suppress("LongParameterList", "LongMethod")
@Composable
fun SendFormAmountTextField(
amountSate: AmountState,
focusManager: FocusManager,
imeAction: ImeAction,
isTransparentRecipient: Boolean,
monetarySeparators: MonetarySeparators,
setAmountState: (AmountState) -> Unit,
walletSnapshot: WalletSnapshot,
) {
val context = LocalContext.current
val zcashCurrency = ZcashCurrency.getLocalizedName(context)
val amountError =
when (amountSate) {
is AmountState.Invalid -> {
if (amountSate.value.isEmpty()) {
null
} else {
stringResource(id = R.string.send_amount_invalid)
}
}
is AmountState.Valid -> {
if (walletSnapshot.spendableBalance() < amountSate.zatoshi) {
stringResource(id = R.string.send_amount_insufficient_balance)
} else {
null
}
}
}
val bringIntoViewRequester = remember { BringIntoViewRequester() }
Column(
modifier =
Modifier
// Animate error show/hide
.animateContentSize()
// Scroll TextField above ime keyboard
.bringIntoViewRequester(bringIntoViewRequester)
) {
Small(text = stringResource(id = R.string.send_amount_label))
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
FormTextField(
value = amountSate.value,
onValueChange = { newValue ->
setAmountState(
AmountState.new(
context = context,
value = newValue,
monetarySeparators = monetarySeparators,
isTransparentRecipient = isTransparentRecipient
)
)
},
modifier = Modifier.fillMaxWidth(),
error = amountError,
placeholder = {
Text(
text =
stringResource(
id = R.string.send_amount_hint,
zcashCurrency
),
style = ZcashTheme.extendedTypography.textFieldHint,
color = ZcashTheme.colors.textFieldHint
)
},
keyboardOptions =
KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = imeAction
),
keyboardActions =
KeyboardActions(
2024-04-23 04:29:12 -07:00
onDone = {
focusManager.clearFocus(true)
},
onNext = {
2024-04-23 04:29:12 -07:00
focusManager.moveFocus(FocusDirection.Down)
}
),
bringIntoViewRequester = bringIntoViewRequester,
)
}
}
// TODO [#1259]: Send.Form screen Memo field stroke bubble style
// TODO [#1259]: https://github.com/Electric-Coin-Company/zashi-android/issues/1259
@OptIn(ExperimentalFoundationApi::class)
2024-04-23 04:29:12 -07:00
@Suppress("LongMethod", "LongParameterList")
@Composable
fun SendFormMemoTextField(
focusManager: FocusManager,
isMemoFieldAvailable: Boolean,
memoState: MemoState,
setMemoState: (MemoState) -> Unit,
2024-04-23 04:29:12 -07:00
scrollState: ScrollState,
scrollTo: Int
) {
2024-04-23 04:29:12 -07:00
val scope = rememberCoroutineScope()
val bringIntoViewRequester = remember { BringIntoViewRequester() }
Column(
modifier =
Modifier
// Animate error show/hide
.animateContentSize()
// Scroll TextField above ime keyboard
.bringIntoViewRequester(bringIntoViewRequester)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.send_paper_plane),
contentDescription = null,
tint =
if (isMemoFieldAvailable) {
ZcashTheme.colors.textCommon
} else {
ZcashTheme.colors.textDisabled
}
)
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingSmall))
Small(
text = stringResource(id = R.string.send_memo_label),
color =
if (isMemoFieldAvailable) {
ZcashTheme.colors.textCommon
} else {
ZcashTheme.colors.textDisabled
}
)
}
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
FormTextField(
enabled = isMemoFieldAvailable,
value =
if (isMemoFieldAvailable) {
memoState.text
} else {
""
},
onValueChange = {
setMemoState(MemoState.new(it))
},
bringIntoViewRequester = bringIntoViewRequester,
keyboardOptions =
KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Done
),
keyboardActions =
KeyboardActions(
2024-04-23 04:29:12 -07:00
onDone = {
focusManager.clearFocus(true)
2024-04-23 04:29:12 -07:00
// Scroll down to make sure the Send button is visible on small screens
if (scrollTo > 0) {
scope.launch {
scrollState.animateScrollTo(scrollTo)
}
}
}
),
placeholder = {
Text(
text = stringResource(id = R.string.send_memo_hint),
style = ZcashTheme.extendedTypography.textFieldHint,
color = ZcashTheme.colors.textFieldHint
)
},
modifier = Modifier.fillMaxWidth(),
minHeight = ZcashTheme.dimens.textFieldMemoPanelDefaultHeight,
)
if (isMemoFieldAvailable) {
Body(
text =
stringResource(
id = R.string.send_memo_bytes_counter,
Memo.MAX_MEMO_LENGTH_BYTES - memoState.byteSize,
Memo.MAX_MEMO_LENGTH_BYTES
),
textFontWeight = FontWeight.Bold,
color =
if (memoState is MemoState.Correct) {
ZcashTheme.colors.textFieldHint
} else {
ZcashTheme.colors.textFieldError
},
textAlign = TextAlign.End,
modifier =
Modifier
.fillMaxWidth()
.padding(top = ZcashTheme.dimens.spacingTiny)
)
}
}
}
@Composable
@Preview("SendFailure")
private fun PreviewSendFailure() {
ZcashTheme(forceDarkMode = false) {
GradientSurface {
SendFailure(
onDone = {},
reason = "Insufficient balance"
)
}
}
}
@Composable
private fun SendFailure(
onDone: () -> Unit,
reason: String,
modifier: Modifier = Modifier
) {
// TODO [#1276]: Once we ensure that the reason contains a localized message, we can leverage it for the UI prompt
// TODO [#1276]: Consider adding support for a specific exception in AppAlertDialog
// TODO [#1276]: https://github.com/Electric-Coin-Company/zashi-android/issues/1276
AppAlertDialog(
title = stringResource(id = R.string.send_dialog_error_title),
text = {
Column(
Modifier.verticalScroll(rememberScrollState())
) {
Text(text = stringResource(id = R.string.send_dialog_error_text))
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
Text(
text = reason,
fontStyle = FontStyle.Italic
)
}
},
confirmButtonText = stringResource(id = R.string.send_dialog_error_btn),
onConfirmButtonClick = onDone,
modifier = modifier
)
}