Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-18 14:05:03 +05:00
parent 328ced0518
commit 31f3394afd
8 changed files with 113 additions and 25 deletions

View file

@ -117,6 +117,7 @@ internal class SendStateFactory(
recipientState = state.recipientState
?: recipientStateConverter.convert(SendRecipientStateConverter.Data(destinationAddress, memo)),
feeState = state.feeState ?: feeStateConverter.convert(Unit),
sendState = confirmStateConverter.convert(Unit),
isEditingDisabled = true,
cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name,
)

View file

@ -57,6 +57,7 @@ internal class SendNotificationFactory(
val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO
buildList {
// errors
addFeeUnreachableNotification(feeState.feeSelectorState)
addExceedBalanceNotification(feeAmount, amountValue)
addExceedsBalanceNotification(feeState.fee)
addMinimumAmountErrorNotification(feeAmount, amountValue)
@ -84,6 +85,12 @@ internal class SendNotificationFactory(
)
}
private fun MutableList<SendNotification>.addFeeUnreachableNotification(feeSelectorState: FeeSelectorState) {
if (feeSelectorState is FeeSelectorState.Error) {
add(SendNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload))
}
}
private fun MutableList<SendNotification>.addExceedBalanceNotification(
feeAmount: BigDecimal,
receivedAmount: BigDecimal,

View file

@ -15,6 +15,8 @@ internal sealed class FeeSelectorState {
val customValues: ImmutableList<SendTextField.CustomFee> = persistentListOf(),
) : FeeSelectorState()
data object Loading : FeeSelectorState()
data object Error : FeeSelectorState()
}

View file

@ -47,6 +47,11 @@ internal class FeeStateFactory(
return state.copy(
amountState = state.amountState?.copy(isFeeLoading = true),
feeState = feeState.copy(
feeSelectorState = if (feeState.feeSelectorState is FeeSelectorState.Content) {
feeState.feeSelectorState
} else {
FeeSelectorState.Loading
},
notifications = persistentListOf(),
isPrimaryButtonEnabled = false,
),

View file

@ -14,7 +14,7 @@ internal class SendFeeStateConverter(
override fun convert(value: Unit): SendStates.FeeState {
return SendStates.FeeState(
feeSelectorState = FeeSelectorState.Error,
feeSelectorState = FeeSelectorState.Loading,
fee = null,
notifications = persistentListOf(),
rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate,

View file

@ -1,21 +1,22 @@
package com.tangem.features.send.impl.presentation.ui.send
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.rows.SelectorRowItem
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
@ -26,20 +27,12 @@ import com.tangem.features.send.impl.presentation.utils.getFiatReference
@Composable
internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) {
val fee = feeState.fee ?: return
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
val (title, icon) = when (feeSelectorState.selectedFee) {
FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24
FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24
FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24
}
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.clickable(enabled = !isSuccess) { onClick() }
.clickable(enabled = !isSuccess && feeState.fee != null) { onClick() }
.padding(TangemTheme.dimens.spacing12),
) {
Text(
@ -47,17 +40,71 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.secondary,
)
SelectorRowItem(
titleRes = title,
iconRes = icon,
preDot = getCryptoReference(fee.amount, feeState.isFeeApproximate),
postDot = getFiatReference(fee.amount, feeState.rate, feeState.appCurrency),
ellipsizeOffset = fee.amount.currencySymbol.length,
isSelected = true,
showDivider = false,
paddingValues = PaddingValues(),
Box(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
)
) {
val feeSelectorState = feeState.feeSelectorState
val feeAmount = feeState.fee?.amount
val (title, icon) = if (feeSelectorState is FeeSelectorState.Content) {
when (feeSelectorState.selectedFee) {
FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24
FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24
FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24
}
} else {
R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
}
SelectorRowItem(
titleRes = title,
iconRes = icon,
preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate),
postDot = feeAmount?.let { getFiatReference(it, feeState.rate, feeState.appCurrency) },
ellipsizeOffset = feeAmount?.currencySymbol?.length,
isSelected = true,
showDivider = false,
paddingValues = PaddingValues(),
)
FeeLoading(feeSelectorState)
FeeError(feeSelectorState)
}
}
}
@Composable
private fun BoxScope.FeeLoading(feeSelectorState: FeeSelectorState) {
AnimatedContent(
targetState = feeSelectorState,
label = "Fee Loading State Change",
modifier = Modifier.align(Alignment.CenterEnd),
) {
if (it == FeeSelectorState.Loading) {
RectangleShimmer(
radius = TangemTheme.dimens.radius3,
modifier = Modifier.size(
height = TangemTheme.dimens.size12,
width = TangemTheme.dimens.size90,
),
)
}
}
}
@Composable
private fun BoxScope.FeeError(feeSelectorState: FeeSelectorState) {
AnimatedContent(
targetState = feeSelectorState,
label = "Fee Error State Change",
modifier = Modifier.align(Alignment.CenterEnd),
) {
if (it == FeeSelectorState.Error) {
Text(
text = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.body2,
)
}
}
}

View file

@ -4,6 +4,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -40,6 +41,7 @@ internal fun RecipientBlock(
.padding(TangemTheme.dimens.spacing12),
) {
AddressBlock(recipientState.addressTextField)
MemoBlock(recipientState.memoTextField)
}
}
@ -70,6 +72,28 @@ private fun AddressBlock(address: SendTextField.RecipientAddress) {
}
}
@Composable
private fun MemoBlock(memo: SendTextField.RecipientMemo?) {
val showMemo = memo != null && memo.value.isNotBlank()
if (showMemo) {
HorizontalDivider(
color = TangemTheme.colors.icon.inactive,
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
)
Text(
text = memo?.label?.resolveReference().orEmpty(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.secondary,
)
Text(
text = memo?.value.orEmpty(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
)
}
}
// region Preview
@Preview
@Composable

View file

@ -359,8 +359,9 @@ internal class SendViewModel @Inject constructor(
stateRouter.showSend()
}
transactionId != null && amount != null && destinationAddress != null -> {
loadFee()
uiState = stateFactory.getReadyState(amount, destinationAddress, memo)
stateRouter.showFee()
stateRouter.showSend()
updateNotifications()
}
else -> {
@ -702,6 +703,7 @@ internal class SendViewModel @Inject constructor(
if (result == null) {
onFeeLoadFailed(isShowStatus, isToNextState)
}
updateNotifications()
updateFeeNotifications()
}.saveIn(feeJobHolder)
.invokeOnCompletion {