From 20dc8c9d90861ae58bf6d400351db1427142f36a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 27 Jun 2024 20:32:12 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/StakingDomainModule.kt | 6 ++ .../transaction/StakingGasEstimateDTO.kt | 2 +- .../data/staking/DefaultStakingRepository.kt | 80 ++++++++++++++++++- .../converters/StakingNetworkTypeConverter.kt | 76 +++++++++++++++++- .../data/staking/converters/TokenConverter.kt | 17 +++- .../data/staking/converters/YieldConverter.kt | 5 +- .../model/transaction/StakingGasEstimate.kt | 2 +- .../InitializeStakingProcessUseCase.kt | 45 +++++++++++ .../staking/repositories/StakingRepository.kt | 17 ++++ .../impl/presentation/state/FeeState.kt | 20 +++++ .../impl/presentation/state/InnerFeeState.kt | 2 - .../presentation/state/StakingStateRouter.kt | 19 +++-- .../impl/presentation/state/StakingUiState.kt | 14 +--- .../ConfirmStakingStatePreviewData.kt | 6 +- .../SetConfirmLoadingStateTransformer.kt | 42 ++++++++++ .../SetConfirmStateDataStateTransformer.kt | 41 +++++++--- .../ui/StakingNavigationButtons.kt | 2 +- .../presentation/ui/block/StakingFeeBlock.kt | 73 +++++++++-------- .../viewmodel/StakingViewModel.kt | 56 ++++++++++++- 19 files changed, 436 insertions(+), 89 deletions(-) create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/InitializeStakingProcessUseCase.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/FeeState.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmLoadingStateTransformer.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index 2a6fb034b9..12aad7794a 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -55,4 +55,10 @@ internal object StakingDomainModule { fun provideGetStakingYieldBalanceUseCase(stakingRepository: StakingRepository): GetStakingYieldBalanceUseCase { return GetStakingYieldBalanceUseCase(stakingRepository = stakingRepository) } + + @Provides + @Singleton + fun provideCreateEnterActionUseCase(stakingRepository: StakingRepository): InitializeStakingProcessUseCase { + return InitializeStakingProcessUseCase(stakingRepository) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/transaction/StakingGasEstimateDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/transaction/StakingGasEstimateDTO.kt index 0e22ee6d9f..93ee18f790 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/transaction/StakingGasEstimateDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/transaction/StakingGasEstimateDTO.kt @@ -12,5 +12,5 @@ data class StakingGasEstimateDTO( @Json(name = "token") val token: TokenDTO, @Json(name = "gasLimit") - val gasLimit: BigDecimal, + val gasLimit: String?, ) \ No newline at end of file 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 0acc8d6b43..7f1ad03485 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 @@ -2,27 +2,47 @@ package com.tangem.data.staking import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toCoinId -import com.tangem.data.common.cache.CacheRegistry -import com.tangem.data.staking.converters.* +import com.tangem.data.staking.converters.StakingNetworkTypeConverter +import com.tangem.data.staking.converters.TokenConverter +import com.tangem.data.staking.converters.YieldConverter +import com.tangem.data.staking.converters.action.ActionStatusConverter +import com.tangem.data.staking.converters.action.EnterActionResponseConverter +import com.tangem.data.staking.converters.action.StakingActionTypeConverter +import com.tangem.data.staking.converters.transaction.GasEstimateConverter +import com.tangem.data.staking.converters.transaction.StakingTransactionConverter +import com.tangem.data.staking.converters.transaction.StakingTransactionStatusConverter +import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.api.stakekit.models.request.Address +import com.tangem.datasource.api.stakekit.models.request.ConstructTransactionRequestBody +import com.tangem.datasource.api.stakekit.models.request.EnterActionRequestBody +import com.tangem.datasource.local.token.StakingYieldsStore +import com.tangem.domain.staking.model.StakingAvailability +import com.tangem.domain.staking.model.StakingEntryInfo +import com.tangem.domain.staking.model.Token +import com.tangem.domain.staking.model.Yield +import com.tangem.domain.staking.model.action.EnterAction +import com.tangem.domain.staking.model.transaction.StakingTransaction +import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.staking.converters.* import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO import com.tangem.datasource.local.token.StakingBalanceStore -import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.staking.model.* import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.toFormattedString import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.math.BigDecimal internal class DefaultStakingRepository( private val stakeKitApi: StakeKitApi, @@ -33,13 +53,31 @@ internal class DefaultStakingRepository( ) : StakingRepository { private val stakingNetworkTypeConverter = StakingNetworkTypeConverter() - + private val networkTypeConverter = StakingNetworkTypeConverter() + private val transactionStatusConverter = StakingTransactionStatusConverter() + private val transactionTypeConverter = StakingTransactionTypeConverter() + private val actionStatusConverter = ActionStatusConverter() + private val stakingActionTypeConverter = StakingActionTypeConverter() private val tokenConverter = TokenConverter( stakingNetworkTypeConverter = stakingNetworkTypeConverter, ) private val yieldConverter = YieldConverter( tokenConverter = tokenConverter, ) + private val gasEstimateConverter = GasEstimateConverter( + tokenConverter = tokenConverter, + ) + private val transactionConverter = StakingTransactionConverter( + networkTypeConverter = networkTypeConverter, + transactionStatusConverter = transactionStatusConverter, + transactionTypeConverter = transactionTypeConverter, + gasEstimateConverter = gasEstimateConverter, + ) + private val enterActionResponseConverter = EnterActionResponseConverter( + actionStatusConverter = actionStatusConverter, + stakingActionTypeConverter = stakingActionTypeConverter, + transactionConverter = transactionConverter, + ) private val yieldBalanceConverter = YieldBalanceConverter() @@ -108,6 +146,40 @@ internal class DefaultStakingRepository( } } + override suspend fun createEnterAction( + integrationId: String, + amount: BigDecimal, + address: String, + validatorAddress: String, + token: Token, + ): EnterAction { + return withContext(dispatchers.io) { + val body = EnterActionRequestBody( + integrationId = integrationId, + addresses = Address(address), + args = EnterActionRequestBody.EnterActionRequestBodyArgs( + amount = amount.toFormattedString(token.decimals), + inputToken = tokenConverter.convertBack(token), + validatorAddress = validatorAddress, + ), + ) + val response = stakeKitApi.createEnterAction(body) + + enterActionResponseConverter.convert(response.getOrThrow()) + } + } + + override suspend fun constructTransaction(transactionId: String): StakingTransaction { + return withContext(dispatchers.io) { + val transactionResponse = stakeKitApi.constructTransaction( + transactionId = transactionId, + body = ConstructTransactionRequestBody(), + ) + + transactionConverter.convert(transactionResponse.getOrThrow()) + } + } + override suspend fun fetchSingleYieldBalance( userWalletId: UserWalletId, address: String, diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/StakingNetworkTypeConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/StakingNetworkTypeConverter.kt index f7cd02c340..a768417427 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/StakingNetworkTypeConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/StakingNetworkTypeConverter.kt @@ -2,10 +2,10 @@ package com.tangem.data.staking.converters import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO import com.tangem.domain.staking.model.NetworkType -import com.tangem.utils.converter.Converter +import com.tangem.utils.converter.TwoWayConverter @Suppress("CyclomaticComplexMethod", "LongMethod") -class StakingNetworkTypeConverter : Converter { +class StakingNetworkTypeConverter : TwoWayConverter { override fun convert(value: NetworkTypeDTO): NetworkType { return when (value) { @@ -78,4 +78,76 @@ class StakingNetworkTypeConverter : Converter { else -> NetworkType.UNKNOWN } } + + override fun convertBack(value: NetworkType): NetworkTypeDTO { + return when (value) { + NetworkType.AVALANCHE_C -> NetworkTypeDTO.AVALANCHE_C + NetworkType.AVALANCHE_ATOMIC -> NetworkTypeDTO.AVALANCHE_ATOMIC + NetworkType.AVALANCHE_P -> NetworkTypeDTO.AVALANCHE_P + NetworkType.ARBITRUM -> NetworkTypeDTO.ARBITRUM + NetworkType.BINANCE -> NetworkTypeDTO.BINANCE + NetworkType.CELO -> NetworkTypeDTO.CELO + NetworkType.ETHEREUM -> NetworkTypeDTO.ETHEREUM + NetworkType.ETHEREUM_GOERLI -> NetworkTypeDTO.ETHEREUM_GOERLI + NetworkType.ETHEREUM_HOLESKY -> NetworkTypeDTO.ETHEREUM_HOLESKY + NetworkType.FANTOM -> NetworkTypeDTO.FANTOM + NetworkType.HARMONY -> NetworkTypeDTO.HARMONY + NetworkType.OPTIMISM -> NetworkTypeDTO.OPTIMISM + NetworkType.POLYGON -> NetworkTypeDTO.POLYGON + NetworkType.GNOSIS -> NetworkTypeDTO.GNOSIS + NetworkType.MOONRIVER -> NetworkTypeDTO.MOONRIVER + NetworkType.OKC -> NetworkTypeDTO.OKC + NetworkType.ZKSYNC -> NetworkTypeDTO.ZKSYNC + NetworkType.VICTION -> NetworkTypeDTO.VICTION + NetworkType.AGORIC -> NetworkTypeDTO.AGORIC + NetworkType.AKASH -> NetworkTypeDTO.AKASH + NetworkType.AXELAR -> NetworkTypeDTO.AXELAR + NetworkType.BAND_PROTOCOL -> NetworkTypeDTO.BAND_PROTOCOL + NetworkType.BITSONG -> NetworkTypeDTO.BITSONG + NetworkType.CANTO -> NetworkTypeDTO.CANTO + NetworkType.CHIHUAHUA -> NetworkTypeDTO.CHIHUAHUA + NetworkType.COMDEX -> NetworkTypeDTO.COMDEX + NetworkType.COREUM -> NetworkTypeDTO.COREUM + NetworkType.COSMOS -> NetworkTypeDTO.COSMOS + NetworkType.CRESCENT -> NetworkTypeDTO.CRESCENT + NetworkType.CRONOS -> NetworkTypeDTO.CRONOS + NetworkType.CUDOS -> NetworkTypeDTO.CUDOS + NetworkType.DESMOS -> NetworkTypeDTO.DESMOS + NetworkType.DYDX -> NetworkTypeDTO.DYDX + NetworkType.EVMOS -> NetworkTypeDTO.EVMOS + NetworkType.FETCH_AI -> NetworkTypeDTO.FETCH_AI + NetworkType.GRAVITY_BRIDGE -> NetworkTypeDTO.GRAVITY_BRIDGE + NetworkType.INJECTIVE -> NetworkTypeDTO.INJECTIVE + NetworkType.IRISNET -> NetworkTypeDTO.IRISNET + NetworkType.JUNO -> NetworkTypeDTO.JUNO + NetworkType.KAVA -> NetworkTypeDTO.KAVA + NetworkType.KI_NETWORK -> NetworkTypeDTO.KI_NETWORK + NetworkType.MARS_PROTOCOL -> NetworkTypeDTO.MARS_PROTOCOL + NetworkType.NYM -> NetworkTypeDTO.NYM + NetworkType.OKEX_CHAIN -> NetworkTypeDTO.OKEX_CHAIN + NetworkType.ONOMY -> NetworkTypeDTO.ONOMY + NetworkType.OSMOSIS -> NetworkTypeDTO.OSMOSIS + NetworkType.PERSISTENCE -> NetworkTypeDTO.PERSISTENCE + NetworkType.QUICKSILVER -> NetworkTypeDTO.QUICKSILVER + NetworkType.REGEN -> NetworkTypeDTO.REGEN + NetworkType.SECRET -> NetworkTypeDTO.SECRET + NetworkType.SENTINEL -> NetworkTypeDTO.SENTINEL + NetworkType.SOMMELIER -> NetworkTypeDTO.SOMMELIER + NetworkType.STAFI -> NetworkTypeDTO.STAFI + NetworkType.STARGAZE -> NetworkTypeDTO.STARGAZE + NetworkType.STRIDE -> NetworkTypeDTO.STRIDE + NetworkType.TERITORI -> NetworkTypeDTO.TERITORI + NetworkType.TGRADE -> NetworkTypeDTO.TGRADE + NetworkType.UMEE -> NetworkTypeDTO.UMEE + NetworkType.POLKADOT -> NetworkTypeDTO.POLKADOT + NetworkType.KUSAMA -> NetworkTypeDTO.KUSAMA + NetworkType.WESTEND -> NetworkTypeDTO.WESTEND + NetworkType.BINANCEBEACON -> NetworkTypeDTO.BINANCEBEACON + NetworkType.NEAR -> NetworkTypeDTO.NEAR + NetworkType.SOLANA -> NetworkTypeDTO.SOLANA + NetworkType.TEZOS -> NetworkTypeDTO.TEZOS + NetworkType.TRON -> NetworkTypeDTO.TRON + else -> NetworkTypeDTO.UNKNOWN + } + } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/TokenConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/TokenConverter.kt index 480d4574bc..2148e18511 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/TokenConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/TokenConverter.kt @@ -2,11 +2,11 @@ package com.tangem.data.staking.converters import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO import com.tangem.domain.staking.model.Token -import com.tangem.utils.converter.Converter +import com.tangem.utils.converter.TwoWayConverter class TokenConverter( private val stakingNetworkTypeConverter: StakingNetworkTypeConverter, -) : Converter { +) : TwoWayConverter { override fun convert(value: TokenDTO): Token { return Token( @@ -20,4 +20,17 @@ class TokenConverter( isPoints = value.isPoints, ) } + + override fun convertBack(value: Token): TokenDTO { + return TokenDTO( + name = value.name, + network = stakingNetworkTypeConverter.convertBack(value.network), + symbol = value.symbol, + decimals = value.decimals, + address = value.address, + coinGeckoId = value.coinGeckoId, + logoURI = value.logoURI, + isPoints = value.isPoints, + ) + } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt index 8dd5a5536f..09e1026127 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt @@ -20,7 +20,10 @@ class YieldConverter( rewardRate = value.rewardRate, rewardType = convertRewardType(value.rewardType), metadata = convertMetadata(value.metadata), - validators = value.validators.map { convertValidator(it) }, + validators = value.validators + .filter { it.preferred } + .map { convertValidator(it) } + .sortedByDescending { it.apr }, isAvailable = value.isAvailable, ) } diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/transaction/StakingGasEstimate.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/transaction/StakingGasEstimate.kt index 9dacd68d30..292adc35d5 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/transaction/StakingGasEstimate.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/transaction/StakingGasEstimate.kt @@ -6,5 +6,5 @@ import java.math.BigDecimal data class StakingGasEstimate( val amount: BigDecimal, val token: Token, - val gasLimit: BigDecimal, + val gasLimit: String?, ) \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InitializeStakingProcessUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InitializeStakingProcessUseCase.kt new file mode 100644 index 0000000000..a12b75f7ae --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InitializeStakingProcessUseCase.kt @@ -0,0 +1,45 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.Token +import com.tangem.domain.staking.model.action.EnterAction +import com.tangem.domain.staking.model.transaction.StakingTransaction +import com.tangem.domain.staking.repositories.StakingRepository +import kotlinx.coroutines.delay +import java.math.BigDecimal + +/** + * Use case for creating enter action + */ +class InitializeStakingProcessUseCase(private val stakingRepository: StakingRepository) { + + suspend operator fun invoke( + integrationId: String, + amount: BigDecimal, + address: String, + validatorAddress: String, + token: Token, + ): Either> { + return Either.catch { + val createAction = stakingRepository.createEnterAction( + integrationId = integrationId, + amount = amount, + address = address, + validatorAddress = validatorAddress, + token = token, + ) + + // workaround, sometimes transaction is not created immediately after actions/enter + delay(PATCH_TRANSACTION_REQUEST_DELAY) + + val createdTransaction = createAction.transactions?.get(0) ?: error("No available transaction to patch") + val patchedTransaction = stakingRepository.constructTransaction(createdTransaction.id) + + createAction to patchedTransaction + } + } + + companion object { + private const val PATCH_TRANSACTION_REQUEST_DELAY = 1000L + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 3a81eb1ae7..c48ed07b5c 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -1,5 +1,12 @@ package com.tangem.domain.staking.repositories +import com.tangem.domain.staking.model.StakingAvailability +import com.tangem.domain.staking.model.StakingEntryInfo +import com.tangem.domain.staking.model.Token +import com.tangem.domain.staking.model.Yield +import com.tangem.domain.staking.model.action.EnterAction +import com.tangem.domain.staking.model.transaction.StakingTransaction +import java.math.BigDecimal import com.tangem.domain.staking.model.* import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAddress @@ -46,4 +53,14 @@ interface StakingRepository { addresses: List, integrationId: String, ): Flow + + suspend fun createEnterAction( + integrationId: String, + amount: BigDecimal, + address: String, + validatorAddress: String, + token: Token, + ): EnterAction + + suspend fun constructTransaction(transactionId: String): StakingTransaction } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/FeeState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/FeeState.kt new file mode 100644 index 0000000000..c58332a870 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/FeeState.kt @@ -0,0 +1,20 @@ +package com.tangem.features.staking.impl.presentation.state + +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.domain.appcurrency.model.AppCurrency +import java.math.BigDecimal + +sealed class FeeState { + + data class Content( + val fee: Fee?, + val rate: BigDecimal?, + val isFeeConvertibleToFiat: Boolean, + val appCurrency: AppCurrency, + val isFeeApproximate: Boolean, + ) : FeeState() + + data object Loading : FeeState() + + data object Error : FeeState() +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerFeeState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerFeeState.kt index 2511ffa889..7a0acf09f9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerFeeState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerFeeState.kt @@ -1,9 +1,7 @@ package com.tangem.features.staking.impl.presentation.state -import androidx.compose.runtime.Immutable import com.tangem.blockchain.common.transaction.TransactionFee -@Immutable internal sealed class InnerFeeState { data class Content( 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 79fff44a09..eef49189ea 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 @@ -7,22 +7,20 @@ internal class StakingStateRouter( private val fragmentManager: WeakReference, private val stateController: StakingStateController, ) { - fun clear() { - stateController.update { it.copy(currentStep = getInitialState()) } - } - fun popBackStack() { + private fun closeStaking() { fragmentManager.get()?.popBackStack() + stateController.clear() } fun onBackClick(isSuccess: Boolean = false) { val type = stateController.uiState.value.currentStep when { - isSuccess -> popBackStack() + isSuccess -> closeStaking() else -> when (type) { StakingStep.Amount -> showInitial() StakingStep.Confirm -> showAmount() - else -> popBackStack() + else -> closeStaking() } } } @@ -34,14 +32,17 @@ internal class StakingStateRouter( StakingStep.Amount, -> showConfirm() StakingStep.Confirm -> showSuccess() - StakingStep.Success -> onBackClick() + StakingStep.Success -> closeStaking() } } fun onPrevClick() { when (stateController.uiState.value.currentStep) { StakingStep.Amount -> showInitial() - else -> popBackStack() + StakingStep.Confirm -> showAmount() + StakingStep.Success -> closeStaking() + StakingStep.Validators -> showConfirm() + else -> closeStaking() } } @@ -64,6 +65,4 @@ internal class StakingStateRouter( fun showSuccess() { stateController.update { it.copy(currentStep = StakingStep.Success) } } - - private fun getInitialState() = StakingStep.InitialInfo } \ No newline at end of file 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 5d20156911..e0112121e4 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 @@ -1,16 +1,13 @@ package com.tangem.features.staking.impl.presentation.state import androidx.compose.runtime.Immutable -import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.TextReference -import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.features.staking.impl.presentation.state.transformers.InfoType import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents import kotlinx.collections.immutable.ImmutableList -import java.math.BigDecimal /** * Ui states of the staking screen @@ -79,21 +76,12 @@ internal sealed class StakingStates { override val isPrimaryButtonEnabled: Boolean = false, ) : ConfirmStakingState() } - - data class FeeState( - val innerFeeState: InnerFeeState, - val fee: Fee?, - val rate: BigDecimal?, - val isFeeConvertibleToFiat: Boolean, - val appCurrency: AppCurrency, - val isFeeApproximate: Boolean, - ) } enum class StakingStep { InitialInfo, Amount, - Validators, Confirm, + Validators, Success, } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmStakingStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmStakingStatePreviewData.kt index 3e81a056fc..161f39ee33 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmStakingStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmStakingStatePreviewData.kt @@ -3,10 +3,9 @@ package com.tangem.features.staking.impl.presentation.state.previewdata import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType.Coin import com.tangem.blockchain.common.transaction.Fee -import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.Yield -import com.tangem.features.staking.impl.presentation.state.InnerFeeState +import com.tangem.features.staking.impl.presentation.state.* 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.ValidatorState @@ -65,8 +64,7 @@ internal object ConfirmStakingStatePreviewData { val confirmStakingState = StakingStates.ConfirmStakingState.Data( isPrimaryButtonEnabled = true, - feeState = StakingStates.FeeState( - innerFeeState = InnerFeeState.Content(TransactionFee.Single(fee)), + feeState = FeeState.Content( fee = fee, rate = BigDecimal.ONE, appCurrency = AppCurrency.Default, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmLoadingStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmLoadingStateTransformer.kt new file mode 100644 index 0000000000..76ff266521 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmLoadingStateTransformer.kt @@ -0,0 +1,42 @@ +package com.tangem.features.staking.impl.presentation.state.transformers + +import com.tangem.domain.staking.model.Yield +import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.state.StakingNotification +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.features.staking.impl.presentation.state.ValidatorState +import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.persistentListOf + +@Suppress("UnusedPrivateMember") +internal class SetConfirmLoadingStateTransformer( + private val yield: Yield, +) : Transformer { + + override fun transform(prevState: StakingUiState): StakingUiState { + val possibleConfirmStakingState = prevState.confirmStakingState as? StakingStates.ConfirmStakingState.Data + val possibleValidatorState = possibleConfirmStakingState?.validatorState as? ValidatorState.Content + val chosenValidator = possibleValidatorState?.chosenValidator ?: yield.validators[0] // TODO staking add sorting + + return prevState.copy( + confirmStakingState = StakingStates.ConfirmStakingState.Data( + isPrimaryButtonEnabled = false, + feeState = FeeState.Loading, + validatorState = ValidatorState.Content( + chosenValidator = chosenValidator, + availableValidators = yield.validators, + ), + notifications = persistentListOf( + StakingNotification.Warning.EarnRewards( + currencyName = yield.token.name, + days = yield.metadata.cooldownPeriod.days, + ), + ), + footerText = "", + isSuccess = false, + isStaking = false, // TODO staking move to enum + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmStateDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmStateDataStateTransformer.kt index 1097794d4b..d37ad3406b 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmStateDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmStateDataStateTransformer.kt @@ -2,34 +2,49 @@ 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.Yield +import com.tangem.domain.staking.model.transaction.StakingGasEstimate import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase +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.previewdata.ConfirmStakingStatePreviewData import com.tangem.utils.Provider import com.tangem.utils.transformer.Transformer +import com.tangem.blockchain.common.Amount @Suppress("UnusedPrivateMember") internal class SetConfirmStateDataStateTransformer( - private val yield: Yield, private val appCurrencyProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, - private val isFeeApproximateUseCase: IsFeeApproximateUseCase, + private val stakingGasEstimate: StakingGasEstimate, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { - // TODO staking fill with real data return prevState.copy( - confirmStakingState = ConfirmStakingStatePreviewData.confirmStakingState, + confirmStakingState = prevState.confirmStakingState.copyWrapped(stakingGasEstimate), ) } - private fun isFeeApproximate(fee: Fee): Boolean { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - return isFeeApproximateUseCase( - networkId = cryptoCurrencyStatus.currency.network.id, - amountType = fee.amount.type, - ) + private fun StakingStates.ConfirmStakingState.copyWrapped( + gasEstimate: StakingGasEstimate, + ): StakingStates.ConfirmStakingState { + if (this is StakingStates.ConfirmStakingState.Data) { + return copy( + feeState = FeeState.Content( + fee = Fee.Common( + Amount( + currencySymbol = gasEstimate.token.symbol, + value = gasEstimate.amount, + decimals = gasEstimate.token.decimals, + ), + ), + rate = cryptoCurrencyStatusProvider().value.fiatRate, + isFeeConvertibleToFiat = cryptoCurrencyStatusProvider().currency.network.hasFiatFeeRate, + appCurrency = appCurrencyProvider(), + isFeeApproximate = false, + ), + ) + } else { + return this + } } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt index 9ae31fc482..025e254aab 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt @@ -116,8 +116,8 @@ private fun isButtonEnabled(uiState: StakingUiState): Boolean { return when (uiState.currentStep) { StakingStep.InitialInfo -> uiState.initialInfoState.isPrimaryButtonEnabled StakingStep.Amount -> uiState.amountState.isPrimaryButtonEnabled - StakingStep.Validators -> uiState.confirmStakingState.isPrimaryButtonEnabled StakingStep.Confirm -> uiState.confirmStakingState.isPrimaryButtonEnabled StakingStep.Success -> true + StakingStep.Validators -> true } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt index 3aa945bd20..4dff5385fb 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.transaction.Fee -import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.res.TangemThemePreview @@ -26,12 +25,11 @@ import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.utils.getCryptoReference import com.tangem.common.ui.amountScreen.utils.getFiatReference import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.features.staking.impl.presentation.state.InnerFeeState -import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.FeeState import java.math.BigDecimal @Composable -internal fun StakingFeeBlock(feeState: StakingStates.FeeState) { +internal fun StakingFeeBlock(feeState: FeeState) { Column( modifier = Modifier .fillMaxWidth() @@ -48,37 +46,45 @@ internal fun StakingFeeBlock(feeState: StakingStates.FeeState) { Box( modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), ) { - val feeAmount = feeState.fee?.amount - val (title, icon) = R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 - SelectorRowItem( - titleRes = title, - iconRes = icon, - preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate), - postDot = if (feeState.isFeeConvertibleToFiat) { - getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency) - } else { - null - }, - ellipsizeOffset = feeAmount?.currencySymbol?.length, - isSelected = true, - showDivider = false, - showSelectedAppearance = false, - paddingValues = PaddingValues(), - ) - FeeLoading(feeState.innerFeeState) - FeeError(feeState.innerFeeState) + when (feeState) { + is FeeState.Content -> { + val feeAmount = feeState.fee?.amount + val (title, icon) = R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 + SelectorRowItem( + titleRes = title, + iconRes = icon, + preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate), + postDot = if (feeState.isFeeConvertibleToFiat) { + getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency) + } else { + null + }, + ellipsizeOffset = feeAmount?.currencySymbol?.length, + isSelected = true, + showDivider = false, + showSelectedAppearance = false, + paddingValues = PaddingValues(), + ) + } + is FeeState.Loading -> { + FeeLoading(feeState) + } + is FeeState.Error -> { + FeeError(feeState) + } + } } } } @Composable -private fun BoxScope.FeeLoading(feeSelectorState: InnerFeeState) { +private fun BoxScope.FeeLoading(feeState: FeeState) { AnimatedContent( - targetState = feeSelectorState, + targetState = feeState, label = "Fee Loading State Change", modifier = Modifier.align(Alignment.CenterEnd), ) { - if (it == InnerFeeState.Loading) { + if (it == FeeState.Loading) { RectangleShimmer( radius = TangemTheme.dimens.radius3, modifier = Modifier.size( @@ -91,13 +97,13 @@ private fun BoxScope.FeeLoading(feeSelectorState: InnerFeeState) { } @Composable -private fun BoxScope.FeeError(feeSelectorState: InnerFeeState) { +private fun BoxScope.FeeError(feeState: FeeState) { AnimatedContent( - targetState = feeSelectorState, + targetState = feeState, label = "Fee Error State Change", modifier = Modifier.align(Alignment.CenterEnd), ) { - if (it == InnerFeeState.Error) { + if (it == FeeState.Error) { Text( text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, color = TangemTheme.colors.text.primary1, @@ -111,7 +117,7 @@ private fun BoxScope.FeeError(feeSelectorState: InnerFeeState) { @Preview @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: StakingStates.FeeState) { +private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: FeeState.Content) { TangemThemePreview { StakingFeeBlock( feeState = value, @@ -119,9 +125,9 @@ private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) va } } -private class FeeBlockPreviewProvider : PreviewParameterProvider { +private class FeeBlockPreviewProvider : PreviewParameterProvider { - override val values: Sequence + override val values: Sequence get() = sequenceOf( feeState, ) @@ -135,8 +141,7 @@ private class FeeBlockPreviewProvider : PreviewParameterProvider { + initStaking() + } + StakingStep.InitialInfo -> { + // TODO staking + } + StakingStep.Amount -> { + // TODO staking + } + StakingStep.Success -> { + // TODO staking + } + StakingStep.Validators -> { + // TODO staking + } + } + } + + private fun initStaking() { + viewModelScope.launch { + stateController.update( + SetConfirmLoadingStateTransformer( + yield = yield, + ), + ) + + val actionWithTransaction = initializeStakingProcessUseCase( + integrationId = yield.id, + amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value + ?: error("No amount provided"), + address = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value + ?: error("No available address"), + validatorAddress = yield.validators.getOrNull(0)?.address ?: error("No available validator"), + token = yield.token, + ) + val (enterAction, stakingTransaction) = actionWithTransaction.getOrElse { + error("Can't retrieve action and transaction") + } + + val stakingGasEstimate = stakingTransaction.gasEstimate ?: error("Can't get fee info") + + stateController.update( + SetConfirmStateDataStateTransformer( + appCurrencyProvider = Provider { appCurrency }, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + stakingGasEstimate = stakingGasEstimate, + ), + ) + } } override fun onPrevClick() { - stakingStateRouter.onBackClick() + stakingStateRouter.onPrevClick() } override fun onInfoClick(infoType: InfoType) {