Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-03 14:19:06 +05:00
parent 553078f9f6
commit 1fe12ed9d7
17 changed files with 53 additions and 43 deletions

View file

@ -256,7 +256,7 @@ internal class DefaultStakingRepository(
params,
),
)
StakingActionCommonType.Exit -> stakeKitApi.createExitAction(
is StakingActionCommonType.Exit -> stakeKitApi.createExitAction(
createActionRequestBody(
userWalletId,
network,
@ -286,7 +286,7 @@ internal class DefaultStakingRepository(
params,
),
)
StakingActionCommonType.Exit -> stakeKitApi.estimateGasOnExit(
is StakingActionCommonType.Exit -> stakeKitApi.estimateGasOnExit(
createActionRequestBody(
userWalletId,
network,

View file

@ -3,7 +3,7 @@ package com.tangem.domain.staking.model.stakekit.action
sealed class StakingActionCommonType {
data object Enter : StakingActionCommonType()
data object Exit : StakingActionCommonType()
data class Exit(val partiallyUnstakeDisabled: Boolean) : StakingActionCommonType()
sealed class Pending : StakingActionCommonType() {
data object Restake : Pending()
data object Rewards : Pending()

View file

@ -114,7 +114,7 @@ internal class StakingAnalyticSender(
return when (value.actionType) {
StakingActionCommonType.Enter -> StakingActionType.STAKE
StakingActionCommonType.Exit -> StakingActionType.UNSTAKE
is StakingActionCommonType.Exit -> StakingActionType.UNSTAKE
is StakingActionCommonType.Pending -> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN
}
}

View file

@ -2,10 +2,9 @@ package com.tangem.features.staking.impl.presentation.state
import com.tangem.common.routing.AppRouter
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.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender
import com.tangem.lib.crypto.BlockchainUtils.isSolana
internal class StakingStateRouter(
private val appRouter: AppRouter,
@ -23,10 +22,9 @@ internal class StakingStateRouter(
fun onNextClick() {
when (stateController.value.currentStep) {
StakingStep.InitialInfo -> when (stateController.value.actionType) {
StakingStep.InitialInfo -> when (val actionType = stateController.value.actionType) {
StakingActionCommonType.Enter -> showAmount()
// TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing
StakingActionCommonType.Exit -> if (isSolana(stateController.value.cryptoCurrencyBlockchainId)) {
is StakingActionCommonType.Exit -> if (actionType.partiallyUnstakeDisabled) {
showConfirmation()
} else {
showAmount()
@ -54,15 +52,12 @@ internal class StakingStateRouter(
StakingStep.Amount,
-> showInitial()
StakingStep.Confirmation -> {
val isEnter = uiState.actionType == StakingActionCommonType.Enter
val isExit = uiState.actionType == StakingActionCommonType.Exit
// TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing
val isSolana = isSolana(uiState.cryptoCurrencyBlockchainId)
if (isEnter || isExit && !isSolana) {
showAmount()
} else {
showInitial()
when (val actionType = uiState.actionType) {
StakingActionCommonType.Enter -> showAmount()
is StakingActionCommonType.Pending -> showInitial()
is StakingActionCommonType.Exit -> {
if (actionType.partiallyUnstakeDisabled) showInitial() else showAmount()
}
}
}
StakingStep.Validators -> showConfirmation()

View file

@ -120,6 +120,7 @@ internal sealed class StakingStates {
val footerText: TextReference,
val transactionDoneState: TransactionDoneState,
val isApprovalNeeded: Boolean,
val isAmountEditable: Boolean,
val allowance: BigDecimal,
val reduceAmountBy: BigDecimal?,
) : ConfirmationState()

View file

@ -48,5 +48,6 @@ internal object ConfirmationStatePreviewData {
allowance = BigDecimal.ZERO,
reduceAmountBy = null,
pendingActions = null,
isAmountEditable = true,
)
}

View file

@ -26,7 +26,7 @@ internal class SetAmountDataTransformer(
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
override fun transform(prevState: StakingUiState): StakingUiState {
val title = if (prevState.actionType == StakingActionCommonType.Exit) {
val title = if (prevState.actionType is StakingActionCommonType.Exit) {
resourceReference(R.string.staking_staked_amount)
} else {
stringReference(userWalletProvider().name)

View file

@ -133,7 +133,7 @@ internal class SetButtonsStateTransformer(
resourceReference(R.string.common_stake)
}
}
StakingActionCommonType.Exit -> resourceReference(R.string.common_unstake)
is StakingActionCommonType.Exit -> resourceReference(R.string.common_unstake)
is StakingActionCommonType.Pending -> confirmationState.pendingAction?.type.getPendingActionTitle()
}
}

View file

@ -3,17 +3,21 @@ package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.staking.model.StakingApproval
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.Yield
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.isCompositePendingActions
import com.tangem.features.staking.impl.presentation.state.utils.isTronStakedBalance
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.extensions.isPositive
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
@Suppress("LongParameterList")
internal class SetConfirmationStateInitTransformer(
private val isEnter: Boolean,
private val isExplicitExit: Boolean,
@ -21,9 +25,9 @@ internal class SetConfirmationStateInitTransformer(
private val stakingApproval: StakingApproval,
private val stakingAllowance: BigDecimal,
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val yieldArgs: Yield.Args,
private val pendingActions: ImmutableList<PendingAction>? = null,
private val pendingAction: PendingAction? = pendingActions?.firstOrNull(),
) : Transformer<StakingUiState> {
private val networkId
@ -41,10 +45,10 @@ internal class SetConfirmationStateInitTransformer(
override fun transform(prevState: StakingUiState): StakingUiState {
val actionType = when {
isEnter -> StakingActionCommonType.Enter
isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit
isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit(isPartiallyUnstakeDisabled(prevState))
else -> when (pendingAction?.type) {
StakingActionType.STAKE -> StakingActionCommonType.Enter
StakingActionType.UNSTAKE -> StakingActionCommonType.Exit
StakingActionType.UNSTAKE -> StakingActionCommonType.Exit(isPartiallyUnstakeDisabled(prevState))
StakingActionType.CLAIM_REWARDS,
StakingActionType.RESTAKE_REWARDS,
-> StakingActionCommonType.Pending.Rewards
@ -67,10 +71,26 @@ internal class SetConfirmationStateInitTransformer(
transactionDoneState = TransactionDoneState.Empty,
isApprovalNeeded = stakingApproval is StakingApproval.Needed,
allowance = stakingAllowance,
isAmountEditable = actionType == StakingActionCommonType.Enter ||
actionType is StakingActionCommonType.Exit &&
!actionType.partiallyUnstakeDisabled,
reduceAmountBy = null,
pendingAction = pendingAction,
pendingActions = pendingActions.takeIf { isComposePendingActions },
),
)
}
private fun isPartiallyUnstakeDisabled(state: StakingUiState): Boolean {
// TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing
if (BlockchainUtils.isSolana(state.cryptoCurrencyBlockchainId)) {
return true
}
val exitArgs = yieldArgs.exit ?: return false
val exitAmount = exitArgs.args[Yield.Args.ArgType.AMOUNT] ?: return false
val min = exitAmount.minimum ?: return false
val max = exitAmount.maximum ?: return false
return !min.isPositive() && !max.isPositive()
}
}

View file

@ -35,7 +35,7 @@ internal object SetTitleTransformer : Transformer<StakingUiState> {
R.string.staking_title_stake,
wrappedList(prevState.cryptoCurrencyName),
)
StakingActionCommonType.Exit -> resourceReference(
is StakingActionCommonType.Exit -> resourceReference(
R.string.staking_title_unstake,
wrappedList(prevState.cryptoCurrencyName),
)

View file

@ -20,7 +20,7 @@ internal class AmountChangeStateTransformer(
override fun transform(prevState: StakingUiState): StakingUiState {
val actionType = prevState.actionType
val maxEnterAmount = if (actionType == StakingActionCommonType.Exit) {
val maxEnterAmount = if (actionType is StakingActionCommonType.Exit) {
EnterAmountBoundary(
amount = prevState.balanceState?.cryptoAmount,
fiatAmount = prevState.balanceState?.fiatAmount,

View file

@ -19,7 +19,7 @@ internal class AmountMaxValueStateTransformer(
private val maxEnterAmountConverter = MaxEnterAmountConverter()
override fun transform(prevState: StakingUiState): StakingUiState {
val maxEnterAmount = if (actionType == StakingActionCommonType.Exit) {
val maxEnterAmount = if (actionType is StakingActionCommonType.Exit) {
EnterAmountBoundary(
amount = prevState.balanceState?.cryptoAmount,
fiatAmount = prevState.balanceState?.fiatAmount,

View file

@ -61,7 +61,7 @@ internal class AmountRequirementStateTransformer(
R.string.staking_amount_tron_integer_error,
wrappedList(roundedDownCrypto),
)
StakingActionCommonType.Exit -> resourceReference(
is StakingActionCommonType.Exit -> resourceReference(
R.string.staking_amount_tron_integer_error_unstaking,
wrappedList(roundedDownCrypto),
)
@ -96,7 +96,7 @@ internal class AmountRequirementStateTransformer(
val enterRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]
enterRequirements?.getError(amountDecimal, R.string.staking_amount_requirement_error)
}
StakingActionCommonType.Exit -> {
is StakingActionCommonType.Exit -> {
val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT)
exitRequirements?.getError(amountDecimal, R.string.staking_unstake_amount_requirement_error)
}
@ -107,7 +107,7 @@ internal class AmountRequirementStateTransformer(
private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean {
val cryptoAmountValue = amountState.amountTextField.cryptoAmount.value ?: return false
val isEnterOrExit = actionType == StakingActionCommonType.Enter || actionType == StakingActionCommonType.Exit
val isEnterOrExit = actionType == StakingActionCommonType.Enter || actionType is StakingActionCommonType.Exit
val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value)
val isIntegerOnly = cryptoAmountValue.isZero() || cryptoAmountValue.remainder(BigDecimal.ONE).isZero()

View file

@ -45,7 +45,7 @@ internal class StakingInfoNotificationsFactory(
when (prevState.actionType) {
StakingActionCommonType.Enter -> addEnterInfoNotifications(sendingAmount, feeValue)
StakingActionCommonType.Exit -> addExitInfoNotifications()
is StakingActionCommonType.Exit -> addExitInfoNotifications()
is StakingActionCommonType.Pending -> addPendingInfoNotifications(prevState)
}
}
@ -167,7 +167,7 @@ internal class StakingInfoNotificationsFactory(
prevState: StakingUiState,
actionAmount: BigDecimal,
) {
if (prevState.actionType != StakingActionCommonType.Exit) return
if (prevState.actionType !is StakingActionCommonType.Exit) return
val maxAmount = prevState.balanceState?.cryptoAmount ?: return
val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT) ?: return

View file

@ -18,7 +18,6 @@ import com.tangem.core.ui.components.transactions.TransactionDoneTitle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
import com.tangem.features.staking.impl.presentation.state.StakingNotification
@ -39,11 +38,8 @@ internal fun StakingConfirmationContent(
state: StakingStates.ConfirmationState,
validatorState: StakingStates.ValidatorState,
clickIntents: StakingClickIntents,
type: StakingActionCommonType,
isSolana: Boolean, // TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing
) {
if (state !is StakingStates.ConfirmationState.Data) return
val isAmountEditable = type == StakingActionCommonType.Enter || type == StakingActionCommonType.Exit && !isSolana
val isTransactionSent = state.innerState == InnerConfirmationStakingState.COMPLETED
val isTransactionInProgress = state.notifications.any { it is StakingNotification.Warning.TransactionInProgress }
Column(
@ -65,8 +61,8 @@ internal fun StakingConfirmationContent(
}
AmountBlock(
amountState = amountState,
isClickDisabled = !isAmountEditable || isTransactionSent || isTransactionInProgress,
isEditingDisabled = !isAmountEditable && state.innerState != InnerConfirmationStakingState.COMPLETED,
isClickDisabled = !state.isAmountEditable || isTransactionSent || isTransactionInProgress,
isEditingDisabled = !state.isAmountEditable && state.innerState != InnerConfirmationStakingState.COMPLETED,
onClick = clickIntents::onPrevClick,
)
ValidatorBlock(
@ -90,8 +86,6 @@ private fun Preview_StakingConfirmationContent() {
state = ConfirmationStatePreviewData.assentStakingState,
validatorState = ValidatorStatePreviewData.validatorState,
clickIntents = StakingClickIntentsStub,
type = StakingActionCommonType.Enter,
isSolana = false,
)
}
}

View file

@ -28,7 +28,6 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingAc
import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingInfoBottomSheetConfig
import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingActionSelectorBottomSheet
import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingInfoBottomSheet
import com.tangem.lib.crypto.BlockchainUtils.isSolana
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@ -170,8 +169,6 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
state = uiState.confirmationState,
validatorState = uiState.validatorState,
clickIntents = uiState.clickIntents,
type = uiState.actionType,
isSolana = isSolana(uiState.cryptoCurrencyBlockchainId),
)
StakingStep.RestakeValidator,
StakingStep.Validators,

View file

@ -241,6 +241,7 @@ internal class StakingViewModel @Inject constructor(
cryptoCurrencyStatus = cryptoCurrencyStatus,
stakingApproval = stakingApproval,
stakingAllowance = stakingAllowance,
yieldArgs = yield.args,
),
)
}
@ -711,7 +712,7 @@ internal class StakingViewModel @Inject constructor(
feeValue = fee.orZero(),
reduceAmountBy = reduceAmountBy,
)
StakingActionCommonType.Exit,
is StakingActionCommonType.Exit,
is StakingActionCommonType.Pending,
-> BigDecimal.ZERO
}
@ -996,6 +997,7 @@ internal class StakingViewModel @Inject constructor(
pendingActions = pendingActions,
pendingAction = pendingAction,
stakingAllowance = stakingAllowance,
yieldArgs = yield.args,
),
ValidatorSelectChangeTransformer(
selectedValidator = validator,