Updated on 2026-08-14

This commit is contained in:
Tangem 2024-01-31 15:33:05 +03:00
commit ef30b51f10
10 changed files with 147 additions and 67 deletions

View file

@ -70,7 +70,7 @@
<string name="common_biometric_authentication">биометрическую аутентификацию</string>
<string name="common_biometrics">биометрией</string>
<string name="common_buy">Купить</string>
<string name="common_buy_currency">Купить %1$s</string>
<string name="common_buy_currency">Перейти на %1$s</string>
<string name="common_camera_denied_alert_message">Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности.</string>
<string name="common_cancel">Отмена</string>
<string name="common_close">Закрыть</string>
@ -478,8 +478,6 @@
<string name="send_network_fee_warning_title">Покрытие сетевой комиссии</string>
<string name="send_notification_exceed_balance_text">Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса</string>
<string name="send_notification_exceed_balance_title">Недостаточно средств</string>
<string name="send_notification_exceed_fee_text">Размер комиссии превышает баланс сети. Для продолжения необходимо пополнить баланс сети.</string>
<string name="send_notification_exceed_fee_title">Комиссия превышает баланс</string>
<string name="send_notification_high_fee_title">Увеличение комиссии</string>
<string name="send_notification_high_fee_text">Комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить 0.01.</string>
<string name="send_notification_fee_too_high_accept">Оставить %s XTZ</string>
@ -494,7 +492,6 @@
<string name="send_notification_transaction_delay_text">Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции</string>
<string name="send_notification_transaction_delay_title">Возможны задержки по транзакции</string>
<string name="send_optional_field">Необязательное</string>
<string name="send_qrcode_scan_amount_alert_title">QR код содержит информацию о сумме отправки равной %s</string>
<string name="send_recent_transactions">Последние</string>
<string name="send_recipient">Получатель</string>
<string name="send_recipient_address_footer">Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов</string>

View file

@ -68,7 +68,7 @@
<string name="common_biometric_authentication">biometric authentication</string>
<string name="common_biometrics">biometrics</string>
<string name="common_buy">Buy</string>
<string name="common_buy_currency">Buy %1$s</string>
<string name="common_buy_currency">Go to %1$s</string>
<string name="common_camera_denied_alert_message">You have not given access to your camera, please adjust your privacy settings</string>
<string name="common_cancel">Cancel</string>
<string name="common_close">Close</string>
@ -483,8 +483,6 @@
<string name="send_network_fee_warning_title">Network fee coverage</string>
<string name="send_notification_exceed_balance_text">Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance</string>
<string name="send_notification_exceed_balance_title">Total exceeds balance</string>
<string name="send_notification_exceed_fee_text">The commission fee exceeds the network balance. To continue, it is necessary to replenish the network balance.</string>
<string name="send_notification_exceed_fee_title">Fee exceeds balance</string>
<string name="send_notification_high_fee_title">Fee is increased</string>
<string name="send_notification_high_fee_text">The fee for transferring the entire balance is higher. To reduce the commission, you can leave 0.01.</string>
<string name="send_notification_fee_too_high_accept">Reduce by %s XTZ</string>
@ -503,11 +501,6 @@
<string name="send_notification_existential_deposit_title">Existential deposit</string>
<string name="send_notification_existential_deposit_text">The account will be wiped from the blockchain if a balance goes below the existential deposit. Please ensure that the remaining balance after sending will not be less than %s.</string>
<string name="send_optional_field">Optional</string>
<string name="send_qrcode_alert_change">Change</string>
<string name="send_qrcode_alert_decline">Decline</string>
<string name="send_qrcode_scan_address_success">Recipients address scanned</string>
<string name="send_qrcode_scan_amount_alert_text">Change the entered amount?</string>
<string name="send_qrcode_scan_amount_alert_title">QR code contains information about the sending amount equal to %s</string>
<string name="send_qrcode_scan_info">Please align your QR code with the square to scan it. Ensure you scan %s network address.</string>
<string name="send_recent_transactions">Recent</string>
<string name="send_recipient">Recipient</string>

View file

@ -75,7 +75,12 @@ internal class SendStateFactory(
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
)
}
private val feeStateConverter by lazy { SendFeeStateConverter() }
private val feeStateConverter by lazy {
SendFeeStateConverter(
appCurrencyProvider = appCurrencyProvider,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
)
}
private val recipientListStateConverter by lazy {
SendRecipientListConverter(

View file

@ -7,6 +7,7 @@ import com.tangem.blockchain.common.transaction.Fee
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
import com.tangem.features.send.impl.presentation.state.amount.SendAmountSegmentedButtonsConfig
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
@ -74,6 +75,8 @@ internal sealed class SendStates {
val fee: Fee?,
val receivedAmountValue: BigDecimal,
val receivedAmount: String,
val rate: BigDecimal?,
val appCurrency: AppCurrency,
val notifications: ImmutableList<SendFeeNotification>,
) : SendStates()

View file

@ -7,6 +7,7 @@ import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.SendUiStateType
@ -118,25 +119,37 @@ internal class FeeNotificationFactory(
add(
SendFeeNotification.Error.ExceedsBalance(
warning.coinCurrency.networkIconResId,
) {
clickIntents.onTokenDetailsClick(
userWalletProvider().walletId,
warning.coinCurrency,
)
},
networkName = warning.coinCurrency.name,
currencyName = cryptoCurrencyStatus.currency.name,
feeName = warning.coinCurrency.name,
feeSymbol = warning.coinCurrency.symbol,
onClick = {
clickIntents.onTokenDetailsClick(
userWalletId = userWalletId,
currency = warning.coinCurrency,
)
},
),
)
}
is CryptoCurrencyWarning.CustomTokenNotEnoughForFee -> {
val currency = warning.feeCurrency ?: warning.currency
val currency = warning.feeCurrency
add(
SendFeeNotification.Error.ExceedsBalance(
currency.networkIconResId,
) {
clickIntents.onTokenDetailsClick(
userWalletId,
currency,
)
},
networkIconId = currency?.networkIconResId ?: R.drawable.ic_alert_24,
currencyName = warning.currency.name,
feeName = warning.feeCurrencyName,
feeSymbol = warning.feeCurrencySymbol,
networkName = warning.networkName,
onClick = currency?.let {
{
clickIntents.onTokenDetailsClick(
userWalletId,
currency,
)
}
},
),
)
}
else -> Unit

View file

@ -62,7 +62,7 @@ sealed class SendFeeNotification(val config: NotificationConfig) {
val title: TextReference,
val subtitle: TextReference,
val iconResId: Int,
val buttonsState: NotificationConfig.ButtonsState,
val buttonsState: NotificationConfig.ButtonsState? = null,
) : SendFeeNotification(
config = NotificationConfig(
title = title,
@ -73,15 +73,27 @@ sealed class SendFeeNotification(val config: NotificationConfig) {
) {
data class ExceedsBalance(
val networkIconId: Int,
val onClick: () -> Unit,
val currencyName: String,
val feeName: String,
val feeSymbol: String,
val networkName: String,
val onClick: (() -> Unit)? = null,
) : Error(
title = resourceReference(id = R.string.send_notification_exceed_fee_title),
subtitle = resourceReference(id = R.string.send_notification_exceed_fee_text),
iconResId = networkIconId,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.common_go_to_provider),
onClick = onClick,
title = resourceReference(
id = R.string.warning_send_blocked_funds_for_fee_title,
wrappedList(feeName),
),
subtitle = resourceReference(
id = R.string.warning_send_blocked_funds_for_fee_message,
formatArgs = wrappedList(currencyName, networkName, currencyName, feeName, feeSymbol),
),
iconResId = networkIconId,
buttonsState = onClick?.let {
NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.common_buy_currency, wrappedList(feeName)),
onClick = onClick,
)
},
)
}
}

View file

@ -1,13 +1,20 @@
package com.tangem.features.send.impl.presentation.state.fee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
internal class SendFeeStateConverter : Converter<Unit, SendStates.FeeState> {
internal class SendFeeStateConverter(
private val appCurrencyProvider: Provider<AppCurrency>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
) : Converter<Unit, SendStates.FeeState> {
override fun convert(value: Unit): SendStates.FeeState {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
return SendStates.FeeState(
feeSelectorState = FeeSelectorState.Loading,
isSubtractAvailable = false,
@ -17,6 +24,8 @@ internal class SendFeeStateConverter : Converter<Unit, SendStates.FeeState> {
receivedAmountValue = BigDecimal.ZERO,
receivedAmount = "",
notifications = persistentListOf(),
rate = cryptoCurrencyStatus.value.fiatRate,
appCurrency = appCurrencyProvider(),
)
}
}

View file

@ -40,7 +40,7 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S
key = FEE_SELECTOR_KEY,
) {
SendSpeedSelector(
state = feeSendState,
state = state,
clickIntents = clickIntents,
modifier = Modifier.animateItemPlacement(),
)

View file

@ -19,21 +19,27 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
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
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import java.math.BigDecimal
private val DEFAULT_FEE_OPTIONS = listOf(
R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24,
@ -44,7 +50,7 @@ private val DEFAULT_FEE_OPTIONS = listOf(
@Suppress("LongMethod")
@Composable
internal fun SendSpeedSelector(
state: FeeSelectorState,
state: SendStates.FeeState,
clickIntents: SendClickIntents,
modifier: Modifier = Modifier,
) {
@ -58,7 +64,7 @@ internal fun SendSpeedSelector(
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action),
) {
when (state) {
when (val feeSelectorState = state.feeSelectorState) {
FeeSelectorState.Error -> {
SendSpeedSelectorItemError()
}
@ -66,36 +72,42 @@ internal fun SendSpeedSelector(
SendSpeedSelectorItemLoading()
}
is FeeSelectorState.Content -> {
when (state.fees) {
when (val fees = feeSelectorState.fees) {
is TransactionFee.Choosable -> {
val isSelected = state.selectedFee
val isSelected = feeSelectorState.selectedFee
val minimumAmount = fees.minimum.amount
SendSpeedSelectorItem(
titleRes = R.string.common_fee_selector_option_slow,
iconRes = R.drawable.ic_tortoise_24,
amount = TextReference.Str(state.fees.minimum.amount.value.toString()),
symbol = TextReference.Str(state.fees.minimum.amount.currencySymbol),
amount = getCryptoReference(minimumAmount),
fiatAmount = getFiatReference(minimumAmount, state.rate, state.appCurrency),
symbolLength = minimumAmount.currencySymbol.length,
isSelected = isSelected == FeeType.SLOW,
onSelect = { clickIntents.onFeeSelectorClick(FeeType.SLOW) },
)
val normalAmount = fees.normal.amount
SendSpeedSelectorItem(
titleRes = R.string.common_fee_selector_option_market,
iconRes = R.drawable.ic_bird_24,
amount = TextReference.Str(state.fees.normal.amount.value.toString()),
symbol = TextReference.Str(state.fees.normal.amount.currencySymbol),
amount = getCryptoReference(normalAmount),
fiatAmount = getFiatReference(normalAmount, state.rate, state.appCurrency),
symbolLength = normalAmount.currencySymbol.length,
isSelected = isSelected == FeeType.MARKET,
onSelect = { clickIntents.onFeeSelectorClick(FeeType.MARKET) },
)
val priorityAmount = fees.priority.amount
SendSpeedSelectorItem(
titleRes = R.string.common_fee_selector_option_fast,
iconRes = R.drawable.ic_hare_24,
amount = TextReference.Str(state.fees.priority.amount.value.toString()),
symbol = TextReference.Str(state.fees.priority.amount.currencySymbol),
amount = getCryptoReference(priorityAmount),
fiatAmount = getFiatReference(priorityAmount, state.rate, state.appCurrency),
symbolLength = priorityAmount.currencySymbol.length,
isSelected = isSelected == FeeType.FAST,
onSelect = { clickIntents.onFeeSelectorClick(FeeType.FAST) },
showDivider = state.fees.normal is Fee.Ethereum,
showDivider = fees.normal is Fee.Ethereum,
)
AnimatedVisibility(
visible = state.fees.normal is Fee.Ethereum,
visible = fees.normal is Fee.Ethereum,
label = "Custom fee appearance animation",
) {
SendSpeedSelectorItem(
@ -103,17 +115,19 @@ internal fun SendSpeedSelector(
iconRes = R.drawable.ic_edit_24,
isSelected = isSelected == FeeType.CUSTOM,
onSelect = { clickIntents.onFeeSelectorClick(FeeType.CUSTOM) },
showDivider = state.fees.normal !is Fee.Ethereum,
showDivider = fees.normal !is Fee.Ethereum,
)
}
}
is TransactionFee.Single -> {
val normalAmount = feeSelectorState.fees.normal.amount
SendSpeedSelectorItem(
titleRes = R.string.common_fee_selector_option_market,
iconRes = R.drawable.ic_bird_24,
isSelected = true,
amount = TextReference.Str(state.fees.normal.amount.value.toString()),
symbol = TextReference.Str(state.fees.normal.amount.currencySymbol),
amount = getCryptoReference(normalAmount),
fiatAmount = getFiatReference(normalAmount, state.rate, state.appCurrency),
symbolLength = normalAmount.currencySymbol.length,
onSelect = { clickIntents.onFeeSelectorClick(FeeType.MARKET) },
showDivider = false,
)
@ -125,6 +139,24 @@ internal fun SendSpeedSelector(
}
}
// todo remove after refactoring [REDACTED_JIRA]
private fun getCryptoReference(amount: Amount) = stringReference(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = amount.value,
cryptoCurrency = amount.currencySymbol,
decimals = amount.decimals,
),
)
// todo remove after refactoring [REDACTED_JIRA]
private fun getFiatReference(amount: Amount, rate: BigDecimal?, appCurrency: AppCurrency) = stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = rate?.let { amount.value?.multiply(it) },
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),
)
@Composable
private fun SendSpeedSelectorItemLoading() {
repeat(DEFAULT_FEE_OPTIONS.size) {
@ -183,7 +215,8 @@ private fun SendSpeedSelectorItem(
onSelect: () -> Unit,
modifier: Modifier = Modifier,
amount: TextReference? = null,
symbol: TextReference? = null,
fiatAmount: TextReference? = null,
symbolLength: Int? = null,
isSelected: Boolean = false,
showDivider: Boolean = true,
) {
@ -214,10 +247,11 @@ private fun SendSpeedSelectorItem(
iconTint = iconTint,
textStyle = textStyle,
)
if (amount != null && symbol != null) {
if (amount != null && symbolLength != null && fiatAmount != null) {
SelectorValueContent(
amount = amount,
symbol = symbol,
fiatAmount = fiatAmount,
symbolLength = symbolLength,
textStyle = textStyle,
)
}
@ -267,14 +301,18 @@ private fun SelectorTitleContent(
}
@Composable
private fun RowScope.SelectorValueContent(amount: TextReference, symbol: TextReference, textStyle: TextStyle) {
Text(
private fun RowScope.SelectorValueContent(
amount: TextReference,
fiatAmount: TextReference,
symbolLength: Int,
textStyle: TextStyle,
) {
EllipsisText(
text = amount.resolveReference(),
style = textStyle,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.End,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
ellipsis = TextEllipsis.OffsetEnd(symbolLength),
modifier = Modifier
.weight(1f)
.padding(
@ -284,12 +322,12 @@ private fun RowScope.SelectorValueContent(amount: TextReference, symbol: TextRef
),
)
Text(
text = symbol.resolveReference(),
text = "(${fiatAmount.resolveReference()})",
style = textStyle,
color = TangemTheme.colors.text.primary1,
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing1,
start = TangemTheme.dimens.spacing4,
end = TangemTheme.dimens.spacing12,
top = TangemTheme.dimens.spacing14,
bottom = TangemTheme.dimens.spacing14,

View file

@ -24,9 +24,11 @@ import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.BigDecimalFormatter.formatCryptoAmount
import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount
import com.tangem.domain.tokens.model.AmountType
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendNotification
@ -127,7 +129,7 @@ private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: Boolean,
cryptoCurrency = amount.cryptoAmount.currencySymbol,
decimals = amount.cryptoAmount.decimals,
)
val fiatAmount = BigDecimalFormatter.formatFiatAmount(
val fiatAmount = formatFiatAmount(
fiatAmount = amount.fiatAmount.value,
fiatCurrencyCode = (amount.fiatAmount.type as AmountType.FiatType).code,
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
@ -174,14 +176,22 @@ private fun RecipientBlock(recipientState: SendStates.RecipientState, isSuccess:
@Composable
private fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) {
val fee = feeState.fee ?: return
val feeValue = formatCryptoAmount(
val feeCryptoValue = formatCryptoAmount(
cryptoAmount = fee.amount.value,
cryptoCurrency = fee.amount.currencySymbol,
decimals = fee.amount.decimals,
)
val feeFiatValue = formatFiatAmount(
fiatAmount = feeState.rate?.let { fee.amount.value?.multiply(it) },
fiatCurrencyCode = feeState.appCurrency.code,
fiatCurrencySymbol = feeState.appCurrency.symbol,
)
InputRowDefault(
title = TextReference.Res(R.string.common_network_fee_title),
text = TextReference.Str(feeValue),
title = resourceReference(R.string.common_network_fee_title),
text = resourceReference(
id = R.string.send_wallet_balance_format,
wrappedList(feeCryptoValue, feeFiatValue),
),
modifier = Modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)