From df186a5f5d516b45f7dda983f8a12e2fbf1ec8f7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 29 Oct 2025 15:59:29 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/common/ui/account/AccountNameUM.kt | 6 +++++- data/account/build.gradle.kts | 5 +++++ .../data/account/di/AccountDataModule.kt | 4 ++++ .../DefaultAccountsCRUDRepository.kt | 18 ++++++++++++++++++ .../DefaultAccountsCRUDRepositoryTest.kt | 4 ++++ .../repository/AccountsCRUDRepository.kt | 9 +++++++++ .../usecase/AddCryptoPortfolioUseCase.kt | 14 ++++++++++++-- .../usecase/UpdateCryptoPortfolioUseCase.kt | 19 +++++++++++++------ .../usecase/AddCryptoPortfolioUseCaseTest.kt | 4 ++++ 9 files changed, 74 insertions(+), 9 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountNameUM.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountNameUM.kt index 39ba4af467..305212c2bc 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountNameUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountNameUM.kt @@ -1,5 +1,6 @@ package com.tangem.common.ui.account +import androidx.annotation.StringRes import androidx.compose.runtime.Immutable import arrow.core.Either import arrow.core.raise.either @@ -25,7 +26,10 @@ sealed interface AccountNameUM { */ data object DefaultMain : AccountNameUM { - override val value: TextReference = resourceReference(R.string.account_main_account_title) + @get:StringRes + val stringResId: Int = R.string.account_main_account_title + + override val value: TextReference = resourceReference(stringResId) } /** diff --git a/data/account/build.gradle.kts b/data/account/build.gradle.kts index 3123fadcd8..93820578c2 100644 --- a/data/account/build.gradle.kts +++ b/data/account/build.gradle.kts @@ -15,9 +15,14 @@ tasks.withType().configureEach { dependencies { + // region Project - Common + implementation(projects.common.ui) // It's needed for getting AccountName.DefaultMain value + // endregion + // region Project - Core implementation(projects.core.datasource) implementation(projects.core.configToggles) + implementation(projects.core.res) api(projects.core.utils) // endregion diff --git a/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt b/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt index b996298a5e..e85b397865 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt @@ -1,5 +1,6 @@ package com.tangem.data.account.di +import android.content.Context import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.data.account.converter.AccountConverterFactoryContainer import com.tangem.data.account.featuretoggle.DefaultAccountsFeatureToggles @@ -21,6 +22,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @@ -43,6 +45,7 @@ internal object AccountDataModule { userWalletsStore: UserWalletsStore, userTokensSaver: UserTokensSaver, accountConverterFactoryContainer: AccountConverterFactoryContainer, + @ApplicationContext context: Context, dispatchers: CoroutineDispatcherProvider, ): AccountsCRUDRepository { return DefaultAccountsCRUDRepository( @@ -54,6 +57,7 @@ internal object AccountDataModule { userTokensSaver = userTokensSaver, archivedAccountsETagStore = RuntimeStateStore(emptyMap()), convertersContainer = accountConverterFactoryContainer, + resources = context.resources, dispatchers = dispatchers, ) } diff --git a/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt b/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt index cdb3dcba43..1510ff5479 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt @@ -1,8 +1,11 @@ package com.tangem.data.account.repository +import android.content.res.Resources import arrow.core.Option import arrow.core.raise.option import arrow.core.toOption +import com.tangem.common.ui.account.AccountNameUM +import com.tangem.core.res.getStringSafe import com.tangem.data.account.converter.AccountConverterFactoryContainer import com.tangem.data.account.converter.ArchivedAccountConverter import com.tangem.data.account.store.AccountsResponseStore @@ -26,6 +29,7 @@ import com.tangem.domain.account.models.ArchivedAccount import com.tangem.domain.account.repository.AccountsCRUDRepository import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.account.AccountName import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -48,6 +52,7 @@ internal class DefaultAccountsCRUDRepository( private val userTokensSaver: UserTokensSaver, private val archivedAccountsETagStore: RuntimeStateStore>, private val convertersContainer: AccountConverterFactoryContainer, + private val resources: Resources, private val dispatchers: CoroutineDispatcherProvider, ) : AccountsCRUDRepository { @@ -197,6 +202,19 @@ internal class DefaultAccountsCRUDRepository( override fun getUserWalletsSync(): List = userWalletsStore.userWalletsSync + override fun checkDefaultAccountName(accountList: AccountList, accountName: AccountName) { + val hasDefaultName = accountList.accounts.any { it.accountName is AccountName.DefaultMain } + + if (!hasDefaultName) return + + val defaultName = resources.getStringSafe(AccountNameUM.DefaultMain.stringResId) + .let(AccountName::invoke).getOrNull() + + require(defaultName != accountName) { + "Cannot use default account name \"$accountName\" for custom accounts" + } + } + private suspend fun saveETag(userWalletId: UserWalletId, apiResponse: ApiResponse<*>) { val eTag = apiResponse.headers[ETAG_HEADER]?.firstOrNull() diff --git a/data/account/src/test/java/com/tangem/data/account/repository/DefaultAccountsCRUDRepositoryTest.kt b/data/account/src/test/java/com/tangem/data/account/repository/DefaultAccountsCRUDRepositoryTest.kt index f338ee9cf1..34d8946b96 100644 --- a/data/account/src/test/java/com/tangem/data/account/repository/DefaultAccountsCRUDRepositoryTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/repository/DefaultAccountsCRUDRepositoryTest.kt @@ -1,5 +1,6 @@ package com.tangem.data.account.repository +import android.content.res.Resources import arrow.core.None import arrow.core.toOption import com.google.common.truth.Truth @@ -59,6 +60,8 @@ class DefaultAccountsCRUDRepositoryTest { private val accountListConverter: AccountListConverter = mockk() private val cryptoPortfolioConverter: CryptoPortfolioConverter = mockk() + private val resources: Resources = mockk() + private val repository = DefaultAccountsCRUDRepository( tangemTechApi = tangemTechApi, walletAccountsSaver = walletAccountsSaver, @@ -68,6 +71,7 @@ class DefaultAccountsCRUDRepositoryTest { userTokensSaver = userTokensSaver, archivedAccountsETagStore = archivedAccountsETagStore, convertersContainer = convertersContainer, + resources = resources, dispatchers = TestingCoroutineDispatcherProvider(), ) diff --git a/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt b/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt index 598771815e..5c965fe538 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt @@ -5,6 +5,7 @@ import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.models.ArchivedAccount import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.account.AccountName import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import kotlinx.coroutines.flow.Flow @@ -113,4 +114,12 @@ interface AccountsCRUDRepository { /** Synchronously retrieves all user wallets */ fun getUserWalletsSync(): List + + /** Checks if the provided account name is the default name within the given account list + * + * @param accountList the list of accounts to check against + * @param accountName the account name to be checked + * @throws IllegalArgumentException if the account name matches the default name + */ + fun checkDefaultAccountName(accountList: AccountList, accountName: AccountName) } \ No newline at end of file diff --git a/domain/account/src/main/java/com/tangem/domain/account/usecase/AddCryptoPortfolioUseCase.kt b/domain/account/src/main/java/com/tangem/domain/account/usecase/AddCryptoPortfolioUseCase.kt index 8085ff2eb1..cf7992e6ab 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/usecase/AddCryptoPortfolioUseCase.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/usecase/AddCryptoPortfolioUseCase.kt @@ -1,10 +1,12 @@ package com.tangem.domain.account.usecase import arrow.core.Either +import arrow.core.Either.Companion.catch import arrow.core.getOrElse import arrow.core.raise.Raise import arrow.core.raise.catch import arrow.core.raise.either +import arrow.core.raise.withError import com.tangem.domain.account.fetcher.SingleAccountListFetcher import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.repository.AccountsCRUDRepository @@ -49,8 +51,10 @@ class AddCryptoPortfolioUseCase( val newAccount = createAccount(userWalletId, accountName, icon, derivationIndex) - val updatedAccounts = (accountList + newAccount).getOrElse { - raise(Error.AccountListRequirementsNotMet(it)) + val updatedAccounts = withError({ Error.AccountListRequirementsNotMet(it) }) { + checkDefaultName(accountList, accountName) + + (accountList + newAccount).bind() } saveAccounts(accountList = updatedAccounts) @@ -81,6 +85,12 @@ class AddCryptoPortfolioUseCase( ) } + private fun Raise.checkDefaultName(accountList: AccountList, accountName: AccountName) { + withError({ AccountList.Error.DuplicateAccountNames }) { + catch { crudRepository.checkDefaultAccountName(accountList, accountName) }.bind() + } + } + private suspend fun Raise.getAccountList(userWalletId: UserWalletId): AccountList { return catch( block = { crudRepository.getAccountListSync(userWalletId = userWalletId) }, diff --git a/domain/account/src/main/java/com/tangem/domain/account/usecase/UpdateCryptoPortfolioUseCase.kt b/domain/account/src/main/java/com/tangem/domain/account/usecase/UpdateCryptoPortfolioUseCase.kt index 9b0487ea55..f70ff26efa 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/usecase/UpdateCryptoPortfolioUseCase.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/usecase/UpdateCryptoPortfolioUseCase.kt @@ -2,10 +2,7 @@ package com.tangem.domain.account.usecase import arrow.core.Either import arrow.core.getOrElse -import arrow.core.raise.Raise -import arrow.core.raise.catch -import arrow.core.raise.either -import arrow.core.raise.ensure +import arrow.core.raise.* import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.repository.AccountsCRUDRepository import com.tangem.domain.models.account.Account @@ -50,8 +47,12 @@ class UpdateCryptoPortfolioUseCase( .setName(name = accountName) .setIcon(icon = icon) - val updatedAccounts = (accountList + updatedAccount).getOrElse { - raise(Error.AccountListRequirementsNotMet(it)) + val updatedAccounts = withError({ Error.AccountListRequirementsNotMet(it) }) { + if (accountName != null) { + checkDefaultName(accountList, accountName) + } + + (accountList + updatedAccount).bind() } saveAccounts(updatedAccounts) @@ -88,6 +89,12 @@ class UpdateCryptoPortfolioUseCase( return if (icon != null) this.copy(icon = icon) else this } + private fun Raise.checkDefaultName(accountList: AccountList, accountName: AccountName) { + withError({ AccountList.Error.DuplicateAccountNames }) { + Either.Companion.catch { crudRepository.checkDefaultAccountName(accountList, accountName) }.bind() + } + } + /** * Represents possible errors that can occur during the update operation */ diff --git a/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/AddCryptoPortfolioUseCaseTest.kt b/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/AddCryptoPortfolioUseCaseTest.kt index 32e113b374..1ea298d6e1 100644 --- a/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/AddCryptoPortfolioUseCaseTest.kt +++ b/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/AddCryptoPortfolioUseCaseTest.kt @@ -69,6 +69,7 @@ class AddCryptoPortfolioUseCaseTest { coVerifySequence { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) crudRepository.getAccountListSync(userWalletId) + crudRepository.checkDefaultAccountName(accountList, newAccount.accountName) crudRepository.saveAccounts(updatedAccountList) mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex) } @@ -177,6 +178,7 @@ class AddCryptoPortfolioUseCaseTest { coVerifySequence { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) crudRepository.getAccountListSync(userWalletId) + crudRepository.checkDefaultAccountName(accountList, newAccount.accountName) } coVerify(inverse = true) { @@ -249,6 +251,7 @@ class AddCryptoPortfolioUseCaseTest { coVerifySequence { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) crudRepository.getAccountListSync(userWalletId) + crudRepository.checkDefaultAccountName(accountList, newAccount.accountName) crudRepository.saveAccounts(updatedAccountList) } @@ -288,6 +291,7 @@ class AddCryptoPortfolioUseCaseTest { coVerifySequence { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) crudRepository.getAccountListSync(userWalletId) + crudRepository.checkDefaultAccountName(accountList, newAccount.accountName) crudRepository.saveAccounts(updatedAccountList) mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex) }