Updated on 2026-08-14
This commit is contained in:
parent
4215c91a37
commit
df186a5f5d
9 changed files with 74 additions and 9 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.common.ui.account
|
package com.tangem.common.ui.account
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.raise.either
|
import arrow.core.raise.either
|
||||||
|
|
@ -25,7 +26,10 @@ sealed interface AccountNameUM {
|
||||||
*/
|
*/
|
||||||
data object DefaultMain : 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,14 @@ tasks.withType<Test>().configureEach {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
// region Project - Common
|
||||||
|
implementation(projects.common.ui) // It's needed for getting AccountName.DefaultMain value
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region Project - Core
|
// region Project - Core
|
||||||
implementation(projects.core.datasource)
|
implementation(projects.core.datasource)
|
||||||
implementation(projects.core.configToggles)
|
implementation(projects.core.configToggles)
|
||||||
|
implementation(projects.core.res)
|
||||||
api(projects.core.utils)
|
api(projects.core.utils)
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.data.account.di
|
package com.tangem.data.account.di
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
import com.tangem.data.account.converter.AccountConverterFactoryContainer
|
import com.tangem.data.account.converter.AccountConverterFactoryContainer
|
||||||
import com.tangem.data.account.featuretoggle.DefaultAccountsFeatureToggles
|
import com.tangem.data.account.featuretoggle.DefaultAccountsFeatureToggles
|
||||||
|
|
@ -21,6 +22,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
@ -43,6 +45,7 @@ internal object AccountDataModule {
|
||||||
userWalletsStore: UserWalletsStore,
|
userWalletsStore: UserWalletsStore,
|
||||||
userTokensSaver: UserTokensSaver,
|
userTokensSaver: UserTokensSaver,
|
||||||
accountConverterFactoryContainer: AccountConverterFactoryContainer,
|
accountConverterFactoryContainer: AccountConverterFactoryContainer,
|
||||||
|
@ApplicationContext context: Context,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): AccountsCRUDRepository {
|
): AccountsCRUDRepository {
|
||||||
return DefaultAccountsCRUDRepository(
|
return DefaultAccountsCRUDRepository(
|
||||||
|
|
@ -54,6 +57,7 @@ internal object AccountDataModule {
|
||||||
userTokensSaver = userTokensSaver,
|
userTokensSaver = userTokensSaver,
|
||||||
archivedAccountsETagStore = RuntimeStateStore(emptyMap()),
|
archivedAccountsETagStore = RuntimeStateStore(emptyMap()),
|
||||||
convertersContainer = accountConverterFactoryContainer,
|
convertersContainer = accountConverterFactoryContainer,
|
||||||
|
resources = context.resources,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package com.tangem.data.account.repository
|
package com.tangem.data.account.repository
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
import arrow.core.raise.option
|
import arrow.core.raise.option
|
||||||
import arrow.core.toOption
|
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.AccountConverterFactoryContainer
|
||||||
import com.tangem.data.account.converter.ArchivedAccountConverter
|
import com.tangem.data.account.converter.ArchivedAccountConverter
|
||||||
import com.tangem.data.account.store.AccountsResponseStore
|
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.account.repository.AccountsCRUDRepository
|
||||||
import com.tangem.domain.models.account.Account
|
import com.tangem.domain.models.account.Account
|
||||||
import com.tangem.domain.models.account.AccountId
|
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.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -48,6 +52,7 @@ internal class DefaultAccountsCRUDRepository(
|
||||||
private val userTokensSaver: UserTokensSaver,
|
private val userTokensSaver: UserTokensSaver,
|
||||||
private val archivedAccountsETagStore: RuntimeStateStore<Map<String, String?>>,
|
private val archivedAccountsETagStore: RuntimeStateStore<Map<String, String?>>,
|
||||||
private val convertersContainer: AccountConverterFactoryContainer,
|
private val convertersContainer: AccountConverterFactoryContainer,
|
||||||
|
private val resources: Resources,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : AccountsCRUDRepository {
|
) : AccountsCRUDRepository {
|
||||||
|
|
||||||
|
|
@ -197,6 +202,19 @@ internal class DefaultAccountsCRUDRepository(
|
||||||
|
|
||||||
override fun getUserWalletsSync(): List<UserWallet> = userWalletsStore.userWalletsSync
|
override fun getUserWalletsSync(): List<UserWallet> = 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<*>) {
|
private suspend fun saveETag(userWalletId: UserWalletId, apiResponse: ApiResponse<*>) {
|
||||||
val eTag = apiResponse.headers[ETAG_HEADER]?.firstOrNull()
|
val eTag = apiResponse.headers[ETAG_HEADER]?.firstOrNull()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.data.account.repository
|
package com.tangem.data.account.repository
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
import arrow.core.None
|
import arrow.core.None
|
||||||
import arrow.core.toOption
|
import arrow.core.toOption
|
||||||
import com.google.common.truth.Truth
|
import com.google.common.truth.Truth
|
||||||
|
|
@ -59,6 +60,8 @@ class DefaultAccountsCRUDRepositoryTest {
|
||||||
private val accountListConverter: AccountListConverter = mockk()
|
private val accountListConverter: AccountListConverter = mockk()
|
||||||
private val cryptoPortfolioConverter: CryptoPortfolioConverter = mockk()
|
private val cryptoPortfolioConverter: CryptoPortfolioConverter = mockk()
|
||||||
|
|
||||||
|
private val resources: Resources = mockk()
|
||||||
|
|
||||||
private val repository = DefaultAccountsCRUDRepository(
|
private val repository = DefaultAccountsCRUDRepository(
|
||||||
tangemTechApi = tangemTechApi,
|
tangemTechApi = tangemTechApi,
|
||||||
walletAccountsSaver = walletAccountsSaver,
|
walletAccountsSaver = walletAccountsSaver,
|
||||||
|
|
@ -68,6 +71,7 @@ class DefaultAccountsCRUDRepositoryTest {
|
||||||
userTokensSaver = userTokensSaver,
|
userTokensSaver = userTokensSaver,
|
||||||
archivedAccountsETagStore = archivedAccountsETagStore,
|
archivedAccountsETagStore = archivedAccountsETagStore,
|
||||||
convertersContainer = convertersContainer,
|
convertersContainer = convertersContainer,
|
||||||
|
resources = resources,
|
||||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.tangem.domain.account.models.AccountList
|
||||||
import com.tangem.domain.account.models.ArchivedAccount
|
import com.tangem.domain.account.models.ArchivedAccount
|
||||||
import com.tangem.domain.models.account.Account
|
import com.tangem.domain.models.account.Account
|
||||||
import com.tangem.domain.models.account.AccountId
|
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.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
@ -113,4 +114,12 @@ interface AccountsCRUDRepository {
|
||||||
|
|
||||||
/** Synchronously retrieves all user wallets */
|
/** Synchronously retrieves all user wallets */
|
||||||
fun getUserWalletsSync(): List<UserWallet>
|
fun getUserWalletsSync(): List<UserWallet>
|
||||||
|
|
||||||
|
/** 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)
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package com.tangem.domain.account.usecase
|
package com.tangem.domain.account.usecase
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
|
import arrow.core.Either.Companion.catch
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import arrow.core.raise.Raise
|
import arrow.core.raise.Raise
|
||||||
import arrow.core.raise.catch
|
import arrow.core.raise.catch
|
||||||
import arrow.core.raise.either
|
import arrow.core.raise.either
|
||||||
|
import arrow.core.raise.withError
|
||||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||||
import com.tangem.domain.account.models.AccountList
|
import com.tangem.domain.account.models.AccountList
|
||||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||||
|
|
@ -49,8 +51,10 @@ class AddCryptoPortfolioUseCase(
|
||||||
|
|
||||||
val newAccount = createAccount(userWalletId, accountName, icon, derivationIndex)
|
val newAccount = createAccount(userWalletId, accountName, icon, derivationIndex)
|
||||||
|
|
||||||
val updatedAccounts = (accountList + newAccount).getOrElse {
|
val updatedAccounts = withError({ Error.AccountListRequirementsNotMet(it) }) {
|
||||||
raise(Error.AccountListRequirementsNotMet(it))
|
checkDefaultName(accountList, accountName)
|
||||||
|
|
||||||
|
(accountList + newAccount).bind()
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAccounts(accountList = updatedAccounts)
|
saveAccounts(accountList = updatedAccounts)
|
||||||
|
|
@ -81,6 +85,12 @@ class AddCryptoPortfolioUseCase(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Raise<AccountList.Error>.checkDefaultName(accountList: AccountList, accountName: AccountName) {
|
||||||
|
withError({ AccountList.Error.DuplicateAccountNames }) {
|
||||||
|
catch { crudRepository.checkDefaultAccountName(accountList, accountName) }.bind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
|
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
|
||||||
return catch(
|
return catch(
|
||||||
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
|
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@ package com.tangem.domain.account.usecase
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import arrow.core.raise.Raise
|
import arrow.core.raise.*
|
||||||
import arrow.core.raise.catch
|
|
||||||
import arrow.core.raise.either
|
|
||||||
import arrow.core.raise.ensure
|
|
||||||
import com.tangem.domain.account.models.AccountList
|
import com.tangem.domain.account.models.AccountList
|
||||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||||
import com.tangem.domain.models.account.Account
|
import com.tangem.domain.models.account.Account
|
||||||
|
|
@ -50,8 +47,12 @@ class UpdateCryptoPortfolioUseCase(
|
||||||
.setName(name = accountName)
|
.setName(name = accountName)
|
||||||
.setIcon(icon = icon)
|
.setIcon(icon = icon)
|
||||||
|
|
||||||
val updatedAccounts = (accountList + updatedAccount).getOrElse {
|
val updatedAccounts = withError({ Error.AccountListRequirementsNotMet(it) }) {
|
||||||
raise(Error.AccountListRequirementsNotMet(it))
|
if (accountName != null) {
|
||||||
|
checkDefaultName(accountList, accountName)
|
||||||
|
}
|
||||||
|
|
||||||
|
(accountList + updatedAccount).bind()
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAccounts(updatedAccounts)
|
saveAccounts(updatedAccounts)
|
||||||
|
|
@ -88,6 +89,12 @@ class UpdateCryptoPortfolioUseCase(
|
||||||
return if (icon != null) this.copy(icon = icon) else this
|
return if (icon != null) this.copy(icon = icon) else this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Raise<AccountList.Error>.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
|
* Represents possible errors that can occur during the update operation
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ class AddCryptoPortfolioUseCaseTest {
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||||
crudRepository.getAccountListSync(userWalletId)
|
crudRepository.getAccountListSync(userWalletId)
|
||||||
|
crudRepository.checkDefaultAccountName(accountList, newAccount.accountName)
|
||||||
crudRepository.saveAccounts(updatedAccountList)
|
crudRepository.saveAccounts(updatedAccountList)
|
||||||
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
|
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
|
||||||
}
|
}
|
||||||
|
|
@ -177,6 +178,7 @@ class AddCryptoPortfolioUseCaseTest {
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||||
crudRepository.getAccountListSync(userWalletId)
|
crudRepository.getAccountListSync(userWalletId)
|
||||||
|
crudRepository.checkDefaultAccountName(accountList, newAccount.accountName)
|
||||||
}
|
}
|
||||||
|
|
||||||
coVerify(inverse = true) {
|
coVerify(inverse = true) {
|
||||||
|
|
@ -249,6 +251,7 @@ class AddCryptoPortfolioUseCaseTest {
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||||
crudRepository.getAccountListSync(userWalletId)
|
crudRepository.getAccountListSync(userWalletId)
|
||||||
|
crudRepository.checkDefaultAccountName(accountList, newAccount.accountName)
|
||||||
crudRepository.saveAccounts(updatedAccountList)
|
crudRepository.saveAccounts(updatedAccountList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,6 +291,7 @@ class AddCryptoPortfolioUseCaseTest {
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||||
crudRepository.getAccountListSync(userWalletId)
|
crudRepository.getAccountListSync(userWalletId)
|
||||||
|
crudRepository.checkDefaultAccountName(accountList, newAccount.accountName)
|
||||||
crudRepository.saveAccounts(updatedAccountList)
|
crudRepository.saveAccounts(updatedAccountList)
|
||||||
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
|
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue