From 28b4c29befabd09f4e0a81545582942d9136606f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 10 Sep 2025 16:15:18 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/common/ui/account/AccountNameUM.kt | 56 ++++++++++++++++ .../models/account/WalletAccountDTO.kt | 2 +- .../account/converter/AccountConvertersExt.kt | 7 -- .../account/converter/AccountNameConverter.kt | 31 +++++++++ .../converter/ArchivedAccountConverter.kt | 2 +- .../converter/CryptoPortfolioConverter.kt | 4 +- .../SaveWalletAccountsResponseConverter.kt | 2 +- .../account/converter/AccountConverterExt.kt | 3 +- .../converter/AccountNameConverterTest.kt | 65 +++++++++++++++++++ .../converter/ArchivedAccountConverterTest.kt | 10 ++- .../converter/CryptoPortfolioConverterTest.kt | 8 +++ ...SaveWalletAccountsResponseConverterTest.kt | 3 +- .../domain/account/models/AccountList.kt | 19 ++++-- .../domain/account/models/AccountListTest.kt | 2 +- .../tangem/domain/models/account/Account.kt | 2 +- .../domain/models/account/AccountName.kt | 56 ++++++++++------ .../domain/models/account/AccountNameTest.kt | 11 ---- .../domain/models/account/AccountTest.kt | 2 +- .../archived/ArchivedAccountListModel.kt | 3 +- .../createedit/AccountCreateEditModel.kt | 15 +++-- .../createedit/entity/AccountCreateEditUM.kt | 5 +- .../entity/AccountCreateEditUMBuilder.kt | 9 +-- .../createedit/ui/AccountCreateEditContent.kt | 33 ++++++---- .../account/details/AccountDetailsModel.kt | 2 +- .../details/entity/AccountDetailsUM.kt | 3 +- .../details/ui/AccountDetailsContent.kt | 4 +- .../utils/AccountItemsDelegate.kt | 3 +- 27 files changed, 278 insertions(+), 84 deletions(-) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/account/AccountNameUM.kt create mode 100644 data/account/src/main/kotlin/com/tangem/data/account/converter/AccountNameConverter.kt create mode 100644 data/account/src/test/java/com/tangem/data/account/converter/AccountNameConverterTest.kt 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 new file mode 100644 index 0000000000..e86dda636b --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountNameUM.kt @@ -0,0 +1,56 @@ +package com.tangem.common.ui.account + +import androidx.compose.runtime.Immutable +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.models.account.AccountName + +/** + * Represents a user model (UM) for an [AccountName] in the UI layer. + * This sealed interface provides a way to handle different types of account names. + */ +@Immutable +sealed interface AccountNameUM { + + /** The textual representation of the account name */ + val value: TextReference + + /** + * Represents the default main account name. + * If the user renames the main account, it will be converted to a [Custom] account name. + */ + data object DefaultMain : AccountNameUM { + + override val value: TextReference = resourceReference(R.string.account_main_account_title) + } + + /** + * Represents a custom account name provided by the user + * + * @property raw the raw string value of the custom account name + */ + class Custom(internal val raw: String) : AccountNameUM { + + override val value: TextReference = stringReference(value = raw) + } +} + +/** Extension function to convert a domain model [AccountName] to its corresponding UI model [AccountNameUM] */ +fun AccountName.toUM(): AccountNameUM { + return when (this) { + is AccountName.Custom -> AccountNameUM.Custom(raw = value) + AccountName.DefaultMain -> AccountNameUM.DefaultMain + } +} + +/** Extension function to convert a UI model [AccountNameUM] to its corresponding domain model [AccountName] */ +fun AccountNameUM.toDomain(): Either = either { + when (this@toDomain) { + is AccountNameUM.Custom -> AccountName(value = raw).bind() + AccountNameUM.DefaultMain -> AccountName.DefaultMain + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/account/WalletAccountDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/account/WalletAccountDTO.kt index 343b6cdf2a..2e9e49b4af 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/account/WalletAccountDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/account/WalletAccountDTO.kt @@ -7,7 +7,7 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse @JsonClass(generateAdapter = true) data class WalletAccountDTO( @Json(name = "id") val id: String, - @Json(name = "name") val name: String, + @Json(name = "name") val name: String?, @Json(name = "derivation") val derivationIndex: Int, @Json(name = "icon") val icon: String, @Json(name = "iconColor") val iconColor: String, diff --git a/data/account/src/main/kotlin/com/tangem/data/account/converter/AccountConvertersExt.kt b/data/account/src/main/kotlin/com/tangem/data/account/converter/AccountConvertersExt.kt index ab8eef91a8..3d35e7327c 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/converter/AccountConvertersExt.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/converter/AccountConvertersExt.kt @@ -3,7 +3,6 @@ package com.tangem.data.account.converter import arrow.core.getOrElse import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.domain.models.account.AccountId -import com.tangem.domain.models.account.AccountName import com.tangem.domain.models.account.CryptoPortfolioIcon import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.wallet.UserWalletId @@ -14,12 +13,6 @@ internal fun String.toAccountId(userWalletId: UserWalletId): AccountId { } } -internal fun String.toAccountName(): AccountName { - return AccountName(value = this).getOrElse { - error("Unable to create AccountName from value: $this. Cause: $it") - } -} - internal fun WalletAccountDTO.toIcon(): CryptoPortfolioIcon { return CryptoPortfolioIconConverter.convert( value = CryptoPortfolioIconConverter.DataModel(icon = icon, color = iconColor), diff --git a/data/account/src/main/kotlin/com/tangem/data/account/converter/AccountNameConverter.kt b/data/account/src/main/kotlin/com/tangem/data/account/converter/AccountNameConverter.kt new file mode 100644 index 0000000000..d4d20d7364 --- /dev/null +++ b/data/account/src/main/kotlin/com/tangem/data/account/converter/AccountNameConverter.kt @@ -0,0 +1,31 @@ +package com.tangem.data.account.converter + +import arrow.core.getOrElse +import com.tangem.domain.models.account.AccountName +import com.tangem.utils.converter.TwoWayConverter + +/** + * A converter for transforming [AccountName] domain models into their string representations. + * This is used to handle the conversion logic between the domain layer and other layers. + * +[REDACTED_AUTHOR] + */ +internal object AccountNameConverter : TwoWayConverter { + + override fun convert(value: AccountName): String? { + return when (value) { + is AccountName.Custom -> value.value + AccountName.DefaultMain -> null + } + } + + override fun convertBack(value: String?): AccountName { + return if (value == null) { + AccountName.DefaultMain + } else { + AccountName(value = value).getOrElse { + error("Unable to create AccountName from value: $value. Cause: $it") + } + } + } +} \ No newline at end of file diff --git a/data/account/src/main/kotlin/com/tangem/data/account/converter/ArchivedAccountConverter.kt b/data/account/src/main/kotlin/com/tangem/data/account/converter/ArchivedAccountConverter.kt index 072c82e3fc..764ede3152 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/converter/ArchivedAccountConverter.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/converter/ArchivedAccountConverter.kt @@ -19,7 +19,7 @@ internal class ArchivedAccountConverter( override fun convert(value: WalletAccountDTO): ArchivedAccount { return ArchivedAccount( accountId = value.id.toAccountId(userWalletId = userWalletId), - name = value.name.toAccountName(), + name = AccountNameConverter.convertBack(value = value.name), icon = value.toIcon(), derivationIndex = value.derivationIndex.toDerivationIndex(), tokensCount = value.totalTokens ?: error("Total tokens should not be null"), diff --git a/data/account/src/main/kotlin/com/tangem/data/account/converter/CryptoPortfolioConverter.kt b/data/account/src/main/kotlin/com/tangem/data/account/converter/CryptoPortfolioConverter.kt index bae26707a6..0117b2b653 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/converter/CryptoPortfolioConverter.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/converter/CryptoPortfolioConverter.kt @@ -29,7 +29,7 @@ internal class CryptoPortfolioConverter @AssistedInject constructor( return Account.CryptoPortfolio( accountId = value.id.toAccountId(userWallet.walletId), - accountName = value.name.toAccountName(), + accountName = AccountNameConverter.convertBack(value = value.name), icon = value.toIcon(), derivationIndex = value.derivationIndex.toDerivationIndex(), cryptoCurrencies = if (tokens.isNotEmpty()) { @@ -46,7 +46,7 @@ internal class CryptoPortfolioConverter @AssistedInject constructor( override fun convertBack(value: Account.CryptoPortfolio): WalletAccountDTO { return WalletAccountDTO( id = value.accountId.value, - name = value.accountName.value, + name = AccountNameConverter.convert(value = value.accountName), derivationIndex = value.derivationIndex.value, icon = value.icon.value.name, iconColor = value.icon.color.name, diff --git a/data/account/src/main/kotlin/com/tangem/data/account/converter/SaveWalletAccountsResponseConverter.kt b/data/account/src/main/kotlin/com/tangem/data/account/converter/SaveWalletAccountsResponseConverter.kt index 25c5307b84..07c76c7c24 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/converter/SaveWalletAccountsResponseConverter.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/converter/SaveWalletAccountsResponseConverter.kt @@ -24,7 +24,7 @@ internal object SaveWalletAccountsResponseConverter : Converter { + return listOf( + ConvertModel( + value = AccountName.Custom("MyAccount").getOrNull()!!, + expected = "MyAccount", + ), + ConvertModel(value = AccountName.DefaultMain, expected = null), + ) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class ConvertBack { + + @ParameterizedTest + @ProvideTestModels + fun convertBack(model: ConvertBackModel) { + // Act + val actual = AccountNameConverter.convertBack(value = model.value) + + // Assert + val expected = model.expected + Truth.assertThat(actual).isEqualTo(expected) + } + + private fun provideTestModels(): List { + return listOf( + ConvertBackModel(value = "MyAccount", expected = AccountName.Custom("MyAccount").getOrNull()!!), + ConvertBackModel(value = null, expected = AccountName.DefaultMain), + ) + } + } + + data class ConvertModel(val value: AccountName, val expected: String?) + + data class ConvertBackModel(val value: String?, val expected: AccountName) +} \ No newline at end of file diff --git a/data/account/src/test/java/com/tangem/data/account/converter/ArchivedAccountConverterTest.kt b/data/account/src/test/java/com/tangem/data/account/converter/ArchivedAccountConverterTest.kt index 90831c1d3b..bf08d5b7d9 100644 --- a/data/account/src/test/java/com/tangem/data/account/converter/ArchivedAccountConverterTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/converter/ArchivedAccountConverterTest.kt @@ -54,6 +54,12 @@ class ArchivedAccountConverterTest { ), ), ), + TestModel( + value = createDTO(name = null), + expected = Result.success( + createDomain().copy(name = AccountName.DefaultMain), + ), + ), TestModel( value = createDTO(name = ""), expected = Result.failure( @@ -104,7 +110,7 @@ class ArchivedAccountConverterTest { private fun createDTO( accountId: String = "957B88B12730E646E0F33D3618B77DFA579E8231E3C59C7104BE7165611C8027", - name: String = "Test Account", + name: String? = "Test Account", icon: String = "Letter", iconColor: String = "Azure", derivationIndex: Int = 0, @@ -126,7 +132,7 @@ class ArchivedAccountConverterTest { private fun createDomain(): ArchivedAccount { return ArchivedAccount( accountId = AccountId.forCryptoPortfolio(userWalletId, DerivationIndex(0).getOrNull()!!), - name = "Test Account".toAccountName(), + name = AccountName("Test Account").getOrNull()!!, derivationIndex = 0.toDerivationIndex(), icon = CryptoPortfolioIcon.ofCustomAccount( value = CryptoPortfolioIcon.Icon.Letter, diff --git a/data/account/src/test/java/com/tangem/data/account/converter/CryptoPortfolioConverterTest.kt b/data/account/src/test/java/com/tangem/data/account/converter/CryptoPortfolioConverterTest.kt index f046bae96a..7e3e41a243 100644 --- a/data/account/src/test/java/com/tangem/data/account/converter/CryptoPortfolioConverterTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/converter/CryptoPortfolioConverterTest.kt @@ -75,6 +75,14 @@ class CryptoPortfolioConverterTest { ), ), ), + ConvertModel( + value = createWalletAccountDTO(userWalletId = userWallet.walletId, accountName = null), + expected = Result.success( + createCryptoPortfolio(userWalletId = userWallet.walletId).copy( + accountName = AccountName.DefaultMain, + ), + ), + ), ConvertModel( value = createWalletAccountDTO(userWalletId = userWallet.walletId, accountName = ""), expected = Result.failure( diff --git a/data/account/src/test/java/com/tangem/data/account/converter/SaveWalletAccountsResponseConverterTest.kt b/data/account/src/test/java/com/tangem/data/account/converter/SaveWalletAccountsResponseConverterTest.kt index 13ad114fe4..6d5ebee814 100644 --- a/data/account/src/test/java/com/tangem/data/account/converter/SaveWalletAccountsResponseConverterTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/converter/SaveWalletAccountsResponseConverterTest.kt @@ -5,6 +5,7 @@ import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsRes import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.domain.account.models.AccountList import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountName import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import io.mockk.every @@ -37,7 +38,7 @@ class SaveWalletAccountsResponseConverterTest { accounts = listOf( WalletAccountDTO( id = accountList.mainAccount.accountId.value, - name = accountList.mainAccount.accountName.value, + name = (accountList.mainAccount.accountName as? AccountName.Custom)?.value, derivationIndex = accountList.mainAccount.derivationIndex.value, icon = accountList.mainAccount.icon.value.name, iconColor = accountList.mainAccount.icon.color.name, diff --git a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt index 89f7fd33c8..0a5b08f87b 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt @@ -6,6 +6,7 @@ import arrow.core.raise.ensure import com.tangem.domain.models.TokensGroupType import com.tangem.domain.models.TokensSortType import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountName import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.utils.extensions.addOrReplace @@ -119,8 +120,8 @@ data class AccountList private constructor( } @Serializable - data object DuplicateAccountNames : Error { - override fun toString(): String = "$tag: Account list contains duplicate account names" + data class DuplicateAccountNames(val message: String) : Error { + override fun toString(): String = "$tag: Account list contains duplicate account names. $message" } } @@ -160,8 +161,18 @@ data class AccountList private constructor( val uniqueAccountIdsCount = accounts.map { it.accountId.value }.distinct().size ensure(accounts.size == uniqueAccountIdsCount) { Error.DuplicateAccountIds } - val uniqueAccountNameCount = accounts.map { it.accountName.value }.distinct().size - ensure(accounts.size == uniqueAccountNameCount) { Error.DuplicateAccountNames } + 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.") + } AccountList( userWallet = userWallet, diff --git a/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt b/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt index 0c43198fa0..4ed41500f0 100644 --- a/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt +++ b/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt @@ -157,7 +157,7 @@ class AccountListTest { createAccount(userWalletId = userWalletId, name = "Name", derivationIndex = 0), createAccount(userWalletId = userWalletId, name = "Name", derivationIndex = 1), ), - expected = AccountList.Error.DuplicateAccountNames.left(), + expected = AccountList.Error.DuplicateAccountNames("Custom account names must be unique.").left(), ), ) } diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/account/Account.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/account/Account.kt index b376be0db1..d3962fbd50 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/account/Account.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/account/Account.kt @@ -162,7 +162,7 @@ sealed interface Account { userWalletId = userWalletId, derivationIndex = derivationIndex, ), - accountName = AccountName.Main, + accountName = AccountName.DefaultMain, icon = CryptoPortfolioIcon.ofMainAccount(userWalletId), derivationIndex = derivationIndex, cryptoCurrencies = cryptoCurrencies, diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/account/AccountName.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/account/AccountName.kt index 29fe653a8b..ead33f51a3 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/account/AccountName.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/account/AccountName.kt @@ -8,14 +8,44 @@ import kotlinx.serialization.Serializable /** * Represents an account name * - * @property value the validated account name as a string - * [REDACTED_AUTHOR] */ @Serializable -data class AccountName private constructor( - val value: String, -) { +sealed interface AccountName { + + /** + * Represents the default main account name. + * If the user renames the main account, it will be converted to a [Custom] account name. + */ + @Serializable + data object DefaultMain : AccountName + + /** + * Represents a custom account name provided by the user + * + * @property value the string value of the custom account name + */ + @Serializable + data class Custom private constructor(val value: String) : AccountName { + + companion object { + + /** + * Factory method to create an [AccountName.Custom] instance. + * Validates the input string to ensure it is not blank and does not exceed the maximum length. + * + * @param value the input string representing the account name + */ + operator fun invoke(value: String): Either = either { + val trimmedValue = value.trim() + + ensure(trimmedValue.isNotBlank()) { Error.Empty } + ensure(trimmedValue.length <= MAX_LENGTH) { Error.ExceedsMaxLength } + + Custom(value = trimmedValue) + } + } + } /** * Represents possible validation errors @@ -44,26 +74,14 @@ data class AccountName private constructor( companion object { - private const val MAIN_ACCOUNT_NAME = "Main Account" private const val MAX_LENGTH = 20 - /** Default name for the main account */ - val Main: AccountName - get() = AccountName(value = MAIN_ACCOUNT_NAME) - /** - * Factory method to create an `AccountName` instance. + * Factory method to create an [AccountName] instance. * Validates the input string to ensure it is not blank and does not exceed the maximum length. * * @param value the input string representing the account name */ - operator fun invoke(value: String): Either = either { - val trimmedValue = value.trim() - - ensure(trimmedValue.isNotBlank()) { Error.Empty } - ensure(trimmedValue.length <= MAX_LENGTH) { Error.ExceedsMaxLength } - - AccountName(value = trimmedValue) - } + operator fun invoke(value: String): Either = Custom(value) } } \ No newline at end of file diff --git a/domain/models/src/test/kotlin/com/tangem/domain/models/account/AccountNameTest.kt b/domain/models/src/test/kotlin/com/tangem/domain/models/account/AccountNameTest.kt index e2aee138be..d68bc46ab9 100644 --- a/domain/models/src/test/kotlin/com/tangem/domain/models/account/AccountNameTest.kt +++ b/domain/models/src/test/kotlin/com/tangem/domain/models/account/AccountNameTest.kt @@ -3,7 +3,6 @@ package com.tangem.domain.models.account import arrow.core.Either import arrow.core.left import com.google.common.truth.Truth -import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource @@ -14,16 +13,6 @@ import org.junit.jupiter.params.provider.MethodSource @TestInstance(TestInstance.Lifecycle.PER_CLASS) class AccountNameTest { - @Test - fun main_returnsMainAccountName() { - // Act - val main = AccountName.Main.value - - // Assert - val expected = "Main Account" - Truth.assertThat(main).isEqualTo(expected) - } - @ParameterizedTest @MethodSource("provideTestModels") fun invoke(model: InvokeTestModel) { diff --git a/domain/models/src/test/kotlin/com/tangem/domain/models/account/AccountTest.kt b/domain/models/src/test/kotlin/com/tangem/domain/models/account/AccountTest.kt index 91baaf2a76..9578f81fb1 100644 --- a/domain/models/src/test/kotlin/com/tangem/domain/models/account/AccountTest.kt +++ b/domain/models/src/test/kotlin/com/tangem/domain/models/account/AccountTest.kt @@ -147,7 +147,7 @@ class AccountTest { userWalletId = userWalletId, derivationIndex = derivationIndex, ), - accountName = AccountName.Main, + accountName = AccountName.DefaultMain, icon = CryptoPortfolioIcon.ofMainAccount(userWalletId), derivationIndex = derivationIndex, cryptoCurrencies = emptySet(), diff --git a/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt index 95554e161b..f170b6653b 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.account.archived +import com.tangem.common.ui.account.toUM import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router @@ -50,7 +51,7 @@ internal class ArchivedAccountListModel @Inject constructor( title = resourceReference(R.string.account_archived_recover_dialog_title), message = resourceReference( id = R.string.account_archived_recover_dialog_description, - formatArgs = wrappedList(account.accountName.value), + formatArgs = wrappedList(account.accountName.toUM().value), ), firstActionBuilder = { firstAction }, secondActionBuilder = { secondAction }, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt index 03934e051e..a7cdd84c16 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt @@ -1,6 +1,8 @@ package com.tangem.features.account.createedit +import com.tangem.common.ui.account.AccountNameUM import com.tangem.common.ui.account.toDomain +import com.tangem.common.ui.account.toUM import com.tangem.core.analytics.api.AnalyticsExceptionHandler import com.tangem.core.analytics.models.ExceptionAnalyticsEvent import com.tangem.core.decompose.di.ModelScoped @@ -16,7 +18,6 @@ import com.tangem.core.ui.utils.showErrorDialog import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase -import com.tangem.domain.models.account.AccountName import com.tangem.domain.models.account.CryptoPortfolioIcon import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.wallet.UserWalletId @@ -92,7 +93,7 @@ internal class AccountCreateEditModel @Inject constructor( private suspend fun createNewCryptoPortfolio(params: AccountCreateEditComponent.Params.Create) { val state = uiState.value - val name = AccountName(value = state.account.name).getOrNull() ?: return + val name = state.account.name.toDomain().getOrNull() ?: return val icon = state.account.portfolioIcon.toDomain() val index = state.account.derivationInfo.index ?: return val derivationIndex = DerivationIndex(value = index).getOrNull() ?: return @@ -107,7 +108,7 @@ internal class AccountCreateEditModel @Inject constructor( private suspend fun editCryptoPortfolio(params: AccountCreateEditComponent.Params.Edit) { val state = uiState.value - val name = AccountName(state.account.name).getOrNull() ?: return + val name = state.account.name.toDomain().getOrNull() ?: return val icon = state.account.portfolioIcon.toDomain() val isNewName = name != params.account.accountName val isNewIcon = icon != params.account.portfolioIcon @@ -132,18 +133,20 @@ internal class AccountCreateEditModel @Inject constructor( .validateNewState() } - private fun onNameChange(name: String) { + private fun onNameChange(name: AccountNameUM) { uiState.value = uiState.value .updateName(name) .validateNewState() } private fun AccountCreateEditUM.validateNewState(): AccountCreateEditUM { - val isValidName = AccountName(this.account.name).isRight() + val isValidName = this.account.name.toDomain().isRight() val isAvailableForConfirm = when (params) { is AccountCreateEditComponent.Params.Create -> isValidName is AccountCreateEditComponent.Params.Edit -> { - val isNewName = this.account.name != params.account.accountName.value + val oldName = params.account.accountName.toUM() + + val isNewName = this.account.name != oldName val isNewIcon = this.account.portfolioIcon != params.account.portfolioIcon isValidName && (isNewName || isNewIcon) } diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUM.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUM.kt index 4b4365ba95..b9d22c8aea 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUM.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUM.kt @@ -1,5 +1,6 @@ package com.tangem.features.account.createedit.entity +import com.tangem.common.ui.account.AccountNameUM import com.tangem.common.ui.account.CryptoPortfolioIconUM import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.models.account.CryptoPortfolioIcon @@ -15,11 +16,11 @@ internal data class AccountCreateEditUM( ) { data class Account( - val name: String, + val name: AccountNameUM, val portfolioIcon: CryptoPortfolioIconUM, val derivationInfo: DerivationInfo, val inputPlaceholder: TextReference, - val onNameChange: (String) -> Unit, + val onNameChange: (AccountNameUM) -> Unit, ) sealed interface DerivationInfo { diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt index 24b69681c9..87947361a4 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt @@ -1,5 +1,6 @@ package com.tangem.features.account.createedit.entity +import com.tangem.common.ui.account.AccountNameUM import com.tangem.common.ui.account.toUM import com.tangem.core.res.R import com.tangem.core.ui.extensions.TextReference @@ -24,17 +25,17 @@ internal class AccountCreateEditUMBuilder( is AccountCreateEditComponent.Params.Edit -> resourceReference(R.string.account_form_title_edit) } - fun initAccountUM(onNameChange: (String) -> Unit): AccountCreateEditUM.Account { + fun initAccountUM(onNameChange: (AccountNameUM) -> Unit): AccountCreateEditUM.Account { return when (params) { is AccountCreateEditComponent.Params.Create -> AccountCreateEditUM.Account( - name = "", + name = AccountNameUM.Custom(raw = ""), portfolioIcon = createIcon, derivationInfo = AccountCreateEditUM.DerivationInfo.Empty, inputPlaceholder = resourceReference(R.string.account_form_placeholder_new_account), onNameChange = onNameChange, ) is AccountCreateEditComponent.Params.Edit -> AccountCreateEditUM.Account( - name = params.account.accountName.value, + name = params.account.accountName.toUM(), portfolioIcon = params.account.portfolioIcon.toUM(), derivationInfo = createAccountDerivationInfo( index = (params.account as Account.CryptoPortfolio).derivationIndex.value, @@ -108,7 +109,7 @@ internal class AccountCreateEditUMBuilder( ) } - fun AccountCreateEditUM.updateName(name: String): AccountCreateEditUM { + fun AccountCreateEditUM.updateName(name: AccountNameUM): AccountCreateEditUM { return this.copy(account = this.account.copy(name = name)) } diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/ui/AccountCreateEditContent.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/ui/AccountCreateEditContent.kt index 639b44419e..2b7635a7df 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/ui/AccountCreateEditContent.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/ui/AccountCreateEditContent.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -24,11 +25,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import com.tangem.common.ui.R -import com.tangem.common.ui.account.AccountIcon -import com.tangem.common.ui.account.AccountIconPreviewData -import com.tangem.common.ui.account.AccountIconSize -import com.tangem.common.ui.account.getResId -import com.tangem.common.ui.account.getUiColor +import com.tangem.common.ui.account.* import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.SpacerH24 @@ -43,7 +40,6 @@ import com.tangem.features.account.createedit.entity.AccountCreateEditUM import com.tangem.features.account.createedit.entity.AccountCreateEditUM.Account import kotlinx.collections.immutable.toImmutableList -@Suppress("LongMethod", "MagicNumber") @Composable internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modifier = Modifier) { Column( @@ -65,7 +61,6 @@ internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modi modifier = Modifier .padding(horizontal = 16.dp) .weight(1f), - ) { AccountSummary(state.account) SpacerH24() @@ -103,7 +98,7 @@ private fun AccountSummary(account: Account) { Spacer(modifier = Modifier.height(24.dp)) AccountIcon( - name = stringReference(account.name), + name = account.name.value, icon = account.portfolioIcon, size = AccountIconSize.Large, ) @@ -116,13 +111,27 @@ private fun AccountSummary(account: Account) { ) Spacer(modifier = Modifier.height(2.dp)) + val wasDefault = remember { account.name is AccountNameUM.DefaultMain } + val defaultAccountName = AccountNameUM.DefaultMain.value.resolveReference() AutoSizeTextField( centered = true, textStyle = TangemTheme.typography.head, placeholder = account.inputPlaceholder, - value = account.name, + value = account.name.value.resolveReference(), singleLine = true, - onValueChange = account.onNameChange, + onValueChange = { + /* + * If the user had the default main account name and enters the same name during renaming, + * we should use the default value instead of custom to avoid breaking the name validation process. + */ + val newName = if (wasDefault && it == defaultAccountName) { + AccountNameUM.DefaultMain + } else { + AccountNameUM.Custom(raw = it) + } + + account.onNameChange(newName) + }, ) SpacerH(20.dp) } @@ -279,7 +288,7 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider Unit, val onAccountEditClick: () -> Unit, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/details/ui/AccountDetailsContent.kt b/features/account/impl/src/main/java/com/tangem/features/account/details/ui/AccountDetailsContent.kt index 7f3c86da48..510f776f53 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/details/ui/AccountDetailsContent.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/details/ui/AccountDetailsContent.kt @@ -143,7 +143,7 @@ private fun AccountRow(state: AccountDetailsUM) { horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { AccountRow( - title = stringReference(state.accountName), + title = state.accountName, subtitle = resourceReference(R.string.account_form_name), icon = state.accountIcon, modifier = Modifier.weight(1f), @@ -177,7 +177,7 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider