Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-21 17:33:05 +04:00
parent 9491a9cae6
commit 9abc118480
7 changed files with 91 additions and 66 deletions

View file

@ -10,9 +10,9 @@ import com.tangem.data.account.store.ArchivedAccountsStoreFactory
import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration
import com.tangem.data.common.account.WalletAccountsFetcher
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.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.repository.AccountsCRUDRepository
@ -42,7 +42,6 @@ internal object AccountDataModule {
accountsResponseStoreFactory: AccountsResponseStoreFactory,
userWalletsStore: UserWalletsStore,
userTokensSaver: UserTokensSaver,
eTagsStore: ETagsStore,
accountConverterFactoryContainer: AccountConverterFactoryContainer,
dispatchers: CoroutineDispatcherProvider,
): AccountsCRUDRepository {
@ -53,7 +52,7 @@ internal object AccountDataModule {
archivedAccountsStoreFactory = ArchivedAccountsStoreFactory,
userWalletsStore = userWalletsStore,
userTokensSaver = userTokensSaver,
eTagsStore = eTagsStore,
archivedAccountsETagStore = RuntimeStateStore(emptyMap()),
convertersContainer = accountConverterFactoryContainer,
dispatchers = dispatchers,
)

View file

@ -10,12 +10,15 @@ 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.common.account.WalletAccountsSaver
import com.tangem.data.common.cache.etag.ETagsStore
import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException
import com.tangem.datasource.api.common.response.ETAG_HEADER
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.utils.getSyncOrNull
import com.tangem.domain.account.models.AccountList
@ -43,7 +46,7 @@ internal class DefaultAccountsCRUDRepository(
private val archivedAccountsStoreFactory: ArchivedAccountsStoreFactory,
private val userWalletsStore: UserWalletsStore,
private val userTokensSaver: UserTokensSaver,
private val eTagsStore: ETagsStore,
private val archivedAccountsETagStore: RuntimeStateStore<Map<String, String?>>,
private val convertersContainer: AccountConverterFactoryContainer,
private val dispatchers: CoroutineDispatcherProvider,
) : AccountsCRUDRepository {
@ -90,19 +93,38 @@ internal class DefaultAccountsCRUDRepository(
}
override suspend fun fetchArchivedAccounts(userWalletId: UserWalletId) {
val response = withContext(dispatchers.io) {
tangemTechApi.getWalletArchivedAccounts(
walletId = userWalletId.stringValue,
eTag = getETag(userWalletId),
).getOrThrow()
}
val eTag = archivedAccountsETagStore.getSyncOrNull()?.get(key = userWalletId.stringValue)
val store = getArchivedAccountsStore(userWalletId = userWalletId)
val converter = ArchivedAccountConverter(userWalletId = userWalletId)
val archivedAccounts = converter.convertList(input = response.accounts)
val response = safeApiCall(
call = {
val apiResponse = withContext(dispatchers.io) {
tangemTechApi.getWalletArchivedAccounts(
walletId = userWalletId.stringValue,
eTag = eTag,
)
}
store.store(value = archivedAccounts)
saveETag(userWalletId, apiResponse)
apiResponse.bind()
},
onError = {
if (it is HttpException && it.code == HttpException.Code.NOT_MODIFIED) {
null
} else {
throw it
}
},
)
if (response != null) {
val converter = ArchivedAccountConverter(userWalletId = userWalletId)
val archivedAccounts = converter.convertList(input = response.accounts)
store.store(value = archivedAccounts)
}
}
override suspend fun saveAccounts(accountList: AccountList) {
@ -167,8 +189,12 @@ internal class DefaultAccountsCRUDRepository(
override fun getUserWalletsSync(): List<UserWallet> = userWalletsStore.userWalletsSync
private suspend fun getETag(userWalletId: UserWalletId): String? {
return eTagsStore.getSyncOrNull(userWalletId = userWalletId, key = ETagsStore.Key.WalletAccounts)
private suspend fun saveETag(userWalletId: UserWalletId, apiResponse: ApiResponse<*>) {
val eTag = apiResponse.headers[ETAG_HEADER]?.firstOrNull()
archivedAccountsETagStore.update {
it + (userWalletId.stringValue to eTag)
}
}
private suspend fun getAccountsResponseSync(userWalletId: UserWalletId): GetWalletAccountsResponse? {

View file

@ -10,7 +10,6 @@ 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.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
@ -54,7 +53,7 @@ class DefaultAccountsCRUDRepositoryTest {
private val userWalletsStore: UserWalletsStore = mockk()
private val userTokensSaver: UserTokensSaver = mockk()
private val eTagsStore: ETagsStore = mockk()
private val archivedAccountsETagStore: RuntimeStateStore<Map<String, String?>> = mockk(relaxUnitFun = true)
private val convertersContainer: AccountConverterFactoryContainer = mockk()
private val accountListConverter: AccountListConverter = mockk()
@ -67,7 +66,7 @@ class DefaultAccountsCRUDRepositoryTest {
archivedAccountsStoreFactory = archivedAccountsStoreFactory,
userWalletsStore = userWalletsStore,
userTokensSaver = userTokensSaver,
eTagsStore = eTagsStore,
archivedAccountsETagStore = archivedAccountsETagStore,
convertersContainer = convertersContainer,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@ -532,7 +531,7 @@ class DefaultAccountsCRUDRepositoryTest {
val archivedAccount = ArchivedAccountConverter(userWalletId).convert(accountDTO)
coEvery { eTagsStore.getSyncOrNull(userWalletId, ETagsStore.Key.WalletAccounts) } returns eTag
coEvery { archivedAccountsETagStore.getSyncOrNull() } returns mapOf(userWalletId.stringValue to eTag)
coEvery {
tangemTechApi.getWalletArchivedAccounts(userWalletId.stringValue, eTag)
@ -546,9 +545,10 @@ class DefaultAccountsCRUDRepositoryTest {
Truth.assertThat(actual).containsExactly(archivedAccount)
coVerifyOrder {
eTagsStore.getSyncOrNull(userWalletId, ETagsStore.Key.WalletAccounts)
tangemTechApi.getWalletArchivedAccounts(userWalletId.stringValue, eTag)
archivedAccountsETagStore.getSyncOrNull()
archivedAccountsStoreFactory.create(userWalletId)
tangemTechApi.getWalletArchivedAccounts(userWalletId.stringValue, eTag)
archivedAccountsETagStore.update(any())
}
}
@ -558,7 +558,7 @@ class DefaultAccountsCRUDRepositoryTest {
val eTag = "etag123"
val exception = Exception("API error")
coEvery { eTagsStore.getSyncOrNull(userWalletId, ETagsStore.Key.WalletAccounts) } returns eTag
coEvery { archivedAccountsETagStore.getSyncOrNull() } returns mapOf(userWalletId.stringValue to eTag)
coEvery { tangemTechApi.getWalletArchivedAccounts(userWalletId.stringValue, eTag) } throws exception
// Act
@ -570,7 +570,7 @@ class DefaultAccountsCRUDRepositoryTest {
Truth.assertThat(archivedAccountsStore.getSyncOrNull()).isNull()
coVerifyOrder {
eTagsStore.getSyncOrNull(userWalletId, ETagsStore.Key.WalletAccounts)
archivedAccountsETagStore.getSyncOrNull()
tangemTechApi.getWalletArchivedAccounts(userWalletId.stringValue, eTag)
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.domain.account.usecase
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.account.models.ArchivedAccount
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.core.lce.Lce
@ -16,7 +15,6 @@ import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.launch
typealias ArchivedAccountList = List<ArchivedAccount>
@ -37,32 +35,19 @@ class GetArchivedAccountsUseCase(
* @param userWalletId the unique identifier of the user wallet
*/
operator fun invoke(userWalletId: UserWalletId): LceFlow<Throwable, ArchivedAccountList> = channelFlow {
val archivedAccounts = getArchivedAccounts(userWalletId = userWalletId)
send(lceLoading())
archivedAccounts
.onRight { send(it.lceContent()) }
.onLeft {
send(lceLoading())
launch {
fetchArchivedAccounts(userWalletId).getOrElse {
send(it.lceError())
}
}
fetchArchivedAccounts(userWalletId = userWalletId)
.onRight {
subscribeOnArchivedAccounts(userWalletId)
}
.onLeft {
send(it.lceError())
return@channelFlow
}
subscribeOnArchivedAccounts(userWalletId)
}
.distinctUntilChanged()
private suspend fun getArchivedAccounts(userWalletId: UserWalletId): Either<Throwable, ArchivedAccountList> {
return Either.catch {
crudRepository.getArchivedAccountListSync(userWalletId = userWalletId).getOrElse {
error("Archived accounts not found for user wallet: $userWalletId")
}
}
}
private suspend fun fetchArchivedAccounts(userWalletId: UserWalletId): Either<Throwable, Unit> {
return Either.catch { crudRepository.fetchArchivedAccounts(userWalletId) }
}

View file

@ -73,7 +73,7 @@ internal sealed class WalletSettingsAccountsUM : WalletSettingsItemUM() {
data class Footer(
override val id: String,
val addAccount: AddAccountUM,
val archivedAccounts: BlockUM,
val archivedAccounts: BlockUM?,
val showDescription: Boolean,
val description: TextReference,
) : WalletSettingsAccountsUM() {

View file

@ -1,6 +1,7 @@
package com.tangem.feature.walletsettings.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@ -325,17 +326,25 @@ private fun AccountsFooter(model: WalletSettingsAccountsUM.Footer, modifier: Mod
),
) {
AddAccountRow(model.addAccount)
HorizontalDivider(
thickness = TangemTheme.dimens.size0_5,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing12),
color = TangemTheme.colors.stroke.primary,
)
BlockItem(
modifier = Modifier.fillMaxWidth(),
model = model.archivedAccounts,
)
AnimatedVisibility(visible = model.archivedAccounts != null) {
model.archivedAccounts ?: return@AnimatedVisibility
Column {
HorizontalDivider(
thickness = TangemTheme.dimens.size0_5,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing12),
color = TangemTheme.colors.stroke.primary,
)
BlockItem(
modifier = Modifier.fillMaxWidth(),
model = model.archivedAccounts,
)
}
}
}
if (!model.showDescription) return
SpacerH8()

View file

@ -85,6 +85,8 @@ internal class AccountItemsDelegate @Inject constructor(
val addAccountEnabled = accounts.size < AccountList.MAX_ACCOUNTS_COUNT
val showDescription = accounts.size > 1
val isArchivedAccountsEnabled = accountStatusList.accountStatuses.size != accountStatusList.totalAccounts
WalletSettingsAccountsUM.Footer(
id = "accounts_footer",
addAccount = AddAccountUM(
@ -94,11 +96,15 @@ internal class AccountItemsDelegate @Inject constructor(
if (addAccountEnabled) openAddAccount(userWalletId) else canNotAddAccountDialog()
},
),
archivedAccounts = BlockUM(
text = resourceReference(R.string.account_archived_accounts),
iconRes = R.drawable.ic_archive_24,
onClick = { openArchivedAccounts(userWalletId) },
),
archivedAccounts = if (isArchivedAccountsEnabled) {
BlockUM(
text = resourceReference(R.string.account_archived_accounts),
iconRes = R.drawable.ic_archive_24,
onClick = { openArchivedAccounts(userWalletId) },
)
} else {
null
},
showDescription = showDescription,
description = resourceReference(R.string.account_reorder_description),
).let(::add)