Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-11 15:39:32 +05:00
parent 72d9b99348
commit de3bdb3cb0
18 changed files with 138 additions and 49 deletions

View file

@ -65,4 +65,6 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
fun onFailedTxEmailClick(errorMessage: String)
fun openTokenDetails(cryptoCurrency: CryptoCurrency)
fun showPrimaryClickAlert()
}

View file

@ -295,13 +295,13 @@ internal class StakingModel @Inject constructor(
)
updateNotifications()
},
onStakingFeeError = {
stateController.update(AddStakingErrorTransformer())
updateNotifications(GetFeeError.UnknownError)
onStakingFeeError = { stakingFeeError ->
stateController.update(AddStakingErrorTransformer)
updateNotifications(stakingError = stakingFeeError)
},
onFeeError = { error ->
analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError)
stateController.update(AddStakingErrorTransformer())
stateController.update(AddStakingErrorTransformer)
updateNotifications(error)
},
onApprovalFee = { fee ->
@ -646,7 +646,7 @@ internal class StakingModel @Inject constructor(
}.saveIn(approvalJobHolder)
}
private fun updateNotifications(feeError: GetFeeError? = null) {
private fun updateNotifications(feeError: GetFeeError? = null, stakingError: StakingError? = null) {
modelScope.launch {
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
val feeState = confirmationState?.feeState as? FeeState.Content
@ -687,6 +687,7 @@ internal class StakingModel @Inject constructor(
currencyCheck = currencyStatus,
isSubtractAvailable = isAmountSubtractAvailable,
feeError = feeError,
stakingError = stakingError,
yield = yield,
),
)
@ -839,6 +840,14 @@ internal class StakingModel @Inject constructor(
innerRouter.openTokenDetails(userWalletId, cryptoCurrency)
}
override fun showPrimaryClickAlert() {
stateController.updateEvent(
StakingEvent.ShowAlert(
StakingAlertUM.StakeMoreClickUnavailable(cryptoCurrencyStatus.currency),
),
)
}
private suspend fun setupApprovalNeeded() {
stakingApproval = isApproveNeededUseCase(cryptoCurrencyStatus.currency).fold(
ifRight = { approval ->

View file

@ -20,20 +20,20 @@ internal object StakingNotification {
buttonState = buttonState,
onCloseClick = onCloseClick,
) {
data class StakedPositionNotFoundError(val message: String) : StakingNotification.Error(
data class StakedPositionNotFoundError(val message: String) : Error(
title = stringReference(message),
subtitle = stringReference(message),
)
data class Common(val subtitle: TextReference) : StakingNotification.Error(
data class Common(val subtitle: TextReference) : Error(
title = resourceReference(R.string.common_error),
subtitle = subtitle,
)
data class CardanoMinimumBalance(
data class MinimumAmountNotReachedError(
val title: TextReference,
val subtitle: TextReference,
) : StakingNotification.Error(
) : Error(
title = title,
subtitle = subtitle,
)
@ -54,9 +54,9 @@ internal object StakingNotification {
data class TransactionInProgress(
val title: TextReference,
val description: TextReference,
) : StakingNotification.Warning(title = title, subtitle = description)
) : Warning(title = title, subtitle = description)
data object LowStakedBalance : StakingNotification.Warning(
data object LowStakedBalance : Warning(
title = resourceReference(R.string.staking_notification_low_staked_balance_title),
subtitle = resourceReference(R.string.staking_notification_low_staked_balance_text),
)
@ -75,12 +75,12 @@ internal object StakingNotification {
) {
data class EarnRewards(
val subtitleText: TextReference,
) : StakingNotification.Info(
) : Info(
title = resourceReference(R.string.staking_notification_earn_rewards_title),
subtitle = subtitleText,
)
data object StakeEntireBalance : StakingNotification.Info(
data object StakeEntireBalance : Info(
title = resourceReference(R.string.common_network_fee_title),
subtitle = resourceReference(R.string.staking_notification_stake_entire_balance_text),
)
@ -88,7 +88,7 @@ internal object StakingNotification {
data class Unstake(
val cooldownPeriodDays: Int,
@StringRes val subtitleRes: Int,
) : StakingNotification.Info(
) : Info(
title = resourceReference(R.string.common_unstake),
subtitle = resourceReference(
subtitleRes,
@ -105,7 +105,7 @@ internal object StakingNotification {
data class Ordinary(
val title: TextReference,
val text: TextReference,
) : StakingNotification.Info(
) : Info(
title = title,
subtitle = text,
)

View file

@ -5,6 +5,7 @@ import com.tangem.common.ui.alerts.models.AlertUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.staking.impl.R
@Immutable
@ -48,4 +49,16 @@ internal sealed class StakingAlertUM : AlertUM {
override val message: TextReference = resourceReference(id = R.string.staking_error_no_validators_message)
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
}
data class StakeMoreClickUnavailable(
val cryptoCurrency: CryptoCurrency,
) : StakingAlertUM() {
override val onConfirmClick: (() -> Unit)? = null
override val title: TextReference? = null
override val message: TextReference = resourceReference(
id = R.string.staking_stake_more_button_unavailability_reason,
wrappedList(cryptoCurrency.name, cryptoCurrency.symbol),
)
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
}
}

View file

@ -74,4 +74,6 @@ internal object StakingClickIntentsStub : StakingClickIntents {
override fun openTokenDetails(cryptoCurrency: CryptoCurrency) {}
override fun onActiveStakeAnalytic() {}
override fun showPrimaryClickAlert() {}
}

View file

@ -1,39 +1,20 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.features.staking.impl.presentation.state.FeeState
import com.tangem.features.staking.impl.presentation.state.StakingNotification
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toPersistentList
internal class AddStakingErrorTransformer(
private val error: StakingError? = null,
) : Transformer<StakingUiState> {
internal object AddStakingErrorTransformer : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val confirmationState =
prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
val notifications = buildList {
addAll(confirmationState.notifications)
error?.let { add(convertToNotification(it)) }
}.toPersistentList()
return prevState.copy(
confirmationState = confirmationState.copy(
notifications = notifications,
feeState = FeeState.Error,
),
)
}
private fun convertToNotification(error: StakingError): NotificationUM {
return StakingNotification.Error.Common(
subtitle = stringReference(error.toString()),
)
}
}

View file

@ -47,15 +47,22 @@ internal class SetButtonsStateTransformer(
val isCompleted = innerConfirmState == InnerConfirmationStakingState.COMPLETED
val isIconVisible = isConfirmation && !isCompleted
val isPrimaryButtonDisabled = prevState.isPrimaryButtonDisabled()
return NavigationButton(
textReference = prevState.getButtonText(),
iconRes = R.drawable.ic_tangem_24,
isSecondary = false,
isDimmed = isPrimaryButtonDisabled,
isIconVisible = isIconVisible,
showProgress = isInProgress,
isEnabled = prevState.isButtonEnabled(),
onClick = { prevState.onPrimaryClick() },
).takeIf { prevState.isPrimaryButtonVisible() }
onClick = {
if (isPrimaryButtonDisabled) {
prevState.clickIntents.showPrimaryClickAlert()
} else {
prevState.onPrimaryClick()
}
},
)
}
private fun getPrevButton(prevState: StakingUiState): NavigationButton? {
@ -186,12 +193,12 @@ internal class SetButtonsStateTransformer(
-> true
}
private fun StakingUiState.isPrimaryButtonVisible(): Boolean {
private fun StakingUiState.isPrimaryButtonDisabled(): Boolean {
val initialState = initialInfoState as? StakingStates.InitialInfoState.Data
val hasNotStaking = initialState?.yieldBalance == InnerYieldBalanceState.Empty
val isCardano = BlockchainUtils.isCardano(cryptoCurrencyBlockchainId)
return hasNotStaking || !(isCardano && currentStep == StakingStep.InitialInfo)
return !hasNotStaking && isCardano && currentStep == StakingStep.InitialInfo
}
private fun StakingUiState.isButtonEnabled(): Boolean {

View file

@ -121,7 +121,7 @@ internal class AmountRequirementStateTransformer(
val isExceedsMaxRequirement = if (maximum?.isPositive() == true) {
maximum?.compareTo(amount) == -1
} else {
cryptoCurrencyStatus.value.amount?.compareTo(amount) == 1
cryptoCurrencyStatus.value.amount?.compareTo(amount) == -1
}
val errorText = when {

View file

@ -11,7 +11,10 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addRentExemptionN
import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.StakingErrors
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.tokens.model.CryptoCurrency
@ -39,6 +42,7 @@ internal class AddStakingNotificationsTransformer(
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
private val currencyWarning: CryptoCurrencyWarning?,
private val feeError: GetFeeError?,
private val stakingError: StakingError?,
private val currencyCheck: CryptoCurrencyCheck,
private val isSubtractAvailable: Boolean,
private val yield: Yield,
@ -93,6 +97,9 @@ internal class AddStakingNotificationsTransformer(
onReload = prevState.clickIntents::getFee,
feeValue = feeValue,
)
addStakingErrorNotifications(
stakingError = stakingError,
)
// warnings
addWarningNotifications(
prevState = prevState,
@ -128,6 +135,22 @@ internal class AddStakingNotificationsTransformer(
)
}
private fun MutableList<NotificationUM>.addStakingErrorNotifications(stakingError: StakingError?) {
when (stakingError) {
is StakingError.StakeKitApiError -> {
if (stakingError.message != StakingErrors.MinimumAmountNotReachedError.message) {
add(
StakingNotification.Error.Common(subtitle = stringReference(stakingError.toString())),
)
}
}
null -> Unit
else -> add(
StakingNotification.Error.Common(subtitle = stringReference(stakingError.toString())),
)
}
}
private fun MutableList<NotificationUM>.addErrorNotifications(
prevState: StakingUiState,
onReload: () -> Unit,
@ -193,7 +216,7 @@ internal class AddStakingNotificationsTransformer(
) {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val appCurrency = appCurrencyProvider()
val cryptoCurrency = cryptoCurrencyStatus.currency
cryptoCurrencyStatus.currency
addRentExemptionNotification(
rentWarning = currencyCheck.rentWarning,

View file

@ -175,7 +175,7 @@ internal class StakingInfoNotificationsFactory(
val balance = cryptoCurrencyStatus.value.amount.orZero()
if (isCardano && balance - feeValue < MINIMUM_STAKE_BALANCE) {
add(
StakingNotification.Error.CardanoMinimumBalance(
StakingNotification.Error.MinimumAmountNotReachedError(
title = resourceReference(R.string.staking_notification_minimum_balance_title),
subtitle = resourceReference(R.string.staking_notification_minimum_stake_ada_text),
),
@ -189,7 +189,7 @@ internal class StakingInfoNotificationsFactory(
val balance = cryptoCurrencyStatus.value.amount.orZero()
if (isCardano && balance - feeValue < MINIMUM_RESTAKE_BALANCE) {
add(
StakingNotification.Error.CardanoMinimumBalance(
StakingNotification.Error.MinimumAmountNotReachedError(
title = resourceReference(R.string.staking_notification_minimum_restake_ada_title),
subtitle = resourceReference(R.string.staking_notification_minimum_restake_ada_text),
),

View file

@ -29,7 +29,7 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t
StakingActionType.REVOTE -> resourceReference(R.string.staking_revote)
StakingActionType.REBOND -> resourceReference(R.string.staking_rebond)
StakingActionType.MIGRATE -> resourceReference(R.string.staking_migrate)
StakingActionType.STAKE -> resourceReference(R.string.common_stake)
StakingActionType.STAKE -> resourceReference(R.string.staking_restake)
StakingActionType.UNSTAKE -> resourceReference(R.string.common_unstake)
StakingActionType.UNKNOWN -> TextReference.EMPTY
null -> TextReference.EMPTY