Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-30 20:42:23 +05:00
commit e817b032f4
12 changed files with 83 additions and 50 deletions

View file

@ -671,6 +671,7 @@
<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_title">Earn staking rewards</string>
<string name="staking_notification_unstake_text">Rewards stop accruing immediately after you start unstaking. The unstaking process takes %s.</string>
<string name="staking_preparing">Preparing</string>
<string name="staking_ready_to_withdraw">Ready to withdraw</string>
<string name="staking_rebond">Rebond</string>
<string name="staking_restake">Restake</string>

View file

@ -78,11 +78,28 @@ data class PendingAction(
enum class BalanceType {
AVAILABLE,
STAKED,
UNSTAKING,
UNSTAKED,
PREPARING,
REWARDS,
LOCKED,
UNSTAKING,
UNLOCKING,
UNSTAKED,
LOCKED,
REWARDS,
UNKNOWN,
;
companion object {
fun BalanceType.isClickable() = when (this) {
STAKED,
UNSTAKED,
-> true
AVAILABLE,
UNSTAKING,
PREPARING,
REWARDS,
LOCKED,
UNLOCKING,
UNKNOWN,
-> false
}
}
}

View file

@ -39,5 +39,6 @@ data class BalanceState(
val fiatAmount: TextReference,
val rawCurrencyId: String?,
val unbondingPeriod: TextReference,
val warmupPeriod: TextReference,
val pendingActions: ImmutableList<PendingAction>,
)

View file

@ -26,9 +26,7 @@ internal class StakingStateRouter(
StakingStep.Validators,
StakingStep.Amount,
-> showConfirmation()
StakingStep.Confirmation -> {
// TODO staking handle
}
StakingStep.Confirmation -> showInitial()
}
}

View file

@ -1,6 +1,9 @@
package com.tangem.features.staking.impl.presentation.state.converters
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
@ -46,16 +49,11 @@ internal class RewardsValidatorStateConverter(
}
val cryptoValue = balance.amount
val fiatValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoValue)
val unbondingPeriod = yield.metadata.cooldownPeriod.days
validator?.toBalanceState(
cryptoCurrencyStatus = cryptoCurrencyStatus,
cryptoValue = cryptoValue,
fiatValue = fiatValue,
unbondingPeriod = pluralReference(
id = R.plurals.common_days,
count = unbondingPeriod,
formatArgs = wrappedList(unbondingPeriod),
),
pendingActions = balance.pendingActions.toPersistentList(),
)
}
@ -64,12 +62,12 @@ internal class RewardsValidatorStateConverter(
cryptoCurrencyStatus: CryptoCurrencyStatus,
cryptoValue: BigDecimal,
fiatValue: BigDecimal?,
unbondingPeriod: TextReference,
pendingActions: ImmutableList<PendingAction>,
): BalanceState {
val appCurrency = appCurrencyProvider()
val cryptoCurrency = cryptoCurrencyStatus.currency
val unbondingPeriod = yield.metadata.cooldownPeriod.days
val warmupPeriod = yield.metadata.warmupPeriod.days
val cryptoAmount = stringReference(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoValue,
@ -94,7 +92,16 @@ internal class RewardsValidatorStateConverter(
cryptoAmount = cryptoAmount,
fiatAmount = fiatAmount,
rawCurrencyId = cryptoCurrency.id.rawCurrencyId,
unbondingPeriod = unbondingPeriod,
unbondingPeriod = pluralReference(
id = R.plurals.common_days,
count = unbondingPeriod,
formatArgs = wrappedList(unbondingPeriod),
),
warmupPeriod = pluralReference(
id = R.plurals.common_days,
count = unbondingPeriod,
formatArgs = wrappedList(warmupPeriod),
),
pendingActions = pendingActions,
)
}

View file

@ -6,6 +6,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.BalanceType.Companion.isClickable
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.BalanceGroupedState
@ -61,7 +62,7 @@ internal class YieldBalancesConverter(
.groupBy { it.type.toGroup() }
.mapNotNull { item ->
val (title, footer) = getGroupTitle(item.key)
val isClickable = getClickableType(item.key)
val isClickable = item.key.isClickable()
title?.let {
BalanceGroupedState(
items = item.value.mapBalances().toPersistentList(),
@ -88,6 +89,7 @@ internal class YieldBalancesConverter(
val cryptoAmount = balance.amount
val fiatAmount = cryptoCurrencyStatus.value.fiatRate?.times(cryptoAmount)
val unbonding = getUnbondingDate(balance.date)
val warmupPeriod = yield.metadata.warmupPeriod.days
validator?.let {
BalanceState(
validator = validator,
@ -108,6 +110,7 @@ internal class YieldBalancesConverter(
),
rawCurrencyId = balance.rawCurrencyId,
unbondingPeriod = unbonding,
warmupPeriod = pluralReference(R.plurals.common_days, warmupPeriod, wrappedList(warmupPeriod)),
pendingActions = balance.pendingActions.toPersistentList(),
)
}
@ -128,27 +131,13 @@ internal class YieldBalancesConverter(
resourceReference(R.string.staking_unstaked_footer)
BalanceType.UNSTAKING -> resourceReference(R.string.staking_unstaking) to null
BalanceType.AVAILABLE -> null to null
BalanceType.PREPARING -> null to null
BalanceType.PREPARING -> resourceReference(R.string.staking_preparing) to null
BalanceType.REWARDS -> null to null
BalanceType.LOCKED -> null to null
BalanceType.UNLOCKING -> null to null
BalanceType.UNKNOWN -> null to null
}
private fun getClickableType(type: BalanceType) = when (type) {
BalanceType.STAKED,
BalanceType.UNSTAKED,
-> true
BalanceType.AVAILABLE,
BalanceType.UNSTAKING,
BalanceType.PREPARING,
BalanceType.REWARDS,
BalanceType.LOCKED,
BalanceType.UNLOCKING,
BalanceType.UNKNOWN,
-> false
}
private fun getUnbondingDate(date: DateTime?): TextReference {
val now = DateTime.now().millis
val nowCalendar = Calendar.getInstance()

View file

@ -6,7 +6,10 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.BalanceGroupedState
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 kotlinx.collections.immutable.persistentListOf
internal object InitialStakingStatePreview {
@ -87,6 +90,7 @@ internal object InitialStakingStatePreview {
),
unbondingPeriod = stringReference("3 days"),
pendingActions = persistentListOf(),
warmupPeriod = stringReference("3 days"),
),
),
),

View file

@ -152,7 +152,7 @@ internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
private fun StakingUiState.onConfirmationClick() {
if (confirmationState is StakingStates.ConfirmationState.Data) {
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
clickIntents.onBackClick()
clickIntents.onNextClick()
} else {
val isEnterAction = actionType == StakingActionCommonType.ENTER
val isApproveNeeded = confirmationState.isApprovalNeeded

View file

@ -4,11 +4,9 @@ import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.*
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.TransactionDoneState
import com.tangem.utils.Provider
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
internal class SetConfirmationStateCompletedTransformer(
private val appCurrencyProvider: Provider<AppCurrency>,
@ -39,6 +37,7 @@ internal class SetConfirmationStateCompletedTransformer(
validatorState = validatorState.copySealed(
isClickable = false,
),
notifications = persistentListOf(),
transactionDoneState = TransactionDoneState.Content(
timestamp = System.currentTimeMillis(),
txUrl = txUrl,

View file

@ -3,11 +3,15 @@ package com.tangem.features.staking.impl.presentation.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -45,11 +49,12 @@ internal fun StakingConfirmationContent(
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
) {
val doneState = state.transactionDoneState
AnimatedVisibility(
visible = state.transactionDoneState is TransactionDoneState.Content,
visible = doneState is TransactionDoneState.Content,
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
) {
val transactionDoneStateContent = state.transactionDoneState as TransactionDoneState.Content
val transactionDoneStateContent = remember(this) { doneState as TransactionDoneState.Content }
TransactionDoneTitle(
titleRes = R.string.sent_transaction_sent_title,
date = transactionDoneStateContent.timestamp,

View file

@ -209,7 +209,7 @@ private fun ActiveStakingBlock(groups: ImmutableList<BalanceGroupedState>, onCli
InputRowImageInfo(
subtitle = stringReference(balance.validator.name),
caption = getCaption(group.type, balance),
isGrayscaleImage = group.type == BalanceType.UNSTAKING,
isGrayscaleImage = !group.isClickable,
infoTitle = balance.fiatAmount,
infoSubtitle = balance.cryptoAmount,
imageUrl = balance.validator.image.orEmpty(),
@ -248,6 +248,18 @@ private fun getCaption(balanceType: BalanceType, balance: BalanceState): TextRef
BalanceType.UNSTAKED -> {
resourceReference(R.string.staking_ready_to_withdraw)
}
BalanceType.PREPARING -> {
combinedReference(
resourceReference(R.string.staking_details_warmup_period),
annotatedReference {
appendSpace()
appendColored(
text = balance.warmupPeriod.resolveReference(),
color = TangemTheme.colors.text.accent,
)
},
)
}
else -> {
combinedReference(
resourceReference(R.string.staking_details_apr),

View file

@ -164,23 +164,19 @@ internal class StakingViewModel @Inject constructor(
stateController.update { it.copy(actionType = actionType) }
}
stakingStateRouter.onNextClick()
if (isAssentState()) {
getFee(pendingAction)
when {
isInitState() -> stateController.update(SetConfirmationStateLoadingTransformer(yield))
isAssentState() -> getFee(pendingAction)
}
}
override fun onActionClick() {
handleOnNextConfirmationClick()
stakingStateRouter.onNextClick()
}
override fun getFee(pendingAction: PendingAction?) {
viewModelScope.launch {
stateController.update(
SetConfirmationStateLoadingTransformer(
yield = yield,
),
)
stateController.update(SetConfirmationStateLoadingTransformer(yield))
val cryptoCurrencyValue = cryptoCurrencyStatus.value
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
?: error("No confirmation state")
@ -871,6 +867,10 @@ internal class StakingViewModel @Inject constructor(
InnerConfirmationStakingState.ASSENT
}
private fun isInitState(): Boolean {
return value.currentStep == StakingStep.InitialInfo
}
private suspend fun checkIfSubtractAvailable() {
isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(userWalletId, cryptoCurrencyStatus.currency)
.getOrElse { false }