Updated on 2026-08-14
This commit is contained in:
commit
1266ffce6a
10 changed files with 291 additions and 216 deletions
|
|
@ -204,11 +204,8 @@ internal class DefaultStakingBalanceStore(
|
|||
|
||||
private fun Set<YieldBalance>.getBalance(address: String?, integrationId: String?): YieldBalance? {
|
||||
return firstOrNull { yieldBalance ->
|
||||
val data = yieldBalance as? YieldBalance.Data
|
||||
val balance = data?.balance
|
||||
|
||||
val isCorrectAddress = address != null && address == data?.address
|
||||
val isCorrectIntegration = integrationId != null && balance?.integrationId == integrationId
|
||||
val isCorrectAddress = address != null && address == yieldBalance.address
|
||||
val isCorrectIntegration = integrationId != null && yieldBalance.integrationId == integrationId
|
||||
|
||||
isCorrectIntegration && isCorrectAddress
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,11 +61,8 @@ import com.tangem.lib.crypto.BlockchainUtils.isCardano
|
|||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
|
@ -188,32 +185,48 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getStakingAvailability(
|
||||
override fun getStakingAvailability(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): StakingAvailability {
|
||||
if (!checkFeatureToggleEnabled(cryptoCurrency.network.id)) return StakingAvailability.Unavailable
|
||||
|
||||
if (checkForInvalidCardBatch(userWalletId, cryptoCurrency)) return StakingAvailability.Unavailable
|
||||
|
||||
val rawCurrencyId = cryptoCurrency.id.rawCurrencyId ?: return StakingAvailability.Unavailable
|
||||
|
||||
val isSupportedInMobileApp = getSupportedIntegrationId(cryptoCurrency.id).isNullOrEmpty().not()
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = getEnabledYieldsSync(),
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
|
||||
return when {
|
||||
prefetchedYield != null && isSupportedInMobileApp -> {
|
||||
StakingAvailability.Available(prefetchedYield.id)
|
||||
): Flow<StakingAvailability> {
|
||||
return channelFlow {
|
||||
if (!checkFeatureToggleEnabled(cryptoCurrency.network.id)) {
|
||||
send(StakingAvailability.Unavailable)
|
||||
return@channelFlow
|
||||
}
|
||||
prefetchedYield == null && isSupportedInMobileApp -> {
|
||||
StakingAvailability.TemporaryUnavailable
|
||||
|
||||
if (checkForInvalidCardBatch(userWalletId, cryptoCurrency)) {
|
||||
send(StakingAvailability.Unavailable)
|
||||
return@channelFlow
|
||||
}
|
||||
else -> StakingAvailability.Unavailable
|
||||
|
||||
val rawCurrencyId = cryptoCurrency.id.rawCurrencyId
|
||||
if (rawCurrencyId == null) {
|
||||
send(StakingAvailability.Unavailable)
|
||||
return@channelFlow
|
||||
}
|
||||
|
||||
val isSupportedInMobileApp = getSupportedIntegrationId(cryptoCurrency.id).isNullOrEmpty().not()
|
||||
|
||||
getEnabledYields()
|
||||
.distinctUntilChanged()
|
||||
.onEach { yields ->
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = yields,
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
when {
|
||||
prefetchedYield != null && isSupportedInMobileApp -> {
|
||||
send(StakingAvailability.Available(prefetchedYield.id))
|
||||
}
|
||||
prefetchedYield == null && isSupportedInMobileApp -> {
|
||||
send(StakingAvailability.TemporaryUnavailable)
|
||||
}
|
||||
else -> send(StakingAvailability.Unavailable)
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -378,6 +391,7 @@ internal class DefaultStakingRepository(
|
|||
} else {
|
||||
FirebaseCrashlytics.getInstance()
|
||||
.log("No yield balance available for currency ${cryptoCurrency.id.value}")
|
||||
send(YieldBalance.Error(integrationId, address))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -633,6 +647,15 @@ internal class DefaultStakingRepository(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getEnabledYields(): Flow<List<Yield>> {
|
||||
return stakingYieldsStore.get().map {
|
||||
YieldConverter.convertListIgnoreErrors(
|
||||
input = it,
|
||||
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBalanceRequestData(address: String, integrationId: String): YieldBalanceRequestBody {
|
||||
return YieldBalanceRequestBody(
|
||||
addresses = Address(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
package com.tangem.domain.staking
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Use case for getting info about staking capability in tangem app.
|
||||
|
|
@ -16,17 +21,15 @@ class GetStakingAvailabilityUseCase(
|
|||
private val stakingErrorResolver: StakingErrorResolver,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Either<StakingError, StakingAvailability> {
|
||||
return Either
|
||||
.catch {
|
||||
stakingRepository.getStakingAvailability(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
}
|
||||
.mapLeft { stakingErrorResolver.resolve(it) }
|
||||
): EitherFlow<StakingError, StakingAvailability> {
|
||||
return stakingRepository.getStakingAvailability(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
).map<StakingAvailability, Either<StakingError, StakingAvailability>> {
|
||||
it.right()
|
||||
}.catch { emit(stakingErrorResolver.resolve(it).left()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ interface StakingRepository {
|
|||
|
||||
suspend fun getYield(yieldId: String): Yield
|
||||
|
||||
suspend fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): StakingAvailability
|
||||
fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Flow<StakingAvailability>
|
||||
|
||||
suspend fun getActions(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
|
|||
|
|
@ -60,13 +60,18 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
val flow = combine(
|
||||
flow = networkFlow,
|
||||
flow2 = promoRepository.getStoryById(StoryContentIds.STORY_FIRST_TIME_SWAP.id).conflate(),
|
||||
) { maybeCoinStatus, maybeSwapStories ->
|
||||
flow3 = stakingRepository.getStakingAvailability(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
).onStart { emit(StakingAvailability.Unavailable) },
|
||||
) { maybeCoinStatus, maybeSwapStories, stakingAvailability ->
|
||||
createTokenActionsState(
|
||||
userWallet = userWallet,
|
||||
coinStatus = maybeCoinStatus.getOrNull(),
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
requirements = requirements,
|
||||
shouldShowSwapStories = maybeSwapStories != null,
|
||||
isStakingAvailable = stakingAvailability is StakingAvailability.Available,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +85,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
requirements: AssetRequirementsCondition?,
|
||||
shouldShowSwapStories: Boolean,
|
||||
isStakingAvailable: Boolean,
|
||||
): TokenActionsState {
|
||||
return TokenActionsState(
|
||||
walletId = userWallet.walletId,
|
||||
|
|
@ -90,6 +96,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
requirements = requirements,
|
||||
shouldShowSwapStories = shouldShowSwapStories,
|
||||
isStakingAvailable = isStakingAvailable,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -105,6 +112,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
requirements: AssetRequirementsCondition?,
|
||||
shouldShowSwapStories: Boolean,
|
||||
isStakingAvailable: Boolean,
|
||||
): List<TokenActionsState.ActionState> {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.MissedDerivation) {
|
||||
|
|
@ -139,7 +147,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
}
|
||||
|
||||
// staking
|
||||
if (isStakingAvailable(userWallet, cryptoCurrency)) {
|
||||
if (isStakingAvailable) {
|
||||
val yield = kotlin.runCatching {
|
||||
stakingRepository.getYield(
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
|
|
@ -354,13 +362,6 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()
|
||||
}
|
||||
|
||||
private suspend fun isStakingAvailable(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): Boolean {
|
||||
return stakingRepository.getStakingAvailability(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
) is StakingAvailability.Available
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val REQUEST_EXCHANGE_DATA_TIMEOUT = 1000L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,10 +43,10 @@ class MockStakingRepository : StakingRepository {
|
|||
|
||||
override suspend fun getYield(yieldId: String) = yield
|
||||
|
||||
override suspend fun getStakingAvailability(
|
||||
override fun getStakingAvailability(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): StakingAvailability = StakingAvailability.Unavailable
|
||||
): Flow<StakingAvailability> = flowOf(StakingAvailability.Unavailable)
|
||||
|
||||
override suspend fun getActions(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ import com.tangem.domain.staking.GetStakingEntryInfoUseCase
|
|||
import com.tangem.domain.staking.GetStakingIntegrationIdUseCase
|
||||
import com.tangem.domain.staking.GetYieldUseCase
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -144,18 +143,16 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private val refreshStateJobHolder = JobHolder()
|
||||
private val warningsJobHolder = JobHolder()
|
||||
private val expressTxJobHolder = JobHolder()
|
||||
private val buttonsJobHolder = JobHolder()
|
||||
private val stakingJobHolder = JobHolder()
|
||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
private var stakingEntryInfo: StakingEntryInfo? = null
|
||||
private var stakingAvailability: StakingAvailability = StakingAvailability.Unavailable
|
||||
private var expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>()
|
||||
|
||||
private val stateFactory = TokenDetailsStateFactory(
|
||||
currentStateProvider = Provider { uiState.value },
|
||||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
stakingEntryInfoProvider = Provider { stakingEntryInfo },
|
||||
stakingAvailabilityProvider = Provider { stakingAvailability },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
clickIntents = this,
|
||||
networkHasDerivationUseCase = networkHasDerivationUseCase,
|
||||
|
|
@ -245,8 +242,6 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
subscribeOnCurrencyStatusUpdates()
|
||||
subscribeOnExpressTransactionsUpdates()
|
||||
updateTxHistory(refresh = false, showItemsLoading = true, initialUpdating = true)
|
||||
|
||||
updateStakingInfo()
|
||||
}
|
||||
|
||||
private fun handleBalanceHiding() {
|
||||
|
|
@ -271,6 +266,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
.saveIn(buttonsJobHolder)
|
||||
}
|
||||
|
||||
private fun updateWarnings(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
|
|
@ -308,6 +304,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
updateWarnings(status)
|
||||
}
|
||||
currencyStatusAnalyticsSender.send(maybeCurrencyStatus)
|
||||
subscribeOnUpdateStakingInfo()
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
|
|
@ -397,24 +394,29 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateStakingInfo() {
|
||||
modelScope.launch {
|
||||
val availability = getStakingAvailabilityUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
).getOrElse { StakingAvailability.Unavailable }
|
||||
private fun subscribeOnUpdateStakingInfo() {
|
||||
getStakingAvailabilityUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
.map { it.getOrElse { StakingAvailability.Unavailable } }
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
if (it is StakingAvailability.Available) {
|
||||
val stakingInfo = getStakingEntryInfoUseCase(
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
|
||||
stakingAvailability = availability
|
||||
|
||||
if (stakingAvailability is StakingAvailability.Available) {
|
||||
val stakingInfo = getStakingEntryInfoUseCase(
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
|
||||
stakingEntryInfo = stakingInfo.getOrNull()
|
||||
val stakingEntryInfo = stakingInfo.getOrNull()
|
||||
internalUiState.update { state ->
|
||||
stateFactory.getStakingInfoState(state, stakingEntryInfo, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
.saveIn(stakingJobHolder)
|
||||
}
|
||||
|
||||
private fun updateTopBarMenu() {
|
||||
|
|
|
|||
|
|
@ -6,16 +6,9 @@ import com.tangem.core.ui.components.marketprice.PriceChangeState
|
|||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.stakekit.RewardBlockType
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -24,10 +17,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsTxHistoryTransactionStateConverter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBSC
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isIncludeStakingTotalBalance
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
|
@ -40,8 +30,6 @@ import java.math.BigDecimal
|
|||
internal class TokenDetailsLoadedBalanceConverter(
|
||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val stakingEntryInfoProvider: Provider<StakingEntryInfo?>,
|
||||
private val stakingAvailabilityProvider: Provider<StakingAvailability>,
|
||||
private val symbol: String,
|
||||
private val decimals: Int,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
|
|
@ -81,7 +69,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
currentState = state.tokenBalanceBlockState,
|
||||
status = status,
|
||||
),
|
||||
stakingBlocksState = getYieldBalance(status, state),
|
||||
stakingBlocksState = state.stakingBlocksState,
|
||||
marketPriceBlockState = getMarketPriceState(status = status.value, currencySymbol = currencyName),
|
||||
pendingTxs = pendingTxs,
|
||||
txHistoryState = if (state.txHistoryState is TxHistoryState.NotSupported) {
|
||||
|
|
@ -144,58 +132,6 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getYieldBalance(status: CryptoCurrencyStatus, state: TokenDetailsState): StakingBlockUM? {
|
||||
return when (stakingAvailabilityProvider.invoke()) {
|
||||
StakingAvailability.TemporaryUnavailable -> StakingBlockUM.TemporaryUnavailable
|
||||
StakingAvailability.Unavailable -> null
|
||||
is StakingAvailability.Available -> getStakingInfoBlock(status, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStakingInfoBlock(status: CryptoCurrencyStatus, state: TokenDetailsState): StakingBlockUM? {
|
||||
val yieldBalance = status.value.yieldBalance as? YieldBalance.Data
|
||||
|
||||
val stakingCryptoAmount = yieldBalance?.getTotalStakingBalance()
|
||||
val pendingBalances = yieldBalance?.balance?.items ?: emptyList()
|
||||
|
||||
val stakingEntryInfo = stakingEntryInfoProvider.invoke()
|
||||
val iconState = state.tokenInfoBlockState.iconState
|
||||
|
||||
return when {
|
||||
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null -> {
|
||||
if (pendingBalances.isEmpty()) {
|
||||
getStakeAvailableState(stakingEntryInfo, iconState, isStakingButtonEnabled(status))
|
||||
} else {
|
||||
getStakedBlockWithFiatAmount(status, pendingBalances.sumOf { it.amount }, null)
|
||||
}
|
||||
}
|
||||
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo == null -> {
|
||||
null
|
||||
}
|
||||
else -> getStakedBlockWithFiatAmount(status, stakingCryptoAmount, yieldBalance?.getRewardStakingBalance())
|
||||
}
|
||||
}
|
||||
|
||||
private fun isStakingButtonEnabled(status: CryptoCurrencyStatus): Boolean {
|
||||
return status.value is CryptoCurrencyStatus.Loaded ||
|
||||
status.value is CryptoCurrencyStatus.NoQuote ||
|
||||
status.value is CryptoCurrencyStatus.Custom
|
||||
}
|
||||
|
||||
private fun getStakedBlockWithFiatAmount(
|
||||
status: CryptoCurrencyStatus,
|
||||
stakingAmount: BigDecimal?,
|
||||
rewardAmount: BigDecimal?,
|
||||
): StakingBlockUM.Staked {
|
||||
val fiatRate = status.value.fiatRate
|
||||
return getStakedState(
|
||||
status = status,
|
||||
stakingCryptoAmount = stakingAmount,
|
||||
stakingFiatAmount = stakingAmount?.let { fiatRate?.multiply(it) },
|
||||
stakingRewardAmount = rewardAmount?.let { fiatRate?.multiply(it) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun getMarketPriceState(
|
||||
status: CryptoCurrencyStatus.Value,
|
||||
currencySymbol: String,
|
||||
|
|
@ -219,52 +155,6 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getStakeAvailableState(
|
||||
stakingEntryInfo: StakingEntryInfo,
|
||||
iconState: IconState,
|
||||
isEnabled: Boolean,
|
||||
): StakingBlockUM.StakeAvailable {
|
||||
val apr = stakingEntryInfo.apr.format { percent() }
|
||||
return StakingBlockUM.StakeAvailable(
|
||||
titleText = resourceReference(
|
||||
id = R.string.token_details_staking_block_title,
|
||||
formatArgs = wrappedList(apr),
|
||||
),
|
||||
subtitleText = resourceReference(
|
||||
id = R.string.staking_notification_earn_rewards_text,
|
||||
formatArgs = wrappedList(stakingEntryInfo.tokenSymbol),
|
||||
),
|
||||
iconState = iconState,
|
||||
isEnabled = isEnabled,
|
||||
onStakeClicked = clickIntents::onStakeBannerClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStakedState(
|
||||
status: CryptoCurrencyStatus,
|
||||
stakingCryptoAmount: BigDecimal?,
|
||||
stakingFiatAmount: BigDecimal?,
|
||||
stakingRewardAmount: BigDecimal?,
|
||||
): StakingBlockUM.Staked {
|
||||
return StakingBlockUM.Staked(
|
||||
cryptoAmount = stakingCryptoAmount,
|
||||
fiatAmount = stakingFiatAmount,
|
||||
cryptoValue = stringReference(
|
||||
stakingCryptoAmount.format { crypto(symbol = symbol, decimals = decimals) },
|
||||
),
|
||||
fiatValue = stringReference(
|
||||
stakingFiatAmount.format {
|
||||
fiat(
|
||||
appCurrencyProvider().code,
|
||||
appCurrencyProvider().symbol,
|
||||
)
|
||||
},
|
||||
),
|
||||
rewardValue = getRewardText(status, stakingRewardAmount),
|
||||
onStakeClicked = clickIntents::onStakeBannerClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.Value.toContentConfig(currencySymbol: String): MarketPriceBlockState.Content {
|
||||
return MarketPriceBlockState.Content(
|
||||
currencySymbol = currencySymbol,
|
||||
|
|
@ -327,31 +217,6 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
return totalAmount.format { crypto(status.currency) }
|
||||
}
|
||||
|
||||
private fun getRewardText(status: CryptoCurrencyStatus, stakingRewardAmount: BigDecimal?): TextReference {
|
||||
val blockchainId = status.currency.network.id.value
|
||||
val rewardBlockType = when {
|
||||
isSolana(blockchainId) || isBSC(blockchainId) -> RewardBlockType.RewardUnavailable
|
||||
stakingRewardAmount.isNullOrZero() -> RewardBlockType.NoRewards
|
||||
else -> RewardBlockType.Rewards
|
||||
}
|
||||
|
||||
return when (rewardBlockType) {
|
||||
RewardBlockType.Rewards -> resourceReference(
|
||||
R.string.staking_details_rewards_to_claim,
|
||||
wrappedList(
|
||||
stakingRewardAmount.format {
|
||||
fiat(
|
||||
appCurrencyProvider().code,
|
||||
appCurrencyProvider().symbol,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
RewardBlockType.NoRewards -> resourceReference(R.string.staking_details_no_rewards_to_claim)
|
||||
RewardBlockType.RewardUnavailable -> TextReference.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.Value.isFlickering(): Boolean = getStatusSource() == StatusSource.CACHE
|
||||
|
||||
private fun CryptoCurrencyStatus.Value.getStatusSource(): StatusSource? {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.stakekit.RewardBlockType
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.IconState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBSC
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TokenDetailsStakingInfoConverter(
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val currentState: TokenDetailsState,
|
||||
private val stakingEntryInfo: StakingEntryInfo?,
|
||||
) : Converter<StakingAvailability, TokenDetailsState> {
|
||||
|
||||
override fun convert(value: StakingAvailability): TokenDetailsState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider.invoke() ?: return currentState
|
||||
return currentState.copy(
|
||||
stakingBlocksState = getYieldBalance(cryptoCurrencyStatus, currentState, value),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getYieldBalance(
|
||||
status: CryptoCurrencyStatus,
|
||||
state: TokenDetailsState,
|
||||
stakingAvailability: StakingAvailability,
|
||||
): StakingBlockUM? {
|
||||
return when (stakingAvailability) {
|
||||
StakingAvailability.TemporaryUnavailable -> StakingBlockUM.TemporaryUnavailable
|
||||
StakingAvailability.Unavailable -> null
|
||||
is StakingAvailability.Available -> getStakingInfoBlock(status, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStakingInfoBlock(status: CryptoCurrencyStatus, state: TokenDetailsState): StakingBlockUM? {
|
||||
val yieldBalance = status.value.yieldBalance as? YieldBalance.Data
|
||||
|
||||
val stakingCryptoAmount = yieldBalance?.getTotalStakingBalance()
|
||||
val pendingBalances = yieldBalance?.balance?.items ?: emptyList()
|
||||
|
||||
val iconState = state.tokenInfoBlockState.iconState
|
||||
|
||||
return when {
|
||||
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null -> {
|
||||
if (pendingBalances.isEmpty()) {
|
||||
getStakeAvailableState(stakingEntryInfo, iconState, isStakingButtonEnabled(status))
|
||||
} else {
|
||||
getStakedBlockWithFiatAmount(status, pendingBalances.sumOf { it.amount }, null)
|
||||
}
|
||||
}
|
||||
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo == null -> {
|
||||
null
|
||||
}
|
||||
else -> getStakedBlockWithFiatAmount(status, stakingCryptoAmount, yieldBalance?.getRewardStakingBalance())
|
||||
}
|
||||
}
|
||||
|
||||
private fun isStakingButtonEnabled(status: CryptoCurrencyStatus): Boolean {
|
||||
return status.value is CryptoCurrencyStatus.Loaded ||
|
||||
status.value is CryptoCurrencyStatus.NoQuote ||
|
||||
status.value is CryptoCurrencyStatus.Custom
|
||||
}
|
||||
|
||||
private fun getStakedBlockWithFiatAmount(
|
||||
status: CryptoCurrencyStatus,
|
||||
stakingAmount: BigDecimal?,
|
||||
rewardAmount: BigDecimal?,
|
||||
): StakingBlockUM.Staked {
|
||||
val fiatRate = status.value.fiatRate
|
||||
return getStakedState(
|
||||
status = status,
|
||||
stakingCryptoAmount = stakingAmount,
|
||||
stakingFiatAmount = stakingAmount?.let { fiatRate?.multiply(it) },
|
||||
stakingRewardAmount = rewardAmount?.let { fiatRate?.multiply(it) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStakeAvailableState(
|
||||
stakingEntryInfo: StakingEntryInfo,
|
||||
iconState: IconState,
|
||||
isEnabled: Boolean,
|
||||
): StakingBlockUM.StakeAvailable {
|
||||
val apr = stakingEntryInfo.apr.format { percent() }
|
||||
return StakingBlockUM.StakeAvailable(
|
||||
titleText = resourceReference(
|
||||
id = R.string.token_details_staking_block_title,
|
||||
formatArgs = wrappedList(apr),
|
||||
),
|
||||
subtitleText = resourceReference(
|
||||
id = R.string.staking_notification_earn_rewards_text,
|
||||
formatArgs = wrappedList(stakingEntryInfo.tokenSymbol),
|
||||
),
|
||||
iconState = iconState,
|
||||
isEnabled = isEnabled,
|
||||
onStakeClicked = clickIntents::onStakeBannerClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStakedState(
|
||||
status: CryptoCurrencyStatus,
|
||||
stakingCryptoAmount: BigDecimal?,
|
||||
stakingFiatAmount: BigDecimal?,
|
||||
stakingRewardAmount: BigDecimal?,
|
||||
): StakingBlockUM.Staked {
|
||||
return StakingBlockUM.Staked(
|
||||
cryptoAmount = stakingCryptoAmount,
|
||||
fiatAmount = stakingFiatAmount,
|
||||
cryptoValue = stringReference(
|
||||
stakingCryptoAmount.format {
|
||||
crypto(
|
||||
symbol = status.currency.symbol,
|
||||
decimals = status.currency.decimals,
|
||||
)
|
||||
},
|
||||
),
|
||||
fiatValue = stringReference(
|
||||
stakingFiatAmount.format {
|
||||
fiat(
|
||||
appCurrencyProvider().code,
|
||||
appCurrencyProvider().symbol,
|
||||
)
|
||||
},
|
||||
),
|
||||
rewardValue = getRewardText(status, stakingRewardAmount),
|
||||
onStakeClicked = clickIntents::onStakeBannerClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRewardText(status: CryptoCurrencyStatus, stakingRewardAmount: BigDecimal?): TextReference {
|
||||
val blockchainId = status.currency.network.id.value
|
||||
val rewardBlockType = when {
|
||||
isSolana(blockchainId) || isBSC(blockchainId) -> RewardBlockType.RewardUnavailable
|
||||
stakingRewardAmount.isNullOrZero() -> RewardBlockType.NoRewards
|
||||
else -> RewardBlockType.Rewards
|
||||
}
|
||||
|
||||
return when (rewardBlockType) {
|
||||
RewardBlockType.Rewards -> resourceReference(
|
||||
R.string.staking_details_rewards_to_claim,
|
||||
wrappedList(
|
||||
stakingRewardAmount.format {
|
||||
fiat(
|
||||
appCurrencyProvider().code,
|
||||
appCurrencyProvider().symbol,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
RewardBlockType.NoRewards -> resourceReference(R.string.staking_details_no_rewards_to_claim)
|
||||
RewardBlockType.RewardUnavailable -> TextReference.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,8 +44,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
internal class TokenDetailsStateFactory(
|
||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val stakingEntryInfoProvider: Provider<StakingEntryInfo?>,
|
||||
private val stakingAvailabilityProvider: Provider<StakingAvailability>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
|
||||
|
|
@ -74,8 +72,6 @@ internal class TokenDetailsStateFactory(
|
|||
TokenDetailsLoadedBalanceConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
stakingEntryInfoProvider = stakingEntryInfoProvider,
|
||||
stakingAvailabilityProvider = stakingAvailabilityProvider,
|
||||
symbol = symbol,
|
||||
decimals = decimals,
|
||||
clickIntents = clickIntents,
|
||||
|
|
@ -129,6 +125,20 @@ internal class TokenDetailsStateFactory(
|
|||
return tokenDetailsLoadedBalanceConverter.convert(cryptoCurrencyEither)
|
||||
}
|
||||
|
||||
fun getStakingInfoState(
|
||||
state: TokenDetailsState,
|
||||
stakingEntryInfo: StakingEntryInfo?,
|
||||
stakingAvailability: StakingAvailability,
|
||||
): TokenDetailsState {
|
||||
return TokenDetailsStakingInfoConverter(
|
||||
currentState = state,
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
clickIntents = clickIntents,
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
stakingEntryInfo = stakingEntryInfo,
|
||||
).convert(stakingAvailability)
|
||||
}
|
||||
|
||||
fun getManageButtonsState(actions: List<TokenActionsState.ActionState>): TokenDetailsState {
|
||||
return tokenDetailsButtonsConverter.convert(actions)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue