Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-28 19:14:03 +05:00
parent 52ea08c979
commit 33e5b99d97
13 changed files with 327 additions and 198 deletions

View file

@ -1,86 +0,0 @@
package com.tangem.common.ui.feeScreen
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import java.math.BigDecimal
import java.math.RoundingMode
/**
* Check and calculates subtracted amount
*/
fun checkAndCalculateSubtractedAmount(
isAmountSubtractAvailable: Boolean,
cryptoCurrencyStatus: CryptoCurrencyStatus,
amountValue: BigDecimal,
feeValue: BigDecimal,
reduceAmountBy: BigDecimal,
): BigDecimal {
val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
val isFeeCoverage = checkFeeCoverage(
isSubtractAvailable = isAmountSubtractAvailable,
balance = balance,
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = reduceAmountBy,
)
return if (isFeeCoverage) {
balance.minus(reduceAmountBy).minus(feeValue)
} else {
amountValue
}
}
/**
* Checks if sending amount with fee is greater than balance
*/
fun checkFeeCoverage(
isSubtractAvailable: Boolean,
balance: BigDecimal,
amountValue: BigDecimal,
feeValue: BigDecimal,
reduceAmountBy: BigDecimal?,
): Boolean {
if (!isSubtractAvailable) return false
val reducedBy = balance - (reduceAmountBy ?: BigDecimal.ZERO)
return reducedBy < amountValue + feeValue && reducedBy > feeValue && reducedBy >= amountValue
}
/**
* Check if custom fee is too low
*/
fun checkIfFeeTooLow(fee: TransactionFee, customValue: BigDecimal, isCustomSelected: Boolean): Boolean {
val multipleFees = fee as? TransactionFee.Choosable ?: return false
val minimumValue = multipleFees.minimum.amount.value ?: return false
return isCustomSelected && minimumValue > customValue
}
/**
* Check if custom fee is too high
*/
fun checkIfFeeTooHigh(
fee: TransactionFee,
customValue: BigDecimal,
isCustomSelected: Boolean,
onShow: (String) -> Unit,
): Boolean {
val multipleFees = fee as? TransactionFee.Choosable ?: return false
val highValue = multipleFees.priority.amount.value ?: return false
val diff = customValue / highValue
val isShow = isCustomSelected && diff > FEE_MAX_DIFF
if (isShow) onShow(diff.parseBigDecimal(ZERO_DECIMALS, RoundingMode.HALF_UP))
return isShow
}
/**
* Checks if fee exceeds fee paid currency balance
*/
fun checkExceedBalance(feeBalance: BigDecimal?, feeAmount: BigDecimal?): Boolean {
return feeAmount == null || feeBalance == null || feeAmount.isZero() || feeAmount > feeBalance
}
private val FEE_MAX_DIFF = BigDecimal("5")
private const val ZERO_DECIMALS = 0

View file

@ -1,61 +1,56 @@
package com.tangem.features.staking.impl.presentation.state
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.*
import com.tangem.features.staking.impl.R
internal sealed class StakingNotification(val config: NotificationConfig) {
internal object StakingNotification {
sealed class Error(
title: TextReference,
subtitle: TextReference,
iconResId: Int = R.drawable.ic_alert_24,
buttonState: NotificationConfig.ButtonsState? = null,
onCloseClick: (() -> Unit)? = null,
) : StakingNotification(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = iconResId,
buttonsState = buttonState,
onCloseClick = onCloseClick,
),
) : NotificationUM.Error(
title = title,
subtitle = subtitle,
iconResId = R.drawable.ic_alert_24,
buttonState = buttonState,
onCloseClick = onCloseClick,
) {
data class StakedPositionNotFoundError(val message: String) : Error(
data class StakedPositionNotFoundError(val message: String) : StakingNotification.Error(
title = stringReference(message),
subtitle = stringReference(message),
)
data class Common(val subtitle: TextReference) : Error(
data class Common(val subtitle: TextReference) : StakingNotification.Error(
title = resourceReference(R.string.common_error),
subtitle = subtitle,
)
}
sealed class Warning(
title: TextReference,
subtitle: TextReference,
buttonsState: NotificationConfig.ButtonsState? = null,
onCloseClick: (() -> Unit)? = null,
) : StakingNotification(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = buttonsState,
onCloseClick = onCloseClick,
),
) : NotificationUM.Warning(
title = title,
subtitle = subtitle,
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = buttonsState,
onCloseClick = onCloseClick,
) {
data class EarnRewards(
val subtitleText: TextReference,
) : Warning(
) : StakingNotification.Warning(
title = resourceReference(R.string.staking_notification_earn_rewards_title),
subtitle = subtitleText,
)
data class Unstake(
val cooldownPeriodDays: Int,
) : Warning(
) : StakingNotification.Warning(
title = resourceReference(R.string.common_unstake),
subtitle = resourceReference(
R.string.staking_notification_unstake_text,
@ -72,6 +67,6 @@ internal sealed class StakingNotification(val config: NotificationConfig) {
data class TransactionInProgress(
val title: TextReference,
val description: TextReference,
) : Warning(title = title, subtitle = description)
) : StakingNotification.Warning(title = title, subtitle = description)
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.event.StateEvent
@ -13,6 +14,7 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
/**
* Ui states of the staking screen
@ -84,10 +86,11 @@ internal sealed class StakingStates {
val feeState: FeeState,
val validatorState: ValidatorState,
val pendingAction: PendingAction?,
val notifications: ImmutableList<StakingNotification>,
val notifications: ImmutableList<NotificationUM>,
val footerText: String,
val transactionDoneState: TransactionDoneState,
val isApprovalNeeded: Boolean,
val reduceAmountBy: BigDecimal?,
) : ConfirmationState()
data class Empty(

View file

@ -89,5 +89,6 @@ internal object ConfirmationStatePreviewData {
transactionDoneState = TransactionDoneState.Empty,
pendingAction = null,
isApprovalNeeded = false,
reduceAmountBy = null,
)
}

View file

@ -1,11 +1,14 @@
package com.tangem.features.staking.impl.presentation.state.stub
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import java.math.BigDecimal
@Suppress("TooManyFunctions")
internal object StakingClickIntentsStub : StakingClickIntents {
@ -49,4 +52,18 @@ internal object StakingClickIntentsStub : StakingClickIntents {
override fun onFailedTxEmailClick(errorMessage: String) {}
override fun onActiveStake(activeStake: BalanceState) {}
override fun getFee(pendingAction: PendingAction?) {}
override fun onAmountReduceByClick(
reduceAmountBy: BigDecimal,
reduceAmountByDiff: BigDecimal,
notification: Class<out NotificationUM>,
) {}
override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>) {}
override fun onNotificationCancel(notification: Class<out NotificationUM>) {}
override fun openTokenDetails(cryptoCurrency: CryptoCurrency) {}
}

View file

@ -1,28 +1,37 @@
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.*
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,
private val error: StakingError? = null,
) : 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 = (confirmationState.notifications + convertToNotification(error)).toPersistentList(),
notifications = notifications,
feeState = FeeState.Error,
),
)
}
private fun convertToNotification(error: StakingError): StakingNotification {
private fun convertToNotification(error: StakingError): NotificationUM {
return when (error) {
is StakingError.StakedPositionNotFoundError -> StakingNotification.Error.StakedPositionNotFoundError(
message = error.toString(),

View file

@ -1,8 +1,6 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.feeScreen.checkAndCalculateSubtractedAmount
import com.tangem.common.ui.feeScreen.checkFeeCoverage
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addExceedBalanceNotification
@ -14,13 +12,17 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountE
import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
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.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.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
import com.tangem.features.staking.impl.presentation.state.utils.checkFeeCoverage
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.Provider
import com.tangem.utils.extensions.orZero
@ -41,17 +43,17 @@ internal class AddStakingNotificationsTransformer(
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val appCurrency = appCurrencyProvider()
val balance = cryptoCurrencyStatus.value.amount.orZero()
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
val amountState = prevState.amountState as? AmountState.Data ?: return prevState
val feeState = confirmationState.feeState as? FeeState.Content ?: return prevState
val feeState = confirmationState.feeState as? FeeState.Content
val amountValue = amountState.amountTextField.cryptoAmount.value.orZero()
val feeValue = feeState.fee?.amount?.value.orZero()
val feeValue = feeState?.fee?.amount?.value.orZero()
val reduceAmountBy = confirmationState.reduceAmountBy.orZero()
val isEnterAction = prevState.actionType == StakingActionCommonType.ENTER
val isFeeCoverage = checkFeeCoverage(
amountValue = amountValue,
feeValue = feeValue,
@ -61,7 +63,7 @@ internal class AddStakingNotificationsTransformer(
)
val sendingAmount = checkAndCalculateSubtractedAmount(
isAmountSubtractAvailable = isSubtractAvailable,
cryptoCurrencyStatus = cryptoCurrencyStatusProvider(),
cryptoCurrencyStatus = cryptoCurrencyStatus,
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = reduceAmountBy,
@ -73,7 +75,7 @@ internal class AddStakingNotificationsTransformer(
prevState = prevState,
feeError = feeError,
sendingAmount = sendingAmount,
onReload = { prevState.clickIntents.loadFee(confirmationState.pendingActions) },
onReload = { prevState.clickIntents.getFee(confirmationState.pendingAction) },
feeValue = feeValue,
)
// warnings
@ -82,7 +84,7 @@ internal class AddStakingNotificationsTransformer(
amountState = amountState,
feeState = feeState,
sendingAmount = sendingAmount,
isFeeCoverage = isFeeCoverage,
isFeeCoverage = isFeeCoverage && isEnterAction,
)
addAll(confirmationState.notifications)
@ -91,6 +93,7 @@ internal class AddStakingNotificationsTransformer(
return prevState.copy(
confirmationState = confirmationState.copy(
notifications = notifications.toImmutableList(),
isPrimaryButtonEnabled = !notifications.any { it is StakingNotification.Error },
),
)
}
@ -151,7 +154,7 @@ internal class AddStakingNotificationsTransformer(
private fun MutableList<NotificationUM>.addWarningNotifications(
prevState: StakingUiState,
amountState: AmountState.Data,
feeState: FeeState.Content,
feeState: FeeState.Content?,
sendingAmount: BigDecimal,
isFeeCoverage: Boolean,
) {
@ -161,7 +164,7 @@ internal class AddStakingNotificationsTransformer(
addExistentialWarningNotification(
existentialDeposit = currencyCheck.existentialDeposit,
feeAmount = feeState.fee?.amount?.value.orZero(),
feeAmount = feeState?.fee?.amount?.value.orZero(),
receivedAmount = sendingAmount,
cryptoCurrencyStatus = cryptoCurrencyStatus,
onReduceClick = prevState.clickIntents::onAmountReduceByClick,
@ -177,7 +180,7 @@ internal class AddStakingNotificationsTransformer(
// blockchain specific
addValidateTransactionNotifications(
dustValue = currencyCheck.dustValue.orZero(),
fee = feeState.fee,
fee = feeState?.fee,
validationError = validatorError,
cryptoCurrency = cryptoCurrency,
onReduceClick = prevState.clickIntents::onAmountReduceToClick,

View file

@ -0,0 +1,25 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.common.ui.notifications.NotificationUM
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.persistentListOf
import kotlinx.collections.immutable.toPersistentList
internal class DismissStakingNotificationsStateTransformer(
private val notification: Class<out NotificationUM>,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
val updatedNotifications = confirmationState?.notifications
?.filterNot { it::class == notification }?.toPersistentList()
?: persistentListOf()
return prevState.copy(
confirmationState = confirmationState?.copy(
notifications = updatedNotifications,
) ?: StakingStates.ConfirmationState.Empty(),
)
}
}

View file

@ -1,5 +1,6 @@
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
@ -34,11 +35,12 @@ internal class SetConfirmationStateLoadingTransformer(
transactionDoneState = TransactionDoneState.Empty,
pendingAction = null,
isApprovalNeeded = false,
reduceAmountBy = null,
),
)
}
private fun getNotifications(prevState: StakingUiState): ImmutableList<StakingNotification> {
private fun getNotifications(prevState: StakingUiState): ImmutableList<NotificationUM> {
return persistentListOf(
if (prevState.actionType == StakingActionCommonType.EXIT) {
StakingNotification.Warning.Unstake(

View file

@ -214,6 +214,7 @@ internal class SetInitialDataStateTransformer(
transactionDoneState = TransactionDoneState.Empty,
pendingAction = null,
isApprovalNeeded = isApprovalNeeded,
reduceAmountBy = null,
)
}

View file

@ -0,0 +1,44 @@
package com.tangem.features.staking.impl.presentation.state.utils
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import java.math.BigDecimal
/**
* Check and calculates subtracted amount
*/
internal fun checkAndCalculateSubtractedAmount(
isAmountSubtractAvailable: Boolean,
cryptoCurrencyStatus: CryptoCurrencyStatus,
amountValue: BigDecimal,
feeValue: BigDecimal,
reduceAmountBy: BigDecimal,
): BigDecimal {
val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
val isFeeCoverage = checkFeeCoverage(
isSubtractAvailable = isAmountSubtractAvailable,
balance = balance,
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = reduceAmountBy,
)
return if (isFeeCoverage) {
balance.minus(reduceAmountBy).minus(feeValue)
} else {
amountValue
}
}
/**
* Checks if sending amount with fee is greater than balance
*/
internal fun checkFeeCoverage(
isSubtractAvailable: Boolean,
balance: BigDecimal,
amountValue: BigDecimal,
feeValue: BigDecimal,
reduceAmountBy: BigDecimal?,
): Boolean {
if (!isSubtractAvailable) return false
val reducedBy = balance - (reduceAmountBy ?: BigDecimal.ZERO)
return reducedBy < amountValue + feeValue && reducedBy > feeValue && reducedBy >= amountValue
}

View file

@ -1,12 +1,16 @@
package com.tangem.features.staking.impl.presentation.viewmodel
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import java.math.BigDecimal
@Suppress("TooManyFunctions")
internal interface StakingClickIntents : AmountScreenClickIntents {
fun onBackClick()
@ -21,6 +25,8 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
fun onInfoClick(infoType: InfoType)
fun getFee(pendingAction: PendingAction?)
override fun onAmountNext() = onNextClick(actionType = null)
fun openValidators()
@ -35,9 +41,21 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
fun onApprovalClick()
fun onAmountReduceByClick(
reduceAmountBy: BigDecimal,
reduceAmountByDiff: BigDecimal,
notification: Class<out NotificationUM>,
)
fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>)
fun onNotificationCancel(notification: Class<out NotificationUM>)
fun onExploreClick()
fun onShareClick()
fun onFailedTxEmailClick(errorMessage: String)
fun openTokenDetails(cryptoCurrency: CryptoCurrency)
}

View file

@ -12,7 +12,9 @@ import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
@ -32,16 +34,15 @@ 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
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -50,10 +51,7 @@ import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.state.events.StakingEventFactory
import com.tangem.features.staking.impl.presentation.state.transformers.*
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountChangeStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountCurrencyChangeStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountMaxValueStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountPasteDismissStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.*
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalBottomSheetInProgressTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalInProgressTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetConfirmationStateAssentApprovalTransformer
@ -103,6 +101,10 @@ internal class StakingViewModel @Inject constructor(
private val feedbackManager: FeedbackManager,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
private val validateTransactionUseCase: ValidateTransactionUseCase,
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
@DelayedWork private val coroutineScope: CoroutineScope,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
@ -140,6 +142,7 @@ internal class StakingViewModel @Inject constructor(
)
private var stakingApproval: StakingApproval = StakingApproval.Empty
private var isAmountSubtractAvailable: Boolean = false
private val allowanceTaskScheduler = SingleTaskScheduler<BigDecimal>()
private val transactionsInProgress: CopyOnWriteArrayList<StakingTransaction> = CopyOnWriteArrayList()
@ -171,6 +174,57 @@ internal class StakingViewModel @Inject constructor(
stakingStateRouter.onNextClick()
}
override fun getFee(pendingAction: PendingAction?) {
viewModelScope.launch {
stateController.update(
SetConfirmationStateLoadingTransformer(
yield = yield,
),
)
val cryptoCurrencyValue = cryptoCurrencyStatus.value
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
?: error("No confirmation state")
val validatorState = confirmationState.validatorState as? ValidatorState.Content
?: error("No validator provided")
val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
?: error("No amount provided")
val sourceAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value
?: error("No available address")
val validatorAddress = validatorState.chosenValidator.address
val approval = stakingApproval as? StakingApproval.Needed
if (approval != null) {
val allowance = getAllowanceUseCase(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrencyStatus.currency,
spenderAddress = approval.spenderAddress,
).getOrElse { BigDecimal.ZERO }
if (allowance < amount) {
getApproveFee(
amount = amount,
validatorAddress = validatorAddress,
)
} else {
estimateGas(
pendingAction = pendingAction,
amount = amount,
sourceAddress = sourceAddress,
validatorAddress = validatorAddress,
)
}
} else {
estimateGas(
pendingAction = pendingAction,
amount = amount,
sourceAddress = sourceAddress,
validatorAddress = validatorAddress,
)
}
}
}
private fun handleOnNextConfirmationClick() {
if (isAssentState()) {
viewModelScope.launch {
@ -236,57 +290,6 @@ internal class StakingViewModel @Inject constructor(
}
}
private fun getFee(pendingAction: PendingAction?) {
viewModelScope.launch {
stateController.update(
SetConfirmationStateLoadingTransformer(
yield = yield,
),
)
val cryptoCurrencyValue = cryptoCurrencyStatus.value
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
?: error("No confirmation state")
val validatorState = confirmationState.validatorState as? ValidatorState.Content
?: error("No validator provided")
val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
?: error("No amount provided")
val sourceAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value
?: error("No available address")
val validatorAddress = validatorState.chosenValidator.address
val approval = stakingApproval as? StakingApproval.Needed
if (approval != null) {
val allowance = getAllowanceUseCase(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrencyStatus.currency,
spenderAddress = approval.spenderAddress,
).getOrElse { BigDecimal.ZERO }
if (allowance < amount) {
getApproveFee(
amount = amount,
validatorAddress = validatorAddress,
)
} else {
estimateGas(
pendingAction = pendingAction,
amount = amount,
sourceAddress = sourceAddress,
validatorAddress = validatorAddress,
)
}
} else {
estimateGas(
pendingAction = pendingAction,
amount = amount,
sourceAddress = sourceAddress,
validatorAddress = validatorAddress,
)
}
}
}
private suspend fun estimateGas(
pendingAction: PendingAction?,
amount: BigDecimal,
@ -310,7 +313,6 @@ internal class StakingViewModel @Inject constructor(
stateController.update(AddStakingErrorTransformer(it))
return
}
stateController.update(
SetConfirmationStateAssentTransformer(
appCurrencyProvider = Provider { appCurrency },
@ -325,24 +327,30 @@ internal class StakingViewModel @Inject constructor(
action = pendingAction,
),
)
updateNotifications()
}
private suspend fun getApproveFee(amount: BigDecimal, validatorAddress: String) {
val approvalFee = getFeeUseCase(
getFeeUseCase(
amount = amount,
destination = validatorAddress,
userWallet = userWallet,
cryptoCurrency = cryptoCurrencyStatus.currency,
).getOrElse {
return stakingEventFactory.createGenericErrorAlert(it.toString())
}
stateController.update(
SetConfirmationStateAssentApprovalTransformer(
appCurrencyProvider = Provider { appCurrency },
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
fee = approvalFee,
),
).fold(
ifRight = { fee ->
stateController.update(
SetConfirmationStateAssentApprovalTransformer(
appCurrencyProvider = Provider { appCurrency },
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
fee = fee,
),
)
updateNotifications()
},
ifLeft = {
stateController.update(AddStakingErrorTransformer())
updateNotifications(it)
},
)
}
@ -499,6 +507,85 @@ internal class StakingViewModel @Inject constructor(
}.saveIn(approvalJobHolder)
}
private fun updateNotifications(feeError: GetFeeError? = null) {
viewModelScope.launch {
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
val feeState = confirmationState?.feeState as? FeeState.Content
val amountState = value.amountState as? AmountState.Data
val amount = amountState?.amountTextField?.cryptoAmount?.value
val fee = feeState?.fee?.amount?.value
val currencyWarning = if (feeCryptoCurrencyStatus != null && fee != null) {
getBalanceNotEnoughForFeeWarningUseCase(
fee = fee,
userWalletId = userWalletId,
tokenStatus = cryptoCurrencyStatus,
coinStatus = feeCryptoCurrencyStatus ?: cryptoCurrencyStatus,
).getOrNull()
} else {
null
}
val validation = amount?.let {
validateTransactionUseCase(
userWalletId = userWalletId,
amount = amount.convertToSdkAmount(cryptoCurrencyStatus.currency),
fee = feeState?.fee,
memo = null,
destination = "",
network = cryptoCurrencyStatus.currency.network,
).leftOrNull()
}
val currencyStatus = getCurrencyCheckUseCase(
userWalletId = userWalletId,
currencyStatus = cryptoCurrencyStatus,
amount = amount,
fee = fee,
)
stateController.update(
AddStakingNotificationsTransformer(
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
appCurrencyProvider = Provider { appCurrency },
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
currencyWarning = currencyWarning,
validatorError = validation,
currencyCheck = currencyStatus,
isSubtractAvailable = isAmountSubtractAvailable,
feeError = feeError,
),
)
}
}
override fun onAmountReduceByClick(
reduceAmountBy: BigDecimal,
reduceAmountByDiff: BigDecimal,
notification: Class<out NotificationUM>,
) {
AmountReduceByStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
value = AmountReduceByTransformer.ReduceByData(
reduceAmountBy = reduceAmountBy,
reduceAmountByDiff = reduceAmountByDiff,
),
)
onNotificationCancel(notification)
}
override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>) {
stateController.update(
AmountReduceToStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
value = reduceAmountTo,
),
)
onNotificationCancel(notification)
}
override fun onNotificationCancel(notification: Class<out NotificationUM>) {
stateController.update(DismissStakingNotificationsStateTransformer(notification))
}
private fun awaitForAllowance(pendingAction: PendingAction?) {
val approval = stakingApproval as? StakingApproval.Needed ?: return
allowanceTaskScheduler.scheduleTask(
@ -586,6 +673,10 @@ internal class StakingViewModel @Inject constructor(
}
}
override fun openTokenDetails(cryptoCurrency: CryptoCurrency) {
innerRouter.openTokenDetails(userWalletId, cryptoCurrency)
}
fun setRouter(router: InnerStakingRouter, stateRouter: StakingStateRouter) {
innerRouter = router
this.stakingStateRouter = stateRouter
@ -612,6 +703,7 @@ internal class StakingViewModel @Inject constructor(
cryptoCurrencyStatus = it
setupApprovalNeeded()
checkIfSubtractAvailable()
stateController.update(
transformer = SetInitialDataStateTransformer(
@ -779,6 +871,11 @@ internal class StakingViewModel @Inject constructor(
InnerConfirmationStakingState.ASSENT
}
private suspend fun checkIfSubtractAvailable() {
isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(userWalletId, cryptoCurrencyStatus.currency)
.getOrElse { false }
}
private data class FullTransactionData(
val stakeKitTransaction: StakingTransaction,
val tangemTransaction: TransactionData.Compiled,