diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 46365c5cc0..a0beef5668 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -735,6 +735,7 @@ Staking is currently unavailable due to network conditions. Please try again later. Staking in the %1$s network with a new validator will automatically transfer all previously staked funds to this validator Reinvests your earned rewards in your staked amount, increasing potential earnings. + Restake lets you move your funds from one validator to another without the need to unstake Unlock your money to withdraw it from staking process. Unlocking takes %s. Your funds will be available for use after the 21-day unbonding period. Reward will be withdrawn along with your unstaking funds. Your funds will be available for use after the %s unbonding period. diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index c3161be225..729477a5cc 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -151,7 +151,7 @@ internal class DefaultStakingRepository( val yield = getYield(cryptoCurrencyId, symbol) StakingEntryInfo( - apr = requireNotNull(yield.validators.maxByOrNull { it.apr.orZero() }?.apr), + apr = requireNotNull(yield.preferredValidators.maxByOrNull { it.apr.orZero() }?.apr), rewardSchedule = yield.metadata.rewardSchedule, tokenSymbol = yield.token.symbol, ) @@ -207,23 +207,21 @@ internal class DefaultStakingRepository( ): StakingAction { return withContext(dispatchers.io) { val response = when (params.actionCommonType) { - StakingActionCommonType.ENTER -> stakeKitApi.createEnterAction( + StakingActionCommonType.Enter -> stakeKitApi.createEnterAction( createActionRequestBody( userWalletId, network, params, ), ) - StakingActionCommonType.EXIT -> stakeKitApi.createExitAction( + StakingActionCommonType.Exit -> stakeKitApi.createExitAction( createActionRequestBody( userWalletId, network, params, ), ) - StakingActionCommonType.PENDING_OTHER, - StakingActionCommonType.PENDING_REWARDS, - -> stakeKitApi.createPendingAction( + is StakingActionCommonType.Pending -> stakeKitApi.createPendingAction( createPendingActionRequestBody(params), ) } @@ -239,23 +237,21 @@ internal class DefaultStakingRepository( ): StakingGasEstimate { return withContext(dispatchers.io) { val gasEstimateDTO = when (params.actionCommonType) { - StakingActionCommonType.ENTER -> stakeKitApi.estimateGasOnEnter( + StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter( createActionRequestBody( userWalletId, network, params, ), ) - StakingActionCommonType.EXIT -> stakeKitApi.estimateGasOnExit( + StakingActionCommonType.Exit -> stakeKitApi.estimateGasOnExit( createActionRequestBody( userWalletId, network, params, ), ) - StakingActionCommonType.PENDING_REWARDS, - StakingActionCommonType.PENDING_OTHER, - -> stakeKitApi.estimateGasOnPending( + is StakingActionCommonType.Pending -> stakeKitApi.estimateGasOnPending( createPendingActionRequestBody(params), ) } diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt index a2a0a6b7fe..9f201fb81e 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt @@ -18,6 +18,11 @@ data class Yield( val isAvailable: Boolean, ) { + val preferredValidators: List + get() = validators.filter { it.preferred } + + fun getCurrentToken(rawCurrencyId: String?) = tokens.firstOrNull { rawCurrencyId == it.coinGeckoId } ?: token + @Serializable data class Status( val enter: Boolean, @@ -130,8 +135,6 @@ data class Yield( APR, // simple rate UNKNOWN, } - - fun getCurrentToken(rawCurrencyId: String?) = tokens.firstOrNull { rawCurrencyId == it.coinGeckoId } ?: token } @Serializable diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt index 5a59ba211c..0e2d7abe32 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt @@ -1,8 +1,12 @@ package com.tangem.domain.staking.model.stakekit.action -enum class StakingActionCommonType { - ENTER, - EXIT, - PENDING_REWARDS, - PENDING_OTHER, +sealed class StakingActionCommonType { + + data object Enter : StakingActionCommonType() + data object Exit : StakingActionCommonType() + sealed class Pending : StakingActionCommonType() { + data object Restake : Pending() + data object Rewards : Pending() + data object Other : Pending() + } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt index 0b3f4fcd35..7fcb116702 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt @@ -33,10 +33,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( StakingAnalyticsEvents.ConfirmationScreenOpened( @@ -55,6 +55,7 @@ internal class StakingAnalyticSender( StakingStep.Amount -> StakeScreenSource.Amount StakingStep.Confirmation -> StakeScreenSource.Confirmation StakingStep.Validators, + StakingStep.RestakeValidator, StakingStep.RewardsValidators, -> StakeScreenSource.Validators }, @@ -78,8 +79,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( @@ -102,8 +102,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( @@ -119,11 +118,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 } } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt index 0b04c60e11..dd8919a82c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt @@ -45,6 +45,12 @@ internal class StakingStateController @Inject constructor( mutableUiState.update(function = titleTransformer::transform) } + fun updateAll(vararg transformer: Transformer) { + 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, ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt index 69cca72194..e0e42070ac 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt @@ -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( StakingAnalyticsEvents.RewardScreenOpened(stateController.value.cryptoCurrencySymbol), ) @@ -73,8 +81,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() { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 4bc8b84bda..56aa176637 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -11,6 +11,7 @@ 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 +35,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 +47,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 +89,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, + ) : 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?, val notifications: ImmutableList, @@ -112,6 +133,7 @@ enum class StakingStep { InitialInfo, RewardsValidators, Amount, + RestakeValidator, Confirmation, Validators, } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/ValidatorState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/ValidatorState.kt deleted file mode 100644 index 7871149443..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/ValidatorState.kt +++ /dev/null @@ -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, - ) : 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 - } - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index 33559d6211..2daa181826 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -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?, 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 { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index f334c448f4..90fc4e9295 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -21,9 +21,12 @@ import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents -import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.StakingStateController +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount -import com.tangem.features.staking.impl.presentation.state.utils.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 @@ -99,7 +102,7 @@ internal class StakingTransactionSender @AssistedInject constructor( } private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction { - return confirmationState.possiblePendingTransaction ?: composeStakeTransaction(confirmationState) + return confirmationState.possiblePendingTransaction ?: composeStakeTransaction() } private suspend fun getStakingTransactions( @@ -107,11 +110,11 @@ internal class StakingTransactionSender @AssistedInject constructor( 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( @@ -172,7 +175,7 @@ internal class StakingTransactionSender @AssistedInject constructor( action: PendingAction? = confirmationState.pendingAction, onConstructError: (StakingError) -> Unit, ): List { - 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") @@ -278,7 +281,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, @@ -289,7 +292,7 @@ internal class StakingTransactionSender @AssistedInject constructor( ) } - private fun composeStakeTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction { + private fun composeStakeTransaction(): PendingTransaction { val state = stateController.value val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data @@ -301,7 +304,7 @@ internal class StakingTransactionSender @AssistedInject constructor( 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, + validator = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator, balancesId = yieldBalance?.getBalancesUniqueId() ?: 0, ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt index d9c7263d8e..016a6a0625 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt @@ -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( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt new file mode 100644 index 0000000000..225057c19c --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt @@ -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, + ) +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index 716cab0e93..9da7141f5a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -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?, - 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?) { - } + override fun getFee() {} override fun onAmountReduceByClick( reduceAmountBy: BigDecimal, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ActionTypeActiveStakeTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ActionTypeActiveStakeTransformer.kt deleted file mode 100644 index 31e1f97f96..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ActionTypeActiveStakeTransformer.kt +++ /dev/null @@ -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 { - - 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 - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt index c8066c1dc2..5c3bfbd6e0 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt @@ -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.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 } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetActionToExecuteTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetActionToExecuteTransformer.kt deleted file mode 100644 index e3adab9f50..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetActionToExecuteTransformer.kt +++ /dev/null @@ -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?, -) : Transformer { - 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(), - ) - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt index 52251d7408..c2761a0713 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt @@ -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 } } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt index 196784785f..85443b4096 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt @@ -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, private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?, private val fee: Fee, - private val action: PendingAction?, - private val actions: ImmutableList?, ) : Transformer { 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 diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt index db27e0c2d9..4ecbf95344 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt @@ -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 } } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt new file mode 100644 index 0000000000..84cd59a951 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt @@ -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 { + override fun transform(prevState: StakingUiState): StakingUiState { + return prevState.copy( + actionType = StakingActionCommonType.Enter, + validatorState = StakingStates.ValidatorState.Empty(), + confirmationState = StakingStates.ConfirmationState.Empty(), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt index 24bdd43c84..9043cc8ae9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt @@ -19,7 +19,6 @@ internal class SetConfirmationStateInProgressTransformer : Transformer? = null, + private val pendingAction: PendingAction? = pendingActions?.firstOrNull(), +) : Transformer { + + 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 }, + possiblePendingTransaction = null, + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt index e32fc821f9..dae491e1b8 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt @@ -10,10 +10,11 @@ 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.R -import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.features.staking.impl.presentation.state.utils.getRewardSchedule import com.tangem.utils.transformer.Transformer -import kotlinx.collections.immutable.persistentListOf internal class SetConfirmationStateLoadingTransformer( private val yield: Yield, @@ -22,39 +23,21 @@ internal class SetConfirmationStateLoadingTransformer( ) : Transformer { 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( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt index ee866db72c..1c1c7103dc 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt @@ -13,7 +13,6 @@ internal object SetConfirmationStateResetAssentTransformer : Transformer, private val userWalletProvider: Provider, @@ -76,7 +74,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 +85,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 +111,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), @@ -214,23 +212,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): TextReference { val aprValues = validators.mapNotNull { it.apr } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt index 578af70403..a29c1a8a85 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt @@ -20,7 +20,9 @@ internal object SetTitleTransformer : Transformer { 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 { 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 { } } - val subtitle = if (currentStep == StakingStep.Confirmation && actionType == StakingActionCommonType.ENTER) { + val subtitle = if (currentStep == StakingStep.Confirmation && actionType == StakingActionCommonType.Enter) { stringReference(prevState.walletName) } else { null diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt index d669d5a7e9..284f9b239b 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt @@ -102,7 +102,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() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt index 939c6877dc..6f331795e4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt @@ -35,7 +35,6 @@ internal class SetConfirmationStateAssentApprovalTransformer( appCurrency = appCurrencyProvider(), isFeeApproximate = false, ), - validatorState = validatorState.copySealed(isClickable = true), isPrimaryButtonEnabled = true, isApprovalNeeded = true, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt index b5631913ad..ca32d6c1be 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt @@ -12,7 +12,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,7 +27,7 @@ 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 diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt index e6ba4e32db..0f79a3ab26 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt @@ -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 { 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, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt index efda617b73..ae8f6d4cb2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt @@ -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?): 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.REVOKE +} + +internal fun isComposePendingActions(networkId: String, pendingActions: ImmutableList?): Boolean { + return when { + isSolana(networkId) -> pendingActions?.any { it.type == StakingActionType.WITHDRAW } == true + else -> false + } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt index 255fd36faa..ad11c31650 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt @@ -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, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt index 5c2e5ceb51..21be700364 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt @@ -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, + ) } } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt index 9ddda86f32..692f5b3050 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt @@ -28,8 +28,8 @@ 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 +39,7 @@ import com.tangem.utils.extensions.orZero */ @Composable internal fun StakingValidatorListContent( - state: ValidatorState, + state: StakingStates.ValidatorState, clickIntents: StakingClickIntents, modifier: Modifier = Modifier, ) { @@ -51,7 +51,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, @@ -131,7 +131,7 @@ private fun RowScope.ValidatorLabel(isStrategicPartner: Boolean) { @Composable private fun StakingValidatorListContent_Preview( @PreviewParameter(StakingValidatorListContentPreviewProvider::class) - data: ValidatorState, + data: StakingStates.ValidatorState, ) { TangemThemePreview { StakingValidatorListContent( @@ -141,8 +141,8 @@ private fun StakingValidatorListContent_Preview( } } -private class StakingValidatorListContentPreviewProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf(ConfirmationStatePreviewData.assentStakingState.validatorState) +private class StakingValidatorListContentPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf(ValidatorStatePreviewData.validatorState) } // endregion \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt index 192d1e5397..2da1751f34 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt @@ -15,39 +15,40 @@ import com.tangem.core.ui.extensions.* 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 = BigDecimalFormatter.formatPercent(state.chosenValidator.apr.orZero(), true), + color = TangemTheme.colors.text.accent, + ) + }, + imageUrl = state.chosenValidator.image.orEmpty(), + onImageError = { ValidatorImagePlaceholder() }, + ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt index 8e55492aa8..7e23d53fc6 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt @@ -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? = 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?) + fun getFee() - override fun onAmountNext() = onNextClick(actionTypeToOverwrite = null) + override fun onAmountNext() = onNextClick() fun openValidators() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index a6a90596ef..8e3d7ac5ee 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -63,12 +63,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 @@ -185,6 +187,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() private val transactionsInProgress: CopyOnWriteArrayList = CopyOnWriteArrayList() @@ -210,70 +213,16 @@ internal class StakingViewModel @Inject constructor( stakingStateRouter.onBackClick() } - override fun onNextClick( - actionTypeToOverwrite: StakingActionCommonType?, - pendingAction: PendingAction?, - pendingActions: ImmutableList?, - 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, - ), - ) - } - } + if (balanceState != null && (isInitState() || isAssentState())) { + stateController.update( + SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus), + ) } } - override fun onActionClick() { - handleOnNextConfirmationClick() - } - - override fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList?) { + override fun getFee() { stateController.update( SetConfirmationStateLoadingTransformer( yield = yield, @@ -283,16 +232,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() @@ -326,7 +271,7 @@ internal class StakingViewModel @Inject constructor( }.saveIn(feeJobHolder) } - private fun handleOnNextConfirmationClick() { + override fun onActionClick() { if (isAssentState()) { viewModelScope.launch { stakingAnalyticSender.sendTransactionStakingClickedAnalytics(value) @@ -412,28 +357,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() } } @@ -474,7 +414,12 @@ internal class StakingViewModel @Inject constructor( validator.name, ), ) - stateController.update(ValidatorSelectChangeTransformer(validator)) + stateController.update( + ValidatorSelectChangeTransformer( + selectedValidator = validator, + yield = yield, + ), + ) } override fun openRewardsValidators() { @@ -493,50 +438,38 @@ internal class StakingViewModel @Inject constructor( token = value.cryptoCurrencySymbol, ), ) - 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, - ) } } @@ -632,7 +565,7 @@ internal class StakingViewModel @Inject constructor( stakingAnalyticSender.sendTransactionApprovalAnalytics(tokenCryptoCurrency) stateController.update(SetApprovalInProgressTransformer) stateController.update(DismissBottomSheetStateTransformer) - awaitForAllowance(confirmationState.pendingAction) + awaitForAllowance() }, ) }.saveIn(approvalJobHolder) @@ -718,7 +651,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, @@ -737,7 +670,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() } }, @@ -775,7 +708,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 @@ -817,6 +750,10 @@ 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 -> @@ -849,23 +786,11 @@ 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 }, - ), - ) + subscribeOnStepChanges() }, ifLeft = { error -> Timber.e(error.toString()) @@ -900,6 +825,69 @@ internal class StakingViewModel @Inject constructor( .launchIn(viewModelScope) } + private fun subscribeOnStepChanges() { + uiState + .distinctUntilChangedBy { it.currentStep } + .onEach { + when { + isInitState() -> { + stateController.updateAll( + SetInitialDataStateTransformer( + clickIntents = this@StakingViewModel, + yield = yield, + isAnyTokenStaked = isAnyTokenStaked, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + userWalletProvider = Provider { userWallet }, + appCurrencyProvider = Provider { appCurrency }, + balancesToShowProvider = Provider { balancesToShow }, + ), + SetConfirmationStateEmptyTransformer, + ) + onRefreshSwipe(isRefreshing = false) + } + isAssentState() -> { + getFee() + val amountState = value.amountState as? AmountState.Data + if (amountState?.amountTextField?.isWarning == true) { + stateController.update( + AmountRoundToIntegerTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + ), + ) + } + } + } + } + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + } + + private fun prepareForConfirmation( + pendingActions: ImmutableList = 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 == diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index 1c340dabc3..fe67dcf443 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -88,6 +88,11 @@ object BlockchainUtils { return blockchain == Blockchain.Cosmos || blockchain == Blockchain.CosmosTestnet } + fun isBSC(networkId: String): Boolean { + val blockchain = Blockchain.fromId(networkId) + return blockchain == Blockchain.BSC || blockchain == Blockchain.BSCTestnet + } + data class BlockchainInfo( val blockchainId: String, val name: String,