Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-04 18:58:01 +05:00
commit 49fc3cc0b0
12 changed files with 126 additions and 76 deletions

View file

@ -774,6 +774,7 @@
<string name="staking_rewards">Belohnungen</string>
<string name="staking_stake_locked">Stake gesperrt</string>
<string name="staking_stake_more">Mehr staken</string>
<string name="staking_staked_amount">Gestakeder Betrag</string>
<string name="staking_summary_description_text">Du setzt %1$s ein und ethältst Deine Belohnung %2$s</string>
<string name="staking_tap_to_unlock">Zum Entsperren antippen</string>
<string name="staking_tap_to_unlock_or_vote">Tippe zum Entsperren oder Abstimmen</string>

View file

@ -764,6 +764,7 @@
<string name="staking_rewards">報酬</string>
<string name="staking_stake_locked">ステーキングはロックされています</string>
<string name="staking_stake_more">もっとステーキングする</string>
<string name="staking_staked_amount">ステーキング中の金額</string>
<string name="staking_summary_description_text">%1$sをステーキングし、報酬%2$sを受け取ります</string>
<string name="staking_tap_to_unlock">タップしてロック解除</string>
<string name="staking_tap_to_unlock_or_vote">タップしてロック解除もしくは投票</string>

View file

@ -714,6 +714,7 @@
<string name="settings_wallet_name_title">Имя</string>
<string name="staking_amount_requirement_error">Сумма для стейкинга должна быть не менее %s</string>
<string name="staking_amount_tron_integer_error">Согласно правилам сети, сумма стейкинга будет округлена до %1$sTRX.</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>
@ -756,6 +757,7 @@
<string name="staking_notification_earn_rewards_text_period_week">Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждую неделю.</string>
<string name="staking_notification_earn_rewards_text_weekly">Стейкайте безопасно и начинайте получать еженедельные награды.</string>
<string name="staking_notification_earn_rewards_title">Получите награду за стейкинг</string>
<string name="staking_notification_low_staked_balance_text">Оставшийся застейканный баланс будет слишком мал для вывода. Вам потребуется застейкать больше средств, чтобы достичь минимальной суммы для вывода.</string>
<string name="staking_notification_network_error_text">Стейкинг временно недоступен из-за проблем в сети. Пожалуйста, попробуйте позже.</string>
<string name="staking_notification_new_validator_funds_transfer">Стейкинг в сети %1$s с новым валидатором автоматически переведет ваши текущие застейканные средства на него.</string>
<string name="staking_notification_restake_rewards_text">Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход</string>
@ -798,7 +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_requirement_error">Сумма для вывода из стейкинга должна быть не менее %s</string>
<string name="staking_unstake_amount_validation_error">Сумма превышает застейканный баланс</string>
<string name="staking_unstaked">Вывод из стейкинга</string>
<string name="staking_unstaking">Завершение стейкинга</string>

View file

@ -741,6 +741,8 @@
<string name="staking_notification_earn_rewards_text_period_week">Staking allows you to earn %1$s. Your staking rewards arrive every week.</string>
<string name="staking_notification_earn_rewards_text_weekly">Stake securely and start earning weekly rewards</string>
<string name="staking_notification_earn_rewards_title">Earn staking rewards</string>
<string name="staking_notification_low_staked_balance_text">Your remaining staked balance will be too low to unstake. Youll need to stake more to meet the minimum unstake amount.</string>
<string name="staking_notification_low_staked_balance_title">Low staked balance</string>
<string name="staking_notification_network_error_text">Staking is currently unavailable due to network conditions. Please try again later.</string>
<string name="staking_notification_new_validator_funds_transfer">Staking in the %1$s network with a new validator will automatically transfer all previously staked funds to this validator</string>
<string name="staking_notification_restake_rewards_text">Reinvests your earned rewards in your staked amount, increasing potential earnings.</string>
@ -861,6 +863,7 @@
<string name="transaction_history_transaction_from_address">from: %s</string>
<string name="transaction_history_transaction_to_address">to: %s</string>
<string name="transaction_history_transaction_validator">validator: %s</string>
<string name="transfer_notification_invalid_minimum_transaction_amount_text">The minimum transaction amount is %1$s.</string>
<string name="try_to_load_data_again_button_title">Try again</string>
<string name="twin_error_same_card">You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d</string>
<string name="twin_error_wrong_twin">You\'ve scanned wrong twin card. Please try another one</string>

View file

@ -21,19 +21,19 @@ internal class SdkTransactionTypeConverter(
TxHistoryItem.TransactionType.Transfer
}
is TransactionType.TronStakingTransactionType.FreezeBalanceV2Contract -> {
TxHistoryItem.TransactionType.StakingTransactionType.Stake
TxHistoryItem.TransactionType.Staking.Stake
}
is TransactionType.TronStakingTransactionType.UnfreezeBalanceV2Contract -> {
TxHistoryItem.TransactionType.StakingTransactionType.Unstake
TxHistoryItem.TransactionType.Staking.Unstake
}
is TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
TxHistoryItem.TransactionType.StakingTransactionType.Vote(value.validatorAddress)
TxHistoryItem.TransactionType.Staking.Vote(value.validatorAddress)
}
is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> {
TxHistoryItem.TransactionType.StakingTransactionType.ClaimRewards
TxHistoryItem.TransactionType.Staking.ClaimRewards
}
is TransactionType.TronStakingTransactionType.WithdrawExpireUnfreezeContract -> {
TxHistoryItem.TransactionType.StakingTransactionType.Withdraw
TxHistoryItem.TransactionType.Staking.Withdraw
}
}
}
@ -46,19 +46,19 @@ internal class SdkTransactionTypeConverter(
"buyVoucher",
"buyVoucherPOL",
"delegate",
-> TxHistoryItem.TransactionType.StakingTransactionType.Stake
-> TxHistoryItem.TransactionType.Staking.Stake
"sellVoucher_new",
"sellVoucher_newPOL",
"undelegate",
-> TxHistoryItem.TransactionType.StakingTransactionType.Unstake
-> TxHistoryItem.TransactionType.Staking.Unstake
"unstakeClaimTokens_new",
"unstakeClaimTokens_newPOL",
"claim",
-> TxHistoryItem.TransactionType.StakingTransactionType.Withdraw
-> TxHistoryItem.TransactionType.Staking.Withdraw
"withdrawRewards",
"withdrawRewardsPOL",
-> TxHistoryItem.TransactionType.StakingTransactionType.ClaimRewards
"redelegate" -> TxHistoryItem.TransactionType.StakingTransactionType.Restake
-> TxHistoryItem.TransactionType.Staking.ClaimRewards
"redelegate" -> TxHistoryItem.TransactionType.Staking.Restake
null -> TxHistoryItem.TransactionType.UnknownOperation
else -> TxHistoryItem.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() })
}

View file

@ -40,13 +40,13 @@ data class TxHistoryItem(
data object UnknownOperation : TransactionType
data class Operation(val name: String) : TransactionType
sealed interface StakingTransactionType : TransactionType {
data class Vote(val validatorAddress: String) : StakingTransactionType
data object ClaimRewards : StakingTransactionType
data object Stake : StakingTransactionType
data object Unstake : StakingTransactionType
data object Withdraw : StakingTransactionType
data object Restake : StakingTransactionType
sealed interface Staking : TransactionType {
data class Vote(val validatorAddress: String) : Staking
data object ClaimRewards : Staking
data object Stake : Staking
data object Unstake : Staking
data object Withdraw : Staking
data object Restake : Staking
}
}

View file

@ -39,7 +39,7 @@ internal object StakingNotification {
) : NotificationUM.Warning(
title = title,
subtitle = subtitle,
iconResId = R.drawable.ic_alert_circle_24,
iconResId = R.drawable.img_attention_20,
buttonsState = buttonsState,
onCloseClick = onCloseClick,
) {
@ -47,6 +47,11 @@ internal object StakingNotification {
val title: TextReference,
val description: TextReference,
) : StakingNotification.Warning(title = title, subtitle = description)
data object LowStakedBalance : StakingNotification.Warning(
title = resourceReference(R.string.staking_notification_low_staked_balance_title),
subtitle = resourceReference(R.string.staking_notification_low_staked_balance_text),
)
}
sealed class Info(

View file

@ -1,5 +1,6 @@
package com.tangem.features.staking.impl.presentation.state.transformers.amount
import androidx.annotation.StringRes
import androidx.compose.ui.text.input.ImeAction
import com.tangem.common.extensions.isZero
import com.tangem.common.ui.amountScreen.models.AmountState
@ -26,25 +27,18 @@ internal class AmountRequirementStateTransformer(
private val actionType: StakingActionCommonType,
) : Transformer<AmountState> {
override fun transform(prevState: AmountState): AmountState {
val amountRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]
return if (prevState is AmountState.Data && amountRequirements != null) {
return if (prevState is AmountState.Data) {
updateWithError(
prevState,
actionType,
amountRequirements,
)
} else {
prevState
}
}
private fun updateWithError(
amountState: AmountState.Data,
actionType: StakingActionCommonType,
amountRequirements: AddressArgument,
): AmountState {
val isRequirementError = isRequirementError(amountState, amountRequirements)
private fun updateWithError(amountState: AmountState.Data, actionType: StakingActionCommonType): AmountState {
val requirementError = getRequirementError(amountState)
val isIntegerOnlyError = isIntegerOnlyError(amountState, actionType)
val cryptoAmount = amountState.amountTextField.cryptoAmount
@ -53,14 +47,7 @@ internal class AmountRequirementStateTransformer(
val errorText = when {
amountState.amountTextField.isError -> amountState.amountTextField.error
isRequirementError -> resourceReference(
R.string.staking_amount_requirement_error,
wrappedList(
amountRequirements.minimum.format {
crypto(cryptoCurrencyStatus.currency)
},
),
)
requirementError != null -> requirementError
isIntegerOnlyError -> when (actionType) {
StakingActionCommonType.Enter -> resourceReference(
R.string.staking_amount_tron_integer_error,
@ -70,11 +57,11 @@ internal class AmountRequirementStateTransformer(
R.string.staking_amount_tron_integer_error_unstaking,
wrappedList(value),
)
else -> TODO()
else -> TextReference.EMPTY
}
else -> TextReference.EMPTY
}
val isError = amountState.amountTextField.isError || isRequirementError
val isError = amountState.amountTextField.isError || requirementError != null
return if (!errorText.isNullOrEmpty()) {
amountState.copy(
isPrimaryButtonEnabled = !isError,
@ -92,17 +79,25 @@ internal class AmountRequirementStateTransformer(
}
}
private fun isRequirementError(prevState: AmountState.Data, amountRequirements: AddressArgument): Boolean {
val amountDecimal = prevState.amountTextField.cryptoAmount.value ?: return false
private fun getRequirementError(prevState: AmountState.Data): TextReference? {
val amountDecimal = prevState.amountTextField.cryptoAmount.value ?: return null
val isAlreadyErrorState = prevState.amountTextField.isError
val isAmountRequired = amountRequirements.required
val isAmountZero = amountDecimal.isZero()
val isExceedsRequirements =
amountRequirements.maximum?.compareTo(amountDecimal) == -1 ||
amountRequirements.minimum?.compareTo(amountDecimal) == 1
return !isAmountZero && isAmountRequired && isExceedsRequirements && !isAlreadyErrorState
if (isAlreadyErrorState || isAmountZero) return null
return when (actionType) {
StakingActionCommonType.Enter -> {
val enterRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]
enterRequirements?.getError(amountDecimal, R.string.staking_amount_requirement_error)
}
StakingActionCommonType.Exit -> {
val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT)
exitRequirements?.getError(amountDecimal, R.string.staking_unstake_amount_requirement_error)
}
else -> null
}
}
private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean {
@ -116,6 +111,20 @@ internal class AmountRequirementStateTransformer(
return isEnterOrExit && isTron && !isIntegerOnly
}
private fun AddressArgument.getError(amount: BigDecimal, @StringRes errorTextRes: Int): TextReference? {
val isExceedsRequirements = maximum?.compareTo(amount) == -1 ||
minimum?.compareTo(amount) == 1
return resourceReference(
errorTextRes,
wrappedList(
minimum.format {
crypto(cryptoCurrencyStatus.currency)
},
),
).takeIf { required && isExceedsRequirements }
}
data class Data(
val amountState: AmountState,
val actionType: StakingActionCommonType,

View file

@ -107,6 +107,7 @@ internal class AddStakingNotificationsTransformer(
notifications = this,
prevState = prevState,
sendingAmount = sendingAmount,
actionAmount = amountValue,
feeValue = feeValue,
)
}.toImmutableList()

View file

@ -17,6 +17,7 @@ import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.lib.crypto.BlockchainUtils.isCosmos
import com.tangem.lib.crypto.BlockchainUtils.isTron
import com.tangem.utils.Provider
import com.tangem.utils.extensions.isZero
import com.tangem.utils.extensions.orZero
import java.math.BigDecimal
@ -26,12 +27,22 @@ internal class StakingInfoNotificationsFactory(
private val isSubtractAvailable: Boolean,
) {
/**
* @param notifications current notification to display
* @param prevState current screen state to update
* @param sendingAmount amount being transferred from user account
* @param actionAmount any amount being transferred or used action
* @param feeValue fee amount payed from user account
*/
fun addInfoNotifications(
notifications: MutableList<NotificationUM>,
prevState: StakingUiState,
sendingAmount: BigDecimal,
actionAmount: BigDecimal,
feeValue: BigDecimal,
) = with(notifications) {
addStakingLowBalanceNotification(prevState, actionAmount)
when (prevState.actionType) {
StakingActionCommonType.Enter -> addEnterInfoNotifications(sendingAmount, feeValue)
StakingActionCommonType.Exit -> addExitInfoNotifications()
@ -149,4 +160,21 @@ internal class StakingInfoNotificationsFactory(
add(StakingNotification.Info.StakeEntireBalance)
}
}
private fun MutableList<NotificationUM>.addStakingLowBalanceNotification(
prevState: StakingUiState,
actionAmount: BigDecimal,
) {
if (prevState.actionType != StakingActionCommonType.Exit) return
val maxAmount = prevState.balanceState?.cryptoAmount ?: return
val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT) ?: return
val amountLeft = maxAmount - actionAmount
val isNotEnoughLeft = !amountLeft.isZero() && amountLeft < exitRequirements.minimum
if (exitRequirements.required && isNotEnoughLeft) {
add(StakingNotification.Warning.LowStakedBalance)
}
}
}

View file

@ -50,14 +50,14 @@ internal class TokenDetailsTxHistoryTransactionStateConverter(
} else {
when (type) {
is TransactionType.Approve -> R.drawable.ic_doc_24
is TransactionType.StakingTransactionType.Stake,
is TransactionType.StakingTransactionType.Vote,
is TransactionType.StakingTransactionType.Restake,
is TransactionType.Staking.Stake,
is TransactionType.Staking.Vote,
is TransactionType.Staking.Restake,
-> R.drawable.ic_transaction_history_staking_24
is TransactionType.StakingTransactionType.ClaimRewards,
is TransactionType.Staking.ClaimRewards,
-> R.drawable.ic_transaction_history_claim_rewards_24
is TransactionType.StakingTransactionType.Unstake,
is TransactionType.StakingTransactionType.Withdraw,
is TransactionType.Staking.Unstake,
is TransactionType.Staking.Withdraw,
-> R.drawable.ic_transaction_history_unstaking_24
is TransactionType.Operation,
is TransactionType.Swap,
@ -73,12 +73,12 @@ internal class TokenDetailsTxHistoryTransactionStateConverter(
is TransactionType.Swap -> resourceReference(R.string.common_swap)
is TransactionType.Transfer -> resourceReference(R.string.common_transfer)
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
is TransactionType.StakingTransactionType.Stake -> resourceReference(R.string.common_stake)
is TransactionType.StakingTransactionType.Unstake -> resourceReference(R.string.common_unstake)
is TransactionType.StakingTransactionType.Vote -> resourceReference(R.string.staking_vote)
is TransactionType.StakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
is TransactionType.StakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw)
is TransactionType.StakingTransactionType.Restake -> resourceReference(R.string.staking_restake)
is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)
is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw)
is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake)
}
private fun TxHistoryItem.extractSubtitle(): TextReference =
@ -121,9 +121,9 @@ internal class TokenDetailsTxHistoryTransactionStateConverter(
}
private fun TxHistoryItem.getAmount(): String {
if (type is TransactionType.StakingTransactionType.Vote ||
type == TransactionType.StakingTransactionType.ClaimRewards ||
type == TransactionType.StakingTransactionType.Withdraw
if (type is TransactionType.Staking.Vote ||
type == TransactionType.Staking.ClaimRewards ||
type == TransactionType.Staking.Withdraw
) {
return ""
}

View file

@ -49,14 +49,14 @@ internal class TxHistoryItemStateConverter(
} else {
when (type) {
is TransactionType.Approve -> R.drawable.ic_doc_24
is TransactionType.StakingTransactionType.Stake,
is TransactionType.StakingTransactionType.Vote,
is TransactionType.StakingTransactionType.Restake,
is TransactionType.Staking.Stake,
is TransactionType.Staking.Vote,
is TransactionType.Staking.Restake,
-> R.drawable.ic_transaction_history_staking_24
is TransactionType.StakingTransactionType.ClaimRewards,
is TransactionType.Staking.ClaimRewards,
-> R.drawable.ic_transaction_history_claim_rewards_24
is TransactionType.StakingTransactionType.Unstake,
is TransactionType.StakingTransactionType.Withdraw,
is TransactionType.Staking.Unstake,
is TransactionType.Staking.Withdraw,
-> R.drawable.ic_transaction_history_unstaking_24
is TransactionType.Operation,
is TransactionType.Swap,
@ -71,12 +71,12 @@ internal class TxHistoryItemStateConverter(
is TransactionType.Operation -> stringReference(type.name)
is TransactionType.Swap -> resourceReference(R.string.common_swap)
is TransactionType.Transfer -> resourceReference(R.string.common_transfer)
is TransactionType.StakingTransactionType.Stake -> resourceReference(R.string.common_stake)
is TransactionType.StakingTransactionType.Unstake -> resourceReference(R.string.common_unstake)
is TransactionType.StakingTransactionType.Vote -> resourceReference(R.string.staking_vote)
is TransactionType.StakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
is TransactionType.StakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw)
is TransactionType.StakingTransactionType.Restake -> resourceReference(R.string.staking_restake)
is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)
is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw)
is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake)
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
}
@ -121,9 +121,9 @@ internal class TxHistoryItemStateConverter(
}
private fun TxHistoryItem.getAmount(): String {
if (type is TransactionType.StakingTransactionType.Vote ||
type == TransactionType.StakingTransactionType.ClaimRewards ||
type == TransactionType.StakingTransactionType.Withdraw
if (type is TransactionType.Staking.Vote ||
type == TransactionType.Staking.ClaimRewards ||
type == TransactionType.Staking.Withdraw
) {
return ""
}