Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-03 20:28:55 +05:00
commit 920d664fb6
11 changed files with 181 additions and 88 deletions

View file

@ -183,6 +183,22 @@ sealed class NotificationUM(val config: NotificationConfig) {
)
}
open class Info(
title: TextReference,
subtitle: TextReference,
iconResId: Int = R.drawable.ic_alert_circle_24,
buttonsState: NotificationConfig.ButtonsState? = null,
onCloseClick: (() -> Unit)? = null,
) : NotificationUM(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = iconResId,
buttonsState = buttonsState,
onCloseClick = onCloseClick,
),
)
sealed interface Cardano {
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Warning(

View file

@ -84,6 +84,7 @@
<string name="common_camera_denied_alert_message">You have not given access to your camera, please adjust your privacy settings</string>
<string name="common_cancel">Cancel</string>
<string name="common_choose_action">Choose action</string>
<string name="common_claim">Claim</string>
<string name="common_claim_rewards">Claim rewards</string>
<string name="common_close">Close</string>
<string name="common_continue">Continue</string>
@ -666,11 +667,13 @@
<string name="staking_locked">Locked</string>
<string name="staking_migrate">Migrate</string>
<string name="staking_native">Native staking</string>
<string name="staking_notification_claim_rewards_text">Earned rewards are sent directly to your address. Once claimed, the funds are available for use.</string>
<string name="staking_notification_earn_rewards_text_period_day">Staking allows you to earn %1$s. Your staking rewards arrive every day.</string>
<string name="staking_notification_earn_rewards_text_period_hour">Staking allows you to earn %1$s. Your staking rewards arrive every hour.</string>
<string name="staking_notification_earn_rewards_text_period_month">Staking allows you to earn %1$s. Your staking rewards arrive every month.</string>
<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_restake_rewards_text">Reinvests your earned rewards in your staked amount, increasing potential earnings.</string>
<string name="staking_notification_unstake_text">Rewards stop accruing immediately after you start unstaking. The unstaking process takes %s.</string>
<string name="staking_notification_withdraw_text">Your funds have been unstaked. You can now withdraw them along with your earned rewards.</string>
<string name="staking_preparing">Preparing</string>

View file

@ -29,6 +29,7 @@ internal object StakingNotification {
subtitle = subtitle,
)
}
sealed class Warning(
title: TextReference,
subtitle: TextReference,
@ -40,17 +41,35 @@ internal object StakingNotification {
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = buttonsState,
onCloseClick = onCloseClick,
) {
data class TransactionInProgress(
val title: TextReference,
val description: TextReference,
) : StakingNotification.Warning(title = title, subtitle = description)
}
sealed class Info(
title: TextReference,
subtitle: TextReference,
buttonsState: NotificationConfig.ButtonsState? = null,
onCloseClick: (() -> Unit)? = null,
) : NotificationUM.Info(
title = title,
subtitle = subtitle,
buttonsState = buttonsState,
onCloseClick = onCloseClick,
) {
data class EarnRewards(
val subtitleText: TextReference,
) : StakingNotification.Warning(
) : StakingNotification.Info(
title = resourceReference(R.string.staking_notification_earn_rewards_title),
subtitle = subtitleText,
)
data class Unstake(
val cooldownPeriodDays: Int,
) : StakingNotification.Warning(
) : StakingNotification.Info(
title = resourceReference(R.string.common_unstake),
subtitle = resourceReference(
R.string.staking_notification_unstake_text,
@ -64,9 +83,12 @@ internal object StakingNotification {
),
)
data class TransactionInProgress(
data class PendingAction(
val title: TextReference,
val description: TextReference,
) : StakingNotification.Warning(title = title, subtitle = description)
val text: TextReference,
) : StakingNotification.Info(
title = title,
subtitle = text,
)
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.features.staking.impl.presentation.state.converters
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
@ -13,7 +12,6 @@ import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.utils.Provider
import com.tangem.utils.StringsSigns.PLUS
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@ -74,14 +72,11 @@ internal class RewardsValidatorStateConverter(
cryptoCurrency = cryptoCurrency,
),
)
val fiatAmount = combinedReference(
stringReference(PLUS),
stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatValue,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),
val fiatAmount = stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatValue,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),
)

View file

@ -79,7 +79,7 @@ internal object ConfirmationStatePreviewData {
),
footerText = "You stake \$715.11 and will be receiving ~\$35 monthly",
notifications = persistentListOf(
StakingNotification.Warning.EarnRewards(
StakingNotification.Info.EarnRewards(
subtitleText = resourceReference(
id = R.string.staking_notification_earn_rewards_text_period_day,
formatArgs = wrappedList("Solana"),

View file

@ -11,12 +11,17 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachable
import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.model.AppCurrency
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.domain.tokens.model.warnings.CryptoCurrencyCheck
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.features.staking.impl.R
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
@ -40,6 +45,7 @@ internal class AddStakingNotificationsTransformer(
private val feeError: GetFeeError?,
private val currencyCheck: CryptoCurrencyCheck,
private val isSubtractAvailable: Boolean,
private val yield: Yield,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
@ -87,7 +93,7 @@ internal class AddStakingNotificationsTransformer(
isFeeCoverage = isFeeCoverage && isEnterAction,
)
addAll(confirmationState.notifications)
addInfoNotifications(prevState)
}.toImmutableList()
return prevState.copy(
@ -186,4 +192,70 @@ internal class AddStakingNotificationsTransformer(
onReduceClick = prevState.clickIntents::onAmountReduceToClick,
)
}
private fun MutableList<NotificationUM>.addInfoNotifications(prevState: StakingUiState) {
when (prevState.actionType) {
StakingActionCommonType.EXIT -> {
add(
StakingNotification.Info.Unstake(
cooldownPeriodDays = yield.metadata.cooldownPeriod.days,
),
)
}
StakingActionCommonType.ENTER -> {
add(
StakingNotification.Info.EarnRewards(
subtitleText = resourceReference(
id = getEarnRewardsPeriod(yield.metadata.rewardSchedule),
formatArgs = wrappedList(yield.token.name),
),
),
)
}
else -> {
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
val pendingActionType = confirmationState?.pendingAction?.type
val (titleId, textId) = when (pendingActionType) {
StakingActionType.CLAIM_REWARDS -> {
R.string.common_claim to R.string.staking_notification_claim_rewards_text
}
StakingActionType.RESTAKE_REWARDS -> {
R.string.staking_restake to R.string.staking_notification_restake_rewards_text
}
else -> null to null
}
if (titleId != null && textId != null) {
add(
StakingNotification.Info.PendingAction(
title = resourceReference(titleId),
text = resourceReference(textId),
),
)
}
}
}
}
private fun getEarnRewardsPeriod(rewardSchedule: Yield.Metadata.RewardSchedule): Int {
return when (rewardSchedule) {
Yield.Metadata.RewardSchedule.BLOCK,
Yield.Metadata.RewardSchedule.DAY,
Yield.Metadata.RewardSchedule.ERA,
Yield.Metadata.RewardSchedule.EPOCH,
-> R.string.staking_notification_earn_rewards_text_period_day
Yield.Metadata.RewardSchedule.HOUR,
-> R.string.staking_notification_earn_rewards_text_period_hour
Yield.Metadata.RewardSchedule.WEEK,
-> R.string.staking_notification_earn_rewards_text_period_week
Yield.Metadata.RewardSchedule.MONTH,
-> R.string.staking_notification_earn_rewards_text_period_month
else
-> R.string.staking_notification_earn_rewards_text_period_day
}
}
}

View file

@ -1,14 +1,8 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
internal class SetConfirmationStateLoadingTransformer(
@ -30,52 +24,13 @@ internal class SetConfirmationStateLoadingTransformer(
chosenValidator = chosenValidator,
availableValidators = yield.validators,
),
notifications = getNotifications(prevState),
notifications = persistentListOf(),
footerText = "",
transactionDoneState = TransactionDoneState.Empty,
pendingAction = null,
pendingAction = possibleConfirmationState?.pendingAction,
isApprovalNeeded = false,
reduceAmountBy = null,
),
)
}
private fun getNotifications(prevState: StakingUiState): ImmutableList<NotificationUM> {
return persistentListOf(
if (prevState.actionType == StakingActionCommonType.EXIT) {
StakingNotification.Warning.Unstake(
cooldownPeriodDays = yield.metadata.cooldownPeriod.days,
)
} else {
StakingNotification.Warning.EarnRewards(
subtitleText = resourceReference(
id = getEarnRewardsPeriod(yield.metadata.rewardSchedule),
formatArgs = wrappedList(yield.token.name),
),
)
},
)
}
private fun getEarnRewardsPeriod(rewardSchedule: Yield.Metadata.RewardSchedule): Int {
return when (rewardSchedule) {
Yield.Metadata.RewardSchedule.BLOCK,
Yield.Metadata.RewardSchedule.DAY,
Yield.Metadata.RewardSchedule.ERA,
Yield.Metadata.RewardSchedule.EPOCH,
-> R.string.staking_notification_earn_rewards_text_period_day
Yield.Metadata.RewardSchedule.HOUR,
-> R.string.staking_notification_earn_rewards_text_period_hour
Yield.Metadata.RewardSchedule.WEEK,
-> R.string.staking_notification_earn_rewards_text_period_week
Yield.Metadata.RewardSchedule.MONTH,
-> R.string.staking_notification_earn_rewards_text_period_month
else
-> R.string.staking_notification_earn_rewards_text_period_day
}
}
}

View file

@ -64,10 +64,7 @@ internal class SetInitialDataStateTransformer(
val cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name
return prevState.copy(
title = TextReference.Res(
R.string.staking_title_stake,
wrappedList(cryptoCurrencyName),
),
title = TextReference.EMPTY,
walletName = userWalletProvider.invoke().name,
cryptoCurrencyName = cryptoCurrencyName,
clickIntents = clickIntents,

View file

@ -1,11 +1,15 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.core.ui.extensions.isNullOrEmpty
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.R
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.StakingStep
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.utils.getPendingActionTitle
import com.tangem.utils.transformer.Transformer
internal object SetTitleTransformer : Transformer<StakingUiState> {
@ -14,26 +18,36 @@ internal object SetTitleTransformer : Transformer<StakingUiState> {
val actionType = prevState.actionType
val currentStep = prevState.currentStep
val title = when {
currentStep == StakingStep.Amount -> {
resourceReference(R.string.send_amount_label)
}
val title = when (currentStep) {
StakingStep.Amount -> resourceReference(R.string.send_amount_label)
StakingStep.Validators -> resourceReference(R.string.staking_validators)
StakingStep.RewardsValidators -> resourceReference(R.string.common_claim_rewards)
StakingStep.InitialInfo -> resourceReference(
R.string.staking_title_stake,
wrappedList(prevState.cryptoCurrencyName),
)
currentStep == StakingStep.Validators -> {
resourceReference(R.string.staking_validators)
}
StakingStep.Confirmation -> {
when (actionType) {
StakingActionCommonType.ENTER -> resourceReference(
R.string.staking_title_stake,
wrappedList(prevState.cryptoCurrencyName),
)
StakingActionCommonType.EXIT -> resourceReference(
R.string.staking_title_unstake,
wrappedList(prevState.cryptoCurrencyName),
)
else -> {
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
val title = confirmationState?.pendingAction?.type?.getPendingActionTitle()
actionType == StakingActionCommonType.EXIT && currentStep != StakingStep.InitialInfo -> {
resourceReference(
R.string.staking_title_unstake,
wrappedList(prevState.cryptoCurrencyName),
)
}
else -> {
resourceReference(
R.string.staking_title_stake,
wrappedList(prevState.cryptoCurrencyName),
)
title?.takeIf { it.isNullOrEmpty() }
?: resourceReference(
R.string.staking_title_stake,
wrappedList(prevState.cryptoCurrencyName),
)
}
}
}
}

View file

@ -33,8 +33,11 @@ internal fun NotificationsBlock(notifications: ImmutableList<NotificationUM>) {
Notification(
config = notification.config,
iconTint = when (notification) {
is StakingNotification.Warning -> TangemTheme.colors.icon.accent
is StakingNotification.Info,
is NotificationUM.Info,
-> TangemTheme.colors.icon.accent
is StakingNotification.Warning,
is NotificationUM.Error.ExceedsBalance,
is NotificationUM.Warning,
-> null

View file

@ -162,6 +162,13 @@ internal class StakingViewModel @Inject constructor(
override fun onNextClick(actionTypeToOverwrite: StakingActionCommonType?, pendingAction: PendingAction?) {
if (actionTypeToOverwrite != null) {
stateController.update { it.copy(actionType = actionTypeToOverwrite) }
stateController.update {
val confirmationState = it.confirmationState as? StakingStates.ConfirmationState.Data
it.copy(
confirmationState = confirmationState?.copy(pendingAction = pendingAction)
?: StakingStates.ConfirmationState.Empty(),
)
}
}
stakingStateRouter.onNextClick()
when {
@ -407,6 +414,14 @@ internal class StakingViewModel @Inject constructor(
ShowActionSelectorBottomSheetTransformer(
pendingActions = activeStake.pendingActions,
onActionSelect = {
stateController.update(ValidatorSelectChangeTransformer(activeStake.validator))
stateController.update(
AmountChangeStateTransformer(
cryptoCurrencyStatus,
activeStake.cryptoValue,
yield,
),
)
onNextClick(actionTypeToOverwrite = StakingActionCommonType.PENDING_OTHER, pendingAction = it)
stateController.update(DismissBottomSheetStateTransformer())
},
@ -548,6 +563,7 @@ internal class StakingViewModel @Inject constructor(
currencyCheck = currencyStatus,
isSubtractAvailable = isAmountSubtractAvailable,
feeError = feeError,
yield = yield,
),
)
}