Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-01 17:47:18 +02:00
parent 8202b6bf2a
commit ab85772d10
47 changed files with 356 additions and 116 deletions

View file

@ -24,6 +24,9 @@ class AmountReduceByTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val value: ReduceByData,
) : Transformer<AmountState> {
private val maxEnterAmountConverter = MaxEnterAmountConverter()
override fun transform(prevState: AmountState): AmountState {
if (prevState !is AmountState.Data) return prevState
@ -40,8 +43,10 @@ class AmountReduceByTransformer(
decimals = fiatDecimals,
)
val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus)
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField)
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
val isZero = if (amountTextField.isFiatValue) {
decimalFiatValue.isNullOrZero()
} else {

View file

@ -24,6 +24,8 @@ class AmountReduceToTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val value: BigDecimal,
) : Transformer<AmountState> {
private val maxEnterAmountConverter = MaxEnterAmountConverter()
override fun transform(prevState: AmountState): AmountState {
if (prevState !is AmountState.Data) return prevState
@ -38,8 +40,10 @@ class AmountReduceToTransformer(
decimals = fiatDecimals,
)
val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus)
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField)
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else value.isNullOrZero()
return prevState.copy(
isPrimaryButtonEnabled = !isExceedBalance && !isZero,

View file

@ -3,8 +3,10 @@ package com.tangem.common.ui.amountScreen.converters
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverter
import com.tangem.common.ui.amountScreen.models.AmountParameters
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
@ -14,7 +16,6 @@ import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.isNullOrZero
@ -25,17 +26,17 @@ import kotlinx.collections.immutable.persistentListOf
*
* @property clickIntents amount screen clicks
* @property appCurrencyProvider selected app currency provider
* @property userWalletProvider selected user wallet provider
* @property maxEnterAmountProvider max enter amount data provider
* @property cryptoCurrencyStatusProvider current cryptocurrency status provider
* @property iconStateConverter currency icon converter
*/
class AmountStateConverter(
private val clickIntents: AmountScreenClickIntents,
private val appCurrencyProvider: Provider<AppCurrency>,
private val userWalletProvider: Provider<UserWallet>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val maxEnterAmountProvider: Provider<MaxEnterAmount>,
private val iconStateConverter: CryptoCurrencyToIconStateConverter,
) : Converter<String, AmountState> {
) : Converter<AmountParameters, AmountState> {
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
AmountFieldConverter(
@ -45,19 +46,19 @@ class AmountStateConverter(
)
}
override fun convert(value: String): AmountState {
val userWallet = userWalletProvider()
override fun convert(value: AmountParameters): AmountState {
val maxEnterAmount = maxEnterAmountProvider()
val appCurrency = appCurrencyProvider()
val status = cryptoCurrencyStatusProvider()
val fiat = formatFiatAmount(status.value.fiatAmount, appCurrency.code, appCurrency.symbol)
val crypto = status.value.amount.format { crypto(status.currency) }
val fiat = formatFiatAmount(maxEnterAmount.fiatAmount, appCurrency.code, appCurrency.symbol)
val crypto = maxEnterAmount.amount.format { crypto(status.currency) }
val noFeeRate = status.value.fiatRate.isNullOrZero()
return AmountState.Data(
walletName = userWallet.name,
walletBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
title = value.title,
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
tokenIconState = iconStateConverter.convert(status),
amountTextField = amountFieldConverter.convert(value),
amountTextField = amountFieldConverter.convert(value.value),
isPrimaryButtonEnabled = false,
appCurrencyCode = appCurrency.code,
segmentedButtonConfig = persistentListOf(

View file

@ -0,0 +1,19 @@
package com.tangem.common.ui.amountScreen.converters
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.converter.Converter
/**
* Converts [CryptoCurrencyStatus] to [MaxEnterAmount]
*/
class MaxEnterAmountConverter : Converter<CryptoCurrencyStatus, MaxEnterAmount> {
override fun convert(value: CryptoCurrencyStatus): MaxEnterAmount {
return MaxEnterAmount(
amount = value.value.amount,
fiatAmount = value.value.fiatAmount,
fiatRate = value.value.fiatRate,
)
}
}

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.common.ui.amountScreen.utils.checkExceedBalance
import com.tangem.common.ui.amountScreen.utils.getCryptoValue
import com.tangem.common.ui.amountScreen.utils.getFiatValue
@ -12,7 +13,6 @@ import com.tangem.common.ui.amountScreen.utils.getKeyboardAction
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.isNullOrZero
import com.tangem.utils.transformer.Transformer
import java.math.BigDecimal
@ -20,11 +20,11 @@ import java.math.BigDecimal
/**
* Amount value change
*
* @property cryptoCurrencyStatus current cryptocurrency status
* @property maxEnterAmount max amount to enter
* @property value amount value
*/
class AmountFieldChangeTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val maxEnterAmount: MaxEnterAmount,
private val value: String,
) : Transformer<AmountState> {
@ -39,19 +39,19 @@ class AmountFieldChangeTransformer(
val trimmedValue = value.trim()
val cryptoValue = trimmedValue.getCryptoValue(
fiatRate = cryptoCurrencyStatus.value.fiatRate,
fiatRate = maxEnterAmount.fiatRate,
isFiatValue = amountTextField.isFiatValue,
decimals = cryptoDecimals,
)
val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals)
val (fiatValue, decimalFiatValue) = trimmedValue.getFiatValue(
fiatRate = cryptoCurrencyStatus.value.fiatRate,
fiatRate = maxEnterAmount.fiatRate,
isFiatValue = amountTextField.isFiatValue,
decimals = fiatDecimals,
)
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField)
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
val isZero = if (amountTextField.isFiatValue) {
decimalFiatValue.isNullOrZero()
} else {

View file

@ -4,8 +4,8 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.isNullOrZero
import com.tangem.utils.transformer.Transformer
import java.math.RoundingMode
@ -13,10 +13,10 @@ import java.math.RoundingMode
/**
* Selects maximum amount value
*
* @property cryptoCurrencyStatus current cryptocurrency status
* @property maxAmount maximum enter amount
*/
class AmountFieldMaxAmountTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
class AmountFieldSetMaxAmountTransformer(
private val maxAmount: MaxEnterAmount,
) : Transformer<AmountState> {
override fun transform(prevState: AmountState): AmountState {
@ -26,8 +26,8 @@ class AmountFieldMaxAmountTransformer(
val cryptoDecimals = amountTextField.cryptoAmount.decimals
val fiatDecimals = amountTextField.fiatAmount.decimals
val decimalCryptoValue = cryptoCurrencyStatus.value.amount
val decimalFiatValue = cryptoCurrencyStatus.value.fiatAmount
val decimalCryptoValue = maxAmount.amount
val decimalFiatValue = maxAmount.fiatAmount
if (decimalCryptoValue.isNullOrZero()) return prevState

View file

@ -0,0 +1,8 @@
package com.tangem.common.ui.amountScreen.models
import com.tangem.core.ui.extensions.TextReference
data class AmountParameters(
val title: TextReference,
val value: String,
)

View file

@ -13,8 +13,8 @@ sealed class AmountState {
/**
* @param isPrimaryButtonEnabled indicates if next state button enabled
* @param walletName user wallet name
* @param walletBalance user crypto currency balance in wallet
* @param title title
* @param availableBalance user crypto currency balance
* @param tokenIconState crypto currency icon state
* @param segmentedButtonConfig currency switcher config
* @param selectedButton selected currency index
@ -24,8 +24,8 @@ sealed class AmountState {
*/
data class Data(
override val isPrimaryButtonEnabled: Boolean,
val walletName: String,
val walletBalance: TextReference,
val title: TextReference,
val availableBalance: TextReference,
val tokenIconState: CurrencyIconState,
val segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
val selectedButton: Int,

View file

@ -0,0 +1,9 @@
package com.tangem.common.ui.amountScreen.models
import java.math.BigDecimal
data class MaxEnterAmount(
val amount: BigDecimal? = null,
val fiatAmount: BigDecimal? = null,
val fiatRate: BigDecimal? = null,
)

View file

@ -17,8 +17,8 @@ object AmountStatePreviewData {
val amountState = AmountState.Data(
isPrimaryButtonEnabled = false,
walletName = "Family Wallet",
walletBalance = stringReference("2 130,88 USDT (2 129,92 \$)"),
title = stringReference("Family Wallet"),
availableBalance = stringReference("2 130,88 USDT (2 129,92 \$)"),
tokenIconState = CurrencyIconState.Loading,
segmentedButtonConfig = persistentListOf(
AmountSegmentedButtonsConfig(

View file

@ -34,14 +34,14 @@ internal fun LazyListScope.amountField(
.background(TangemTheme.colors.background.action),
) {
Text(
text = amountState.walletName,
text = amountState.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing14),
)
val balance = amountState.walletBalance.orMaskWithStars(isBalanceHidden).resolveReference()
val balance = amountState.availableBalance.orMaskWithStars(isBalanceHidden).resolveReference()
AnimatedContent(
targetState = balance,
label = "Hide Balance Animation",

View file

@ -2,9 +2,9 @@ package com.tangem.common.ui.amountScreen.utils
import androidx.compose.ui.text.input.ImeAction
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.isNullOrZero
import java.math.BigDecimal
import java.math.RoundingMode
@ -36,12 +36,9 @@ internal fun String.getFiatValue(
}
}
internal fun String.checkExceedBalance(
cryptoCurrencyStatus: CryptoCurrencyStatus,
amountTextField: AmountFieldModel,
): Boolean {
val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO
internal fun String.checkExceedBalance(maxEnterAmount: MaxEnterAmount, amountTextField: AmountFieldModel): Boolean {
val currencyCryptoAmount = maxEnterAmount.amount ?: BigDecimal.ZERO
val currencyFiatAmount = maxEnterAmount.fiatAmount ?: BigDecimal.ZERO
val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals)
val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals)
return if (amountTextField.isFiatValue) {

View file

@ -698,6 +698,7 @@
<string name="settings_wallet_name_title">Name</string>
<string name="staking_amount_requirement_error">Die Anzahl der zu stakenden Krypros muss mindesten %s betragen</string>
<string name="staking_amount_tron_integer_error">Der Stakingbetrag wird aufgrund der Netzwerkregeln auf %1$s TRX aufgerundet.</string>
<string name="staking_amount_tron_integer_error_unstaking">Der Betrag der unstaked wird, wird aufgrund der Netzwerkregeln auf %1$s TRX gerundet.</string>
<string name="staking_claim_unstaked">Nicht gestakte beanspruche</string>
<string name="staking_details_account_fee">Gebühr für das Staking-Konto</string>
<string name="staking_details_account_fee_info">Ein Staking-Konto ist ein spezielles Konto, auf dem eingesetzte SOL-Token gelagert werden. Es wird erstellt, wenn du deine Token an einen Validator delegierst, um an der Transaktionsvalidierung teilzunehmen und Belohnungen zu verdienen. Für die Erstellung des Staking-Kontos wird eine geringe Gebühr erhoben, die nach Abschluss des Stakings zurückgegeben wird.</string>
@ -783,6 +784,7 @@
<string name="staking_unbonding">Lösen der Bindungen</string>
<string name="staking_unlocked_locked">Gelocktes unlocken</string>
<string name="staking_unlocking">Entsperren</string>
<string name="staking_unstake_amount_requirement_error">Die Anzahl der zu stakenden Krypros muss mindesten %s betragen</string>
<string name="staking_unstake_amount_validation_error">Der Betrag übersteigt das eingesetzte Guthaben</string>
<string name="staking_unstaked">Unstaken</string>
<string name="staking_unstaking">Staking beenden</string>
@ -810,6 +812,7 @@
<string name="swap_promo_title">Neuer Swap-Anbieter verfügbar!</string>
<string name="swapping_alert_cex_description">Der Betrag umfasst:\n- Gebühr des Dienstanbieters\n- Netzgebühr für die Rücksendung von %s von der Vermittlungsstelle an die Adresse des Nutzers.</string>
<string name="swapping_alert_dex_description">Der Betrag enthält die Gebühren des Dienstleisters.</string>
<string name="swapping_alert_slippage_description">Anbieter-Slippage kann bis zu %s ausfallen.</string>
<string name="swapping_alert_title">Gebühren</string>
<string name="swapping_approve_information_text">Alle dezentralen Börsen benötigen Genehmigungen, um zu verhindern, dass intelligente Verträge ohne Ihre Erlaubnis auf Ihre Geldbörse zugreifen. Smart Contracts können nicht auf Ihre Token zugreifen, wenn Sie nicht zustimmen. Indem Sie Ihre Token \"freischalten\", ermächtigen Sie den 1-Zoll-Smart-Contract, sie auszugeben. Die Miner des Netzwerks erhalten eine (von Ihnen bezahlte) Gasgebühr, um diese Aktion in der Blockchain aufzuzeichnen. Sie können Ihre Token tauschen, nachdem Sie Ihre Zustimmung gegeben haben.</string>
<string name="swapping_approve_information_title">Genehmigen</string>

View file

@ -781,6 +781,7 @@
<string name="staking_unbonding">Desunión</string>
<string name="staking_unlocked_locked">Desbloquear</string>
<string name="staking_unlocking">Desbloqueando</string>
<string name="staking_unstake_amount_requirement_error">El monto del staking debe ser al menos %s</string>
<string name="staking_unstake_amount_validation_error">El monto excede el saldo apostado</string>
<string name="staking_unstaked">Sin staking</string>
<string name="staking_unstaking">Unstaking</string>

View file

@ -781,6 +781,7 @@
<string name="staking_unbonding">Dissociation</string>
<string name="staking_unlocked_locked">Débloquer</string>
<string name="staking_unlocking">Déverrouillage</string>
<string name="staking_unstake_amount_requirement_error">Le montant à staker doit être au moins %s</string>
<string name="staking_unstake_amount_validation_error">Le montant dépasse le solde misé</string>
<string name="staking_unstaked">Non-staké</string>
<string name="staking_unstaking">Unstaking</string>

View file

@ -688,6 +688,7 @@
<string name="settings_wallet_name_title">名前</string>
<string name="staking_amount_requirement_error">ステーキング金額は %s 以上である必要があります</string>
<string name="staking_amount_tron_integer_error">ネットワークルールにより、ステーキング金額は%1$s TRX に切り上げられます。</string>
<string name="staking_amount_tron_integer_error_unstaking">ネットワークルールにより、ステーキング解除の量は%1$s TRX に切り上げられます。</string>
<string name="staking_claim_unstaked">ステーキング解除分を請求する</string>
<string name="staking_details_account_fee">ステーキングアカウント手数料</string>
<string name="staking_details_account_fee_info">ステーキングアカウントは、ステーキングされたSOLトークンが保管される特別なアカウントです。取引の検証に参加して報酬を得るために、トークンをバリデーターに委任すると、このアカウントが作成されます。ステーキングアカウントの作成には少額の手数料がかかりますが、この手数料はステーキングが完了すると返金されます。</string>
@ -773,6 +774,7 @@
<string name="staking_unbonding">ステーキング解約中</string>
<string name="staking_unlocked_locked">ロック解除</string>
<string name="staking_unlocking">ロック解除中</string>
<string name="staking_unstake_amount_requirement_error">ステーキング金額は %s 以上である必要があります</string>
<string name="staking_unstake_amount_validation_error">金額がステーキング残高を超えています</string>
<string name="staking_unstaked">ステーキングされていない</string>
<string name="staking_unstaking">ステーキング解除</string>
@ -800,6 +802,7 @@
<string name="swap_promo_title">新しいスワッププロバイダーが利用可能になりました!</string>
<string name="swapping_alert_cex_description">この金額には以下が含まれます:\n- サービスプロバイダーの手数料\n- 取引所からユーザーのアドレスに%s を送り返すためのネットワーク手数料。</string>
<string name="swapping_alert_dex_description">この金額には、サービスプロバイダーの手数料が含まれています。</string>
<string name="swapping_alert_slippage_description">プロバイダーのスリッページは最大%sです。</string>
<string name="swapping_alert_title">手数料</string>
<string name="swapping_approve_information_text">すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金あなたが支払うを受け取ります。承認後、トークンを交換することができます。</string>
<string name="swapping_approve_information_title">承認</string>

View file

@ -798,6 +798,7 @@
<string name="staking_unbonding">Отзыв</string>
<string name="staking_unlocked_locked">Разблокировать</string>
<string name="staking_unlocking">Разблокировка</string>
<string name="staking_unstake_amount_requirement_error">Сумма для стейкинга должна быть не менее %s</string>
<string name="staking_unstake_amount_validation_error">Сумма превышает застейканный баланс</string>
<string name="staking_unstaked">Вывод из стейкинга</string>
<string name="staking_unstaking">Завершение стейкинга</string>

View file

@ -800,6 +800,7 @@
<string name="staking_unbonding">Розблокування</string>
<string name="staking_unlocked_locked">Розблокувати</string>
<string name="staking_unlocking">Розблокування</string>
<string name="staking_unstake_amount_requirement_error">Сума для стейкінгу має бути не менше %s</string>
<string name="staking_unstake_amount_validation_error">Сума перевищує баланс стейкінгу</string>
<string name="staking_unstaked">Вивід зі стейкінгу</string>
<string name="staking_unstaking">Зняти зі стейкінгу</string>

View file

@ -698,6 +698,7 @@
<string name="settings_wallet_name_title">Name</string>
<string name="staking_amount_requirement_error">The amount to stake must be at least %s</string>
<string name="staking_amount_tron_integer_error">Staking amount will be rounded to %1$s TRX due to network rules.</string>
<string name="staking_amount_tron_integer_error_unstaking">Unstaking amount will be rounded to %1$s TRX due to network rules.</string>
<string name="staking_claim_unstaked">Claim unstaked</string>
<string name="staking_details_account_fee">Stake account fee</string>
<string name="staking_details_account_fee_info">A staking account is a special account where staked SOL tokens are stored. It is created when you delegate your tokens to a validator to participate in transaction validation and earn rewards. A small fee is charged for creating the staking account, which is returned after the staking is completed.</string>
@ -773,6 +774,7 @@
<string name="staking_rewards">Rewards</string>
<string name="staking_stake_locked">Stake locked</string>
<string name="staking_stake_more">Stake more</string>
<string name="staking_staked_amount">Staked amount</string>
<string name="staking_summary_description_text">You stake %1$s and will be receiving your reward %2$s</string>
<string name="staking_tap_to_unlock">Tap to unlock</string>
<string name="staking_tap_to_unlock_or_vote">Tap to unlock or vote</string>
@ -783,6 +785,7 @@
<string name="staking_unbonding">Unbonding</string>
<string name="staking_unlocked_locked">Unlock</string>
<string name="staking_unlocking">Unlocking</string>
<string name="staking_unstake_amount_requirement_error">The amount to unstake must be at least %s</string>
<string name="staking_unstake_amount_validation_error">Amount exceeds staked balance</string>
<string name="staking_unstaked">Unstaked</string>
<string name="staking_unstaking">Unstaking</string>

View file

@ -2,10 +2,13 @@ package com.tangem.features.send.impl.presentation.state
import com.tangem.blockchain.common.TransactionData
import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
import com.tangem.common.ui.amountScreen.models.AmountParameters
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
@ -33,14 +36,15 @@ internal class SendStateFactory(
private val isTapHelpPreviewEnabledProvider: Provider<Boolean>,
) {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
private val maxEnterAmountConverter = MaxEnterAmountConverter()
private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) {
AmountStateConverter(
clickIntents = clickIntents,
appCurrencyProvider = appCurrencyProvider,
iconStateConverter = iconStateConverter,
userWalletProvider = userWalletProvider,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
maxEnterAmountProvider = Provider { maxEnterAmountConverter.convert(cryptoCurrencyStatusProvider()) },
)
}
private val recipientStateConverter by lazy(LazyThreadSafetyMode.NONE) {
@ -79,7 +83,12 @@ internal class SendStateFactory(
fun getReadyState(): SendUiState {
val state = currentStateProvider()
val amountState = if (state.amountState is AmountState.Empty) {
amountStateConverter.convert("")
amountStateConverter.convert(
AmountParameters(
title = stringReference(userWalletProvider().name),
value = "",
),
)
} else {
state.amountState
}
@ -96,7 +105,12 @@ internal class SendStateFactory(
fun getReadyState(amount: String, destinationAddress: String, memo: String?): SendUiState {
val state = currentStateProvider()
val amountState = if (state.amountState is AmountState.Empty) {
amountStateConverter.convert(amount)
amountStateConverter.convert(
AmountParameters(
title = stringReference(userWalletProvider().name),
value = amount,
),
)
} else {
state.amountState
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.send.impl.presentation.state.fields
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.state.SendUiState
@ -13,15 +14,19 @@ internal class SendAmountFieldChangeConverter(
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
) : Converter<String, SendUiState> {
private val maxEnterAmountConverter = MaxEnterAmountConverter()
override fun convert(value: String): SendUiState {
val state = currentStateProvider()
val isEditState = stateRouterProvider().isEditState
val amountState = state.getAmountState(isEditState) ?: return state
val amountState = state.getAmountState(isEditState)
val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatusProvider())
return state.copyWrapped(
isEditState = isEditState,
sendState = state.sendState?.copy(reduceAmountBy = null),
amountState = AmountFieldChangeTransformer(cryptoCurrencyStatusProvider(), value).transform(amountState),
amountState = AmountFieldChangeTransformer(maxEnterAmount, value).transform(amountState),
)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.send.impl.presentation.state.fields
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldMaxAmountTransformer
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.StateRouter
@ -14,6 +15,8 @@ internal class SendAmountFieldMaxAmountConverter(
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
) : Converter<Unit, SendUiState> {
private val maxEnterAmountConverter = MaxEnterAmountConverter()
override fun convert(value: Unit): SendUiState {
val state = currentStateProvider()
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
@ -23,10 +26,12 @@ internal class SendAmountFieldMaxAmountConverter(
val decimalCryptoValue = cryptoCurrencyStatus.value.amount
if (decimalCryptoValue.isNullOrZero()) return state
val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus)
return state.copyWrapped(
isEditState = isEditState,
sendState = state.sendState?.copy(reduceAmountBy = null),
amountState = AmountFieldMaxAmountTransformer(cryptoCurrencyStatusProvider()).transform(amountState),
amountState = AmountFieldSetMaxAmountTransformer(maxEnterAmount).transform(amountState),
)
}
}

View file

@ -88,7 +88,7 @@ private fun SendAppBar(uiState: SendUiState, currentState: SendUiCurrentScreen)
-> resourceReference(R.string.common_fee_selector_title) to null
SendUiStateType.Send -> if (uiState.sendState?.isSuccess == false) {
resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencyName)) to
(uiState.amountState as? AmountState.Data)?.walletName
(uiState.amountState as? AmountState.Data)?.title
} else {
null to null
}
@ -108,7 +108,7 @@ private fun SendAppBar(uiState: SendUiState, currentState: SendUiCurrentScreen)
}
AppBarWithBackButtonAndIcon(
text = titleRes?.resolveReference(),
subtitle = subtitleRes,
subtitle = subtitleRes?.resolveReference(),
onBackClick = uiState.clickIntents::onCloseClick,
onIconClick = uiState.clickIntents::onQrCodeScanClick,
backIconRes = backIcon,

View file

@ -30,9 +30,10 @@ internal data class BalanceState(
val subtitle: TextReference?,
val isClickable: Boolean,
val cryptoValue: String,
val cryptoDecimal: BigDecimal,
val cryptoAmount: TextReference,
val fiatAmount: TextReference,
val cryptoAmount: BigDecimal,
val formattedCryptoAmount: TextReference,
val fiatAmount: BigDecimal?,
val formattedFiatAmount: TextReference,
val rawCurrencyId: String?,
val validator: Yield.Validator?,
val pendingActions: ImmutableList<PendingAction>,

View file

@ -75,6 +75,7 @@ internal class StakingStateController @Inject constructor(
walletName = "",
cryptoCurrencyName = "",
cryptoCurrencySymbol = "",
cryptoCurrencyNetworkId = "",
currentStep = StakingStep.InitialInfo,
initialInfoState = StakingStates.InitialInfoState.Empty(),
amountState = AmountState.Empty(),
@ -86,6 +87,7 @@ internal class StakingStateController @Inject constructor(
bottomSheetConfig = null,
actionType = StakingActionCommonType.Enter,
buttonsState = NavigationButtonsState.Empty,
balanceState = null,
)
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender
import com.tangem.lib.crypto.BlockchainUtils.isSolana
internal class StakingStateRouter(
private val appRouter: AppRouter,
@ -24,8 +25,13 @@ internal class StakingStateRouter(
when (stateController.value.currentStep) {
StakingStep.InitialInfo -> when (stateController.value.actionType) {
StakingActionCommonType.Enter -> showAmount()
// TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing
StakingActionCommonType.Exit -> if (isSolana(stateController.value.cryptoCurrencyNetworkId)) {
showConfirmation()
} else {
showAmount()
}
StakingActionCommonType.Pending.Other,
StakingActionCommonType.Exit,
StakingActionCommonType.Pending.Rewards,
-> showConfirmation()
StakingActionCommonType.Pending.Restake -> showRestakeValidators()

View file

@ -29,6 +29,7 @@ internal data class StakingUiState(
val walletName: String,
val cryptoCurrencyName: String,
val cryptoCurrencySymbol: String,
val cryptoCurrencyNetworkId: String,
val currentStep: StakingStep,
val initialInfoState: StakingStates.InitialInfoState,
val amountState: AmountState,
@ -40,6 +41,7 @@ internal data class StakingUiState(
val actionType: StakingActionCommonType,
val buttonsState: NavigationButtonsState,
val event: StateEvent<StakingEvent>,
val balanceState: BalanceState?,
) {
fun copyWrapped(

View file

@ -47,11 +47,12 @@ internal class BalanceItemConverter(
subtitle = getSubtitle(value),
type = value.type,
cryptoValue = cryptoAmount.parseBigDecimal(cryptoCurrency.decimals),
cryptoDecimal = cryptoAmount,
cryptoAmount = stringReference(
cryptoAmount = cryptoAmount,
formattedCryptoAmount = stringReference(
cryptoAmount.format { crypto(cryptoCurrency) },
),
fiatAmount = stringReference(
fiatAmount = fiatAmount,
formattedFiatAmount = stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatAmount,
fiatCurrencyCode = appCurrency.code,

View file

@ -71,7 +71,7 @@ internal class RewardsValidatorStateConverter(
crypto(cryptoCurrency)
},
)
val fiatAmount = stringReference(
val formattedFiatAmount = stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatValue,
fiatCurrencyCode = appCurrency.code,
@ -85,9 +85,10 @@ internal class RewardsValidatorStateConverter(
title = stringReference(this.name),
subtitle = null,
cryptoValue = cryptoValue.parseBigDecimal(cryptoCurrency.decimals),
cryptoDecimal = cryptoValue,
cryptoAmount = cryptoAmount,
fiatAmount = fiatAmount,
cryptoAmount = cryptoValue,
formattedCryptoAmount = cryptoAmount,
fiatAmount = fiatValue,
formattedFiatAmount = formattedFiatAmount,
rawCurrencyId = cryptoCurrency.id.rawCurrencyId,
pendingActions = balance.pendingActions.toPersistentList(),
isClickable = true,

View file

@ -63,7 +63,7 @@ internal class YieldBalancesConverter(
private fun List<BalanceItem>.mapBalances() = asSequence()
.filterNot { it.amount.isZero() || it.type == BalanceType.REWARDS }
.mapNotNull(balanceItemConverter::convert)
.sortedByDescending { it.cryptoDecimal }
.sortedByDescending { it.cryptoAmount }
.sortedBy { it.type.order }
.toPersistentList()

View file

@ -20,7 +20,7 @@ import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.presentation.state.StakingStateController
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions
import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions
import com.tangem.utils.extensions.orZero
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -95,7 +95,11 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
val sourceAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
?: error("No available address")
val gasEstimate = if (isComposePendingActions(cryptoCurrencyStatus.currency.network.id.value, pendingActions)) {
val gasEstimate = if (isCompositePendingActions(
networkId = cryptoCurrencyStatus.currency.network.id.value,
pendingActions = pendingActions,
)
) {
val result = coroutineScope {
pendingActions?.map { action ->
async {

View file

@ -20,7 +20,7 @@ import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions
import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions
import com.tangem.utils.extensions.orZero
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -96,7 +96,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
confirmationState: StakingStates.ConfirmationState.Data,
onConstructError: (StakingError) -> Unit,
) = coroutineScope {
val isComposePendingActions = isComposePendingActions(
val isComposePendingActions = isCompositePendingActions(
cryptoCurrencyStatus.currency.network.id.value,
confirmationState.pendingActions,
)

View file

@ -73,9 +73,10 @@ internal object InitialStakingStatePreview {
groupId = "groupId",
title = stringReference("Binance"),
cryptoValue = "100",
cryptoAmount = stringReference("100 SOL"),
cryptoDecimal = "100".toBigDecimal(),
fiatAmount = stringReference("100 $"),
formattedCryptoAmount = stringReference("100 SOL"),
cryptoAmount = "100".toBigDecimal(),
fiatAmount = null,
formattedFiatAmount = stringReference("100 $"),
rawCurrencyId = null,
validator = Yield.Validator(
address = "address",

View file

@ -26,7 +26,7 @@ internal object StakingClickIntentsStub : StakingClickIntents {
override fun onInfoClick(infoType: InfoType) {}
override fun onEnterClick() {}
override fun onAmountEnterClick() {}
override fun onAmountValueChange(value: String) {}

View file

@ -0,0 +1,55 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
import com.tangem.common.ui.amountScreen.models.AmountParameters
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.transformer.Transformer
internal class SetAmountDataTransformer(
private val clickIntents: StakingClickIntents,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val userWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val maxEnterAmountProvider: Provider<MaxEnterAmount>,
) : Transformer<StakingUiState> {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) {
AmountStateConverter(
clickIntents = clickIntents,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
appCurrencyProvider = appCurrencyProvider,
iconStateConverter = iconStateConverter,
maxEnterAmountProvider = maxEnterAmountProvider,
)
}
override fun transform(prevState: StakingUiState): StakingUiState {
val title = if (prevState.actionType == StakingActionCommonType.Exit) {
resourceReference(R.string.staking_staked_amount)
} else {
stringReference(userWalletProvider().name)
}
return prevState.copy(
amountState = amountStateConverter.convert(
AmountParameters(
title = title,
value = "",
),
),
)
}
}

View file

@ -148,7 +148,7 @@ internal class SetButtonsStateTransformer(
StakingStep.Validators,
StakingStep.RestakeValidator,
-> clickIntents.onNextClick()
StakingStep.Amount -> clickIntents.onEnterClick()
StakingStep.Amount -> clickIntents.onAmountEnterClick()
StakingStep.Confirmation -> onConfirmationClick()
StakingStep.RewardsValidators -> Unit
}

View file

@ -11,6 +11,7 @@ internal object SetConfirmationStateEmptyTransformer : Transformer<StakingUiStat
actionType = StakingActionCommonType.Enter,
validatorState = StakingStates.ValidatorState.Empty(),
confirmationState = StakingStates.ConfirmationState.Empty(),
balanceState = null,
)
}
}

View file

@ -7,7 +7,7 @@ import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions
import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions
import com.tangem.features.staking.impl.presentation.state.utils.isTronStakedBalance
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
@ -16,29 +16,32 @@ import java.math.BigDecimal
internal class SetConfirmationStateInitTransformer(
private val isEnter: Boolean,
private val isExplicitExit: Boolean,
private val balanceState: BalanceState?,
private val stakingApproval: StakingApproval,
private val stakingAllowance: BigDecimal,
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val pendingActions: ImmutableList<PendingAction>? = null,
private val pendingAction: PendingAction? = pendingActions?.firstOrNull(),
) : Transformer<StakingUiState> {
private val networkId
get() = cryptoCurrencyStatus.currency.network.id.value
private val isComposePendingActions
get() = isComposePendingActions(networkId, pendingActions)
get() = isCompositePendingActions(networkId, pendingActions)
private val isTronStakedBalance
get() = isTronStakedBalance(networkId, pendingAction)
private val isExit: Boolean
private val isImplicitExit: Boolean
get() = pendingAction == null && pendingActions?.isEmpty() == true || isTronStakedBalance
override fun transform(prevState: StakingUiState): StakingUiState {
val actionType = when {
isEnter -> StakingActionCommonType.Enter
isExit -> StakingActionCommonType.Exit
isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit
else -> when (pendingAction?.type) {
StakingActionType.STAKE -> StakingActionCommonType.Enter
StakingActionType.UNSTAKE -> StakingActionCommonType.Exit
@ -54,6 +57,7 @@ internal class SetConfirmationStateInitTransformer(
return prevState.copy(
actionType = actionType,
balanceState = balanceState,
confirmationState = StakingStates.ConfirmationState.Data(
isPrimaryButtonEnabled = false,
innerState = InnerConfirmationStakingState.ASSENT,

View file

@ -2,7 +2,9 @@ package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.common.extensions.remove
import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
import com.tangem.common.ui.amountScreen.models.AmountParameters
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.extensions.*
@ -39,6 +41,7 @@ internal class SetInitialDataStateTransformer(
private val userWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val balancesToShowProvider: Provider<List<BalanceItem>>,
private val maxEnterAmountProvider: Provider<MaxEnterAmount>,
) : Transformer<StakingUiState> {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
@ -48,8 +51,8 @@ internal class SetInitialDataStateTransformer(
clickIntents = clickIntents,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
appCurrencyProvider = appCurrencyProvider,
userWalletProvider = userWalletProvider,
iconStateConverter = iconStateConverter,
maxEnterAmountProvider = maxEnterAmountProvider,
)
}
@ -72,6 +75,7 @@ internal class SetInitialDataStateTransformer(
title = TextReference.EMPTY,
cryptoCurrencyName = cryptoCurrency.name,
cryptoCurrencySymbol = cryptoCurrency.symbol,
cryptoCurrencyNetworkId = cryptoCurrency.network.id.value,
clickIntents = clickIntents,
currentStep = StakingStep.InitialInfo,
initialInfoState = createInitialInfoState(),
@ -207,7 +211,12 @@ internal class SetInitialDataStateTransformer(
}
private fun createInitialAmountState(): AmountState {
return amountStateConverter.convert("")
return amountStateConverter.convert(
AmountParameters(
title = stringReference(userWalletProvider().name),
value = "",
),
)
}
private fun getAprRange(validators: List<Yield.Validator>): TextReference {

View file

@ -1,7 +1,10 @@
package com.tangem.features.staking.impl.presentation.state.transformers.amount
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
@ -12,11 +15,21 @@ internal class AmountChangeStateTransformer(
private val yield: Yield,
) : Transformer<StakingUiState> {
private val maxEnterAmountConverter = MaxEnterAmountConverter()
override fun transform(prevState: StakingUiState): StakingUiState {
val updatedAmountState = AmountFieldChangeTransformer(
cryptoCurrencyStatus,
value,
).transform(prevState.amountState)
val actionType = prevState.actionType
val maxEnterAmount = if (actionType == StakingActionCommonType.Exit) {
MaxEnterAmount(
amount = prevState.balanceState?.cryptoAmount,
fiatAmount = prevState.balanceState?.fiatAmount,
fiatRate = cryptoCurrencyStatus.value.fiatRate,
)
} else {
maxEnterAmountConverter.convert(cryptoCurrencyStatus)
}
val updatedAmountState = AmountFieldChangeTransformer(maxEnterAmount, value).transform(prevState.amountState)
return prevState.copy(
amountState = AmountRequirementStateTransformer(

View file

@ -1,18 +1,35 @@
package com.tangem.features.staking.impl.presentation.state.transformers.amount
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldMaxAmountTransformer
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class AmountMaxValueStateTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val actionType: StakingActionCommonType,
private val yield: Yield,
) : Transformer<StakingUiState> {
private val maxEnterAmountConverter = MaxEnterAmountConverter()
override fun transform(prevState: StakingUiState): StakingUiState {
val updatedAmountState = AmountFieldMaxAmountTransformer(cryptoCurrencyStatus).transform(prevState.amountState)
val maxEnterAmount = if (actionType == StakingActionCommonType.Exit) {
MaxEnterAmount(
amount = prevState.balanceState?.cryptoAmount,
fiatAmount = prevState.balanceState?.fiatAmount,
fiatRate = cryptoCurrencyStatus.value.fiatRate,
)
} else {
maxEnterAmountConverter.convert(cryptoCurrencyStatus)
}
val updatedAmountState = AmountFieldSetMaxAmountTransformer(maxEnterAmount)
.transform(prevState.amountState)
return prevState.copy(
amountState = AmountRequirementStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,

View file

@ -61,10 +61,17 @@ internal class AmountRequirementStateTransformer(
},
),
)
isIntegerOnlyError -> resourceReference(
R.string.staking_amount_tron_integer_error,
wrappedList(value),
)
isIntegerOnlyError -> when (actionType) {
StakingActionCommonType.Enter -> resourceReference(
R.string.staking_amount_tron_integer_error,
wrappedList(value),
)
StakingActionCommonType.Exit -> resourceReference(
R.string.staking_amount_tron_integer_error_unstaking,
wrappedList(value),
)
else -> TODO()
}
else -> TextReference.EMPTY
}
val isError = amountState.amountTextField.isError || isRequirementError
@ -101,12 +108,12 @@ internal class AmountRequirementStateTransformer(
private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean {
val cryptoAmountValue = amountState.amountTextField.cryptoAmount.value ?: return false
val isEnter = actionType == StakingActionCommonType.Enter
val isEnterOrExit = actionType == StakingActionCommonType.Enter || actionType == StakingActionCommonType.Exit
val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value)
val isIntegerOnly = cryptoAmountValue.isZero() || cryptoAmountValue.remainder(BigDecimal.ONE).isZero()
return isEnter && isTron && !isIntegerOnly
return isEnterOrExit && isTron && !isIntegerOnly
}
data class Data(

View file

@ -35,12 +35,12 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t
internal fun isSingleAction(networkId: String, activeStake: BalanceState): Boolean {
val isSingleAction = activeStake.pendingActions.size <= 1 // Either single or none pending actions
val isComposePendingActions = isComposePendingActions(networkId, activeStake.pendingActions)
val isCompositePendingActions = isCompositePendingActions(networkId, activeStake.pendingActions)
val isBscRestake = isBSC(networkId) && activeStake.pendingActions.any {
it.type == StakingActionType.RESTAKE
}
return isSingleAction && !isBscRestake || isComposePendingActions
return isSingleAction && !isBscRestake || isCompositePendingActions
}
internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) = if (isBSC(networkId)) {
@ -59,7 +59,7 @@ internal fun isTronStakedBalance(networkId: String, pendingAction: PendingAction
return isTron(networkId) && pendingAction?.type == StakingActionType.REVOTE
}
internal fun isComposePendingActions(networkId: String, pendingActions: ImmutableList<PendingAction>?): Boolean {
internal fun isCompositePendingActions(networkId: String, pendingActions: ImmutableList<PendingAction>?): Boolean {
return when {
isSolana(networkId) -> pendingActions?.any { it.type == StakingActionType.WITHDRAW } == true
else -> false

View file

@ -47,8 +47,8 @@ internal fun StakingClaimRewardsValidatorContent(
)
},
),
infoTitle = item.fiatAmount,
infoSubtitle = item.cryptoAmount,
infoTitle = item.formattedFiatAmount,
infoSubtitle = item.formattedCryptoAmount,
imageUrl = item.validator?.image.orEmpty(),
onImageError = { ValidatorImagePlaceholder() },
modifier = modifier

View file

@ -270,8 +270,8 @@ private fun ActiveStakingBlock(
InputRowImageInfo(
subtitle = balance.title,
caption = balance.subtitle ?: balance.getAprText(),
infoTitle = balance.fiatAmount.orMaskWithStars(isBalanceHidden),
infoSubtitle = balance.cryptoAmount.orMaskWithStars(isBalanceHidden),
infoTitle = balance.formattedFiatAmount.orMaskWithStars(isBalanceHidden),
infoSubtitle = balance.formattedCryptoAmount.orMaskWithStars(isBalanceHidden),
imageUrl = balance.getImage(),
iconRes = icon,
iconTint = iconTint,

View file

@ -26,7 +26,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
fun onInfoClick(infoType: InfoType)
fun onEnterClick()
fun onAmountEnterClick()
fun getFee()

View file

@ -11,6 +11,7 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.MaxEnterAmount
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
import com.tangem.common.ui.notifications.NotificationUM
@ -31,10 +32,6 @@ import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase
import com.tangem.domain.staking.IsAnyTokenStakedUseCase
import com.tangem.domain.staking.IsApproveNeededUseCase
import com.tangem.domain.staking.model.StakingApproval
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.action.StakingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
@ -52,6 +49,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.staking.analytics.StakeScreenSource
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.features.staking.impl.analytics.StakingParamsInterceptor
import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
@ -156,6 +154,14 @@ internal class StakingViewModel @Inject constructor(
).getOrElse { emptyList() }
}
private val maxEnterAmount: MaxEnterAmount
get() =
MaxEnterAmount(
amount = uiState.value.balanceState?.cryptoAmount,
fiatAmount = uiState.value.balanceState?.fiatAmount,
fiatRate = cryptoCurrencyStatus.value.fiatRate,
)
private var isInitialInfoAnalyticSent: Boolean = false
private val balanceUpdater by lazy(LazyThreadSafetyMode.NONE) {
@ -227,6 +233,18 @@ internal class StakingViewModel @Inject constructor(
}
override fun onNextClick(balanceState: BalanceState?) {
if (value.currentStep == StakingStep.InitialInfo && balanceState == null) {
stateController.update(
SetConfirmationStateInitTransformer(
isEnter = true,
isExplicitExit = false,
balanceState = null,
cryptoCurrencyStatus = cryptoCurrencyStatus,
stakingApproval = stakingApproval,
stakingAllowance = stakingAllowance,
),
)
}
stakingStateRouter.onNextClick()
}
@ -350,24 +368,20 @@ internal class StakingViewModel @Inject constructor(
)
}
override fun onEnterClick() {
override fun onAmountEnterClick() {
if (yield.preferredValidators.isEmpty()) {
stateController.updateEvent(
StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators),
)
} else {
stateController.updateAll(
SetConfirmationStateInitTransformer(
isEnter = true,
cryptoCurrencyStatus = cryptoCurrencyStatus,
stakingApproval = stakingApproval,
stakingAllowance = stakingAllowance,
),
ValidatorSelectChangeTransformer(
selectedValidator = null,
yield = yield,
),
)
if (uiState.value.actionType == StakingActionCommonType.Enter) {
stateController.updateAll(
ValidatorSelectChangeTransformer(
selectedValidator = null,
yield = yield,
),
)
}
onNextClick()
}
}
@ -382,7 +396,13 @@ internal class StakingViewModel @Inject constructor(
override fun onMaxValueClick() {
analyticsEventHandler.send(StakingAnalyticsEvent.ButtonMax)
stateController.update(AmountMaxValueStateTransformer(cryptoCurrencyStatus, yield))
stateController.update(
AmountMaxValueStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
actionType = uiState.value.actionType,
yield = yield,
),
)
}
override fun onCurrencyChangeClick(isFiat: Boolean) {
@ -439,23 +459,27 @@ internal class StakingViewModel @Inject constructor(
val networkId = cryptoCurrencyStatus.currency.network.id.value
if (isSingleAction(networkId, activeStake)) {
prepareForConfirmation(
balanceType = activeStake.type,
pendingActions = activeStake.pendingActions,
balanceState = activeStake,
validator = activeStake.validator,
amountValue = activeStake.cryptoValue,
)
onNextClick(balanceState = activeStake)
onNextClick(activeStake)
} else {
stateController.update(
ShowActionSelectorBottomSheetTransformer(
pendingActions = withStubUnstakeAction(networkId, activeStake),
onActionSelect = { action ->
prepareForConfirmation(
balanceType = activeStake.type,
pendingAction = action,
balanceState = activeStake,
validator = activeStake.validator,
amountValue = activeStake.cryptoValue,
)
stateController.update(DismissBottomSheetStateTransformer)
onNextClick(balanceState = activeStake)
onNextClick(activeStake)
},
onDismiss = { stateController.update(DismissBottomSheetStateTransformer) },
),
@ -889,12 +913,15 @@ internal class StakingViewModel @Inject constructor(
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },
balancesToShowProvider = Provider { balancesToShow },
maxEnterAmountProvider = Provider { maxEnterAmount },
),
SetConfirmationStateEmptyTransformer,
)
}
private fun prepareForConfirmation(
balanceType: BalanceType,
balanceState: BalanceState,
pendingActions: ImmutableList<PendingAction> = persistentListOf(),
pendingAction: PendingAction? = pendingActions.firstOrNull(),
validator: Yield.Validator?,
@ -903,6 +930,8 @@ internal class StakingViewModel @Inject constructor(
stateController.updateAll(
SetConfirmationStateInitTransformer(
isEnter = false,
isExplicitExit = balanceType == BalanceType.STAKED,
balanceState = balanceState,
cryptoCurrencyStatus = cryptoCurrencyStatus,
stakingApproval = stakingApproval,
pendingActions = pendingActions,
@ -913,6 +942,13 @@ internal class StakingViewModel @Inject constructor(
selectedValidator = validator,
yield = yield,
),
SetAmountDataTransformer(
clickIntents = this,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },
maxEnterAmountProvider = Provider { maxEnterAmount },
),
AmountChangeStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
value = amountValue,