Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-29 14:31:53 +03:00
commit 652159dbce
316 changed files with 6242 additions and 3084 deletions

View file

@ -32,10 +32,10 @@ internal class StakingAnalyticSender(
fun confirmationScreen(value: StakingUiState) {
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
val validatorName = validatorState?.chosenValidator?.name ?: return
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) return
if (confirmationState?.innerState == InnerConfirmationStakingState.COMPLETED) return
analyticsEventHandler.send(
StakingAnalyticsEvent.ConfirmationScreenOpened(
@ -53,6 +53,7 @@ internal class StakingAnalyticSender(
StakingStep.Amount -> StakeScreenSource.Amount
StakingStep.Confirmation -> StakeScreenSource.Confirmation
StakingStep.Validators,
StakingStep.RestakeValidator,
StakingStep.RewardsValidators,
-> StakeScreenSource.Validators
},
@ -75,8 +76,7 @@ internal class StakingAnalyticSender(
}
fun sendTransactionStakingAnalytics(value: StakingUiState) {
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
val validatorName = validatorState?.chosenValidator?.name ?: return
analyticsEventHandler.send(
@ -98,8 +98,7 @@ internal class StakingAnalyticSender(
}
fun sendTransactionStakingClickedAnalytics(value: StakingUiState) {
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
val validatorName = validatorState?.chosenValidator?.name ?: return
analyticsEventHandler.send(
@ -114,11 +113,9 @@ internal class StakingAnalyticSender(
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
return when (value.actionType) {
StakingActionCommonType.ENTER -> StakingActionType.STAKE
StakingActionCommonType.EXIT -> StakingActionType.UNSTAKE
StakingActionCommonType.PENDING_REWARDS,
StakingActionCommonType.PENDING_OTHER,
-> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN
StakingActionCommonType.Enter -> StakingActionType.STAKE
StakingActionCommonType.Exit -> StakingActionType.UNSTAKE
is StakingActionCommonType.Pending -> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN
}
}
}

View file

@ -45,6 +45,12 @@ internal class StakingStateController @Inject constructor(
mutableUiState.update(function = titleTransformer::transform)
}
fun updateAll(vararg transformer: Transformer<StakingUiState>) {
transformer.forEach { mutableUiState.update(function = it::transform) }
mutableUiState.update(function = buttonsTransformer::transform)
mutableUiState.update(function = titleTransformer::transform)
}
fun clear() {
mutableUiState.update { getInitialState() }
mutableUiState.update(function = buttonsTransformer::transform)
@ -72,12 +78,13 @@ internal class StakingStateController @Inject constructor(
currentStep = StakingStep.InitialInfo,
initialInfoState = StakingStates.InitialInfoState.Empty(),
amountState = AmountState.Empty(),
validatorState = StakingStates.ValidatorState.Empty(),
rewardsValidatorsState = StakingStates.RewardsValidatorsState.Empty(),
confirmationState = StakingStates.ConfirmationState.Empty(),
isBalanceHidden = false,
event = consumedEvent(),
bottomSheetConfig = null,
actionType = StakingActionCommonType.ENTER,
actionType = StakingActionCommonType.Enter,
buttonsState = NavigationButtonsState.Empty,
)
}

View file

@ -23,12 +23,14 @@ internal class StakingStateRouter(
fun onNextClick() {
when (stateController.value.currentStep) {
StakingStep.InitialInfo -> when (stateController.value.actionType) {
StakingActionCommonType.ENTER -> showAmount()
StakingActionCommonType.PENDING_OTHER,
StakingActionCommonType.EXIT,
StakingActionCommonType.Enter -> showAmount()
StakingActionCommonType.Pending.Other,
StakingActionCommonType.Exit,
StakingActionCommonType.Pending.Rewards,
-> showConfirmation()
StakingActionCommonType.PENDING_REWARDS -> showRewardsValidators()
StakingActionCommonType.Pending.Restake -> showRestakeValidators()
}
StakingStep.RestakeValidator,
StakingStep.RewardsValidators,
StakingStep.Validators,
StakingStep.Amount,
@ -41,25 +43,31 @@ internal class StakingStateRouter(
val uiState = stateController.uiState.value
when (uiState.currentStep) {
StakingStep.InitialInfo -> onBackClick()
StakingStep.Amount -> showInitial()
StakingStep.RestakeValidator,
StakingStep.RewardsValidators,
StakingStep.Amount,
-> showInitial()
StakingStep.Confirmation -> {
if (uiState.actionType != StakingActionCommonType.ENTER) {
if (uiState.actionType != StakingActionCommonType.Enter) {
showInitial()
} else {
showAmount()
}
}
StakingStep.Validators -> showConfirmation()
StakingStep.RewardsValidators -> showInitial()
}
}
fun showValidators() {
stateController.update { it.copy(currentStep = StakingStep.Validators) }
}
private fun showInitial() {
analyticSender.initialInfoScreen(stateController.value)
stateController.update { it.copy(currentStep = StakingStep.InitialInfo) }
}
private fun showRewardsValidators() {
fun showRewardsValidators() {
analyticsEventsHandler.send(StakingAnalyticsEvent.RewardScreenOpened)
stateController.update { it.copy(currentStep = StakingStep.RewardsValidators) }
}
@ -69,8 +77,8 @@ internal class StakingStateRouter(
stateController.update { it.copy(currentStep = StakingStep.Amount) }
}
fun showValidators() {
stateController.update { it.copy(currentStep = StakingStep.Validators) }
private fun showRestakeValidators() {
stateController.update { it.copy(currentStep = StakingStep.RestakeValidator) }
}
private fun showConfirmation() {

View file

@ -9,8 +9,8 @@ import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.staking.model.PendingTransaction
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.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
@ -34,6 +34,7 @@ internal data class StakingUiState(
val amountState: AmountState,
val rewardsValidatorsState: StakingStates.RewardsValidatorsState,
val confirmationState: StakingStates.ConfirmationState,
val validatorState: StakingStates.ValidatorState,
val isBalanceHidden: Boolean,
val bottomSheetConfig: TangemBottomSheetConfig?,
val actionType: StakingActionCommonType,
@ -45,10 +46,12 @@ internal data class StakingUiState(
initialInfoState: StakingStates.InitialInfoState = this.initialInfoState,
amountState: AmountState = this.amountState,
confirmationState: StakingStates.ConfirmationState = this.confirmationState,
validatorState: StakingStates.ValidatorState = this.validatorState,
): StakingUiState = copy(
initialInfoState = initialInfoState,
amountState = amountState,
confirmationState = confirmationState,
validatorState = validatorState,
)
}
@ -85,13 +88,30 @@ internal sealed class StakingStates {
) : RewardsValidatorsState()
}
sealed class ValidatorState : StakingStates() {
abstract val isClickable: Boolean
data class Data(
override val isPrimaryButtonEnabled: Boolean,
override val isClickable: Boolean,
val isVisibleOnConfirmation: Boolean,
val chosenValidator: Yield.Validator,
val activeValidator: Yield.Validator?,
val availableValidators: List<Yield.Validator>,
) : ValidatorState()
data class Empty(
override val isClickable: Boolean = false,
override val isPrimaryButtonEnabled: Boolean = false,
) : ValidatorState()
}
/** Confirmation state */
sealed class ConfirmationState : StakingStates() {
data class Data(
override val isPrimaryButtonEnabled: Boolean,
val innerState: InnerConfirmationStakingState,
val feeState: FeeState,
val validatorState: ValidatorState,
val pendingAction: PendingAction?,
val pendingActions: ImmutableList<PendingAction>?,
val notifications: ImmutableList<NotificationUM>,
@ -99,7 +119,6 @@ internal sealed class StakingStates {
val transactionDoneState: TransactionDoneState,
val isApprovalNeeded: Boolean,
val reduceAmountBy: BigDecimal?,
val possiblePendingTransaction: PendingTransaction?,
) : ConfirmationState()
data class Empty(
@ -112,6 +131,7 @@ enum class StakingStep {
InitialInfo,
RewardsValidators,
Amount,
RestakeValidator,
Confirmation,
Validators,
}

View file

@ -1,34 +0,0 @@
package com.tangem.features.staking.impl.presentation.state
import androidx.compose.runtime.Immutable
import com.tangem.domain.staking.model.stakekit.Yield
@Immutable
internal sealed class ValidatorState {
abstract val isClickable: Boolean
data class Content(
override val isClickable: Boolean,
val chosenValidator: Yield.Validator,
val availableValidators: List<Yield.Validator>,
) : ValidatorState()
data object Loading : ValidatorState() {
override val isClickable: Boolean
get() = false
}
data object Error : ValidatorState() {
override val isClickable: Boolean
get() = false
}
fun copySealed(isClickable: Boolean): ValidatorState {
return if (this is Content) {
copy(isClickable = isClickable)
} else {
this
}
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.features.staking.impl.presentation.state.converters
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
@ -47,10 +49,7 @@ internal class BalanceItemConverter(
cryptoValue = cryptoAmount.parseBigDecimal(cryptoCurrency.decimals),
cryptoDecimal = cryptoAmount,
cryptoAmount = stringReference(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoAmount,
cryptoCurrency = cryptoCurrency,
),
cryptoAmount.format { crypto(cryptoCurrency) },
),
fiatAmount = stringReference(
BigDecimalFormatter.formatFiatAmount(

View file

@ -1,6 +1,8 @@
package com.tangem.features.staking.impl.presentation.state.converters
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
@ -65,10 +67,9 @@ internal class RewardsValidatorStateConverter(
val appCurrency = appCurrencyProvider()
val cryptoCurrency = cryptoCurrencyStatus.currency
val cryptoAmount = stringReference(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoValue,
cryptoCurrency = cryptoCurrency,
),
cryptoValue.format {
crypto(cryptoCurrency)
},
)
val fiatAmount = stringReference(
BigDecimalFormatter.formatFiatAmount(

View file

@ -1,6 +1,8 @@
package com.tangem.features.staking.impl.presentation.state.converters
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.*
@ -41,10 +43,7 @@ internal class YieldBalancesConverter(
}
val (type, isActionable) = getRewardBlockType()
InnerYieldBalanceState.Data(
rewardsCrypto = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoRewardsValue,
cryptoCurrency = cryptoCurrency,
),
rewardsCrypto = cryptoRewardsValue.format { crypto(cryptoCurrency) },
rewardsFiat = BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatRewardsValue,
fiatCurrencyCode = appCurrency.code,

View file

@ -1,6 +1,9 @@
package com.tangem.features.staking.impl.presentation.state.helpers
import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase
import com.tangem.domain.staking.FetchActionsUseCase
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -20,16 +23,21 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val fetchActionsUseCase: FetchActionsUseCase,
@DelayedWork private val coroutineScope: CoroutineScope,
@Assisted private val userWallet: UserWallet,
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
@Assisted private val yield: Yield,
) {
fun scheduleUpdates() {
fun fullUpdate() {
coroutineScope.launch {
listOf(
// we should update network to find pending tx after 1 sec
async {
fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrencyStatus.currency.network))
fetchPendingTransactionsUseCase(
userWalletId = userWallet.walletId,
networks = setOf(cryptoCurrencyStatus.currency.network),
)
},
// we should update tx history and network for new balances
async {
@ -41,11 +49,14 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
async {
updateNetworkStatuses()
},
async {
updateProcessingActions()
},
).awaitAll()
}
}
suspend fun instantUpdate() {
suspend fun partialUpdate() {
coroutineScope {
listOf(
async {
@ -54,6 +65,19 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
async {
updateNetworkStatuses(delay = 0)
},
async {
updateProcessingActions()
},
).awaitAll()
}
}
suspend fun initialUpdate() {
coroutineScope {
listOf(
async {
updateStakeBalance()
},
).awaitAll()
}
}
@ -91,9 +115,22 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
}
}
private suspend fun updateProcessingActions() {
fetchActionsUseCase(
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrencyStatus.currency,
networkType = yield.token.network,
stakingActionStatus = StakingActionStatus.PROCESSING,
)
}
@AssistedFactory
interface Factory {
fun create(cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet): StakingBalanceUpdater
fun create(
cryptoCurrencyStatus: CryptoCurrencyStatus,
userWallet: UserWallet,
yield: Yield,
): StakingBalanceUpdater
}
private companion object {

View file

@ -22,8 +22,7 @@ import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.presentation.state.StakingStateController
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.ValidatorState
import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw
import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions
import com.tangem.utils.extensions.orZero
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -48,8 +47,6 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
) {
suspend fun getFee(
pendingAction: PendingAction?,
pendingActions: ImmutableList<PendingAction>?,
onStakingFee: (Fee) -> Unit,
onStakingFeeError: (StakingError) -> Unit,
onApprovalFee: (TransactionFee) -> Unit,
@ -57,17 +54,20 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
) {
val state = stateController.value
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
?: error("No confirmation state")
val validatorState = confirmationState.validatorState as? ValidatorState.Content
?: error("Illegal state")
val validatorState = state.validatorState as? StakingStates.ValidatorState.Data
?: error("No validator provided")
val amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
?: error("No amount provided")
val pendingAction = confirmationState.pendingAction
val pendingActions = confirmationState.pendingActions
val validatorAddress = validatorState.chosenValidator.address
val approval = stakingApproval as? StakingApproval.Needed
if (approval != null && state.actionType == StakingActionCommonType.ENTER) {
if (approval != null && state.actionType == StakingActionCommonType.Enter) {
val allowance = getAllowanceUseCase(
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrencyStatus.currency,
@ -114,7 +114,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
val sourceAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
?: error("No available address")
val gasEstimate = if (isSolanaWithdraw(cryptoCurrencyStatus.currency.network.id.value, pendingActions)) {
val gasEstimate = if (isComposePendingActions(cryptoCurrencyStatus.currency.network.id.value, pendingActions)) {
val result = coroutineScope {
pendingActions?.map { action ->
async {

View file

@ -5,7 +5,6 @@ import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.domain.staking.*
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.SubmitHashData
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
@ -21,7 +20,7 @@ import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw
import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions
import com.tangem.utils.extensions.orZero
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -31,7 +30,6 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import timber.log.Timber
import java.math.BigDecimal
import java.util.UUID
@Suppress("LongParameterList")
internal class StakingTransactionSender @AssistedInject constructor(
@ -43,7 +41,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val submitHashUseCase: SubmitHashUseCase,
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
private val savePendingTransactionUseCase: SavePendingTransactionUseCase,
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
@Assisted private val userWallet: UserWallet,
@Assisted private val yield: Yield,
@ -51,7 +48,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
) {
private val balanceUpdater: StakingBalanceUpdater
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet)
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, yield)
suspend fun constructAndSendTransactions(
onConstructSuccess: (List<StakingTransaction>) -> Unit,
@ -89,26 +86,21 @@ internal class StakingTransactionSender @AssistedInject constructor(
sendStakingTransaction(
fullTransactionsData = fullTransactionsData,
possiblePendingTransaction = composePendingTransaction(confirmationState),
onSendSuccess = onSendSuccess,
onSendError = onSendError,
)
}
private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
return confirmationState.possiblePendingTransaction ?: composeStakeTransaction(confirmationState)
}
private suspend fun getStakingTransactions(
state: StakingUiState,
confirmationState: StakingStates.ConfirmationState.Data,
onConstructError: (StakingError) -> Unit,
) = coroutineScope {
val isAllWithdrawAction = isSolanaWithdraw(
val isComposePendingActions = isComposePendingActions(
cryptoCurrencyStatus.currency.network.id.value,
confirmationState.pendingActions,
)
if (isAllWithdrawAction) {
if (isComposePendingActions) {
confirmationState.pendingActions?.map { action ->
async {
getStakingTransaction(
@ -169,7 +161,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
action: PendingAction? = confirmationState.pendingAction,
onConstructError: (StakingError) -> Unit,
): List<StakingTransaction> {
val validatorState = confirmationState.validatorState as? ValidatorState.Content
val validatorState = state.validatorState as? StakingStates.ValidatorState.Data
?: error("No validator provided")
val fee = (confirmationState.feeState as? FeeState.Content)?.fee
?: error("No fee provided")
@ -202,7 +194,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
private suspend fun sendStakingTransaction(
fullTransactionsData: List<FullTransactionData>,
possiblePendingTransaction: PendingTransaction,
onSendSuccess: (txUrl: String) -> Unit,
onSendError: (SendTransactionError?) -> Unit,
) {
@ -218,24 +209,19 @@ internal class StakingTransactionSender @AssistedInject constructor(
submitHash(
transactions = fullTransactionsData.map { it.stakeKitTransaction },
transactionHashes = transactionHashes,
pendingTransaction = possiblePendingTransaction,
)
val txUrl = getExplorerTransactionUrlUseCase(
txHash = transactionHashes.last(),
networkId = cryptoCurrencyStatus.currency.network.id,
).getOrElse { "" }
balanceUpdater.scheduleUpdates()
balanceUpdater.fullUpdate()
onSendSuccess(txUrl)
},
)
}
private suspend fun submitHash(
transactions: List<StakingTransaction>,
transactionHashes: List<String>,
pendingTransaction: PendingTransaction,
) {
private suspend fun submitHash(transactions: List<StakingTransaction>, transactionHashes: List<String>) {
transactions
.zip(transactionHashes)
.forEach { (transaction, transactionHash) ->
@ -243,7 +229,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
SubmitHashData(
transactionId = transaction.id,
transactionHash = transactionHash,
pendingTransaction = pendingTransaction,
),
)
.onLeft {
@ -253,9 +238,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
)
}.onRight {
Timber.d("Successful hash submission")
if (transaction.type != StakingTransactionType.FREEZE_ENERGY) {
savePendingTransactionUseCase.invoke(userWallet.walletId, pendingTransaction)
}
}
}
}
@ -263,7 +245,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
private fun getAmount(amountState: AmountState.Data, fee: Fee, reduceAmountBy: BigDecimal?): BigDecimal {
val amountValue = amountState.amountTextField.cryptoAmount.value ?: error("No amount value")
val feeValue = fee.amount.value ?: error("No fee value")
val isEnterAction = stateController.value.actionType == StakingActionCommonType.ENTER
val isEnterAction = stateController.value.actionType == StakingActionCommonType.Enter
return checkAndCalculateSubtractedAmount(
isAmountSubtractAvailable = isAmountSubtractAvailable && isEnterAction,
@ -274,23 +256,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
)
}
private fun composeStakeTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
val state = stateController.value
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId)
return PendingTransaction(
groupId = UUID.randomUUID().toString(),
token = token,
type = BalanceType.STAKED,
amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO,
rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId,
validator = (confirmationState.validatorState as? ValidatorState.Content)?.chosenValidator,
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
)
}
private data class FullTransactionData(
val stakeKitTransaction: StakingTransaction,
val tangemTransaction: TransactionData.Compiled,

View file

@ -7,8 +7,6 @@ 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.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import kotlinx.collections.immutable.persistentListOf
@ -16,48 +14,6 @@ import java.math.BigDecimal
internal object ConfirmationStatePreviewData {
private val validatorList = listOf(
Yield.Validator(
address = "0xa6e768fef2d1af36c0cfdb276422e7881a83e951",
status = ValidatorStatus.ACTIVE,
name = "Luganodes",
image = "https://assets.stakek.it/validators/luganodes.png",
apr = BigDecimal("0.054823398040640445"),
commission = 0.1,
stakedBalance = "355544384.45009977",
website = "https://luganodes.com/",
votingPower = 0.09778360195377911,
preferred = true,
isStrategicPartner = false,
),
Yield.Validator(
address = "0x35b1ca0f398905cf752e6fe122b51c88022fca32",
status = ValidatorStatus.ACTIVE,
name = "InfStones",
image = "https://assets.stakek.it/validators/infstones.png",
apr = BigDecimal("0.057786472172836965"),
commission = 0.05,
stakedBalance = "12495684.05643019",
website = "https://infstones.com/",
votingPower = 0.0034366257754399774,
preferred = true,
isStrategicPartner = false,
),
Yield.Validator(
address = "0xd14a87025109013b0a2354a775cb335f926af65a",
status = ValidatorStatus.ACTIVE,
name = "Kiln",
image = "https://assets.stakek.it/validators/kiln.png",
apr = BigDecimal("0.057786472172836965"),
commission = 0.05,
stakedBalance = "85400369.96393165",
website = "https://infstones.com/",
votingPower = 0.023487238579718264,
preferred = true,
isStrategicPartner = true,
),
)
private val fee = Fee.Common(
amount = Amount(
currencySymbol = "MATIC",
@ -77,11 +33,6 @@ internal object ConfirmationStatePreviewData {
isFeeApproximate = false,
isFeeConvertibleToFiat = true,
),
validatorState = ValidatorState.Content(
isClickable = true,
chosenValidator = validatorList[0],
availableValidators = validatorList,
),
footerText = stringReference("You stake \$715.11 and will be receiving ~\$35 monthly"),
notifications = persistentListOf(
StakingNotification.Info.EarnRewards(
@ -96,6 +47,5 @@ internal object ConfirmationStatePreviewData {
isApprovalNeeded = false,
reduceAmountBy = null,
pendingActions = null,
possiblePendingTransaction = null,
)
}

View file

@ -0,0 +1,60 @@
package com.tangem.features.staking.impl.presentation.state.previewdata
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus
import com.tangem.features.staking.impl.presentation.state.StakingStates
import java.math.BigDecimal
internal object ValidatorStatePreviewData {
private val validatorList = listOf(
Yield.Validator(
address = "0xa6e768fef2d1af36c0cfdb276422e7881a83e951",
status = ValidatorStatus.ACTIVE,
name = "Luganodes",
image = "https://assets.stakek.it/validators/luganodes.png",
apr = BigDecimal("0.054823398040640445"),
commission = 0.1,
stakedBalance = "355544384.45009977",
website = "https://luganodes.com/",
votingPower = 0.09778360195377911,
preferred = true,
isStrategicPartner = false,
),
Yield.Validator(
address = "0x35b1ca0f398905cf752e6fe122b51c88022fca32",
status = ValidatorStatus.ACTIVE,
name = "InfStones",
image = "https://assets.stakek.it/validators/infstones.png",
apr = BigDecimal("0.057786472172836965"),
commission = 0.05,
stakedBalance = "12495684.05643019",
website = "https://infstones.com/",
votingPower = 0.0034366257754399774,
preferred = true,
isStrategicPartner = true,
),
Yield.Validator(
address = "0xd14a87025109013b0a2354a775cb335f926af65a",
status = ValidatorStatus.ACTIVE,
name = "Kiln",
image = "https://assets.stakek.it/validators/kiln.png",
apr = BigDecimal("0.057786472172836965"),
commission = 0.05,
stakedBalance = "85400369.96393165",
website = "https://infstones.com/",
votingPower = 0.023487238579718264,
preferred = true,
isStrategicPartner = false,
),
)
val validatorState = StakingStates.ValidatorState.Data(
availableValidators = validatorList,
chosenValidator = validatorList.first(),
isPrimaryButtonEnabled = true,
activeValidator = null,
isClickable = true,
isVisibleOnConfirmation = true,
)
}

View file

@ -2,14 +2,11 @@ package com.tangem.features.staking.impl.presentation.state.stub
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
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 kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
@Suppress("TooManyFunctions")
@ -17,13 +14,7 @@ internal object StakingClickIntentsStub : StakingClickIntents {
override fun onBackClick() {}
override fun onNextClick(
actionTypeToOverwrite: StakingActionCommonType?,
pendingAction: PendingAction?,
pendingActions: ImmutableList<PendingAction>?,
balanceState: BalanceState?,
) {
}
override fun onNextClick(balanceState: BalanceState?) {}
override fun onActionClick() {}
@ -67,8 +58,7 @@ internal object StakingClickIntentsStub : StakingClickIntents {
override fun onActiveStake(activeStake: BalanceState) {}
override fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList<PendingAction>?) {
}
override fun getFee() {}
override fun onAmountReduceByClick(
reduceAmountBy: BigDecimal,

View file

@ -1,33 +0,0 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.lib.crypto.BlockchainUtils.isTron
import com.tangem.utils.transformer.Transformer
internal class ActionTypeActiveStakeTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val activeStake: BalanceState,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val isTronStakedBalance = isTronStakedBalance(
networkId = cryptoCurrencyStatus.currency.network.id,
activeStake = activeStake,
)
val actionType = if (activeStake.pendingActions.isEmpty() || isTronStakedBalance) {
StakingActionCommonType.EXIT
} else {
StakingActionCommonType.PENDING_OTHER
}
return prevState.copy(actionType = actionType)
}
private fun isTronStakedBalance(networkId: Network.ID, activeStake: BalanceState): Boolean {
return isTron(networkId.value) && activeStake.type == BalanceType.STAKED
}
}

View file

@ -65,7 +65,7 @@ internal class AddStakingNotificationsTransformer(
val feeValue = feeState?.fee?.amount?.value.orZero()
val reduceAmountBy = confirmationState.reduceAmountBy.orZero()
val isEnterAction = prevState.actionType == StakingActionCommonType.ENTER
val isEnterAction = prevState.actionType == StakingActionCommonType.Enter
val isFeeCoverage = checkFeeCoverage(
amountValue = amountValue,
feeValue = feeValue,
@ -93,12 +93,7 @@ internal class AddStakingNotificationsTransformer(
prevState = prevState,
feeError = feeError,
sendingAmount = sendingAmount,
onReload = {
prevState.clickIntents.getFee(
confirmationState.pendingAction,
confirmationState.pendingActions,
)
},
onReload = prevState.clickIntents::getFee,
feeValue = feeValue,
)
// warnings
@ -228,7 +223,7 @@ internal class AddStakingNotificationsTransformer(
val showNotification = sendingAmount + feeAmount > balance
if (showNotification) {
val notification = if (actionType == StakingActionCommonType.ENTER) {
val notification = if (actionType == StakingActionCommonType.Enter) {
NotificationUM.Error.TotalExceedsBalance
} else {
with(cryptoCurrencyStatus.currency) {
@ -249,9 +244,9 @@ internal class AddStakingNotificationsTransformer(
private fun MutableList<NotificationUM>.addInfoNotifications(prevState: StakingUiState) {
when (prevState.actionType) {
StakingActionCommonType.EXIT -> addExitInfoNotifications()
StakingActionCommonType.ENTER -> addEnterInfoNotifications()
else -> addPendingInfoNotifications(prevState)
StakingActionCommonType.Enter -> addEnterInfoNotifications()
StakingActionCommonType.Exit -> addExitInfoNotifications()
is StakingActionCommonType.Pending -> addPendingInfoNotifications(prevState)
}
}
@ -312,6 +307,10 @@ internal class AddStakingNotificationsTransformer(
resourceReference(R.string.staking_revote) to
resourceReference(R.string.staking_notifications_revote_tron_text)
}
StakingActionType.RESTAKE -> {
resourceReference(R.string.staking_restake) to
resourceReference(R.string.staking_notification_restake_text)
}
else -> null to null
}

View file

@ -1,25 +0,0 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
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.ImmutableList
internal class SetActionToExecuteTransformer(
private val actionTypeToOverwrite: StakingActionCommonType,
private val pendingAction: PendingAction?,
private val pendingActions: ImmutableList<PendingAction>?,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
return prevState.copy(
actionType = actionTypeToOverwrite,
confirmationState = confirmationState?.copy(
pendingAction = pendingAction,
pendingActions = pendingActions,
) ?: StakingStates.ConfirmationState.Empty(),
)
}
}

View file

@ -109,6 +109,7 @@ internal class SetButtonsStateTransformer(
StakingStep.Confirmation -> getConfirmationButtonText()
StakingStep.Validators -> resourceReference(R.string.common_continue)
StakingStep.Amount,
StakingStep.RestakeValidator,
StakingStep.RewardsValidators,
-> resourceReference(R.string.common_next)
}
@ -120,17 +121,15 @@ internal class SetButtonsStateTransformer(
resourceReference(R.string.common_close)
} else {
when (actionType) {
StakingActionCommonType.ENTER -> {
StakingActionCommonType.Enter -> {
if (confirmationState.isApprovalNeeded) {
resourceReference(R.string.give_permission_title)
} else {
resourceReference(R.string.common_stake)
}
}
StakingActionCommonType.EXIT -> resourceReference(R.string.common_unstake)
StakingActionCommonType.PENDING_OTHER,
StakingActionCommonType.PENDING_REWARDS,
-> confirmationState.pendingAction?.type.getPendingActionTitle()
StakingActionCommonType.Exit -> resourceReference(R.string.common_unstake)
is StakingActionCommonType.Pending -> confirmationState.pendingAction?.type.getPendingActionTitle()
}
}
} else {
@ -140,18 +139,11 @@ internal class SetButtonsStateTransformer(
private fun StakingUiState.onPrimaryClick() {
when (currentStep) {
StakingStep.InitialInfo -> clickIntents.onEnterClick()
StakingStep.Validators -> {
if (confirmationState is StakingStates.ConfirmationState.Data) {
clickIntents.onNextClick(
pendingActions = confirmationState.pendingActions,
pendingAction = confirmationState.pendingAction,
)
} else {
clickIntents.onNextClick()
}
}
StakingStep.Amount -> clickIntents.onNextClick()
StakingStep.InitialInfo -> clickIntents.onNextClick()
StakingStep.Validators,
StakingStep.RestakeValidator,
-> clickIntents.onNextClick()
StakingStep.Amount -> clickIntents.onEnterClick()
StakingStep.Confirmation -> onConfirmationClick()
StakingStep.RewardsValidators -> Unit
}
@ -162,7 +154,7 @@ internal class SetButtonsStateTransformer(
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
clickIntents.onNextClick()
} else {
val isEnterAction = actionType == StakingActionCommonType.ENTER
val isEnterAction = actionType == StakingActionCommonType.Enter
val isApproveNeeded = confirmationState.isApprovalNeeded
if (isEnterAction && isApproveNeeded) {
@ -179,6 +171,7 @@ internal class SetButtonsStateTransformer(
private fun StakingStep.isPrevButtonVisible(): Boolean = when (this) {
StakingStep.InitialInfo,
StakingStep.RewardsValidators,
StakingStep.RestakeValidator,
StakingStep.Confirmation,
StakingStep.Validators,
-> false
@ -192,7 +185,9 @@ internal class SetButtonsStateTransformer(
StakingStep.Amount -> amountState.isPrimaryButtonEnabled
StakingStep.Confirmation -> confirmationState.isPrimaryButtonEnabled
StakingStep.RewardsValidators -> rewardsValidatorsState.isPrimaryButtonEnabled
StakingStep.Validators -> true
StakingStep.RestakeValidator,
StakingStep.Validators,
-> true
}
}
}

View file

@ -2,22 +2,17 @@ package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.FeeState
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.Provider
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
internal class SetConfirmationStateAssentTransformer(
private val appCurrencyProvider: Provider<AppCurrency>,
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
private val fee: Fee,
private val action: PendingAction?,
private val actions: ImmutableList<PendingAction>?,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
@ -30,7 +25,6 @@ internal class SetConfirmationStateAssentTransformer(
if (this is StakingStates.ConfirmationState.Data) {
val isFeeConvertibleToFiat = feeCryptoCurrencyStatus?.currency?.network?.hasFiatFeeRate == true
return copy(
innerState = InnerConfirmationStakingState.ASSENT,
feeState = FeeState.Content(
fee = fee,
rate = feeCryptoCurrencyStatus?.value?.fiatRate,
@ -38,11 +32,7 @@ internal class SetConfirmationStateAssentTransformer(
appCurrency = appCurrencyProvider(),
isFeeApproximate = false,
),
validatorState = validatorState.copySealed(isClickable = true),
pendingAction = action,
pendingActions = actions,
isPrimaryButtonEnabled = true,
isApprovalNeeded = false,
)
} else {
return this

View file

@ -16,11 +16,10 @@ internal class SetConfirmationStateCompletedTransformer(
}
private fun StakingStates.ConfirmationState.copyWrapped(): StakingStates.ConfirmationState {
if (this is StakingStates.ConfirmationState.Data) {
return copy(
return if (this is StakingStates.ConfirmationState.Data) {
copy(
isPrimaryButtonEnabled = true,
innerState = InnerConfirmationStakingState.COMPLETED,
validatorState = validatorState.copySealed(isClickable = false),
footerText = TextReference.EMPTY,
notifications = persistentListOf(),
transactionDoneState = TransactionDoneState.Content(
@ -29,7 +28,7 @@ internal class SetConfirmationStateCompletedTransformer(
),
)
} else {
return this
this
}
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal object SetConfirmationStateEmptyTransformer : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
actionType = StakingActionCommonType.Enter,
validatorState = StakingStates.ValidatorState.Empty(),
confirmationState = StakingStates.ConfirmationState.Empty(),
)
}
}

View file

@ -19,7 +19,6 @@ internal class SetConfirmationStateInProgressTransformer : Transformer<StakingUi
copy(
isPrimaryButtonEnabled = false,
innerState = InnerConfirmationStakingState.IN_PROGRESS,
validatorState = validatorState.copySealed(isClickable = false),
footerText = TextReference.EMPTY,
)
} else {

View file

@ -0,0 +1,69 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.staking.model.StakingApproval
import com.tangem.domain.staking.model.stakekit.PendingAction
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.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions
import com.tangem.features.staking.impl.presentation.state.utils.isTronStakedBalance
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
internal class SetConfirmationStateInitTransformer(
private val isEnter: Boolean,
private val stakingApproval: StakingApproval,
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val pendingActions: ImmutableList<PendingAction>? = null,
private val pendingAction: PendingAction? = pendingActions?.firstOrNull(),
) : Transformer<StakingUiState> {
private val networkId
get() = cryptoCurrencyStatus.currency.network.id.value
private val isComposePendingActions
get() = isComposePendingActions(networkId, pendingActions)
private val isTronStakedBalance
get() = isTronStakedBalance(networkId, pendingAction)
private val isExit: Boolean
get() = pendingAction == null && pendingActions?.isEmpty() == true || isTronStakedBalance
override fun transform(prevState: StakingUiState): StakingUiState {
val actionType = when {
isEnter -> StakingActionCommonType.Enter
isExit -> StakingActionCommonType.Exit
else -> when (pendingAction?.type) {
StakingActionType.STAKE -> StakingActionCommonType.Enter
StakingActionType.UNSTAKE -> StakingActionCommonType.Exit
StakingActionType.CLAIM_REWARDS,
StakingActionType.RESTAKE_REWARDS,
-> StakingActionCommonType.Pending.Rewards
StakingActionType.VOTE_LOCKED,
StakingActionType.RESTAKE,
-> StakingActionCommonType.Pending.Restake
else -> StakingActionCommonType.Pending.Other
}
}
return prevState.copy(
actionType = actionType,
confirmationState = StakingStates.ConfirmationState.Data(
isPrimaryButtonEnabled = false,
innerState = InnerConfirmationStakingState.ASSENT,
feeState = FeeState.Loading,
notifications = persistentListOf(),
footerText = TextReference.EMPTY,
transactionDoneState = TransactionDoneState.Empty,
isApprovalNeeded = stakingApproval is StakingApproval.Needed,
reduceAmountBy = null,
pendingAction = pendingAction,
pendingActions = pendingActions.takeIf { isComposePendingActions },
),
)
}
}

View file

@ -13,7 +13,6 @@ import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.utils.getRewardScheduleText
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
internal class SetConfirmationStateLoadingTransformer(
private val yield: Yield,
@ -22,39 +21,21 @@ internal class SetConfirmationStateLoadingTransformer(
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val filteredValidators = yield.validators.filter {
it.preferred
}
val possibleConfirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
val possibleValidatorState = possibleConfirmationState?.validatorState as? ValidatorState.Content
val chosenValidator = possibleValidatorState?.chosenValidator ?: filteredValidators[0]
return prevState.copy(
confirmationState = StakingStates.ConfirmationState.Data(
confirmationState = possibleConfirmationState?.copy(
isPrimaryButtonEnabled = false,
innerState = InnerConfirmationStakingState.ASSENT,
feeState = FeeState.Loading,
validatorState = ValidatorState.Content(
isClickable = true,
chosenValidator = chosenValidator,
availableValidators = filteredValidators,
),
notifications = persistentListOf(),
footerText = getFooter(prevState),
transactionDoneState = TransactionDoneState.Empty,
pendingAction = possibleConfirmationState?.pendingAction,
pendingActions = possibleConfirmationState?.pendingActions,
isApprovalNeeded = false,
reduceAmountBy = null,
possiblePendingTransaction = null,
),
) ?: prevState.confirmationState,
)
}
private fun getFooter(state: StakingUiState): TextReference {
val amountState = state.amountState as? AmountState.Data
val isEnterAction = state.actionType == StakingActionCommonType.ENTER
val isEnterAction = state.actionType == StakingActionCommonType.Enter
val amountDecimal = amountState?.amountTextField?.fiatAmount?.value
val amountValue = BigDecimalFormatter.formatFiatAmount(

View file

@ -13,7 +13,6 @@ internal object SetConfirmationStateResetAssentTransformer : Transformer<Staking
confirmationState.copy(
isPrimaryButtonEnabled = true,
innerState = InnerConfirmationStakingState.ASSENT,
validatorState = confirmationState.validatorState.copySealed(isClickable = true),
)
} else {
confirmationState

View file

@ -6,8 +6,10 @@ import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.Yield
@ -25,7 +27,6 @@ import com.tangem.utils.Provider
import com.tangem.utils.isNullOrZero
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
@ -33,7 +34,6 @@ import java.math.BigDecimal
internal class SetInitialDataStateTransformer(
private val clickIntents: StakingClickIntents,
private val yield: Yield,
private val isApprovalNeeded: Boolean,
private val isAnyTokenStaked: Boolean,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val userWalletProvider: Provider<UserWallet>,
@ -76,7 +76,7 @@ internal class SetInitialDataStateTransformer(
currentStep = StakingStep.InitialInfo,
initialInfoState = createInitialInfoState(),
amountState = createInitialAmountState(),
confirmationState = createInitialConfirmationState(),
confirmationState = StakingStates.ConfirmationState.Empty(),
rewardsValidatorsState = rewardsValidatorStateConverter.convert(Unit),
bottomSheetConfig = null,
)
@ -87,7 +87,7 @@ internal class SetInitialDataStateTransformer(
return StakingStates.InitialInfoState.Data(
isPrimaryButtonEnabled = !cryptoCurrencyStatusProvider().value.amount.isNullOrZero(),
showBanner = !isAnyTokenStaked && yieldBalance == InnerYieldBalanceState.Empty,
aprRange = getAprRange(yield.validators),
aprRange = getAprRange(yield.preferredValidators),
infoItems = getInfoItems(),
onInfoClick = clickIntents::onInfoClick,
yieldBalance = yieldBalance,
@ -113,7 +113,7 @@ internal class SetInitialDataStateTransformer(
}
private fun createAnnualPercentageRateItem(): RoundedListWithDividersItemData {
val validators = yield.validators
val validators = yield.preferredValidators
return RoundedListWithDividersItemData(
id = R.string.staking_details_annual_percentage_rate,
startText = TextReference.Res(R.string.staking_details_annual_percentage_rate),
@ -128,11 +128,7 @@ internal class SetInitialDataStateTransformer(
id = R.string.staking_details_available,
startText = TextReference.Res(R.string.staking_details_available),
endText = TextReference.Str(
value = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoCurrencyStatus.value.amount,
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
decimals = cryptoCurrencyStatus.currency.decimals,
),
value = cryptoCurrencyStatus.value.amount.format { crypto(cryptoCurrencyStatus.currency) },
),
isEndTextHideable = true,
)
@ -158,11 +154,7 @@ internal class SetInitialDataStateTransformer(
val minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum ?: return null
if (!isPolkadot(cryptoCurrencyStatus.currency.network.id.value)) return null
val formattedAmount = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = minimumCryptoAmount,
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
decimals = cryptoCurrencyStatus.currency.decimals,
)
val formattedAmount = minimumCryptoAmount.format { crypto(cryptoCurrencyStatus.currency) }
return RoundedListWithDividersItemData(
id = R.string.staking_details_minimum_requirement,
@ -218,23 +210,6 @@ internal class SetInitialDataStateTransformer(
return amountStateConverter.convert("")
}
private fun createInitialConfirmationState(): StakingStates.ConfirmationState {
return StakingStates.ConfirmationState.Data(
isPrimaryButtonEnabled = false,
innerState = InnerConfirmationStakingState.ASSENT,
feeState = FeeState.Loading,
validatorState = ValidatorState.Loading,
notifications = persistentListOf(),
footerText = TextReference.EMPTY,
transactionDoneState = TransactionDoneState.Empty,
pendingAction = null,
pendingActions = null,
isApprovalNeeded = isApprovalNeeded,
reduceAmountBy = null,
possiblePendingTransaction = null,
)
}
private fun getAprRange(validators: List<Yield.Validator>): TextReference {
val aprValues = validators
.filter { it.preferred }
@ -243,14 +218,8 @@ internal class SetInitialDataStateTransformer(
val minApr = aprValues.min()
val maxApr = aprValues.max()
val formattedMinApr = BigDecimalFormatter.formatPercent(
percent = minApr,
useAbsoluteValue = true,
).remove("%")
val formattedMaxApr = BigDecimalFormatter.formatPercent(
percent = maxApr,
useAbsoluteValue = true,
)
val formattedMinApr = minApr.format { percent() }.remove("%")
val formattedMaxApr = maxApr.format { percent() }
if (maxApr - minApr < EQUALITY_THRESHOLD) {
return stringReference("$formattedMinApr%")

View file

@ -1,40 +0,0 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class SetPossiblePendingTransactionTransformer(
private val yield: Yield,
private val balanceState: BalanceState,
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val possibleConfirmationState =
prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
val balancesId = yieldBalance?.getBalancesUniqueId() ?: 0
val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId)
return prevState.copy(
confirmationState = possibleConfirmationState.copy(
possiblePendingTransaction = PendingTransaction(
groupId = balanceState.groupId,
token = token,
type = balanceState.type,
amount = balanceState.cryptoDecimal,
rawCurrencyId = balanceState.rawCurrencyId,
validator = balanceState.validator,
balancesId = balancesId,
),
),
)
}
}

View file

@ -20,7 +20,9 @@ internal object SetTitleTransformer : Transformer<StakingUiState> {
val title = when (currentStep) {
StakingStep.Amount -> resourceReference(R.string.send_amount_label)
StakingStep.Validators -> resourceReference(R.string.staking_validators)
StakingStep.RestakeValidator,
StakingStep.Validators,
-> resourceReference(R.string.staking_validators)
StakingStep.RewardsValidators -> resourceReference(R.string.common_claim_rewards)
StakingStep.InitialInfo -> resourceReference(
R.string.staking_title_stake,
@ -29,11 +31,11 @@ internal object SetTitleTransformer : Transformer<StakingUiState> {
StakingStep.Confirmation -> {
when (actionType) {
StakingActionCommonType.ENTER -> resourceReference(
StakingActionCommonType.Enter -> resourceReference(
R.string.staking_title_stake,
wrappedList(prevState.cryptoCurrencyName),
)
StakingActionCommonType.EXIT -> resourceReference(
StakingActionCommonType.Exit -> resourceReference(
R.string.staking_title_unstake,
wrappedList(prevState.cryptoCurrencyName),
)
@ -51,7 +53,7 @@ internal object SetTitleTransformer : Transformer<StakingUiState> {
}
}
val subtitle = if (currentStep == StakingStep.Confirmation && actionType == StakingActionCommonType.ENTER) {
val subtitle = if (currentStep == StakingStep.Confirmation && actionType == StakingActionCommonType.Enter) {
stringReference(prevState.walletName)
} else {
null

View file

@ -7,7 +7,8 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.isNullOrEmpty
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.staking.model.stakekit.AddressArgument
import com.tangem.domain.staking.model.stakekit.Yield
@ -55,11 +56,9 @@ internal class AmountRequirementStateTransformer(
isRequirementError -> resourceReference(
R.string.staking_amount_requirement_error,
wrappedList(
BigDecimalFormatter.formatCryptoAmount(
amountRequirements.minimum,
cryptoCurrencyStatus.currency.symbol,
cryptoCurrencyStatus.currency.decimals,
),
amountRequirements.minimum.format {
crypto(cryptoCurrencyStatus.currency)
},
),
)
isIntegerOnlyError -> resourceReference(
@ -102,7 +101,7 @@ internal class AmountRequirementStateTransformer(
private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean {
val cryptoAmountValue = amountState.amountTextField.cryptoAmount.value ?: return false
val isEnter = actionType == StakingActionCommonType.ENTER
val isEnter = actionType == StakingActionCommonType.Enter
val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value)
val isIntegerOnly = cryptoAmountValue.isZero() || cryptoAmountValue.remainder(BigDecimal.ONE).isZero()

View file

@ -35,7 +35,6 @@ internal class SetConfirmationStateAssentApprovalTransformer(
appCurrency = appCurrencyProvider(),
isFeeApproximate = false,
),
validatorState = validatorState.copySealed(isClickable = true),
isPrimaryButtonEnabled = true,
isApprovalNeeded = true,
)

View file

@ -5,6 +5,8 @@ import com.tangem.common.ui.bottomsheet.permission.state.*
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -12,7 +14,6 @@ import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.FeeState
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.ValidatorState
import com.tangem.utils.Provider
import com.tangem.utils.transformer.Transformer
@ -28,17 +29,15 @@ internal class ShowApprovalBottomSheetTransformer(
val amountState = prevState.amountState as? AmountState.Data ?: return prevState
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
val validatorState = confirmationState.validatorState as? ValidatorState.Content ?: return prevState
val validatorState = prevState.validatorState as? StakingStates.ValidatorState.Data ?: return prevState
val feeState = confirmationState.feeState as? FeeState.Content ?: return prevState
val fee = feeState.fee ?: return prevState
val walletAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value.orEmpty()
val validatorAddress = validatorState.chosenValidator.address
val feeCryptoValue = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = fee.amount.value,
cryptoCurrency = fee.amount.currencySymbol,
decimals = fee.amount.decimals,
)
val feeCryptoValue = fee.amount.value.format {
crypto(fee.amount.currencySymbol, fee.amount.decimals)
}
val feeFiatValue = BigDecimalFormatter.formatFiatAmount(
fiatAmount = feeCryptoCurrencyStatus?.value?.fiatRate?.multiply(fee.amount.value),
fiatCurrencyCode = appCurrencyProvider().code,

View file

@ -1,29 +1,45 @@
package com.tangem.features.staking.impl.presentation.state.transformers.validator
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.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.ValidatorState
import com.tangem.utils.transformer.Transformer
internal class ValidatorSelectChangeTransformer(
private val yield: Yield,
private val selectedValidator: Yield.Validator?,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
selectedValidator ?: return prevState
val validatorState = (confirmationState.validatorState as? ValidatorState.Content)?.copy(
chosenValidator = selectedValidator,
) ?: ValidatorState.Content(
isClickable = false,
availableValidators = emptyList(),
chosenValidator = selectedValidator,
)
val validatorState = prevState.validatorState as? StakingStates.ValidatorState.Data
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
val isRestake = prevState.actionType == StakingActionCommonType.Pending.Restake
val isEnter = prevState.actionType == StakingActionCommonType.Enter
val isFromInfoScreen = prevState.currentStep == StakingStep.InitialInfo
val isVoteLocked = confirmationState?.pendingAction?.type == StakingActionType.VOTE_LOCKED
val activeValidator = selectedValidator.takeIf { isFromInfoScreen && isRestake }
?: validatorState?.activeValidator
val filteredValidators = yield.preferredValidators.filterNot { it == activeValidator }
val selectedValidator = if (isRestake && isFromInfoScreen) {
filteredValidators.firstOrNull()
} else {
selectedValidator
}
return prevState.copy(
confirmationState = confirmationState.copy(
validatorState = validatorState,
validatorState = StakingStates.ValidatorState.Data(
chosenValidator = selectedValidator ?: yield.preferredValidators.first(),
availableValidators = filteredValidators,
isPrimaryButtonEnabled = true,
isClickable = true,
activeValidator = activeValidator,
isVisibleOnConfirmation = isEnter || isRestake || isVoteLocked,
),
)
}

View file

@ -5,8 +5,12 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.lib.crypto.BlockchainUtils.isBSC
import com.tangem.lib.crypto.BlockchainUtils.isSolana
import com.tangem.lib.crypto.BlockchainUtils.isTron
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@Suppress("CyclomaticComplexMethod")
internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (this) {
@ -29,8 +33,35 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t
null -> TextReference.EMPTY
}
internal fun isSolanaWithdraw(networkId: String, pendingActions: ImmutableList<PendingAction>?): Boolean {
val isSolana = isSolana(networkId)
val isWithdraw = pendingActions?.all { it.type == StakingActionType.WITHDRAW } == true
return isSolana && isWithdraw && !pendingActions.isNullOrEmpty()
internal fun isSingleAction(networkId: String, activeStake: BalanceState): Boolean {
val isSingleAction = activeStake.pendingActions.size <= 1 // Either single or none pending actions
val isComposePendingActions = isComposePendingActions(networkId, activeStake.pendingActions)
val isBscRestake = isBSC(networkId) && activeStake.pendingActions.any {
it.type == StakingActionType.RESTAKE
}
return isSingleAction && !isBscRestake || isComposePendingActions
}
internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) = if (isBSC(networkId)) {
activeStake.pendingActions.plus(
PendingAction(
type = StakingActionType.UNSTAKE,
passthrough = "",
args = null,
),
).toPersistentList()
} else {
activeStake.pendingActions
}
internal fun isTronStakedBalance(networkId: String, pendingAction: PendingAction?): Boolean {
return isTron(networkId) && pendingAction?.type == StakingActionType.REVOTE
}
internal fun isComposePendingActions(networkId: String, pendingActions: ImmutableList<PendingAction>?): Boolean {
return when {
isSolana(networkId) -> pendingActions?.any { it.type == StakingActionType.WITHDRAW } == true
else -> false
}
}

View file

@ -12,8 +12,9 @@ import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.inputrow.InputRowImageInfo
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
@ -41,10 +42,7 @@ internal fun StakingClaimRewardsValidatorContent(
annotatedReference {
appendSpace()
appendColored(
text = BigDecimalFormatter.formatPercent(
percent = item.validator?.apr.orZero(),
useAbsoluteValue = true,
),
text = item.validator?.apr.orZero().format { percent() },
color = TangemTheme.colors.text.accent,
)
},

View file

@ -19,13 +19,13 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
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.TransactionDoneState
import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmationStatePreviewData
import com.tangem.features.staking.impl.presentation.state.previewdata.ValidatorStatePreviewData
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
import com.tangem.features.staking.impl.presentation.ui.block.NotificationsBlock
import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock
@ -36,12 +36,12 @@ import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickInten
internal fun StakingConfirmationContent(
amountState: AmountState,
state: StakingStates.ConfirmationState,
validatorState: StakingStates.ValidatorState,
clickIntents: StakingClickIntents,
type: StakingActionCommonType,
) {
if (state !is StakingStates.ConfirmationState.Data) return
val isEnterAction = type == StakingActionCommonType.ENTER
val showValidatorBlock = isEnterAction || state.pendingAction?.type == StakingActionType.VOTE_LOCKED
val isEnterAction = type == StakingActionCommonType.Enter
val isTransactionSent = state.innerState == InnerConfirmationStakingState.COMPLETED
val isTransactionInProgress = state.notifications.any { it is StakingNotification.Warning.TransactionInProgress }
Column(
@ -67,13 +67,11 @@ internal fun StakingConfirmationContent(
isEditingDisabled = !isEnterAction && state.innerState != InnerConfirmationStakingState.COMPLETED,
onClick = clickIntents::onPrevClick,
)
if (showValidatorBlock) {
ValidatorBlock(
validatorState = state.validatorState,
isClickable = !isTransactionInProgress,
onClick = clickIntents::openValidators,
)
}
ValidatorBlock(
validatorState = validatorState,
isClickable = !isTransactionInProgress,
onClick = clickIntents::openValidators,
)
StakingFeeBlock(feeState = state.feeState, isTransactionSent = isTransactionSent)
NotificationsBlock(notifications = state.notifications)
}
@ -88,8 +86,9 @@ private fun Preview_StakingConfirmationContent() {
StakingConfirmationContent(
amountState = AmountStatePreviewData.amountState,
state = ConfirmationStatePreviewData.assentStakingState,
validatorState = ValidatorStatePreviewData.validatorState,
clickIntents = StakingClickIntentsStub,
type = StakingActionCommonType.ENTER,
type = StakingActionCommonType.Enter,
)
}
}

View file

@ -41,11 +41,12 @@ import com.tangem.core.ui.components.inputrow.InputRowImageInfo
import com.tangem.core.ui.components.list.roundedListWithDividersItems
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.RewardBlockType
import com.tangem.features.staking.impl.R
@ -311,10 +312,7 @@ private fun BalanceState.getAprText() = combinedReference(
annotatedReference {
appendSpace()
appendColored(
text = BigDecimalFormatter.formatPercent(
percent = validator?.apr.orZero(),
useAbsoluteValue = true,
),
text = validator?.apr.orZero().format { percent() },
color = TangemTheme.colors.text.accent,
)
},

View file

@ -3,7 +3,6 @@ package com.tangem.features.staking.impl.presentation.ui
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
@ -89,15 +88,12 @@ private fun StakingAppBar(uiState: StakingUiState) {
val (backIcon, click) = when (uiState.currentStep) {
StakingStep.Amount,
StakingStep.Confirmation,
-> {
R.drawable.ic_close_24 to uiState.clickIntents::onBackClick
}
-> R.drawable.ic_close_24 to uiState.clickIntents::onBackClick
StakingStep.Validators,
StakingStep.RewardsValidators,
StakingStep.RestakeValidator,
StakingStep.InitialInfo,
-> {
R.drawable.ic_back_24 to uiState.clickIntents::onPrevClick
}
-> R.drawable.ic_back_24 to uiState.clickIntents::onPrevClick
}
AppBarWithBackButtonAndIcon(
text = uiState.title.resolveReference(),
@ -109,7 +105,6 @@ private fun StakingAppBar(uiState: StakingUiState) {
)
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = Modifier) {
val currentScreen = uiState.currentStep
@ -172,17 +167,16 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
StakingStep.Confirmation -> StakingConfirmationContent(
amountState = uiState.amountState,
state = uiState.confirmationState,
validatorState = uiState.validatorState,
clickIntents = uiState.clickIntents,
type = uiState.actionType,
)
StakingStep.Validators -> {
val confirmState = uiState.confirmationState
if (confirmState !is StakingStates.ConfirmationState.Data) return@AnimatedContent
StakingValidatorListContent(
state = confirmState.validatorState,
clickIntents = uiState.clickIntents,
)
}
StakingStep.RestakeValidator,
StakingStep.Validators,
-> StakingValidatorListContent(
state = uiState.validatorState,
clickIntents = uiState.clickIntents,
)
}
}
}

View file

@ -24,12 +24,13 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.inputrow.InputRowImageSelector
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.ValidatorState
import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmationStatePreviewData
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.previewdata.ValidatorStatePreviewData
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.utils.extensions.orZero
@ -39,7 +40,7 @@ import com.tangem.utils.extensions.orZero
*/
@Composable
internal fun StakingValidatorListContent(
state: ValidatorState,
state: StakingStates.ValidatorState,
clickIntents: StakingClickIntents,
modifier: Modifier = Modifier,
) {
@ -51,7 +52,7 @@ internal fun StakingValidatorListContent(
.background(TangemTheme.colors.background.secondary)
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
if (state is ValidatorState.Content) {
if (state is StakingStates.ValidatorState.Data) {
val validators = state.availableValidators
items(
count = validators.size,
@ -67,7 +68,7 @@ internal fun StakingValidatorListContent(
annotatedReference {
appendSpace()
appendColored(
text = BigDecimalFormatter.formatPercent(item.apr.orZero(), true),
text = item.apr.orZero().format { percent() },
color = TangemTheme.colors.text.accent,
)
},
@ -131,7 +132,7 @@ private fun RowScope.ValidatorLabel(isStrategicPartner: Boolean) {
@Composable
private fun StakingValidatorListContent_Preview(
@PreviewParameter(StakingValidatorListContentPreviewProvider::class)
data: ValidatorState,
data: StakingStates.ValidatorState,
) {
TangemThemePreview {
StakingValidatorListContent(
@ -141,8 +142,8 @@ private fun StakingValidatorListContent_Preview(
}
}
private class StakingValidatorListContentPreviewProvider : PreviewParameterProvider<ValidatorState> {
override val values: Sequence<ValidatorState>
get() = sequenceOf(ConfirmationStatePreviewData.assentStakingState.validatorState)
private class StakingValidatorListContentPreviewProvider : PreviewParameterProvider<StakingStates.ValidatorState> {
override val values: Sequence<StakingStates.ValidatorState>
get() = sequenceOf(ValidatorStatePreviewData.validatorState)
}
// endregion

View file

@ -21,6 +21,9 @@ import com.tangem.common.ui.amountScreen.utils.getFiatReference
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.rows.SelectorRowItem
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fee
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
@ -56,12 +59,12 @@ internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) {
titleRes = R.string.common_fee_selector_option_market,
iconRes = R.drawable.ic_bird_24,
preDot = stringReference(
BigDecimalFormatter.formatCryptoFeeAmount(
cryptoAmount = feeAmount?.value,
cryptoCurrency = feeAmount?.currencySymbol.orEmpty(),
decimals = feeAmount?.decimals ?: 0,
canBeLower = feeState.isFeeApproximate,
),
feeAmount?.value.format {
crypto(
symbol = feeAmount?.currencySymbol.orEmpty(),
decimals = feeAmount?.decimals ?: 0,
).fee(canBeLower = feeState.isFeeApproximate)
},
),
postDot = if (feeState.isFeeConvertibleToFiat) {
getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency)

View file

@ -12,42 +12,44 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import com.tangem.core.ui.components.inputrow.InputRowImageInfo
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.ValidatorState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.ui.ValidatorImagePlaceholder
import com.tangem.utils.extensions.orZero
@Composable
internal fun ValidatorBlock(validatorState: ValidatorState, isClickable: Boolean, onClick: () -> Unit) {
internal fun ValidatorBlock(validatorState: StakingStates.ValidatorState, isClickable: Boolean, onClick: () -> Unit) {
val state = validatorState as? StakingStates.ValidatorState.Data ?: return
if (!state.isVisibleOnConfirmation) return
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.clickable(
enabled = validatorState.isClickable && isClickable,
enabled = state.isClickable && isClickable,
interactionSource = remember { MutableInteractionSource() },
indication = ripple(),
onClick = onClick,
),
) {
if (validatorState is ValidatorState.Content) {
InputRowImageInfo(
title = resourceReference(R.string.staking_validator),
subtitle = stringReference(validatorState.chosenValidator.name),
infoTitle = annotatedReference {
append(resourceReference(R.string.staking_details_apr).resolveReference())
appendSpace()
appendColored(
text = BigDecimalFormatter.formatPercent(validatorState.chosenValidator.apr.orZero(), true),
color = TangemTheme.colors.text.accent,
)
},
imageUrl = validatorState.chosenValidator.image.orEmpty(),
onImageError = { ValidatorImagePlaceholder() },
)
}
InputRowImageInfo(
title = resourceReference(R.string.staking_validator),
subtitle = stringReference(state.chosenValidator.name),
infoTitle = annotatedReference {
append(resourceReference(R.string.staking_details_apr).resolveReference())
appendSpace()
appendColored(
text = state.chosenValidator.apr.orZero().format { percent() },
color = TangemTheme.colors.text.accent,
)
},
imageUrl = state.chosenValidator.image.orEmpty(),
onImageError = { ValidatorImagePlaceholder() },
)
}
}

View file

@ -3,13 +3,10 @@ package com.tangem.features.staking.impl.presentation.viewmodel
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
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 kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
@Suppress("TooManyFunctions")
@ -17,12 +14,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
fun onBackClick()
fun onNextClick(
actionTypeToOverwrite: StakingActionCommonType? = null,
pendingAction: PendingAction? = null,
pendingActions: ImmutableList<PendingAction>? = null,
balanceState: BalanceState? = null,
)
fun onNextClick(balanceState: BalanceState? = null)
fun onActionClick()
@ -36,9 +28,9 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
fun onEnterClick()
fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList<PendingAction>?)
fun getFee()
override fun onAmountNext() = onNextClick(actionTypeToOverwrite = null)
override fun onAmountNext() = onNextClick()
fun openValidators()

View file

@ -26,14 +26,13 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase
import com.tangem.domain.staking.IsAnyTokenStakedUseCase
import com.tangem.domain.staking.IsApproveNeededUseCase
import com.tangem.domain.staking.*
import com.tangem.domain.staking.model.StakingApproval
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.action.StakingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
import com.tangem.domain.tokens.*
@ -65,12 +64,14 @@ import com.tangem.features.staking.impl.presentation.state.transformers.*
import com.tangem.features.staking.impl.presentation.state.transformers.amount.*
import com.tangem.features.staking.impl.presentation.state.transformers.approval.*
import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer
import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw
import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction
import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import com.tangem.utils.extensions.isSingleItem
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -108,6 +109,7 @@ internal class StakingViewModel @Inject constructor(
private val stakingBalanceUpdater: StakingBalanceUpdater.Factory,
private val analyticsEventHandler: AnalyticsEventHandler,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val getActionsUseCase: GetActionsUseCase,
private val paramsInterceptorHolder: ParamsInterceptorHolder,
@DelayedWork private val coroutineScope: CoroutineScope,
savedStateHandle: SavedStateHandle,
@ -132,6 +134,7 @@ internal class StakingViewModel @Inject constructor(
?: error("This screen can't be opened without `Yield`")
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var processingActions: List<StakingAction> = emptyList()
private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var innerRouter: InnerStakingRouter by Delegates.notNull()
@ -142,9 +145,8 @@ internal class StakingViewModel @Inject constructor(
get() {
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
return invalidatePendingTransactionsUseCase(
userWalletId = userWallet.walletId,
balanceItems = yieldBalance?.balance?.items ?: emptyList(),
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
processingActions = processingActions,
).getOrElse { emptyList() }
}
@ -154,6 +156,7 @@ internal class StakingViewModel @Inject constructor(
stakingBalanceUpdater.create(
cryptoCurrencyStatus,
userWallet,
yield,
)
}
@ -188,6 +191,7 @@ internal class StakingViewModel @Inject constructor(
private var stakingApproval: StakingApproval = StakingApproval.Empty
private var isAmountSubtractAvailable: Boolean = false
private var isAnyTokenStaked: Boolean = false
private val allowanceTaskScheduler = SingleTaskScheduler<BigDecimal>()
private val transactionsInProgress: CopyOnWriteArrayList<StakingTransaction> = CopyOnWriteArrayList()
@ -195,6 +199,7 @@ internal class StakingViewModel @Inject constructor(
private var approvalJobHolder: JobHolder = JobHolder()
private var feeJobHolder: JobHolder = JobHolder()
private var sendTransactionJobHolder = JobHolder()
private var stepChangesJobHolder = JobHolder()
init {
subscribeOnSelectedAppCurrency()
@ -208,76 +213,18 @@ internal class StakingViewModel @Inject constructor(
approvalJobHolder.cancel()
feeJobHolder.cancel()
sendTransactionJobHolder.cancel()
stepChangesJobHolder.cancel()
}
override fun onBackClick() {
stakingStateRouter.onBackClick()
}
override fun onNextClick(
actionTypeToOverwrite: StakingActionCommonType?,
pendingAction: PendingAction?,
pendingActions: ImmutableList<PendingAction>?,
balanceState: BalanceState?,
) {
if (actionTypeToOverwrite != null) {
stateController.update(SetActionToExecuteTransformer(actionTypeToOverwrite, pendingAction, pendingActions))
}
override fun onNextClick(balanceState: BalanceState?) {
stakingStateRouter.onNextClick()
when {
isInitState() -> {
stateController.update(SetConfirmationStateResetAssentTransformer)
stateController.update(
SetConfirmationStateLoadingTransformer(
yield = yield,
appCurrency = appCurrency,
cryptoCurrency = cryptoCurrencyStatus.currency,
),
)
if (balanceState != null) {
stateController.update(
SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus),
)
}
stateController.update(
transformer = SetInitialDataStateTransformer(
clickIntents = this@StakingViewModel,
yield = yield,
isAnyTokenStaked = true,
isApprovalNeeded = stakingApproval is StakingApproval.Needed,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },
balancesToShowProvider = Provider { balancesToShow },
),
)
onRefreshSwipe(isRefreshing = false)
}
isAssentState() -> {
getFee(pendingAction, pendingActions)
if (balanceState != null) {
stateController.update(
SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus),
)
}
val amountState = value.amountState as? AmountState.Data
if (amountState?.amountTextField?.isWarning == true) {
stateController.update(
AmountRoundToIntegerTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
),
)
}
}
}
}
override fun onActionClick() {
handleOnNextConfirmationClick()
}
override fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList<PendingAction>?) {
override fun getFee() {
stateController.update(
SetConfirmationStateLoadingTransformer(
yield = yield,
@ -287,16 +234,12 @@ internal class StakingViewModel @Inject constructor(
)
viewModelScope.launch {
feeLoader.getFee(
pendingAction = pendingAction,
pendingActions = pendingActions,
onStakingFee = { gasEstimate ->
stateController.update(
SetConfirmationStateAssentTransformer(
appCurrencyProvider = Provider { appCurrency },
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
fee = gasEstimate,
action = pendingAction,
actions = pendingActions,
),
)
updateNotifications()
@ -324,7 +267,7 @@ internal class StakingViewModel @Inject constructor(
}.saveIn(feeJobHolder)
}
private fun handleOnNextConfirmationClick() {
override fun onActionClick() {
if (isAssentState()) {
viewModelScope.launch {
stakingAnalyticSender.sendTransactionStakingClickedAnalytics(value)
@ -343,7 +286,6 @@ internal class StakingViewModel @Inject constructor(
stateController.update(SetConfirmationStateCompletedTransformer(txUrl))
},
onSendError = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError)
stakingEventFactory.createSendTransactionErrorAlert(error)
stateController.update(SetConfirmationStateResetAssentTransformer)
@ -383,7 +325,7 @@ internal class StakingViewModel @Inject constructor(
override fun onRefreshSwipe(isRefreshing: Boolean) {
stateController.update(SetInitialLoadingStateTransformer(isRefreshing))
coroutineScope.launch {
balanceUpdater.instantUpdate()
balanceUpdater.partialUpdate()
}.invokeOnCompletion {
stateController.update(SetInitialLoadingStateTransformer(false))
}
@ -403,28 +345,23 @@ internal class StakingViewModel @Inject constructor(
}
override fun onEnterClick() {
onAmountValueChange("") // reset amount state
// TODO refactor in [REDACTED_JIRA]
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
val filteredValidator = yield.validators.filter { it.preferred }
if (filteredValidator.isEmpty()) {
if (yield.preferredValidators.isEmpty()) {
stateController.updateEvent(
StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators),
)
} else {
stateController.update {
value.copy(
confirmationState = confirmationState?.copy(
validatorState = ValidatorState.Content(
isClickable = true,
chosenValidator = filteredValidator[0],
availableValidators = filteredValidator,
),
) as StakingStates.ConfirmationState,
)
}
onNextClick(StakingActionCommonType.ENTER)
stateController.updateAll(
SetConfirmationStateInitTransformer(
isEnter = true,
cryptoCurrencyStatus = cryptoCurrencyStatus,
stakingApproval = stakingApproval,
),
ValidatorSelectChangeTransformer(
selectedValidator = null,
yield = yield,
),
)
onNextClick()
}
}
@ -463,7 +400,12 @@ internal class StakingViewModel @Inject constructor(
validator = validator.name,
),
)
stateController.update(ValidatorSelectChangeTransformer(validator))
stateController.update(
ValidatorSelectChangeTransformer(
selectedValidator = validator,
yield = yield,
),
)
}
override fun openRewardsValidators() {
@ -479,50 +421,38 @@ internal class StakingViewModel @Inject constructor(
source = StakeScreenSource.Info,
),
)
onNextClick(actionTypeToOverwrite = StakingActionCommonType.PENDING_REWARDS)
stateController.update {
value.copy(actionType = StakingActionCommonType.Pending.Rewards)
}
stakingStateRouter.showRewardsValidators()
}
}
override fun onActiveStake(activeStake: BalanceState) {
val isAllWithdrawActions = isSolanaWithdraw(
cryptoCurrencyStatus.currency.network.id.value,
activeStake.pendingActions,
)
val isMultiplePendingActions = activeStake.pendingActions.size > 1
if (isMultiplePendingActions && !isAllWithdrawActions) {
val networkId = cryptoCurrencyStatus.currency.network.id.value
if (isSingleAction(networkId, activeStake)) {
prepareForConfirmation(
pendingActions = activeStake.pendingActions,
validator = activeStake.validator,
amountValue = activeStake.cryptoValue,
)
onNextClick(balanceState = activeStake)
} else {
stateController.update(
ShowActionSelectorBottomSheetTransformer(
pendingActions = activeStake.pendingActions,
pendingActions = withStubUnstakeAction(networkId, activeStake),
onActionSelect = { action ->
stateController.update(ValidatorSelectChangeTransformer(activeStake.validator))
stateController.update(
AmountChangeStateTransformer(
cryptoCurrencyStatus,
activeStake.cryptoValue,
yield,
),
)
onNextClick(
actionTypeToOverwrite = StakingActionCommonType.PENDING_OTHER,
prepareForConfirmation(
pendingAction = action,
balanceState = activeStake,
validator = activeStake.validator,
amountValue = activeStake.cryptoValue,
)
stateController.update(DismissBottomSheetStateTransformer)
onNextClick(balanceState = activeStake)
},
onDismiss = { stateController.update(DismissBottomSheetStateTransformer) },
),
)
} else {
stateController.update(ActionTypeActiveStakeTransformer(cryptoCurrencyStatus, activeStake))
stateController.update(ValidatorSelectChangeTransformer(activeStake.validator))
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, activeStake.cryptoValue, yield))
onNextClick(
actionTypeToOverwrite = null,
pendingAction = activeStake.pendingActions.firstOrNull(),
pendingActions = activeStake.pendingActions.takeIf { isAllWithdrawActions },
balanceState = activeStake,
)
}
}
@ -617,7 +547,7 @@ internal class StakingViewModel @Inject constructor(
stakingAnalyticSender.sendTransactionApprovalAnalytics(tokenCryptoCurrency)
stateController.update(SetApprovalInProgressTransformer)
stateController.update(DismissBottomSheetStateTransformer)
awaitForAllowance(confirmationState.pendingAction)
awaitForAllowance()
},
)
}.saveIn(approvalJobHolder)
@ -703,7 +633,7 @@ internal class StakingViewModel @Inject constructor(
stateController.update(DismissStakingNotificationsStateTransformer(notification))
}
private fun awaitForAllowance(pendingAction: PendingAction?) {
private fun awaitForAllowance() {
val approval = stakingApproval as? StakingApproval.Needed ?: return
allowanceTaskScheduler.scheduleTask(
scope = viewModelScope,
@ -722,7 +652,7 @@ internal class StakingViewModel @Inject constructor(
val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
?: error("No amount provided")
if (allowance >= amount) {
getFee(pendingAction = pendingAction, pendingActions = null)
getFee()
allowanceTaskScheduler.cancelTask()
}
},
@ -760,7 +690,7 @@ internal class StakingViewModel @Inject constructor(
val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrElse { error("CardInfo must be not null") }
val amountState = uiState.value.amountState as? AmountState.Data
val confirmationState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
val validatorState = uiState.value.validatorState as? StakingStates.ValidatorState.Data
val feeState = confirmationState?.feeState as? FeeState.Content
val validator = validatorState?.chosenValidator
@ -802,13 +732,16 @@ internal class StakingViewModel @Inject constructor(
stakingApproval = isApproveNeededUseCase(cryptoCurrencyStatus.currency).getOrElse { StakingApproval.Empty }
}
private suspend fun setupIsAnyTokenStaked() {
isAnyTokenStaked = isAnyTokenStakedUseCase(userWalletId).getOrNull() ?: false
}
private fun subscribeOnCurrencyStatusUpdates() {
getUserWalletUseCase(userWalletId).fold(
ifRight = { wallet ->
userWallet = wallet
},
ifLeft = {
Timber.e(it.toString())
stakingEventFactory.createGenericErrorAlert(it.toString())
stateController.update(SetConfirmationStateResetAssentTransformer)
},
@ -820,6 +753,8 @@ internal class StakingViewModel @Inject constructor(
.onEach { maybeStatus ->
maybeStatus.fold(
ifRight = { status ->
if (status.value !is CryptoCurrencyStatus.Loaded) return@fold
if (!isInitialInfoAnalyticSent) {
isInitialInfoAnalyticSent = true
val balances = status.value.yieldBalance as? YieldBalance.Data
@ -836,26 +771,14 @@ internal class StakingViewModel @Inject constructor(
feeCryptoCurrencyStatus =
getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull()
cryptoCurrencyStatus = status
val isAnyTokenStaked = isAnyTokenStakedUseCase(userWalletId).getOrNull() ?: false
setupApprovalNeeded()
setupIsAnyTokenStaked()
checkIfSubtractAvailable()
stateController.update(
transformer = SetInitialDataStateTransformer(
clickIntents = this@StakingViewModel,
yield = yield,
isAnyTokenStaked = isAnyTokenStaked,
isApprovalNeeded = stakingApproval is StakingApproval.Needed,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },
balancesToShowProvider = Provider { balancesToShow },
),
)
subscribeOnActionsUpdates()
subscribeOnStepChanges()
},
ifLeft = { error ->
Timber.e(error.toString())
stakingEventFactory.createGenericErrorAlert(error.toString())
stateController.update(SetConfirmationStateResetAssentTransformer)
},
@ -887,6 +810,93 @@ internal class StakingViewModel @Inject constructor(
.launchIn(viewModelScope)
}
private fun subscribeOnStepChanges() {
uiState
.distinctUntilChangedBy { it.currentStep }
.onEach {
when {
isInitState() -> {
updateInitialData()
balanceUpdater.initialUpdate()
}
isAssentState() -> {
getFee()
val amountState = value.amountState as? AmountState.Data
if (amountState?.amountTextField?.isWarning == true) {
stateController.update(
AmountRoundToIntegerTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
),
)
}
}
}
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
.saveIn(stepChangesJobHolder)
}
private fun subscribeOnActionsUpdates() {
getActionsUseCase(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrencyId,
)
.conflate()
.distinctUntilChanged()
.onEach { result ->
result.getOrNull()?.let { actions ->
processingActions = actions
if (isInitState()) {
updateInitialData()
}
}
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
}
private fun updateInitialData() {
stateController.updateAll(
SetInitialDataStateTransformer(
clickIntents = this@StakingViewModel,
yield = yield,
isAnyTokenStaked = isAnyTokenStaked,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },
balancesToShowProvider = Provider { balancesToShow },
),
SetConfirmationStateEmptyTransformer,
)
}
private fun prepareForConfirmation(
pendingActions: ImmutableList<PendingAction> = persistentListOf(),
pendingAction: PendingAction? = pendingActions.firstOrNull(),
validator: Yield.Validator?,
amountValue: String,
) {
stateController.updateAll(
SetConfirmationStateInitTransformer(
isEnter = false,
cryptoCurrencyStatus = cryptoCurrencyStatus,
stakingApproval = stakingApproval,
pendingActions = pendingActions,
pendingAction = pendingAction,
),
ValidatorSelectChangeTransformer(
selectedValidator = validator,
yield = yield,
),
AmountChangeStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
value = amountValue,
yield = yield,
),
)
}
private fun isAssentState(): Boolean {
return value.currentStep == StakingStep.Confirmation &&
(value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState ==