Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-19 11:44:37 +03:00
commit 1146554992
1461 changed files with 62266 additions and 17096 deletions

View file

@ -36,8 +36,6 @@ dependencies {
api(projects.domain.visa)
// endregion
implementation(projects.features.tangempay.details.api) // Remove after TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED
// region Project - Data
implementation(projects.data.common)
// endregion

View file

@ -1,6 +1,7 @@
package com.tangem.data.account.converter
import arrow.core.getOrElse
import arrow.core.right
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.CryptoPortfolioIcon
@ -8,7 +9,10 @@ import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWalletId
internal fun String.toAccountId(userWalletId: UserWalletId): AccountId {
return AccountId.forCryptoPortfolio(value = this, userWalletId = userWalletId).getOrElse {
return when {
startsWith(AccountId.PaymentAccountIdPrefix) -> AccountId.forPaymentAccount(userWalletId).right()
else -> AccountId.forCryptoPortfolio(value = this, userWalletId = userWalletId)
}.getOrElse {
error("Unable to create AccountId from value: $this. Cause: $it")
}
}

View file

@ -1,14 +1,9 @@
package com.tangem.data.account.di
import android.content.Context
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.squareup.moshi.Moshi
import com.tangem.data.account.converter.AccountConverterFactoryContainer
import com.tangem.data.account.fetcher.DefaultWalletAccountsFetcher
import com.tangem.data.account.repository.AccountsExpandedDTO
import com.tangem.data.account.repository.DefaultAccountsCRUDRepository
import com.tangem.data.account.repository.DefaultAccountsExpandedRepository
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.store.ArchivedAccountsStoreFactory
import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration
@ -17,14 +12,9 @@ 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.tangemTech.TangemTechApi
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.accounts.AccountTokenMigrationStore
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.mapWithStringKeyTypes
import com.tangem.datasource.utils.setTypes
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.repository.AccountsExpandedRepository
import com.tangem.domain.account.tokens.MainAccountTokensMigration
import com.tangem.utils.coroutines.AppCoroutineScope
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -63,28 +53,6 @@ internal object AccountDataModule {
)
}
@Provides
@Singleton
fun provideAccountsExpandedRepository(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
appScope: AppCoroutineScope,
): AccountsExpandedRepository {
val store = DataStoreFactory.create<Map<String, Set<AccountsExpandedDTO>>>(
serializer = MoshiDataStoreSerializer(
moshi = moshi,
types = mapWithStringKeyTypes(valueTypes = setTypes<AccountsExpandedDTO>()),
defaultValue = emptyMap(),
),
produceFile = { context.dataStoreFile(fileName = "account_expanded_store") },
scope = appScope,
)
return DefaultAccountsExpandedRepository(
store = store,
)
}
@Provides
@Singleton
fun provideWalletAccountsFetcher(impl: DefaultWalletAccountsFetcher): WalletAccountsFetcher = impl

View file

@ -0,0 +1,18 @@
package com.tangem.data.account.di
import com.tangem.data.account.repository.DefaultAccountsExpandedRepository
import com.tangem.domain.account.repository.AccountsExpandedRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
internal interface AccountUtilsModule {
@Binds
fun provideAccountsExpandedRepositoryFactory(
factory: DefaultAccountsExpandedRepository.Factory,
): AccountsExpandedRepository.Factory
}

View file

@ -11,8 +11,6 @@ import com.tangem.domain.common.wallets.getSyncStrict
import com.tangem.domain.core.flow.FlowProducerTools
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
@ -23,7 +21,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
/**
* Default implementation of [SingleAccountListProducer].
@ -39,7 +36,6 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
@Assisted val params: SingleAccountListProducer.Params,
override val flowProducerTools: FlowProducerTools,
private val walletAccountListFlowFactory: WalletAccountListFlowFactory,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val userWalletsListRepository: UserWalletsListRepository,
private val dispatchers: CoroutineDispatcherProvider,
) : SingleAccountListProducer {
@ -52,30 +48,7 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
override fun produce(): Flow<AccountList> {
val walletId = params.userWalletId
logger.i("produce() called for $walletId")
val accountListFlow: Flow<AccountList> = if (tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled) {
combineWithPaymentAccount(walletId)
} else {
walletAccountListFlowFactory.create(userWalletId = walletId)
}
return accountListFlow
.onEach { accountList ->
logger.i(
"produce()[$walletId] emit: accounts=${accountList.accounts.size}, " +
"currencies=${accountList.flattenMapCurrencies().size}",
)
}
.flowOn(dispatchers.default)
}
private fun combineWithPaymentAccount(walletId: UserWalletId): Flow<AccountList> {
return walletAccountListFlowFactory.create(walletId)
.onEach { accountList ->
logger.i(
"produce()[$walletId]: accountList received accounts=${accountList.accounts.size}, " +
"currencies=${accountList.flattenMapCurrencies().size}",
)
}
.map { accountList ->
val userWallet = userWalletsListRepository.getSyncStrict(id = walletId)
val isPaymentSupported = userWallet.isPaymentAccountSupported()
@ -91,6 +64,7 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
accountList
}
}
.flowOn(dispatchers.default)
}
private fun UserWallet.isPaymentAccountSupported(): Boolean = when (this) {

View file

@ -1,17 +1,28 @@
package com.tangem.data.account.repository
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.tangem.data.account.converter.toAccountId
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.mapWithStringKeyTypes
import com.tangem.datasource.utils.setTypes
import com.tangem.domain.account.models.AccountExpandedState
import com.tangem.domain.account.repository.AccountsExpandedRepository
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.AppCoroutineScope
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
internal class DefaultAccountsExpandedRepository(
internal class DefaultAccountsExpandedRepository constructor(
private val store: DataStore<Map<String, Set<AccountsExpandedDTO>>>,
) : AccountsExpandedRepository {
@ -41,7 +52,7 @@ internal class DefaultAccountsExpandedRepository(
}
override suspend fun clearStore() {
store.updateData { emptyMap() }
store.updateData { map -> map.mapValues { emptySet() } }
}
override suspend fun update(accountState: AccountExpandedState) {
@ -59,6 +70,28 @@ internal class DefaultAccountsExpandedRepository(
map.plus(walletId.stringValue to updatedSet)
}
}
internal class Factory @Inject constructor(
@NetworkMoshi private val moshi: Moshi,
@ApplicationContext private val context: Context,
private val appScope: AppCoroutineScope,
) : AccountsExpandedRepository.Factory {
override fun create(storeFileName: String): DefaultAccountsExpandedRepository {
val store = DataStoreFactory.create<Map<String, Set<AccountsExpandedDTO>>>(
serializer = MoshiDataStoreSerializer(
moshi = moshi,
types = mapWithStringKeyTypes(valueTypes = setTypes<AccountsExpandedDTO>()),
defaultValue = emptyMap(),
),
produceFile = { context.dataStoreFile(fileName = storeFileName) },
scope = appScope,
)
return DefaultAccountsExpandedRepository(
store = store,
)
}
}
}
@JsonClass(generateAdapter = true)

View file

@ -8,7 +8,7 @@ import com.tangem.domain.core.flow.FlowProducerTools
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.test.core.getEmittedValues
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
@ -31,21 +31,22 @@ class DefaultSingleAccountListProducerTest {
private val userWalletId = UserWalletId("011")
private val flowProducerTools: FlowProducerTools = mockk()
private val tangemPayFeatureToggles = mockk<TangemPayFeatureToggles> {
every { this@mockk.isTangemPayAccountsRefactorEnabled } returns false
private val userWallet = mockk<UserWallet.Hot> {
every { walletId } returns userWalletId
every { hotWalletId } returns mockk {
every { authType } returns HotWalletId.AuthType.NoPassword
}
}
private val userWalletsListRepository = mockk<UserWalletsListRepository>()
private val userWallet = mockk<UserWallet> {
every { this@mockk.walletId } returns userWalletId
private val userWalletsListRepository = mockk<UserWalletsListRepository> {
every { userWallets } returns MutableStateFlow<List<UserWallet>?>(value = listOf(userWallet))
}
private val producer = DefaultSingleAccountListProducer(
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
walletAccountListFlowFactory = walletAccountListFlowFactory,
dispatchers = TestingCoroutineDispatcherProvider(),
flowProducerTools = flowProducerTools,
tangemPayFeatureToggles = tangemPayFeatureToggles,
userWalletsListRepository = userWalletsListRepository,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@AfterEach
@ -56,8 +57,6 @@ class DefaultSingleAccountListProducerTest {
@Test
fun produce() = runTest {
// Arrange
MutableStateFlow(listOf(userWallet))
val accountList = AccountList.empty(userWalletId)
every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList)