Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-29 22:23:18 +04:00
parent a1bce4b52c
commit 08521b6c11
12 changed files with 230 additions and 90 deletions

View file

@ -1,7 +1,9 @@
package com.tangem.tap.di.domain
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.tokens.MainAccountTokensMigration
import com.tangem.domain.account.usecase.*
import dagger.Module
import dagger.Provides
@ -15,8 +17,16 @@ internal object AccountDomainModule {
@Provides
@Singleton
fun provideAddCryptoPortfolioUseCase(accountsCRUDRepository: AccountsCRUDRepository): AddCryptoPortfolioUseCase {
return AddCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
fun provideAddCryptoPortfolioUseCase(
accountsCRUDRepository: AccountsCRUDRepository,
singleAccountListFetcher: SingleAccountListFetcher,
mainAccountTokensMigration: MainAccountTokensMigration,
): AddCryptoPortfolioUseCase {
return AddCryptoPortfolioUseCase(
crudRepository = accountsCRUDRepository,
singleAccountListFetcher = singleAccountListFetcher,
mainAccountTokensMigration = mainAccountTokensMigration,
)
}
@Provides

View file

@ -7,13 +7,16 @@ import com.tangem.data.account.fetcher.DefaultWalletAccountsFetcher
import com.tangem.data.account.repository.DefaultAccountsCRUDRepository
import com.tangem.data.account.store.AccountsResponseStoreFactory
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.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.tokens.MainAccountTokensMigration
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -61,4 +64,16 @@ internal object AccountDataModule {
@Provides
@Singleton
fun provideWalletAccountsSaver(impl: DefaultWalletAccountsFetcher): WalletAccountsSaver = impl
@Provides
@Singleton
fun provideMainAccountTokensMigration(
accountsResponseStoreFactory: AccountsResponseStoreFactory,
userTokensSaver: UserTokensSaver,
): MainAccountTokensMigration {
return DefaultMainAccountTokensMigration(
accountsResponseStoreFactory = accountsResponseStoreFactory,
userTokensSaver = userTokensSaver,
)
}
}

View file

@ -11,6 +11,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
/**
* Implementation of [MultiAccountListFetcher]
@ -20,7 +21,7 @@ import java.util.concurrent.ConcurrentHashMap
*
[REDACTED_AUTHOR]
*/
internal class DefaultMultiAccountListFetcher(
internal class DefaultMultiAccountListFetcher @Inject constructor(
private val singleAccountListFetcher: SingleAccountListFetcher,
private val userWalletsStore: UserWalletsStore,
) : MultiAccountListFetcher {

View file

@ -3,6 +3,7 @@ package com.tangem.data.account.fetcher
import arrow.core.Either
import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
import javax.inject.Inject
/**
* Implementation of [SingleAccountListFetcher]
@ -11,7 +12,7 @@ import com.tangem.domain.account.fetcher.SingleAccountListFetcher
*
[REDACTED_AUTHOR]
*/
internal class DefaultSingleAccountListFetcher(
internal class DefaultSingleAccountListFetcher @Inject constructor(
private val walletAccountsFetcher: WalletAccountsFetcher,
) : SingleAccountListFetcher {

View file

@ -10,7 +10,6 @@ import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.utils.assignTokens
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.account.WalletAccountsSaver
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
@ -28,14 +27,12 @@ import timber.log.Timber
*
* @property accountsResponseStoreFactory Factory for creating stores to access cached account responses.
* @property userTokensSaver Saver for updating user tokens in persistent storage.
* @property walletAccountsSaver Saver for updating wallet accounts in persistent storage.
*
[REDACTED_AUTHOR]
*/
internal class DefaultMainAccountTokensMigration(
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
private val userTokensSaver: UserTokensSaver,
private val walletAccountsSaver: WalletAccountsSaver,
) : MainAccountTokensMigration {
override suspend fun migrate(
@ -81,8 +78,6 @@ internal class DefaultMainAccountTokensMigration(
},
)
walletAccountsSaver.store(userWalletId = userWalletId, response = updatedResponse)
userTokensSaver.push(
userWalletId = userWalletId,
response = updatedResponse.toUserTokensResponse(),

View file

@ -8,7 +8,6 @@ import com.tangem.data.account.store.AccountsResponseStore
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.account.WalletAccountsSaver
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
@ -32,11 +31,9 @@ class DefaultMainAccountTokensMigrationTest {
private val accountsResponseStoreFlow = MutableStateFlow<GetWalletAccountsResponse?>(value = null)
private val userTokensSaver = mockk<UserTokensSaver>(relaxed = true)
private val walletAccountsSaver = mockk<WalletAccountsSaver>(relaxed = true)
private val migration = DefaultMainAccountTokensMigration(
accountsResponseStoreFactory = accountsResponseStoreFactory,
userTokensSaver = userTokensSaver,
walletAccountsSaver = walletAccountsSaver,
)
private val userWalletId = UserWalletId("011")
@ -50,7 +47,7 @@ class DefaultMainAccountTokensMigrationTest {
@AfterEach
fun tearDown() {
clearMocks(accountsResponseStoreFactory, accountsResponseStore, userTokensSaver, walletAccountsSaver)
clearMocks(accountsResponseStoreFactory, accountsResponseStore, userTokensSaver)
accountsResponseStoreFlow.value = null
}
@ -65,7 +62,6 @@ class DefaultMainAccountTokensMigrationTest {
coVerify(inverse = true) {
accountsResponseStoreFactory.create(any())
accountsResponseStore.data
walletAccountsSaver.store(userWalletId = any(), response = any())
userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any())
}
}
@ -85,7 +81,6 @@ class DefaultMainAccountTokensMigrationTest {
}
coVerify(inverse = true) {
walletAccountsSaver.store(userWalletId = any(), response = any())
userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any())
}
}
@ -115,7 +110,6 @@ class DefaultMainAccountTokensMigrationTest {
}
coVerify(inverse = true) {
walletAccountsSaver.store(userWalletId = any(), response = any())
userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any())
}
}
@ -151,7 +145,6 @@ class DefaultMainAccountTokensMigrationTest {
}
coVerify(inverse = true) {
walletAccountsSaver.store(userWalletId = any(), response = any())
userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any())
}
}
@ -206,7 +199,6 @@ class DefaultMainAccountTokensMigrationTest {
coVerifySequence {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
walletAccountsSaver.store(userWalletId = userWalletId, response = migratedResponse)
userTokensSaver.push(
userWalletId = userWalletId,
response = migratedResponse.toUserTokensResponse(),

View file

@ -120,8 +120,8 @@ data class AccountList private constructor(
}
@Serializable
data class DuplicateAccountNames(val message: String) : Error {
override fun toString(): String = "$tag: Account list contains duplicate account names. $message"
data object DuplicateAccountNames : Error {
override fun toString(): String = "$tag: Account list contains duplicate account names"
}
}
@ -161,17 +161,11 @@ data class AccountList private constructor(
val uniqueAccountIdsCount = accounts.map { it.accountId.value }.distinct().size
ensure(accounts.size == uniqueAccountIdsCount) { Error.DuplicateAccountIds }
val defaultMainNameCount = accounts.count { it.accountName is AccountName.DefaultMain }
val customNames = accounts.mapNotNull { (it.accountName as? AccountName.Custom)?.value }
val uniqueCustomNameCount = customNames.distinct().size
ensure(defaultMainNameCount == 0 || defaultMainNameCount == 1) {
Error.DuplicateAccountNames("Only one account can have the default main name.")
}
ensure(customNames.size == uniqueCustomNameCount) {
Error.DuplicateAccountNames("Custom account names must be unique.")
Error.DuplicateAccountNames
}
AccountList(

View file

@ -1,15 +1,14 @@
package com.tangem.domain.account.usecase
import arrow.core.Either
import arrow.core.Option
import arrow.core.getOrElse
import arrow.core.raise.Raise
import arrow.core.raise.catch
import arrow.core.raise.either
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.models.TokensGroupType
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.account.tokens.MainAccountTokensMigration
import com.tangem.domain.models.account.*
import com.tangem.domain.models.wallet.UserWalletId
@ -17,11 +16,15 @@ import com.tangem.domain.models.wallet.UserWalletId
* Use case for adding a new crypto portfolio account
*
* @property crudRepository the repository used for performing CRUD operations on accounts
* @property singleAccountListFetcher fetches the list of accounts for a single user wallet
* @property mainAccountTokensMigration handles the migration of tokens from the main account to the new
*
[REDACTED_AUTHOR]
*/
class AddCryptoPortfolioUseCase(
private val crudRepository: AccountsCRUDRepository,
private val singleAccountListFetcher: SingleAccountListFetcher,
private val mainAccountTokensMigration: MainAccountTokensMigration,
) {
/**
@ -40,21 +43,29 @@ class AddCryptoPortfolioUseCase(
icon: CryptoPortfolioIcon,
derivationIndex: DerivationIndex,
): Either<Error, Account.CryptoPortfolio> = either {
val newAccount = createAccount(userWalletId, accountName, icon, derivationIndex)
fetchAccountList(userWalletId)
val accountList = getAccountList(userWalletId = userWalletId).getOrElse {
createNewAccountList(userWalletId = userWalletId)
}
val accountList = getAccountList(userWalletId = userWalletId)
val newAccount = createAccount(userWalletId, accountName, icon, derivationIndex)
val updatedAccounts = (accountList + newAccount).getOrElse {
raise(Error.AccountListRequirementsNotMet(it))
}
saveAccounts(updatedAccounts)
saveAccounts(accountList = updatedAccounts)
mainAccountTokensMigration.migrate(userWalletId, derivationIndex)
newAccount
}
private suspend fun Raise<Error>.fetchAccountList(userWalletId: UserWalletId) {
singleAccountListFetcher(params = SingleAccountListFetcher.Params(userWalletId)).onLeft {
raise(Error.DataOperationFailed(cause = it))
}
}
private fun createAccount(
userWalletId: UserWalletId,
accountName: AccountName,
@ -70,26 +81,14 @@ class AddCryptoPortfolioUseCase(
)
}
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): Option<AccountList> {
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
return catch(
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
catch = { raise(Error.DataOperationFailed(cause = it)) },
)
}
private fun Raise<Error>.createNewAccountList(userWalletId: UserWalletId): AccountList {
val userWallet = catch(
block = { crudRepository.getUserWallet(userWalletId = userWalletId) },
catch = { raise(Error.DataOperationFailed(cause = it)) },
)
// TODO: [REDACTED_JIRA]
return AccountList.empty(
userWallet = userWallet,
cryptoCurrencies = emptySet(),
sortType = TokensSortType.NONE,
groupType = TokensGroupType.NONE,
)
.getOrElse {
raise(Error.DataOperationFailed(message = "Account list not found for wallet $userWalletId"))
}
}
private suspend fun Raise<Error>.saveAccounts(accountList: AccountList) {
@ -115,7 +114,8 @@ class AddCryptoPortfolioUseCase(
/** Error indicating that a data operation failed */
data class DataOperationFailed(val cause: Throwable) : Error {
override fun toString(): String = "Data operation failed: ${cause.message ?: "Unknown error"}"
constructor(message: String) : this(cause = IllegalStateException(message))
}
}
}

View file

@ -157,7 +157,7 @@ class AccountListTest {
createAccount(userWalletId = userWalletId, name = "Name", derivationIndex = 0),
createAccount(userWalletId = userWalletId, name = "Name", derivationIndex = 1),
),
expected = AccountList.Error.DuplicateAccountNames("Custom account names must be unique.").left(),
expected = AccountList.Error.DuplicateAccountNames.left(),
),
)
}

View file

@ -5,8 +5,11 @@ import arrow.core.left
import arrow.core.right
import arrow.core.toOption
import com.google.common.truth.Truth
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.tokens.MainAccountTokensMigration
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase.Error
import com.tangem.domain.account.utils.createAccount
import com.tangem.domain.account.utils.createAccounts
import com.tangem.domain.models.account.Account
@ -23,13 +26,20 @@ import org.junit.jupiter.api.TestInstance
class AddCryptoPortfolioUseCaseTest {
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
private val useCase = AddCryptoPortfolioUseCase(crudRepository)
private val singleAccountListFetcher = mockk<SingleAccountListFetcher>()
private val mainAccountTokensMigration = mockk<MainAccountTokensMigration>()
private val useCase = AddCryptoPortfolioUseCase(
crudRepository = crudRepository,
singleAccountListFetcher = singleAccountListFetcher,
mainAccountTokensMigration = mainAccountTokensMigration,
)
private val userWallet = mockk<UserWallet>()
@BeforeEach
fun resetMocks() {
clearMocks(crudRepository, userWallet)
clearMocks(crudRepository, singleAccountListFetcher, mainAccountTokensMigration, userWallet)
every { userWallet.walletId } returns userWalletId
}
@ -41,7 +51,13 @@ class AddCryptoPortfolioUseCaseTest {
val accountList = AccountList.empty(userWallet)
val updatedAccountList = (accountList + newAccount).getOrNull()!!
coEvery {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
} returns Unit.right()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
coEvery {
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
} returns Unit.right()
// Act
val actual = useCase(
@ -55,22 +71,23 @@ class AddCryptoPortfolioUseCaseTest {
val expected = newAccount.right()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
coVerifySequence {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
crudRepository.getAccountListSync(userWalletId)
crudRepository.saveAccounts(updatedAccountList)
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
}
coVerify(inverse = true) { crudRepository.getUserWallet(userWalletId) }
}
@Test
fun `invoke should create new account list if none exists`() = runTest {
fun `invoke should return error if fetch is failed`() = runTest {
// Arrange
val newAccount = createNewAccount()
val newAccountList = (AccountList.empty(userWallet) + newAccount).getOrNull()!!
val exception = Exception("Fetch error")
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
coEvery { crudRepository.getUserWallet(userWalletId) } returns userWallet
coEvery {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
} returns exception.left()
// Act
val actual = useCase(
@ -81,13 +98,54 @@ class AddCryptoPortfolioUseCaseTest {
)
// Assert
val expected = newAccount.right()
Truth.assertThat(actual).isEqualTo(expected)
val expected = exception
Truth.assertThat((actual.leftOrNull() as Error.DataOperationFailed).cause).isInstanceOf(expected::class.java)
Truth.assertThat((actual.leftOrNull() as Error.DataOperationFailed).cause).hasMessageThat()
.isEqualTo(expected.message)
coVerifyOrder {
coVerifySequence {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
}
coVerify(inverse = true) {
crudRepository.getAccountListSync(any())
crudRepository.saveAccounts(any())
mainAccountTokensMigration.migrate(any(), any())
}
}
@Test
fun `invoke should return error if account list none exists`() = runTest {
// Arrange
val newAccount = createNewAccount()
coEvery {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
} returns Unit.right()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
// Act
val actual = useCase(
userWalletId = userWalletId,
accountName = newAccount.accountName,
icon = newAccount.icon,
derivationIndex = newAccount.derivationIndex,
)
// Assert
val expected = IllegalStateException("Accounts for $userWalletId are not created")
Truth.assertThat((actual.leftOrNull() as Error.DataOperationFailed).cause).isInstanceOf(expected::class.java)
Truth.assertThat((actual.leftOrNull() as Error.DataOperationFailed).cause).hasMessageThat()
.isEqualTo(expected.message)
coVerifySequence {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
crudRepository.getAccountListSync(userWalletId)
crudRepository.getUserWallet(userWalletId)
crudRepository.saveAccounts(newAccountList)
}
coVerify(inverse = true) {
crudRepository.saveAccounts(any())
mainAccountTokensMigration.migrate(any(), any())
}
}
@ -102,6 +160,9 @@ class AddCryptoPortfolioUseCaseTest {
val newAccount = createNewAccount(derivationIndex = 21)
coEvery {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
} returns Unit.right()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
// Act
@ -113,17 +174,20 @@ class AddCryptoPortfolioUseCaseTest {
)
// Assert
val expected = AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet(
val expected = Error.AccountListRequirementsNotMet(
cause = AccountList.Error.ExceedsMaxAccountsCount,
).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
coVerifySequence {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
crudRepository.getAccountListSync(userWalletId)
}
coVerify(inverse = true) {
crudRepository.getUserWallet(any())
crudRepository.saveAccounts(any())
mainAccountTokensMigration.migrate(any(), any())
}
}
@ -133,6 +197,9 @@ class AddCryptoPortfolioUseCaseTest {
val newAccount = createNewAccount()
val exception = IllegalStateException("Test error")
coEvery {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
} returns Unit.right()
coEvery { crudRepository.getAccountListSync(userWalletId) } throws exception
// Act
@ -144,14 +211,17 @@ class AddCryptoPortfolioUseCaseTest {
)
// Assert
val expected = AddCryptoPortfolioUseCase.Error.DataOperationFailed(cause = exception).left()
val expected = Error.DataOperationFailed(cause = exception).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
coVerifySequence {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
crudRepository.getAccountListSync(userWalletId)
}
coVerify(inverse = true) {
crudRepository.getUserWallet(any())
crudRepository.saveAccounts(any())
mainAccountTokensMigration.migrate(any(), any())
}
}
@ -164,6 +234,9 @@ class AddCryptoPortfolioUseCaseTest {
val exception = IllegalStateException("Test error")
coEvery {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
} returns Unit.right()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
@ -176,15 +249,54 @@ class AddCryptoPortfolioUseCaseTest {
)
// Assert
val expected = AddCryptoPortfolioUseCase.Error.DataOperationFailed(cause = exception).left()
val expected = Error.DataOperationFailed(cause = exception).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
coVerifySequence {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
crudRepository.getAccountListSync(userWalletId)
crudRepository.saveAccounts(updatedAccountList)
}
coVerify(inverse = true) { crudRepository.getUserWallet(userWalletId) }
coVerify(inverse = true) {
mainAccountTokensMigration.migrate(any(), any())
}
}
@Test
fun `invoke should return new account if migrate returns error`() = runTest {
// Arrange
val newAccount = createNewAccount()
val accountList = AccountList.empty(userWallet)
val updatedAccountList = (accountList + newAccount).getOrNull()!!
val exception = Exception("Migration error")
coEvery {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
} returns Unit.right()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
coEvery {
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
} returns exception.left()
// Act
val actual = useCase(
userWalletId = userWalletId,
accountName = newAccount.accountName,
icon = newAccount.icon,
derivationIndex = newAccount.derivationIndex,
)
// Assert
val expected = newAccount.right()
Truth.assertThat(actual).isEqualTo(expected)
coVerifySequence {
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
crudRepository.getAccountListSync(userWalletId)
crudRepository.saveAccounts(updatedAccountList)
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
}
}
private companion object {

View file

@ -1,8 +1,8 @@
package com.tangem.features.account.createedit
import androidx.annotation.StringRes
import com.tangem.common.ui.account.AccountNameUM
import com.tangem.common.ui.account.toDomain
import androidx.annotation.StringRes
import com.tangem.common.ui.account.toUM
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
@ -18,6 +18,7 @@ import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.ToastMessage
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase
import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase
import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase
@ -110,14 +111,30 @@ internal class AccountCreateEditModel @Inject constructor(
derivationIndex = derivationIndex,
)
uiState.value = uiState.value.toggleProgress(showProgress = false)
result
.onLeft { showMessage(it.toString()) }
.onLeft(::handleAddAccountError)
.onRight {
showMessage(R.string.account_create_success_message)
router.pop()
}
}
private fun handleAddAccountError(error: AddCryptoPortfolioUseCase.Error) {
if (error is AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet &&
error.cause is AccountList.Error.DuplicateAccountNames
) {
// TODO("account") show alert that the account name already exists
return
}
// TODO: show alert https://www.figma.com/design/09KKG4ZVuFDZhj8WLv5rGJ/%F0%9F%9A%A7-App-experience?node-id=38882-113775&t=vk6TCy4MkYol1cPb-4
logError(
error = AccountFeatureError.CreateAccount.FailedToCreateAccount(cause = error),
)
}
private suspend fun editCryptoPortfolio(params: AccountCreateEditComponent.Params.Edit) {
val state = uiState.value
val name = state.account.name.toDomain().getOrNull() ?: return
@ -203,33 +220,30 @@ internal class AccountCreateEditModel @Inject constructor(
}
}
.onLeft { cause ->
handleError(
error = AccountFeatureError.CreateAccount.UnableToGetDerivationIndex,
message = cause.toString(),
val error = AccountFeatureError.CreateAccount.UnableToGetDerivationIndex(cause)
logError(
error = error,
params = mapOf(
"userWalletId" to userWalletId.stringValue,
"cause" to cause.toString(),
),
)
messageSender.showErrorDialog(universalError = error, onDismiss = router::pop)
return@launch
}
}
}
private fun handleError(
error: AccountFeatureError,
message: String? = null,
params: Map<String, String> = mapOf(),
) {
val exception = IllegalStateException("$error. Cause: $message")
private fun logError(error: AccountFeatureError, params: Map<String, String> = mapOf()) {
val exception = IllegalStateException(error.toString())
Timber.e(exception)
analyticsExceptionHandler.sendException(
event = ExceptionAnalyticsEvent(exception = exception, params = params),
)
messageSender.showErrorDialog(universalError = error, onDismiss = router::pop)
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.features.account.createedit.error
import com.tangem.core.error.UniversalError
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase
import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase
sealed interface AccountFeatureError : UniversalError {
@ -14,9 +16,13 @@ sealed interface AccountFeatureError : UniversalError {
override val subsystemCode: String get() = "001"
data object UnableToGetDerivationIndex : CreateAccount {
data class UnableToGetDerivationIndex(val cause: GetUnoccupiedAccountIndexUseCase.Error) : CreateAccount {
override val specificErrorCode: String = "001"
}
data class FailedToCreateAccount(val cause: AddCryptoPortfolioUseCase.Error) : CreateAccount {
override val specificErrorCode: String = "002"
}
}
sealed interface EditAccount : AccountFeatureError {