Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-15 17:21:47 +04:00
parent 9ebc1ac551
commit b42fa5283c
9 changed files with 45 additions and 43 deletions

View file

@ -18,7 +18,6 @@ import com.tangem.domain.account.models.AccountList
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.Provider
import timber.log.Timber
import javax.inject.Inject
@ -69,12 +68,13 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
return
}
val userWalletProvider = Provider { userWalletsStore.getSyncStrict(key = userWalletId) }
val accountDTOs = savedAccountsResponse?.accounts.orDefault(userWalletProvider = userWalletProvider)
val (accountDTOs, userTokensResponse) = if (savedAccountsResponse == null) {
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
val userTokensResponse = savedAccountsResponse?.toUserTokensResponse()
.orFromLegacyStore(userWalletProvider = userWalletProvider)
.orDefault(userWalletProvider = userWalletProvider)
createDefaultAccountDTOs(userWallet) to getFromLegacyStore(userWalletId).orDefault(userWallet)
} else {
savedAccountsResponse.accounts to savedAccountsResponse.toUserTokensResponse()
}
val isNotFoundError = error.isNetworkError(code = Code.NOT_FOUND)
if (isNotFoundError) {
@ -86,11 +86,7 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
storeWalletAccounts(userWalletId, response)
}
private fun List<WalletAccountDTO>?.orDefault(userWalletProvider: Provider<UserWallet>): List<WalletAccountDTO> {
if (this != null) return this
val userWallet = userWalletProvider()
private fun createDefaultAccountDTOs(userWallet: UserWallet): List<WalletAccountDTO> {
val accounts = AccountList.empty(userWallet).accounts
.filterIsInstance<Account.CryptoPortfolio>()
@ -99,24 +95,16 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
return converter.convertListBack(input = accounts)
}
private suspend fun UserTokensResponse?.orFromLegacyStore(
userWalletProvider: Provider<UserWallet>,
): UserTokensResponse? {
if (this != null) return this
val userWalletId = userWalletProvider().walletId
private suspend fun getFromLegacyStore(userWalletId: UserWalletId): UserTokensResponse? {
return userTokensResponseStore.getSyncOrNull(userWalletId)
.also { userTokensResponseStore.clear(userWalletId) }
}
private fun UserTokensResponse?.orDefault(userWalletProvider: Provider<UserWallet>): UserTokensResponse {
private fun UserTokensResponse?.orDefault(userWallet: UserWallet): UserTokensResponse {
if (this != null) return this
return userTokensResponseFactory.createUserTokensResponse(
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(
userWallet = userWalletProvider(),
),
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet = userWallet),
isGroupedByNetwork = false,
isSortedByBalance = false,
)

View file

@ -1,5 +1,7 @@
package com.tangem.data.networks.multi
import arrow.core.Option
import arrow.core.some
import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.networks.store.NetworksStatusesStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
@ -29,8 +31,7 @@ internal class DefaultMultiNetworkStatusProducer @AssistedInject constructor(
private val dispatchers: CoroutineDispatcherProvider,
) : MultiNetworkStatusProducer {
override val fallback: Set<NetworkStatus>
get() = setOf()
override val fallback: Option<Set<NetworkStatus>> = emptySet<NetworkStatus>().some()
override fun produce(): Flow<Set<NetworkStatus>> {
return networksStatusesStore.get(userWalletId = params.userWalletId)

View file

@ -1,5 +1,7 @@
package com.tangem.data.networks.single
import arrow.core.Option
import arrow.core.some
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
@ -28,8 +30,8 @@ internal class DefaultSingleNetworkStatusProducer @AssistedInject constructor(
private val dispatchers: CoroutineDispatcherProvider,
) : SingleNetworkStatusProducer {
override val fallback: NetworkStatus
get() = NetworkStatus(network = params.network, value = NetworkStatus.Unreachable(address = null))
override val fallback: Option<NetworkStatus>
get() = NetworkStatus(network = params.network, value = NetworkStatus.Unreachable(address = null)).some()
override fun produce(): Flow<NetworkStatus> {
return multiNetworkStatusSupplier(

View file

@ -1,5 +1,7 @@
package com.tangem.data.quotes.single
import arrow.core.Option
import arrow.core.some
import com.tangem.data.quotes.store.QuotesStatusesStore
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
@ -7,7 +9,10 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull
/**
* Default implementation of [SingleQuoteStatusProducer]
@ -21,11 +26,12 @@ internal class DefaultSingleQuoteStatusProducer @AssistedInject constructor(
private val dispatchers: CoroutineDispatcherProvider,
) : SingleQuoteStatusProducer {
override val fallback: QuoteStatus = QuoteStatus(rawCurrencyId = params.rawCurrencyId)
private val default = QuoteStatus(rawCurrencyId = params.rawCurrencyId)
override val fallback: Option<QuoteStatus> = default.some()
override fun produce(): Flow<QuoteStatus> {
return quotesStatusesStore.get()
.mapNotNull { quotes -> quotes.firstOrNull { it.rawCurrencyId == params.rawCurrencyId } ?: fallback }
.mapNotNull { quotes -> quotes.firstOrNull { it.rawCurrencyId == params.rawCurrencyId } ?: default }
.distinctUntilChanged()
.flowOn(dispatchers.default)
}

View file

@ -1,5 +1,7 @@
package com.tangem.data.staking.multi
import arrow.core.Option
import arrow.core.some
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.staking.multi.MultiYieldBalanceProducer
@ -27,8 +29,7 @@ internal class DefaultMultiYieldBalanceProducer @AssistedInject constructor(
private val dispatchers: CoroutineDispatcherProvider,
) : MultiYieldBalanceProducer {
override val fallback: Set<YieldBalance>
get() = setOf()
override val fallback: Option<Set<YieldBalance>> = emptySet<YieldBalance>().some()
override fun produce(): Flow<Set<YieldBalance>> {
return yieldsBalancesStore.get(userWalletId = params.userWalletId)

View file

@ -1,5 +1,7 @@
package com.tangem.data.staking.single
import arrow.core.Option
import arrow.core.some
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.domain.models.staking.YieldBalance
@ -34,9 +36,7 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor(
private val dispatchers: CoroutineDispatcherProvider,
) : SingleYieldBalanceProducer {
override val fallback: YieldBalance by lazy {
YieldBalance.Error(stakingId = params.stakingId)
}
override val fallback: Option<YieldBalance> = YieldBalance.Error(stakingId = params.stakingId).some()
override fun produce(): Flow<YieldBalance> {
Timber.i("Producing yield balance for params:\n$params")

View file

@ -1,5 +1,7 @@
package com.tangem.data.tokens
import arrow.core.Option
import arrow.core.some
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
@ -31,8 +33,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducer @AssistedInject constr
private val dispatchers: CoroutineDispatcherProvider,
) : MultiWalletCryptoCurrenciesProducer {
override val fallback: Set<CryptoCurrency>
get() = emptySet()
override val fallback: Option<Set<CryptoCurrency>> = emptySet<CryptoCurrency>().some()
override fun produce(): Flow<Set<CryptoCurrency>> {
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId)
@ -51,7 +52,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducer @AssistedInject constr
userWallet = userWallet,
).toSet()
}
.onEmpty { emit(fallback) }
.onEmpty { emit(emptySet()) }
.flowOn(dispatchers.default)
}

View file

@ -1,5 +1,6 @@
package com.tangem.domain.core.flow
import arrow.core.Option
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.retryWhen
@ -14,7 +15,7 @@ import kotlinx.coroutines.flow.retryWhen
interface FlowProducer<Data : Any> {
/** Fallback value if [Flow] throws exception */
val fallback: Data
val fallback: Option<Data>
/** Produce [Flow] */
fun produce(): Flow<Data>
@ -22,7 +23,7 @@ interface FlowProducer<Data : Any> {
/** Produce [Flow] with retry mechanism */
fun produceWithFallback(): Flow<Data> {
return produce().retryWhen { _, _ ->
emit(value = fallback)
fallback.onSome { emit(value = it) }
delay(timeMillis = 2000)

View file

@ -1,5 +1,7 @@
package com.tangem.domain.core.flow
import arrow.core.Option
import arrow.core.some
import com.google.common.truth.Truth
import io.mockk.every
import io.mockk.mockk
@ -105,8 +107,8 @@ internal class FlowCachingSupplierTest {
private class MockFlowProducer(private val params: Int) : FlowProducer<String> {
override val fallback: String
get() = "fallback"
override val fallback: Option<String>
get() = "fallback".some()
override fun produce(): Flow<String> = flowOf("test_$params")
@ -125,8 +127,8 @@ internal class FlowCachingSupplierTest {
private class MockErrorFlowProducer : FlowProducer<String> {
override val fallback: String
get() = "fallback"
override val fallback: Option<String>
get() = "fallback".some()
override fun produce(): Flow<String> = flow {
throw IllegalStateException()