Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-08 17:12:51 +04:00
parent a8f084ae88
commit 4d5b9b87e6
12 changed files with 105 additions and 17 deletions

View file

@ -39,5 +39,13 @@ internal class DefaultUserTokensResponseStore(
)
}
override suspend fun clear(userWalletId: UserWalletId) {
appPreferencesStore.updateData { preferences ->
val key = createPreferencesKey(userWalletId = userWalletId.stringValue)
preferences.toMutablePreferences().apply { remove(key) }
}
}
private fun createPreferencesKey(userWalletId: String) = stringPreferencesKey(name = "user_tokens_$userWalletId")
}

View file

@ -17,4 +17,6 @@ interface UserTokensResponseStore {
suspend fun getSyncOrNull(userWalletId: UserWalletId): UserTokensResponse?
suspend fun store(userWalletId: UserWalletId, response: UserTokensResponse)
suspend fun clear(userWalletId: UserWalletId)
}

View file

@ -14,7 +14,6 @@ import dagger.assisted.AssistedInject
*/
internal class GetWalletAccountsResponseConverter @AssistedInject constructor(
@Assisted private val userWallet: UserWallet,
@Assisted val version: Int,
cryptoPortfolioConverterFactory: CryptoPortfolioConverter.Factory,
) : Converter<AccountList, GetWalletAccountsResponse> {
@ -25,7 +24,6 @@ internal class GetWalletAccountsResponseConverter @AssistedInject constructor(
override fun convert(value: AccountList): GetWalletAccountsResponse {
return GetWalletAccountsResponse(
wallet = GetWalletAccountsResponse.Wallet(
version = version,
group = TokensGroupTypeConverter.convertBack(value.groupType),
sort = TokensSortTypeConverter.convertBack(value.sortType),
totalAccounts = value.totalAccounts,
@ -39,6 +37,6 @@ internal class GetWalletAccountsResponseConverter @AssistedInject constructor(
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet, version: Int): GetWalletAccountsResponseConverter
fun create(userWallet: UserWallet): GetWalletAccountsResponseConverter
}
}

View file

@ -104,9 +104,10 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
): UserTokensResponse? {
if (this != null) return this
return userTokensResponseStore.getSyncOrNull(
userWalletId = userWalletProvider().walletId,
)
val userWalletId = userWalletProvider().walletId
return userTokensResponseStore.getSyncOrNull(userWalletId)
.also { userTokensResponseStore.clear(userWalletId) }
}
private fun UserTokensResponse?.orDefault(userWalletProvider: Provider<UserWallet>): UserTokensResponse {

View file

@ -112,10 +112,7 @@ internal class DefaultAccountsCRUDRepository(
.getOrThrow()
}
val converter = convertersContainer.getWalletAccountsResponseCF.create(
userWallet = accountList.userWallet,
version = version,
)
val converter = convertersContainer.getWalletAccountsResponseCF.create(userWallet = accountList.userWallet)
val accountsResponse = converter.convert(value = accountList)

View file

@ -26,7 +26,6 @@ class GetWalletAccountsResponseConverterTest {
private val cryptoPortfolioConverter = mockk<CryptoPortfolioConverter>()
private val converter = GetWalletAccountsResponseConverter(
userWallet = userWallet,
version = 0,
cryptoPortfolioConverterFactory = cryptoPortfolioConverterFactory,
)

View file

@ -30,7 +30,7 @@ class FetchWalletAccountsErrorHandlerTest {
private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true)
private val userWalletsStore: UserWalletsStore = mockk()
private val userTokensResponseStore: UserTokensResponseStore = mockk()
private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxUnitFun = true)
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory = mockk()
private val cryptoPortfolioConverter = mockk<CryptoPortfolioConverter>()
private val userTokensResponseFactory: UserTokensResponseFactory = mockk()

View file

@ -595,7 +595,7 @@ class DefaultAccountsCRUDRepositoryTest {
}
every {
convertersContainer.getWalletAccountsResponseCF.create(userWallet = userWallet, version = version)
convertersContainer.getWalletAccountsResponseCF.create(userWallet = userWallet)
} returns converter
coEvery { accountsResponseStore.updateData(transform = any()) } returns accountsResponse
@ -610,7 +610,7 @@ class DefaultAccountsCRUDRepositoryTest {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
tangemTechApi.saveWalletAccounts(userWalletId.stringValue, version.toString(), body)
convertersContainer.getWalletAccountsResponseCF.create(userWallet, version)
convertersContainer.getWalletAccountsResponseCF.create(userWallet)
converter.convert(accountList)
accountsResponseStore.updateData(any())
}
@ -656,7 +656,7 @@ class DefaultAccountsCRUDRepositoryTest {
}
coVerify(inverse = true) {
convertersContainer.getWalletAccountsResponseCF.create(any(), any())
convertersContainer.getWalletAccountsResponseCF.create(any())
accountsResponseStore.updateData(any())
}
}

View file

@ -0,0 +1,40 @@
package com.tangem.data.common.cache.etag
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.stringPreferencesKey
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.domain.models.wallet.UserWalletId
import timber.log.Timber
/**
* Default implementation of the [ETagsStore] interface for managing ETag values
*
* @property appPreferencesStore the preferences store used for saving and retrieving ETag values
*/
internal class DefaultETagsStore(
private val appPreferencesStore: AppPreferencesStore,
) : ETagsStore {
override suspend fun getSyncOrNull(userWalletId: UserWalletId, key: ETagsStore.Key): String? {
val key = getAccountsETagKey(userWalletId = userWalletId, key = key)
return appPreferencesStore.getSyncOrNull(key = key)
}
override suspend fun store(userWalletId: UserWalletId, key: ETagsStore.Key, value: String) {
if (value.isBlank()) {
Timber.e("ETag value is blank, not storing it. userWalletId: $userWalletId, key: $key")
return
}
val key = getAccountsETagKey(userWalletId = userWalletId, key = key)
appPreferencesStore.store(key = key, value = value)
}
private fun getAccountsETagKey(userWalletId: UserWalletId, key: ETagsStore.Key): Preferences.Key<String> {
return stringPreferencesKey(name = "etag_${key}_${userWalletId.stringValue}")
}
}

View file

@ -0,0 +1,34 @@
package com.tangem.data.common.cache.etag
import com.tangem.domain.models.wallet.UserWalletId
/**
* Interface for working with ETag (Entity Tag), which is used for data caching and validation.
*
[REDACTED_AUTHOR]
*/
interface ETagsStore {
/**
* Retrieves the stored ETag value for the specified wallet and key
*
* @param userWalletId identifier of the user wallet
* @param key the key for which to get the ETag value
*/
suspend fun getSyncOrNull(userWalletId: UserWalletId, key: Key): String?
/**
* Stores the ETag value for the specified wallet and key
*
* @param userWalletId identifier of the user wallet
* @param key the key for which to get the ETag value
*/
suspend fun store(userWalletId: UserWalletId, key: Key, value: String)
/** Enumeration of possible keys for storing ETag values */
enum class Key {
WalletAccounts,
UserTokens,
;
}
}

View file

@ -50,9 +50,9 @@ object UserTokensResponseAccountIdEnricher {
if (enrichedTokens.isEmpty()) return tokens
val enrichedTokenMap = enrichedTokens.associateBy { it }
return tokens.map { token ->
val enrichedToken = enrichedTokens.firstOrNull { it == token }
enrichedToken ?: token
enrichedTokenMap[token] ?: token
}
}

View file

@ -1,10 +1,13 @@
package com.tangem.data.common.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.cache.etag.DefaultETagsStore
import com.tangem.data.common.cache.etag.ETagsStore
import com.tangem.data.common.currency.*
import com.tangem.data.common.quote.DefaultQuotesFetcher
import com.tangem.data.common.quote.QuotesFetcher
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
@ -76,4 +79,10 @@ internal object DataCommonModule {
fun provideQuotesFetcher(tangemTechApi: TangemTechApi, dispatchers: CoroutineDispatcherProvider): QuotesFetcher {
return DefaultQuotesFetcher(tangemTechApi = tangemTechApi, dispatchers = dispatchers)
}
@Provides
@Singleton
fun provideETagsStore(appPreferencesStore: AppPreferencesStore): ETagsStore {
return DefaultETagsStore(appPreferencesStore = appPreferencesStore)
}
}