Updated on 2026-08-14
This commit is contained in:
parent
3eb843a05f
commit
9c6d5fc4fa
15 changed files with 192 additions and 73 deletions
|
|
@ -16,6 +16,8 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
|
|
@ -508,19 +510,34 @@ internal class StakingModel @Inject constructor(
|
|||
analyticsEventHandler.send(StakingAnalyticsEvent.ButtonRewards)
|
||||
val rewardsValidators =
|
||||
stateController.value.rewardsValidatorsState as? StakingStates.RewardsValidatorsState.Data
|
||||
val rewards = rewardsValidators?.rewards
|
||||
if (rewards != null && rewards.isSingleItem()) {
|
||||
onActiveStake(rewards.first())
|
||||
} else {
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvent.ButtonValidator(
|
||||
source = StakeScreenSource.Info,
|
||||
),
|
||||
|
||||
val initialInfoState = stateController.value.initialInfoState as? StakingStates.InitialInfoState.Data
|
||||
val yieldBalance = initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data
|
||||
val rewardBlockType = yieldBalance?.reward?.rewardBlockType
|
||||
val rewardPendingActionConstraints = yieldBalance?.reward?.rewardConstraints
|
||||
|
||||
if (rewardBlockType == RewardBlockType.RewardsRequirementsError) {
|
||||
stakingEventFactory.createStakingRewardsMinimumRequirementsErrorAlert(
|
||||
cryptoCurrencyName = cryptoCurrencyStatus.currency.name,
|
||||
cryptoAmountValue = rewardPendingActionConstraints?.amountArg?.minimum?.format {
|
||||
crypto(cryptoCurrencyStatus.currency)
|
||||
}.orEmpty(),
|
||||
)
|
||||
stateController.update {
|
||||
value.copy(actionType = StakingActionCommonType.Pending.Rewards)
|
||||
} else {
|
||||
val rewards = rewardsValidators?.rewards
|
||||
if (rewards != null && rewards.isSingleItem()) {
|
||||
onActiveStake(rewards.first())
|
||||
} else {
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvent.ButtonValidator(
|
||||
source = StakeScreenSource.Info,
|
||||
),
|
||||
)
|
||||
stateController.update {
|
||||
value.copy(actionType = StakingActionCommonType.Pending.Rewards)
|
||||
}
|
||||
stakingStateRouter.showRewardsValidators()
|
||||
}
|
||||
stakingStateRouter.showRewardsValidators()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,18 +2,14 @@ package com.tangem.features.staking.impl.presentation.state
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.RewardBlockType
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Immutable
|
||||
internal sealed class InnerYieldBalanceState {
|
||||
data class Data(
|
||||
val rewardsCrypto: String,
|
||||
val rewardsFiat: String,
|
||||
val rewardBlockType: RewardBlockType,
|
||||
val reward: YieldReward,
|
||||
val isActionable: Boolean,
|
||||
val balances: ImmutableList<BalanceState>,
|
||||
) : InnerYieldBalanceState()
|
||||
|
|
@ -37,4 +33,12 @@ internal data class BalanceState(
|
|||
val validator: Yield.Validator?,
|
||||
val pendingActions: ImmutableList<PendingAction>,
|
||||
val isPending: Boolean,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal data class YieldReward(
|
||||
val rewardsCrypto: String,
|
||||
val rewardsFiat: String,
|
||||
val rewardBlockType: RewardBlockType,
|
||||
val rewardConstraints: PendingActionConstraints?,
|
||||
)
|
||||
|
|
@ -6,8 +6,10 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.YieldReward
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBSC
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -33,7 +35,8 @@ internal class YieldBalancesConverter(
|
|||
val balanceToShowItems = balancesToShowProvider()
|
||||
|
||||
return if (yieldBalance is YieldBalance.Data || balanceToShowItems.any { it.isPending }) {
|
||||
val cryptoRewardsValue = (yieldBalance as? YieldBalance.Data)?.getRewardStakingBalance()
|
||||
val yieldBalance = yieldBalance as? YieldBalance.Data
|
||||
val cryptoRewardsValue = yieldBalance?.getRewardStakingBalance()
|
||||
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate
|
||||
val fiatRewardsValue = if (fiatRate != null && cryptoRewardsValue != null) {
|
||||
|
|
@ -41,17 +44,25 @@ internal class YieldBalancesConverter(
|
|||
} else {
|
||||
null
|
||||
}
|
||||
val (type, isActionable) = getRewardBlockType()
|
||||
val type = getRewardBlockType()
|
||||
val pendingRewardsConstraints = yieldBalance?.balance?.items
|
||||
?.firstOrNull { it.type == BalanceType.REWARDS }
|
||||
?.pendingActionsConstraints
|
||||
?.firstOrNull { it.type == StakingActionType.CLAIM_REWARDS }
|
||||
|
||||
InnerYieldBalanceState.Data(
|
||||
rewardsCrypto = cryptoRewardsValue.format { crypto(cryptoCurrency) },
|
||||
rewardsFiat = fiatRewardsValue.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
rewardBlockType = type,
|
||||
isActionable = isActionable,
|
||||
reward = YieldReward(
|
||||
rewardsCrypto = cryptoRewardsValue.format { crypto(cryptoCurrency) },
|
||||
rewardsFiat = fiatRewardsValue.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
rewardBlockType = type,
|
||||
rewardConstraints = pendingRewardsConstraints,
|
||||
),
|
||||
isActionable = type.isActionable,
|
||||
balances = balanceToShowItems.mapBalances(),
|
||||
)
|
||||
} else {
|
||||
|
|
@ -66,7 +77,7 @@ internal class YieldBalancesConverter(
|
|||
.sortedBy { it.type.order }
|
||||
.toPersistentList()
|
||||
|
||||
private fun getRewardBlockType(): Pair<RewardBlockType, Boolean> {
|
||||
private fun getRewardBlockType(): RewardBlockType {
|
||||
val blockchainId = cryptoCurrencyStatus.currency.network.id.value
|
||||
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
|
||||
val rewards = yieldBalance?.balance?.items
|
||||
|
|
@ -76,9 +87,10 @@ internal class YieldBalancesConverter(
|
|||
val isRewardsClaimable = rewards?.isNotEmpty() == true
|
||||
|
||||
return when {
|
||||
isSolana(blockchainId) || isBSC(blockchainId) -> RewardBlockType.RewardUnavailable to false
|
||||
isRewardsClaimable -> RewardBlockType.Rewards to isActionable
|
||||
else -> RewardBlockType.NoRewards to false
|
||||
isSolana(blockchainId) || isBSC(blockchainId) -> RewardBlockType.RewardUnavailable
|
||||
isRewardsClaimable && isActionable -> RewardBlockType.Rewards
|
||||
isRewardsClaimable && !isActionable -> RewardBlockType.RewardsRequirementsError
|
||||
else -> RewardBlockType.NoRewards
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,4 +68,17 @@ internal sealed class StakingAlertUM : AlertUM {
|
|||
override val message: TextReference = resourceReference(id = R.string.staking_notification_ton_activate_account)
|
||||
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
|
||||
}
|
||||
|
||||
data class RewardsMinimumRequirementsError(
|
||||
val cryptoCurrencyName: String,
|
||||
val cryptoAmountValue: String,
|
||||
) : StakingAlertUM() {
|
||||
override val onConfirmClick: (() -> Unit)? = null
|
||||
override val title: TextReference? = null
|
||||
override val message: TextReference = resourceReference(
|
||||
id = R.string.staking_details_min_rewards_notification,
|
||||
formatArgs = wrappedList(cryptoCurrencyName, cryptoAmountValue),
|
||||
)
|
||||
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
|
||||
}
|
||||
}
|
||||
|
|
@ -51,4 +51,15 @@ internal class StakingEventFactory(
|
|||
val alert = StakingEvent.ShowAlert(alert = StakingAlertUM.InitializeAccount)
|
||||
stateController.updateEvent(alert)
|
||||
}
|
||||
|
||||
fun createStakingRewardsMinimumRequirementsErrorAlert(cryptoCurrencyName: String, cryptoAmountValue: String) {
|
||||
stateController.updateEvent(
|
||||
StakingEvent.ShowAlert(
|
||||
alert = StakingAlertUM.RewardsMinimumRequirementsError(
|
||||
cryptoCurrencyName = cryptoCurrencyName,
|
||||
cryptoAmountValue = cryptoAmountValue,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.previewdata
|
||||
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.domain.staking.model.stakekit.RewardBlockType
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.features.staking.impl.R
|
|||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.YieldReward
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal object InitialStakingStatePreview {
|
||||
|
|
@ -63,9 +64,12 @@ internal object InitialStakingStatePreview {
|
|||
|
||||
val stateWithYield = defaultState.copy(
|
||||
yieldBalance = InnerYieldBalanceState.Data(
|
||||
rewardsFiat = "100 $",
|
||||
rewardsCrypto = "100 SOL",
|
||||
rewardBlockType = RewardBlockType.RewardUnavailable,
|
||||
reward = YieldReward(
|
||||
rewardsFiat = "100 $",
|
||||
rewardsCrypto = "100 SOL",
|
||||
rewardBlockType = RewardBlockType.RewardUnavailable,
|
||||
rewardConstraints = null,
|
||||
),
|
||||
isActionable = true,
|
||||
balances = persistentListOf(
|
||||
BalanceState(
|
||||
|
|
|
|||
|
|
@ -43,28 +43,7 @@ internal class SetConfirmationStateInitTransformer(
|
|||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val actionType = when {
|
||||
isEnter -> StakingActionCommonType.Enter(
|
||||
skipEnterAmount = yieldArgs.enter.isPartialAmountDisabled,
|
||||
)
|
||||
isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit(isPartiallyUnstakeDisabled(prevState))
|
||||
else -> when (pendingAction?.type) {
|
||||
StakingActionType.STAKE -> StakingActionCommonType.Pending.Stake(
|
||||
skipEnterAmount = yieldArgs.enter.isPartialAmountDisabled,
|
||||
)
|
||||
StakingActionType.UNSTAKE -> StakingActionCommonType.Exit(
|
||||
partiallyUnstakeDisabled = isPartiallyUnstakeDisabled(prevState),
|
||||
)
|
||||
StakingActionType.CLAIM_REWARDS,
|
||||
StakingActionType.RESTAKE_REWARDS,
|
||||
-> StakingActionCommonType.Pending.Rewards
|
||||
StakingActionType.VOTE_LOCKED,
|
||||
StakingActionType.RESTAKE,
|
||||
-> StakingActionCommonType.Pending.Restake
|
||||
else -> StakingActionCommonType.Pending.Other
|
||||
}
|
||||
}
|
||||
|
||||
val actionType = getActionType(prevState)
|
||||
return prevState.copy(
|
||||
actionType = actionType,
|
||||
balanceState = balanceState,
|
||||
|
|
@ -88,6 +67,26 @@ internal class SetConfirmationStateInitTransformer(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getActionType(prevState: StakingUiState): StakingActionCommonType {
|
||||
val isPartialEnterAmountDisabled = yieldArgs.enter.isPartialAmountDisabled
|
||||
val isPartialExitAmountDisabled = isPartiallyUnstakeDisabled(prevState)
|
||||
return when {
|
||||
isEnter -> StakingActionCommonType.Enter(isPartialEnterAmountDisabled)
|
||||
isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit(isPartialExitAmountDisabled)
|
||||
else -> when (pendingAction?.type) {
|
||||
StakingActionType.STAKE -> StakingActionCommonType.Pending.Stake(isPartialEnterAmountDisabled)
|
||||
StakingActionType.UNSTAKE -> StakingActionCommonType.Exit(isPartialExitAmountDisabled)
|
||||
StakingActionType.CLAIM_REWARDS,
|
||||
StakingActionType.RESTAKE_REWARDS,
|
||||
-> StakingActionCommonType.Pending.Rewards
|
||||
StakingActionType.VOTE_LOCKED,
|
||||
StakingActionType.RESTAKE,
|
||||
-> StakingActionCommonType.Pending.Restake
|
||||
else -> StakingActionCommonType.Pending.Other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isPartiallyUnstakeDisabled(state: StakingUiState): Boolean {
|
||||
val isSolana = BlockchainUtils.isSolana(state.cryptoCurrencyBlockchainId)
|
||||
val isValidatorPreferred = balanceState?.validator?.preferred == true
|
||||
|
|
|
|||
|
|
@ -98,9 +98,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
onReload = prevState.clickIntents::getFee,
|
||||
feeValue = feeValue,
|
||||
)
|
||||
addStakingErrorNotifications(
|
||||
stakingError = stakingError,
|
||||
)
|
||||
addStakingErrorNotifications(stakingError = stakingError)
|
||||
// warnings
|
||||
addWarningNotifications(
|
||||
prevState = prevState,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import androidx.compose.ui.unit.Density
|
|||
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
|
||||
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
|
||||
import com.tangem.core.ui.components.inputrow.InputRowDefault
|
||||
import com.tangem.core.ui.components.inputrow.InputRowImageInfo
|
||||
import com.tangem.core.ui.components.list.roundedListWithDividersItems
|
||||
|
|
@ -38,19 +39,18 @@ import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
|||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.domain.staking.model.stakekit.RewardBlockType
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.previewdata.InitialStakingStatePreview
|
||||
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
|
||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
||||
|
|
@ -214,14 +214,17 @@ private fun StakingRewardBlock(
|
|||
onRewardsClick: () -> Unit,
|
||||
isBalanceHidden: Boolean,
|
||||
) {
|
||||
val (text, textColor) = when (yieldBalanceState.rewardBlockType) {
|
||||
RewardBlockType.Rewards -> {
|
||||
val reward = yieldBalanceState.reward
|
||||
val (text, textColor) = when (yieldBalanceState.reward.rewardBlockType) {
|
||||
RewardBlockType.RewardsRequirementsError,
|
||||
RewardBlockType.Rewards,
|
||||
-> {
|
||||
annotatedReference {
|
||||
append(yieldBalanceState.rewardsFiat.orMaskWithStars(isBalanceHidden))
|
||||
append(reward.rewardsFiat.orMaskWithStars(isBalanceHidden))
|
||||
appendSpace()
|
||||
append(DOT)
|
||||
appendSpace()
|
||||
append(yieldBalanceState.rewardsCrypto.orMaskWithStars(isBalanceHidden))
|
||||
append(reward.rewardsCrypto.orMaskWithStars(isBalanceHidden))
|
||||
} to TangemTheme.colors.text.primary1
|
||||
}
|
||||
RewardBlockType.RewardUnavailable -> {
|
||||
|
|
@ -232,7 +235,7 @@ private fun StakingRewardBlock(
|
|||
resourceReference(R.string.staking_details_no_rewards_to_claim) to TangemTheme.colors.text.tertiary
|
||||
}
|
||||
}
|
||||
val isShowIcon = yieldBalanceState.rewardBlockType == RewardBlockType.Rewards && yieldBalanceState.isActionable
|
||||
val isShowIcon = reward.rewardBlockType == RewardBlockType.Rewards && yieldBalanceState.isActionable
|
||||
InputRowDefault(
|
||||
title = resourceReference(R.string.staking_rewards),
|
||||
text = text,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue