Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-03 16:13:36 +03:00
commit 1706a25d1a
61 changed files with 717 additions and 347 deletions

View file

@ -36,6 +36,8 @@ internal class SendRecipientMemoFieldConverter(
Blockchain.Stellar.id,
Blockchain.Hedera.id,
Blockchain.Algorand.id,
Blockchain.Sei.id,
Blockchain.InternetComputer.id,
-> {
convert(
value = Data(memo = memo, label = R.string.send_extras_hint_memo),

View file

@ -85,17 +85,12 @@ internal object StakingNotification {
),
)
data class PendingAction(
data class Ordinary(
val title: TextReference,
val text: TextReference,
) : StakingNotification.Info(
title = title,
subtitle = text,
)
data object TronRevote : StakingNotification.Info(
title = resourceReference(R.string.staking_revote),
subtitle = resourceReference(R.string.staking_notifications_revote_tron_text),
)
}
}

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
@ -98,7 +99,7 @@ internal sealed class StakingStates {
val transactionDoneState: TransactionDoneState,
val isApprovalNeeded: Boolean,
val reduceAmountBy: BigDecimal?,
val balanceState: BalanceState?,
val possiblePendingTransaction: PendingTransaction?,
) : ConfirmationState()
data class Empty(

View file

@ -8,6 +8,7 @@ import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.BalanceType.Companion.isClickable
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.BalanceState
@ -83,7 +84,11 @@ internal class BalanceItemConverter(
private fun getSubtitle(balance: BalanceItem) = when (balance.type) {
BalanceType.UNSTAKING -> getUnbondingDate(balance.date)
BalanceType.UNSTAKED -> resourceReference(R.string.staking_tap_to_withdraw)
BalanceType.LOCKED -> resourceReference(R.string.staking_tap_to_unlock)
BalanceType.LOCKED -> if (balance.pendingActions.any { it.type == StakingActionType.VOTE_LOCKED }) {
resourceReference(R.string.staking_tap_to_unlock_or_vote)
} else {
resourceReference(R.string.staking_tap_to_unlock)
}
BalanceType.PREPARING -> {
val warmupPeriod = yield.metadata.warmupPeriod.days
combinedReference(
@ -100,8 +105,8 @@ internal class BalanceItemConverter(
-> null
}
private fun getUnbondingDate(date: DateTime?): TextReference {
val unbondingPeriod = yield.metadata.cooldownPeriod.days
private fun getUnbondingDate(date: DateTime?): TextReference? {
val unbondingPeriod = yield.metadata.cooldownPeriod?.days ?: return null
if (date == null) {
return combinedReference(
resourceReference(R.string.staking_details_unbonding_period),

View file

@ -28,12 +28,17 @@ internal class YieldBalancesConverter(
val cryptoCurrency = cryptoCurrencyStatus.currency
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance
val balanceToShowItems = balancesToShowProvider()
return if (yieldBalance is YieldBalance.Data) {
val cryptoRewardsValue = yieldBalance.getRewardStakingBalance()
val fiatRewardsValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoRewardsValue)
return if (yieldBalance is YieldBalance.Data || balanceToShowItems.any { it.isPending }) {
val cryptoRewardsValue = (yieldBalance as? YieldBalance.Data)?.getRewardStakingBalance()
val balanceToShowItems = balancesToShowProvider()
val fiatRate = cryptoCurrencyStatus.value.fiatRate
val fiatRewardsValue = if (fiatRate != null && cryptoRewardsValue != null) {
fiatRate.times(cryptoRewardsValue)
} else {
null
}
InnerYieldBalanceState.Data(
rewardsCrypto = BigDecimalFormatter.formatCryptoAmount(

View file

@ -8,10 +8,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.staking.*
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.SubmitHashData
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
@ -35,6 +32,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import timber.log.Timber
import java.math.BigDecimal
import java.util.UUID
@Suppress("LongParameterList")
internal class StakingTransactionSender @AssistedInject constructor(
@ -91,12 +89,16 @@ internal class StakingTransactionSender @AssistedInject constructor(
sendStakingTransaction(
fullTransactionsData = fullTransactionsData,
balanceState = confirmationState.balanceState,
possiblePendingTransaction = composePendingTransaction(confirmationState),
onSendSuccess = onSendSuccess,
onSendError = onSendError,
)
}
private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
return confirmationState.possiblePendingTransaction ?: composeStakeTransaction(confirmationState)
}
private suspend fun getStakingTransactions(
state: StakingUiState,
confirmationState: StakingStates.ConfirmationState.Data,
@ -204,7 +206,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
private suspend fun sendStakingTransaction(
fullTransactionsData: List<FullTransactionData>,
balanceState: BalanceState?,
possiblePendingTransaction: PendingTransaction,
onSendSuccess: (txUrl: String) -> Unit,
onSendError: (SendTransactionError?) -> Unit,
) {
@ -218,13 +220,9 @@ internal class StakingTransactionSender @AssistedInject constructor(
},
ifRight = { transactionHashes ->
submitHash(
transactionIds = fullTransactionsData.map { it.stakeKitTransaction.id },
transactions = fullTransactionsData.map { it.stakeKitTransaction },
transactionHashes = transactionHashes,
groupId = balanceState?.groupId,
validator = balanceState?.validator,
amount = balanceState?.cryptoDecimal,
balanceType = balanceState?.type,
rawCurrencyId = balanceState?.rawCurrencyId,
pendingTransaction = possiblePendingTransaction,
)
val txUrl = getExplorerTransactionUrlUseCase(
txHash = transactionHashes.last(),
@ -238,25 +236,18 @@ internal class StakingTransactionSender @AssistedInject constructor(
}
private suspend fun submitHash(
transactionIds: List<String>,
transactions: List<StakingTransaction>,
transactionHashes: List<String>,
groupId: String?,
validator: Yield.Validator?,
amount: BigDecimal?,
balanceType: BalanceType?,
rawCurrencyId: String?,
pendingTransaction: PendingTransaction,
) {
transactionIds
transactions
.zip(transactionHashes)
.forEach { (transactionId, transactionHash) ->
.forEach { (transaction, transactionHash) ->
submitHashUseCase(
SubmitHashData(
transactionId = transactionId,
transactionId = transaction.id,
transactionHash = transactionHash,
validator = validator,
amount = amount,
balanceType = balanceType,
rawCurrencyId = rawCurrencyId,
pendingTransaction = pendingTransaction,
),
)
.onLeft { error ->
@ -267,20 +258,14 @@ internal class StakingTransactionSender @AssistedInject constructor(
),
)
saveUnsubmittedHashUseCase.invoke(
transactionId = transactionId,
transactionId = transaction.id,
transactionHash = transactionHash,
)
}.onRight {
Timber.d("Successful hash submission")
savePendingTransactionUseCase.invoke(
PendingTransaction(
groupId = groupId,
type = balanceType,
amount = amount,
rawCurrencyId = rawCurrencyId,
validator = validator,
),
)
if (transaction.type != StakingTransactionType.FREEZE_ENERGY) {
savePendingTransactionUseCase.invoke(userWallet.walletId, pendingTransaction)
}
}
}
}
@ -299,6 +284,19 @@ internal class StakingTransactionSender @AssistedInject constructor(
)
}
private fun composeStakeTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
val state = stateController.value
return PendingTransaction(
groupId = UUID.randomUUID().toString(),
type = BalanceType.STAKED,
amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO,
rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId,
validator = (confirmationState.validatorState as? ValidatorState.Content)?.chosenValidator,
balancesId = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.getBalancesUniqueId() ?: 0,
)
}
private data class FullTransactionData(
val stakeKitTransaction: StakingTransaction,
val tangemTransaction: TransactionData.Compiled,

View file

@ -28,6 +28,7 @@ internal object ConfirmationStatePreviewData {
website = "https://luganodes.com/",
votingPower = 0.09778360195377911,
preferred = true,
isStrategicPartner = false,
),
Yield.Validator(
address = "0x35b1ca0f398905cf752e6fe122b51c88022fca32",
@ -40,6 +41,7 @@ internal object ConfirmationStatePreviewData {
website = "https://infstones.com/",
votingPower = 0.0034366257754399774,
preferred = true,
isStrategicPartner = false,
),
Yield.Validator(
address = "0xd14a87025109013b0a2354a775cb335f926af65a",
@ -52,6 +54,7 @@ internal object ConfirmationStatePreviewData {
website = "https://infstones.com/",
votingPower = 0.023487238579718264,
preferred = true,
isStrategicPartner = true,
),
)
@ -93,6 +96,6 @@ internal object ConfirmationStatePreviewData {
isApprovalNeeded = false,
reduceAmountBy = null,
pendingActions = null,
balanceState = null,
possiblePendingTransaction = null,
)
}

View file

@ -86,6 +86,7 @@ internal object InitialStakingStatePreview {
stakedBalance = null,
votingPower = null,
preferred = false,
isStrategicPartner = false,
),
pendingActions = persistentListOf(),
isClickable = true,

View file

@ -255,30 +255,23 @@ internal class AddStakingNotificationsTransformer(
}
private fun MutableList<NotificationUM>.addExitInfoNotifications() {
add(
StakingNotification.Info.Unstake(
cooldownPeriodDays = yield.metadata.cooldownPeriod.days,
subtitleRes = if (isCosmos(cryptoCurrencyStatusProvider().currency.network.id.value)) {
R.string.staking_notification_unstake_cosmos_text
} else {
R.string.staking_notification_unstake_text
},
),
)
val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days
if (cooldownPeriodDays != null) {
add(
StakingNotification.Info.Unstake(
cooldownPeriodDays = cooldownPeriodDays,
subtitleRes = if (isCosmos(cryptoCurrencyStatusProvider().currency.network.id.value)) {
R.string.staking_notification_unstake_cosmos_text
} else {
R.string.staking_notification_unstake_text
},
),
)
}
}
private fun MutableList<NotificationUM>.addEnterInfoNotifications() {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value)
val hasStakedBalance = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.balance
?.items?.any {
it.type == BalanceType.PREPARING ||
it.type == BalanceType.STAKED ||
it.type == BalanceType.LOCKED
} == true
if (hasStakedBalance && isTron) {
add(StakingNotification.Info.TronRevote)
}
addTronRevoteNotification()
}
private fun MutableList<NotificationUM>.addPendingInfoNotifications(prevState: StakingUiState) {
@ -298,28 +291,55 @@ internal class AddStakingNotificationsTransformer(
resourceReference(R.string.staking_notification_withdraw_text)
}
StakingActionType.UNLOCK_LOCKED -> {
val cooldownPeriodDays = yield.metadata.cooldownPeriod.days
resourceReference(R.string.staking_unlocked_locked) to resourceReference(
R.string.staking_notification_unlock_text,
wrappedList(
pluralReference(
id = R.plurals.common_days,
count = cooldownPeriodDays,
formatArgs = wrappedList(cooldownPeriodDays),
val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days
if (cooldownPeriodDays != null) {
resourceReference(R.string.staking_unlocked_locked) to resourceReference(
R.string.staking_notification_unlock_text,
wrappedList(
pluralReference(
id = R.plurals.common_days,
count = cooldownPeriodDays,
formatArgs = wrappedList(cooldownPeriodDays),
),
),
),
)
)
} else {
null to null
}
}
StakingActionType.VOTE_LOCKED -> {
resourceReference(R.string.staking_revote) to
resourceReference(R.string.staking_notifications_revote_tron_text)
}
else -> null to null
}
if (titleReference != null && textReference != null) {
add(
StakingNotification.Info.PendingAction(
StakingNotification.Info.Ordinary(
title = titleReference,
text = textReference,
),
)
}
}
private fun MutableList<NotificationUM>.addTronRevoteNotification() {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value)
val hasStakedBalance = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.balance
?.items?.any {
it.type == BalanceType.PREPARING ||
it.type == BalanceType.STAKED ||
it.type == BalanceType.LOCKED
} == true
if (isTron && hasStakedBalance) {
add(
StakingNotification.Info.Ordinary(
title = resourceReference(R.string.staking_revote),
text = resourceReference(R.string.staking_notifications_revote_tron_text),
),
)
}
}
}

View file

@ -1,18 +0,0 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.transformer.Transformer
internal class SetBalanceStateTransformer(
private val balanceState: BalanceState? = null,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val possibleConfirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
?: return prevState
return prevState.copy(
confirmationState = possibleConfirmationState.copy(balanceState = balanceState),
)
}
}

View file

@ -144,9 +144,19 @@ internal class SetButtonsStateTransformer(
clickIntents.onAmountValueChange("") // reset amount state
clickIntents.onNextClick(StakingActionCommonType.ENTER)
}
StakingStep.Validators,
StakingStep.Amount,
-> clickIntents.onNextClick()
StakingStep.Validators -> {
if (confirmationState is StakingStates.ConfirmationState.Data) {
clickIntents.onNextClick(
pendingActions = confirmationState.pendingActions,
pendingAction = confirmationState.pendingAction,
)
} else {
clickIntents.onNextClick()
}
}
StakingStep.Amount -> {
clickIntents.onNextClick()
}
StakingStep.Confirmation -> onConfirmationClick()
StakingStep.RewardsValidators -> Unit
}

View file

@ -20,9 +20,12 @@ internal class SetConfirmationStateLoadingTransformer(
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val filteredValidators = yield.validators.filter {
it.preferred
}
val possibleConfirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
val possibleValidatorState = possibleConfirmationState?.validatorState as? ValidatorState.Content
val chosenValidator = possibleValidatorState?.chosenValidator ?: yield.validators[0]
val chosenValidator = possibleValidatorState?.chosenValidator ?: filteredValidators[0]
return prevState.copy(
confirmationState = StakingStates.ConfirmationState.Data(
@ -32,7 +35,7 @@ internal class SetConfirmationStateLoadingTransformer(
validatorState = ValidatorState.Content(
isClickable = true,
chosenValidator = chosenValidator,
availableValidators = yield.validators,
availableValidators = filteredValidators,
),
notifications = persistentListOf(),
footerText = getFooter(prevState, chosenValidator),
@ -41,7 +44,7 @@ internal class SetConfirmationStateLoadingTransformer(
pendingActions = possibleConfirmationState?.pendingActions,
isApprovalNeeded = false,
reduceAmountBy = null,
balanceState = null,
possiblePendingTransaction = null,
),
)
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.ValidatorState
import com.tangem.utils.transformer.Transformer
internal object SetConfirmationStateResetAssentTransformer : Transformer<StakingUiState> {
@ -13,7 +14,7 @@ internal object SetConfirmationStateResetAssentTransformer : Transformer<Staking
confirmationState.copy(
isPrimaryButtonEnabled = true,
innerState = InnerConfirmationStakingState.ASSENT,
validatorState = confirmationState.validatorState.copySealed(isClickable = true),
validatorState = ValidatorState.Loading,
)
} else {
confirmationState

View file

@ -104,7 +104,7 @@ internal class SetInitialDataStateTransformer(
return listOfNotNull(
createAnnualPercentageRateItem(yield.validators),
createAvailableItem(cryptoCurrencyStatus),
createUnbondingPeriodItem(yield.metadata.cooldownPeriod.days),
createUnbondingPeriodItem(yield.metadata.cooldownPeriod?.days),
createMinimumRequirementItem(
cryptoCurrencyStatus = cryptoCurrencyStatus,
minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum,
@ -140,7 +140,8 @@ internal class SetInitialDataStateTransformer(
)
}
private fun createUnbondingPeriodItem(cooldownPeriodDays: Int): RoundedListWithDividersItemData {
private fun createUnbondingPeriodItem(cooldownPeriodDays: Int?): RoundedListWithDividersItemData? {
cooldownPeriodDays ?: return null
return RoundedListWithDividersItemData(
id = R.string.staking_details_unbonding_period,
startText = TextReference.Res(R.string.staking_details_unbonding_period),
@ -231,7 +232,7 @@ internal class SetInitialDataStateTransformer(
pendingActions = null,
isApprovalNeeded = isApprovalNeeded,
reduceAmountBy = null,
balanceState = null,
possiblePendingTransaction = null,
)
}

View file

@ -0,0 +1,33 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.transformer.Transformer
internal class SetPossiblePendingTransactionTransformer(
private val balanceState: BalanceState,
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val possibleConfirmationState =
prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
val balancesId = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.getBalancesUniqueId() ?: 0
return prevState.copy(
confirmationState = possibleConfirmationState.copy(
possiblePendingTransaction = PendingTransaction(
groupId = balanceState.groupId,
type = balanceState.type,
amount = balanceState.cryptoDecimal,
rawCurrencyId = balanceState.rawCurrencyId,
validator = balanceState.validator,
balancesId = balancesId,
),
),
)
}
}

View file

@ -19,6 +19,7 @@ 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.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
import com.tangem.features.staking.impl.presentation.state.StakingStates
@ -39,6 +40,7 @@ internal fun StakingConfirmationContent(
) {
if (state !is StakingStates.ConfirmationState.Data) return
val isEnterAction = type == StakingActionCommonType.ENTER
val showValidatorBlock = isEnterAction || state.pendingAction?.type == StakingActionType.VOTE_LOCKED
val isTransactionSent = state.innerState == InnerConfirmationStakingState.COMPLETED
Column(
modifier = Modifier
@ -63,7 +65,7 @@ internal fun StakingConfirmationContent(
isEditingDisabled = !isEnterAction && state.innerState != InnerConfirmationStakingState.COMPLETED,
onClick = clickIntents::onPrevClick,
)
if (isEnterAction) {
if (showValidatorBlock) {
ValidatorBlock(validatorState = state.validatorState, onClick = clickIntents::openValidators)
}
StakingFeeBlock(feeState = state.feeState, isTransactionSent = isTransactionSent)

View file

@ -5,16 +5,22 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.inputrow.InputRowImageSelector
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.*
@ -78,6 +84,9 @@ internal fun StakingValidatorListContent(
false,
)
.background(TangemTheme.colors.background.action),
subtitleExtraContent = {
ValidatorLabel(item.isStrategicPartner)
},
selectorContent = { checked, _, _ ->
AnimatedVisibility(
modifier = Modifier,
@ -99,6 +108,23 @@ internal fun StakingValidatorListContent(
}
}
@Composable
private fun RowScope.ValidatorLabel(isStrategicPartner: Boolean) {
if (isStrategicPartner) {
Text(
text = stringResource(R.string.staking_validators_label),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.icon.constant,
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(horizontal = 6.dp)
.clip(RoundedCornerShape(6.dp))
.background(TangemTheme.colors.text.accent)
.padding(horizontal = 8.dp),
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)

View file

@ -133,9 +133,12 @@ internal class StakingViewModel @Inject constructor(
private val balancesToShow: List<BalanceItem>
get() {
return cryptoCurrencyStatus.value.yieldBalance?.let {
invalidatePendingTransactionsUseCase(it).getOrElse { emptyList() }
} ?: emptyList()
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
return invalidatePendingTransactionsUseCase(
userWalletId = userWallet.walletId,
balanceItems = yieldBalance?.balance?.items ?: emptyList(),
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
).getOrElse { emptyList() }
}
private var isInitialInfoAnalyticSent: Boolean = false
@ -215,13 +218,31 @@ internal class StakingViewModel @Inject constructor(
stakingStateRouter.onNextClick()
when {
isInitState() -> {
stateController.update(SetConfirmationStateResetAssentTransformer)
stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency))
stateController.update(SetBalanceStateTransformer(balanceState))
if (balanceState != null) {
stateController.update(SetPossiblePendingTransactionTransformer(balanceState, cryptoCurrencyStatus))
}
stateController.update(
transformer = SetInitialDataStateTransformer(
clickIntents = this@StakingViewModel,
yield = yield,
isAnyTokenStaked = true,
isApprovalNeeded = stakingApproval is StakingApproval.Needed,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },
balancesToShowProvider = Provider { balancesToShow },
),
)
onRefreshSwipe(isRefreshing = false)
}
isAssentState() -> {
getFee(pendingAction, pendingActions)
stateController.update(SetBalanceStateTransformer(balanceState))
if (balanceState != null) {
stateController.update(SetPossiblePendingTransactionTransformer(balanceState, cryptoCurrencyStatus))
}
val amountState = value.amountState as? AmountState.Data
if (amountState?.amountTextField?.isWarning == true) {
stateController.update(
@ -325,6 +346,7 @@ internal class StakingViewModel @Inject constructor(
if (value.currentStep == StakingStep.Confirmation) {
when ((value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState) {
InnerConfirmationStakingState.ASSENT -> {
stateController.update(SetConfirmationStateResetAssentTransformer)
stakingStateRouter.onPrevClick()
}
null,

View file

@ -14,6 +14,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.RewardBlockType
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.error.CurrencyStatusError
@ -38,6 +39,7 @@ internal class TokenDetailsLoadedBalanceConverter(
private val currentStateProvider: Provider<TokenDetailsState>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val stakingEntryInfoProvider: Provider<StakingEntryInfo?>,
private val pendingBalancesProvider: Provider<List<BalanceItem>>,
private val stakingAvailabilityProvider: Provider<StakingAvailability>,
private val symbol: String,
private val decimals: Int,
@ -145,6 +147,8 @@ internal class TokenDetailsLoadedBalanceConverter(
val stakingEntryInfo = stakingEntryInfoProvider.invoke()
val stakingAvailability = stakingAvailabilityProvider.invoke()
val iconState = state.tokenInfoBlockState.iconState
val pendingBalances = pendingBalancesProvider.invoke()
val fiatRate = status.value.fiatRate
return when {
stakingAvailability == StakingAvailability.TemporaryUnavailable -> {
@ -153,14 +157,24 @@ internal class TokenDetailsLoadedBalanceConverter(
stakingAvailability == StakingAvailability.Unavailable -> {
null
}
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null -> {
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null && pendingBalances.isEmpty() -> {
getStakeAvailableState(stakingEntryInfo, iconState)
}
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null && pendingBalances.isNotEmpty() -> {
val pendingBalancesCryptoAmount = pendingBalances.sumOf { it.amount }
val stakingFiatAmount = fiatRate?.multiply(pendingBalancesCryptoAmount)
getStakedState(
status = status,
stakingCryptoAmount = pendingBalancesCryptoAmount,
stakingFiatAmount = stakingFiatAmount,
stakingRewardAmount = null,
)
}
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo == null -> {
null
}
else -> {
val fiatRate = status.value.fiatRate
val stakingRewardAmount = yieldBalance?.getRewardStakingBalance()?.let { fiatRate?.multiply(it) }
val stakingFiatAmount = stakingCryptoAmount?.let { fiatRate?.multiply(it) }

View file

@ -20,6 +20,7 @@ import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.staking.GetStakingIntegrationIdUseCase
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
@ -52,6 +53,7 @@ internal class TokenDetailsStateFactory(
private val stakingEntryInfoProvider: Provider<StakingEntryInfo?>,
private val stakingAvailabilityProvider: Provider<StakingAvailability>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
private val pendingBalancesProvider: Provider<List<BalanceItem>>,
private val clickIntents: TokenDetailsClickIntents,
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
@ -82,6 +84,7 @@ internal class TokenDetailsStateFactory(
appCurrencyProvider = appCurrencyProvider,
stakingEntryInfoProvider = stakingEntryInfoProvider,
stakingAvailabilityProvider = stakingAvailabilityProvider,
pendingBalancesProvider = pendingBalancesProvider,
symbol = symbol,
decimals = decimals,
clickIntents = clickIntents,

View file

@ -32,12 +32,14 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
import com.tangem.domain.staking.GetStakingPendingTransactionsUseCase
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
import com.tangem.domain.staking.GetStakingIntegrationIdUseCase
import com.tangem.domain.staking.GetYieldUseCase
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo
@ -119,6 +121,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val associateAssetUseCase: AssociateAssetUseCase,
private val getStakingPendingTransactionsUseCase: GetStakingPendingTransactionsUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val vibratorHapticManager: VibratorHapticManager,
@ -148,6 +151,8 @@ internal class TokenDetailsViewModel @Inject constructor(
private val warningsJobHolder = JobHolder()
private val swapTxJobHolder = JobHolder()
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private val stakingPendingBalances: List<BalanceItem>
get() = getStakingPendingTransactionsUseCase(userWalletId).getOrElse { emptyList() }
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var stakingEntryInfo: StakingEntryInfo? = null
@ -160,6 +165,7 @@ internal class TokenDetailsViewModel @Inject constructor(
stakingEntryInfoProvider = Provider { stakingEntryInfo },
stakingAvailabilityProvider = Provider { stakingAvailability },
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
pendingBalancesProvider = Provider { stakingPendingBalances },
clickIntents = this,
networkHasDerivationUseCase = networkHasDerivationUseCase,
getUserWalletUseCase = getUserWalletUseCase,