From 6901522cf76cb9321901d446a160a19677c8cf02 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 6 Mar 2025 19:15:00 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../api/common/response/ApiResponseExt.kt | 7 +++ .../local/token/DefaultStakingBalanceStore.kt | 10 +++- .../data/staking/DefaultStakingRepository.kt | 48 ++++++++++++------- .../TokenDetailsStakingInfoConverter.kt | 3 +- .../state/factory/TokenDetailsStateFactory.kt | 3 +- .../viewmodels/TokenDetailsViewModel.kt | 24 ++++++---- 6 files changed, 66 insertions(+), 29 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseExt.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseExt.kt index 256c45afe2..9fc9554c4d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseExt.kt @@ -3,4 +3,11 @@ package com.tangem.datasource.api.common.response fun ApiResponse.getOrThrow(): T = when (this) { is ApiResponse.Error -> throw cause is ApiResponse.Success -> data +} + +fun ApiResponse.fold(onSuccess: (T) -> R, onError: (ApiResponseError) -> R): R { + return when (this) { + is ApiResponse.Error -> onError(cause) + is ApiResponse.Success -> onSuccess(data) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt index 486d520094..fbe1a1435b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt @@ -179,7 +179,15 @@ internal class DefaultStakingBalanceStore( cachedBalances: Set, runtimeBalances: Set, ): Set { - 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 -> 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 2bca8708d4..8482f9a190 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 @@ -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") }, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt index 01c5bcb827..befa868154 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt @@ -27,7 +27,7 @@ import com.tangem.utils.isNullOrZero import java.math.BigDecimal internal class TokenDetailsStakingInfoConverter( - private val cryptoCurrencyStatusProvider: Provider, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val appCurrencyProvider: Provider, private val clickIntents: TokenDetailsClickIntents, private val currentState: TokenDetailsState, @@ -35,7 +35,6 @@ internal class TokenDetailsStakingInfoConverter( ) : Converter { override fun convert(value: StakingAvailability): TokenDetailsState { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider.invoke() ?: return currentState return currentState.copy( stakingBlocksState = getYieldBalance(cryptoCurrencyStatus, currentState, value), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 1ee5772d32..602b035351 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -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, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index ed991eda47..9b4d62818d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -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)