Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-14 12:36:36 +04:00
parent 65e9a1cb40
commit 500bc90ca3
13 changed files with 126 additions and 26 deletions

View file

@ -26,6 +26,7 @@ dependencies {
api(projects.domain.card)
api(projects.domain.common)
api(projects.domain.models)
api(projects.domain.tokens)
// endregion
// region Project - Data

View file

@ -50,7 +50,9 @@ internal class CryptoPortfolioConverter @AssistedInject constructor(
derivationIndex = value.derivationIndex.value,
icon = value.icon.value.name,
iconColor = value.icon.color.name,
tokens = value.cryptoCurrencies.map(userTokensResponseFactory::createResponseToken),
tokens = value.cryptoCurrencies.map {
userTokensResponseFactory.createResponseToken(currency = it, accountId = value.accountId)
},
)
}

View file

@ -41,6 +41,7 @@ internal object AccountDataModule {
walletAccountsSaver: WalletAccountsSaver,
accountsResponseStoreFactory: AccountsResponseStoreFactory,
userWalletsStore: UserWalletsStore,
userTokensSaver: UserTokensSaver,
eTagsStore: ETagsStore,
accountConverterFactoryContainer: AccountConverterFactoryContainer,
dispatchers: CoroutineDispatcherProvider,
@ -51,6 +52,7 @@ internal object AccountDataModule {
accountsResponseStoreFactory = accountsResponseStoreFactory,
archivedAccountsStoreFactory = ArchivedAccountsStoreFactory,
userWalletsStore = userWalletsStore,
userTokensSaver = userTokensSaver,
eTagsStore = eTagsStore,
convertersContainer = accountConverterFactoryContainer,
dispatchers = dispatchers,

View file

@ -0,0 +1,26 @@
package com.tangem.data.account.di
import com.tangem.data.account.producer.AccountListCryptoCurrenciesProducer
import com.tangem.data.account.producer.DefaultMultiWalletCryptoCurrenciesProducer
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object MultiWalletCryptoCurrenciesProducerModule {
@Singleton
@Provides
fun provideMultiWalletCryptoCurrenciesProducerFactory(
accountsFeatureToggles: AccountsFeatureToggles,
defaultImpl: DefaultMultiWalletCryptoCurrenciesProducer.Factory,
accountsImpl: AccountListCryptoCurrenciesProducer.Factory,
): MultiWalletCryptoCurrenciesProducer.Factory {
return if (accountsFeatureToggles.isFeatureEnabled) accountsImpl else defaultImpl
}
}

View file

@ -0,0 +1,64 @@
package com.tangem.data.account.producer
import arrow.core.Option
import arrow.core.some
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.*
/**
* Implementation of [MultiWalletCryptoCurrenciesProducer] that produces crypto currencies of all accounts
*
* @property params params
* @property userWalletsStore UserWallet's store
* @property accountsResponseStoreFactory factory to create store with accounts response
* @property responseCryptoCurrenciesFactory factory for creating [CryptoCurrency] from `UserTokensResponse`
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
internal class AccountListCryptoCurrenciesProducer @AssistedInject constructor(
@Assisted val params: MultiWalletCryptoCurrenciesProducer.Params,
private val userWalletsStore: UserWalletsStore,
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
private val dispatchers: CoroutineDispatcherProvider,
) : MultiWalletCryptoCurrenciesProducer {
override val fallback: Option<Set<CryptoCurrency>> = emptySet<CryptoCurrency>().some()
override fun produce(): Flow<Set<CryptoCurrency>> {
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId)
if (!userWallet.isMultiCurrency) {
error("${this::class.simpleName} supports only multi-currency wallet")
}
return accountsResponseStoreFactory.create(userWalletId = userWallet.walletId).data
.distinctUntilChanged()
.map { response ->
if (response == null) return@map emptySet()
responseCryptoCurrenciesFactory.createCurrencies(
response = response.toUserTokensResponse(),
userWallet = userWallet,
).toSet()
}
.onEmpty { emit(emptySet()) }
.flowOn(dispatchers.default)
}
@AssistedFactory
interface Factory : MultiWalletCryptoCurrenciesProducer.Factory {
override fun create(params: MultiWalletCryptoCurrenciesProducer.Params): AccountListCryptoCurrenciesProducer
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.data.tokens
package com.tangem.data.account.producer
import arrow.core.Option
import arrow.core.some

View file

@ -9,8 +9,10 @@ import com.tangem.data.account.store.AccountsResponseStore
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.store.ArchivedAccountsStore
import com.tangem.data.account.store.ArchivedAccountsStoreFactory
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.account.WalletAccountsSaver
import com.tangem.data.common.cache.etag.ETagsStore
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
@ -28,6 +30,7 @@ import com.tangem.utils.extensions.replaceBy
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import timber.log.Timber
/**
[REDACTED_AUTHOR]
@ -39,6 +42,7 @@ internal class DefaultAccountsCRUDRepository(
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
private val archivedAccountsStoreFactory: ArchivedAccountsStoreFactory,
private val userWalletsStore: UserWalletsStore,
private val userTokensSaver: UserTokensSaver,
private val eTagsStore: ETagsStore,
private val convertersContainer: AccountConverterFactoryContainer,
private val dispatchers: CoroutineDispatcherProvider,
@ -131,6 +135,17 @@ internal class DefaultAccountsCRUDRepository(
}
}
override suspend fun syncTokens(userWalletId: UserWalletId) {
val response = getAccountsResponseSync(userWalletId = userWalletId)
if (response == null) {
Timber.e("Can't sync tokens. No accounts response found for wallet: $userWalletId")
return
}
userTokensSaver.push(userWalletId = userWalletId, response = response.toUserTokensResponse())
}
override suspend fun getTotalAccountsCountSync(userWalletId: UserWalletId): Option<Int> = option {
val accountListResponse = getAccountsResponseSync(userWalletId = userWalletId)

View file

@ -1,4 +1,4 @@
package com.tangem.data.tokens
package com.tangem.data.account.producer
import com.google.common.truth.Truth
import com.tangem.blockchain.common.Blockchain

View file

@ -11,6 +11,7 @@ import com.tangem.data.account.store.ArchivedAccountsStore
import com.tangem.data.account.store.ArchivedAccountsStoreFactory
import com.tangem.data.common.account.WalletAccountsSaver
import com.tangem.data.common.cache.etag.ETagsStore
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
@ -52,6 +53,7 @@ class DefaultAccountsCRUDRepositoryTest {
private val archivedAccountsStore = ArchivedAccountsStore(runtimeStore = archivedAccountsInnerStore)
private val userWalletsStore: UserWalletsStore = mockk()
private val userTokensSaver: UserTokensSaver = mockk()
private val eTagsStore: ETagsStore = mockk()
private val convertersContainer: AccountConverterFactoryContainer = mockk()
@ -64,6 +66,7 @@ class DefaultAccountsCRUDRepositoryTest {
accountsResponseStoreFactory = accountsResponseStoreFactory,
archivedAccountsStoreFactory = archivedAccountsStoreFactory,
userWalletsStore = userWalletsStore,
userTokensSaver = userTokensSaver,
eTagsStore = eTagsStore,
convertersContainer = convertersContainer,
dispatchers = TestingCoroutineDispatcherProvider(),

View file

@ -1,18 +1,21 @@
package com.tangem.data.common.currency
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import javax.inject.Inject
// TODO: [REDACTED_JIRA]
class UserTokensResponseFactory @Inject constructor() {
fun createUserTokensResponse(
currencies: List<CryptoCurrency>,
isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean,
accountId: AccountId? = null,
): UserTokensResponse {
return UserTokensResponse(
tokens = currencies.map(::createResponseToken),
tokens = currencies.map { createResponseToken(currency = it, accountId = accountId) },
group = if (isGroupedByNetwork) {
UserTokensResponse.GroupType.NETWORK
} else {
@ -26,10 +29,11 @@ class UserTokensResponseFactory @Inject constructor() {
)
}
fun createResponseToken(currency: CryptoCurrency): UserTokensResponse.Token {
fun createResponseToken(currency: CryptoCurrency, accountId: AccountId? = null): UserTokensResponse.Token {
return with(currency) {
UserTokensResponse.Token(
id = id.rawCurrencyId?.value,
accountId = accountId?.value,
networkId = network.backendId,
derivationPath = network.derivationPath.value,
name = name,

View file

@ -1,20 +0,0 @@
package com.tangem.data.tokens.di
import com.tangem.data.tokens.DefaultMultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface MultiWalletCryptoCurrenciesProducerModule {
@Singleton
@Binds
fun bindMultiWalletCryptoCurrenciesProducerFactory(
impl: DefaultMultiWalletCryptoCurrenciesProducer.Factory,
): MultiWalletCryptoCurrenciesProducer.Factory
}

View file

@ -75,6 +75,9 @@ interface AccountsCRUDRepository {
*/
suspend fun saveAccount(account: Account.CryptoPortfolio)
/** Synchronizes tokens for a specific [userWalletId] with remote data source */
suspend fun syncTokens(userWalletId: UserWalletId)
/**
* Retrieves the total count of accounts associated with a specific user wallet including archived accounts
*

View file

@ -201,7 +201,7 @@ class SaveCryptoCurrenciesUseCase(
),
)
currenciesRepository.syncTokens(userWalletId)
accountsCRUDRepository.syncTokens(userWalletId)
}
private suspend fun refreshYieldBalances(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {