Updated on 2026-08-14
This commit is contained in:
commit
920d664fb6
11 changed files with 181 additions and 88 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue