Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-06 19:15:00 +04:00
parent dc097a7815
commit 6901522cf7
6 changed files with 66 additions and 29 deletions

View file

@ -3,4 +3,11 @@ package com.tangem.datasource.api.common.response
fun <T : Any> ApiResponse<T>.getOrThrow(): T = when (this) {
is ApiResponse.Error -> throw cause
is ApiResponse.Success -> data
}
fun <T : Any, R : Any> ApiResponse<T>.fold(onSuccess: (T) -> R, onError: (ApiResponseError) -> R): R {
return when (this) {
is ApiResponse.Error -> onError(cause)
is ApiResponse.Success -> onSuccess(data)
}
}

View file

@ -179,7 +179,15 @@ internal class DefaultStakingBalanceStore(
cachedBalances: Set<YieldBalance>,
runtimeBalances: Set<YieldBalance>,
): Set<YieldBalance> {
if (runtimeBalances.isEmpty()) return cachedBalances
if (runtimeBalances.isEmpty()) {
return cachedBalances.mapNotNullTo(hashSetOf()) {
when (it) {
is YieldBalance.Data -> it.copy(source = StatusSource.ONLY_CACHE)
is YieldBalance.Empty -> it.copy(source = StatusSource.ONLY_CACHE)
is YieldBalance.Error -> null
}
}
}
return runtimeBalances
.map { runtime ->

View file

@ -24,6 +24,7 @@ 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.ApiResponse
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.stakekit.models.request.*
@ -59,8 +60,11 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
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
@ -108,10 +112,17 @@ internal class DefaultStakingRepository(
key = YIELDS_STORE_KEY,
skipCache = refresh,
block = {
val stakingTokensWithYields = stakeKitApi.getEnabledYields(preferredValidatorsOnly = false)
.getOrThrow()
stakingYieldsStore.store(stakingTokensWithYields.data.filter { it.isAvailable ?: false })
when (val stakingTokensWithYields = stakeKitApi.getEnabledYields(preferredValidatorsOnly = false)) {
is ApiResponse.Success -> stakingYieldsStore.store(
stakingTokensWithYields.data.data.filter {
it.isAvailable ?: false
},
)
else -> {
stakingYieldsStore.store(emptyList())
throw (stakingTokensWithYields as ApiResponse.Error).cause
}
}
},
)
}
@ -209,6 +220,11 @@ internal class DefaultStakingRepository(
getEnabledYields()
.distinctUntilChanged()
.onEach { yields ->
if (yields.isEmpty()) {
send(StakingAvailability.TemporaryUnavailable)
return@onEach
}
val prefetchedYield = findPrefetchedYield(
yields = yields,
currencyId = rawCurrencyId,
@ -446,21 +462,21 @@ internal class DefaultStakingRepository(
)
}
val yieldDTOs = withTimeoutOrNull(YIELDS_WATITING_TIMEOUT) {
runCatching { stakingYieldsStore.get().firstOrNull() }.getOrNull()
}
if (yieldDTOs == null) {
Timber.i("No enabled yields for $userWalletId")
stakingBalanceStore.store(userWalletId, emptySet())
return@withContext
}
cacheRegistry.invokeOnExpire(
key = getYieldBalancesKey(userWalletId),
skipCache = refresh,
block = {
val yieldDTOs = withTimeoutOrNull(YIELDS_WATITING_TIMEOUT) {
runCatching { stakingYieldsStore.get().firstOrNull() }.getOrNull()
}
if (yieldDTOs == null) {
Timber.i("No enabled yields for $userWalletId")
stakingBalanceStore.store(userWalletId, emptySet())
return@invokeOnExpire
}
val yields = YieldConverter.convertListIgnoreErrors(
input = yieldDTOs,
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },

View file

@ -27,7 +27,7 @@ import com.tangem.utils.isNullOrZero
import java.math.BigDecimal
internal class TokenDetailsStakingInfoConverter(
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val appCurrencyProvider: Provider<AppCurrency>,
private val clickIntents: TokenDetailsClickIntents,
private val currentState: TokenDetailsState,
@ -35,7 +35,6 @@ internal class TokenDetailsStakingInfoConverter(
) : Converter<StakingAvailability, TokenDetailsState> {
override fun convert(value: StakingAvailability): TokenDetailsState {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider.invoke() ?: return currentState
return currentState.copy(
stakingBlocksState = getYieldBalance(cryptoCurrencyStatus, currentState, value),
)

View file

@ -129,10 +129,11 @@ internal class TokenDetailsStateFactory(
state: TokenDetailsState,
stakingEntryInfo: StakingEntryInfo?,
stakingAvailability: StakingAvailability,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): TokenDetailsState {
return TokenDetailsStakingInfoConverter(
currentState = state,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
cryptoCurrencyStatus = cryptoCurrencyStatus,
clickIntents = clickIntents,
appCurrencyProvider = appCurrencyProvider,
stakingEntryInfo = stakingEntryInfo,

View file

@ -321,9 +321,9 @@ internal class TokenDetailsViewModel @Inject constructor(
cryptoCurrencyStatus = status
updateButtons(currencyStatus = status)
updateWarnings(status)
subscribeOnUpdateStakingInfo(status)
}
currencyStatusAnalyticsSender.send(maybeCurrencyStatus)
subscribeOnUpdateStakingInfo()
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
@ -409,7 +409,7 @@ internal class TokenDetailsViewModel @Inject constructor(
}
}
private fun subscribeOnUpdateStakingInfo() {
private fun subscribeOnUpdateStakingInfo(cryptoCurrencyStatus: CryptoCurrencyStatus) {
getStakingAvailabilityUseCase(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
@ -417,16 +417,22 @@ internal class TokenDetailsViewModel @Inject constructor(
.map { it.getOrElse { StakingAvailability.Unavailable } }
.distinctUntilChanged()
.onEach {
if (it is StakingAvailability.Available) {
val stakingInfo = getStakingEntryInfoUseCase(
val stakingEntryInfo = if (it is StakingAvailability.Available) {
getStakingEntryInfoUseCase(
cryptoCurrencyId = cryptoCurrency.id,
symbol = cryptoCurrency.symbol,
)
).getOrNull()
} else {
null
}
val stakingEntryInfo = stakingInfo.getOrNull()
internalUiState.update { state ->
stateFactory.getStakingInfoState(state, stakingEntryInfo, it)
}
internalUiState.update { state ->
stateFactory.getStakingInfoState(
state = state,
stakingEntryInfo = stakingEntryInfo,
stakingAvailability = it,
cryptoCurrencyStatus = cryptoCurrencyStatus,
)
}
}
.flowOn(dispatchers.main)