Updated on 2026-08-14
This commit is contained in:
parent
eec5a60676
commit
20dc8c9d90
19 changed files with 436 additions and 89 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,5 +12,5 @@ data class StakingGasEstimateDTO(
|
|||
@Json(name = "token")
|
||||
val token: TokenDTO,
|
||||
@Json(name = "gasLimit")
|
||||
val gasLimit: BigDecimal,
|
||||
val gasLimit: String?,
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<NetworkTypeDTO, NetworkType> {
|
||||
class StakingNetworkTypeConverter : TwoWayConverter<NetworkTypeDTO, NetworkType> {
|
||||
|
||||
override fun convert(value: NetworkTypeDTO): NetworkType {
|
||||
return when (value) {
|
||||
|
|
@ -78,4 +78,76 @@ class StakingNetworkTypeConverter : Converter<NetworkTypeDTO, NetworkType> {
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TokenDTO, Token> {
|
||||
) : TwoWayConverter<TokenDTO, Token> {
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ import java.math.BigDecimal
|
|||
data class StakingGasEstimate(
|
||||
val amount: BigDecimal,
|
||||
val token: Token,
|
||||
val gasLimit: BigDecimal,
|
||||
val gasLimit: String?,
|
||||
)
|
||||
|
|
@ -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<Throwable, Pair<EnterAction, StakingTransaction>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CryptoCurrencyAddress>,
|
||||
integrationId: String,
|
||||
): Flow<YieldBalanceList>
|
||||
|
||||
suspend fun createEnterAction(
|
||||
integrationId: String,
|
||||
amount: BigDecimal,
|
||||
address: String,
|
||||
validatorAddress: String,
|
||||
token: Token,
|
||||
): EnterAction
|
||||
|
||||
suspend fun constructTransaction(transactionId: String): StakingTransaction
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -7,22 +7,20 @@ internal class StakingStateRouter(
|
|||
private val fragmentManager: WeakReference<FragmentManager>,
|
||||
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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<StakingUiState> {
|
||||
|
||||
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
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AppCurrency>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||
private val stakingGasEstimate: StakingGasEstimate,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<StakingStates.FeeState> {
|
||||
private class FeeBlockPreviewProvider : PreviewParameterProvider<FeeState.Content> {
|
||||
|
||||
override val values: Sequence<StakingStates.FeeState>
|
||||
override val values: Sequence<FeeState.Content>
|
||||
get() = sequenceOf(
|
||||
feeState,
|
||||
)
|
||||
|
|
@ -135,8 +141,7 @@ private class FeeBlockPreviewProvider : PreviewParameterProvider<StakingStates.F
|
|||
),
|
||||
)
|
||||
|
||||
private val feeState = StakingStates.FeeState(
|
||||
innerFeeState = InnerFeeState.Content(TransactionFee.Single(normal = fee)),
|
||||
private val feeState = FeeState.Content(
|
||||
fee = fee,
|
||||
rate = BigDecimal.ONE,
|
||||
appCurrency = AppCurrency.Default,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import androidx.lifecycle.viewModelScope
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.bundle.unbundle
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.staking.InitializeStakingProcessUseCase
|
||||
import com.tangem.domain.staking.model.Yield
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -21,6 +23,7 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
|||
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStateController
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStateRouter
|
||||
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.transformers.*
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountChangeStateTransformer
|
||||
|
|
@ -45,6 +48,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val initializeStakingProcessUseCase: InitializeStakingProcessUseCase,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
|
||||
|
||||
|
|
@ -85,10 +89,60 @@ internal class StakingViewModel @Inject constructor(
|
|||
|
||||
override fun onNextClick() {
|
||||
stakingStateRouter.onNextClick()
|
||||
when (value.currentStep) {
|
||||
StakingStep.Confirm -> {
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue