Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-13 16:23:18 +04:00
parent cf6b5dbabb
commit 853304539d
12 changed files with 285 additions and 28 deletions

View file

@ -20,6 +20,7 @@ dependencies {
implementation(projects.core.analytics.models)
implementation(projects.core.utils)
implementation(projects.core.ui)
implementation(projects.core.error)
implementation(projects.core.res)
implementation(projects.core.decompose)
implementation(projects.core.navigation)

View file

@ -1,5 +1,7 @@
package com.tangem.features.account.createedit
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -9,11 +11,14 @@ import com.tangem.core.res.R
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
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
import com.tangem.features.account.AccountCreateEditComponent
import com.tangem.features.account.common.toDomain
import com.tangem.features.account.createedit.entity.AccountCreateEditUM
@ -21,15 +26,20 @@ import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateButton
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateColorSelect
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateDerivationIndex
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateIconSelect
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateName
import com.tangem.features.account.createedit.error.AccountFeatureError
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ModelScoped
@Suppress("LongParameterList")
internal class AccountCreateEditModel @Inject constructor(
paramsContainer: ParamsContainer,
private val messageSender: UiMessageSender,
@ -37,13 +47,21 @@ internal class AccountCreateEditModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val updateCryptoPortfolioUseCase: UpdateCryptoPortfolioUseCase,
private val addCryptoPortfolioUseCase: AddCryptoPortfolioUseCase,
private val getUnoccupiedAccountIndexUseCase: GetUnoccupiedAccountIndexUseCase,
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
) : Model() {
private val params = paramsContainer.require<AccountCreateEditComponent.Params>()
private val umBuilder = AccountCreateEditUMBuilder(params)
val uiState: StateFlow<AccountCreateEditUM> get() = _uiState
private val _uiState = MutableStateFlow(value = getInitialState())
val uiState: StateFlow<AccountCreateEditUM>
field = MutableStateFlow(value = getInitialState())
init {
if (params is AccountCreateEditComponent.Params.Create) {
updateDerivationInfo(userWalletId = params.userWalletId)
}
}
private fun unsaveChangeDialog() {
val secondAction = EventMessageAction(
@ -74,13 +92,16 @@ internal class AccountCreateEditModel @Inject constructor(
private suspend fun createNewCryptoPortfolio(params: AccountCreateEditComponent.Params.Create) {
val state = uiState.value
val name = AccountName(state.account.name).getOrNull() ?: return
val name = AccountName(value = state.account.name).getOrNull() ?: return
val icon = state.account.portfolioIcon.toDomain()
val index = state.account.derivationInfo.index ?: return
val derivationIndex = DerivationIndex(value = index).getOrNull() ?: return
addCryptoPortfolioUseCase(
userWalletId = params.userWalletId,
accountName = name,
icon = icon,
derivationIndex = DerivationIndex.Main, // todo account
derivationIndex = derivationIndex,
)
}
@ -100,19 +121,19 @@ internal class AccountCreateEditModel @Inject constructor(
private fun onCloseClick() = unsaveChangeDialog()
private fun onIconSelect(icon: CryptoPortfolioIcon.Icon) {
_uiState.value = uiState.value
uiState.value = uiState.value
.updateIconSelect(icon)
.validateNewState()
}
private fun onColorSelect(color: CryptoPortfolioIcon.Color) {
_uiState.value = uiState.value
uiState.value = uiState.value
.updateColorSelect(color)
.validateNewState()
}
private fun onNameChange(name: String) {
_uiState.value = uiState.value
uiState.value = uiState.value
.updateName(name)
.validateNewState()
}
@ -140,4 +161,35 @@ internal class AccountCreateEditModel @Inject constructor(
onCloseClick = ::onCloseClick,
)
}
private fun updateDerivationInfo(userWalletId: UserWalletId) {
modelScope.launch(dispatchers.default) {
getUnoccupiedAccountIndexUseCase(userWalletId = userWalletId)
.onRight { derivationIndex ->
uiState.update {
it.updateDerivationIndex(derivationIndex = derivationIndex.value)
}
}
.onLeft {
handleError(
error = AccountFeatureError.CreateAccount.UnableToGetDerivationIndex,
params = mapOf("userWalletId" to userWalletId.stringValue),
)
return@launch
}
}
}
private fun handleError(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

@ -17,11 +17,23 @@ data class AccountCreateEditUM(
data class Account(
val name: String,
val portfolioIcon: CryptoPortfolioIconUM,
val derivationInfo: TextReference,
val derivationInfo: DerivationInfo,
val inputPlaceholder: TextReference,
val onNameChange: (String) -> Unit,
)
sealed interface DerivationInfo {
val text: TextReference
val index: Int?
data class Content(override val text: TextReference, override val index: Int) : DerivationInfo
data object Empty : DerivationInfo {
override val text: TextReference = TextReference.EMPTY
override val index: Int? = null
}
}
data class Colors(
val selected: CryptoPortfolioIcon.Color,
val list: ImmutableList<CryptoPortfolioIcon.Color>,

View file

@ -3,15 +3,15 @@ package com.tangem.features.account.createedit.entity
import com.tangem.core.res.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.CryptoPortfolioIcon
import com.tangem.features.account.AccountCreateEditComponent
import com.tangem.features.account.common.toUM
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
internal class AccountCreateEditUMBuilder @Inject constructor(
val params: AccountCreateEditComponent.Params,
internal class AccountCreateEditUMBuilder(
private val params: AccountCreateEditComponent.Params,
) {
private val accountColors = CryptoPortfolioIcon.Color.entries.toImmutableList()
@ -29,14 +29,16 @@ internal class AccountCreateEditUMBuilder @Inject constructor(
is AccountCreateEditComponent.Params.Create -> AccountCreateEditUM.Account(
name = "",
portfolioIcon = createIcon,
derivationInfo = TextReference.EMPTY,
derivationInfo = AccountCreateEditUM.DerivationInfo.Empty,
inputPlaceholder = resourceReference(R.string.account_form_placeholder_new_account),
onNameChange = onNameChange,
)
is AccountCreateEditComponent.Params.Edit -> AccountCreateEditUM.Account(
name = params.account.name.value,
portfolioIcon = params.account.portfolioIcon.toUM(),
derivationInfo = TextReference.EMPTY, // todo account use Account.CryptoPortfolio.derivationIndex ?
derivationInfo = createAccountDerivationInfo(
index = (params.account as Account.CryptoPortfolio).derivationIndex.value,
),
inputPlaceholder = resourceReference(R.string.account_form_placeholder_edit_account),
onNameChange = onNameChange,
)
@ -113,5 +115,25 @@ internal class AccountCreateEditUMBuilder @Inject constructor(
fun AccountCreateEditUM.updateButton(isButtonEnabled: Boolean): AccountCreateEditUM {
return this.copy(buttonState = this.buttonState.copy(isButtonEnabled = isButtonEnabled))
}
fun AccountCreateEditUM.updateDerivationIndex(derivationIndex: Int): AccountCreateEditUM {
return this.copy(
account = this.account.copy(
derivationInfo = createAccountDerivationInfo(index = derivationIndex),
),
)
}
private fun createAccountDerivationInfo(index: Int): AccountCreateEditUM.DerivationInfo {
val derivationIndexText = if (index.toString().length == 1) "0$index" else "$index"
return AccountCreateEditUM.DerivationInfo.Content(
text = resourceReference(
id = R.string.account_form_account_index,
formatArgs = wrappedList(derivationIndexText),
),
index = index,
)
}
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.features.account.createedit.error
import com.tangem.core.error.UniversalError
sealed interface AccountFeatureError : UniversalError {
val subsystemCode: String
val specificErrorCode: String
override val errorCode: Int
get() = "108$subsystemCode$specificErrorCode".toInt()
sealed interface CreateAccount : AccountFeatureError {
override val subsystemCode: String get() = "001"
data object UnableToGetDerivationIndex : CreateAccount {
override val specificErrorCode: String = "001"
}
}
sealed interface EditAccount : AccountFeatureError {
override val subsystemCode: String get() = "002"
data object RequiredCryptoPortfolio : EditAccount {
override val specificErrorCode: String = "001"
}
}
}

View file

@ -32,10 +32,7 @@ import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.components.fields.AutoSizeTextField
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.models.account.CryptoPortfolioIcon
@ -76,7 +73,7 @@ internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modi
SpacerH8()
Text(
modifier = Modifier.padding(horizontal = 8.dp),
text = state.account.derivationInfo.resolveReference(),
text = state.account.derivationInfo.text.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
@ -93,7 +90,7 @@ internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modi
}
@Composable
private fun AccountSummary(account: AccountCreateEditUM.Account) {
private fun AccountSummary(account: Account) {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
@ -126,7 +123,7 @@ private fun AccountSummary(account: AccountCreateEditUM.Account) {
}
@Composable
private fun AccountIcon(account: AccountCreateEditUM.Account) {
private fun AccountIcon(account: Account) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
@ -308,7 +305,10 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountC
portfolioIcon = portfolioIcon,
inputPlaceholder = resourceReference(R.string.account_form_placeholder_new_account),
onNameChange = {},
derivationInfo = stringReference("Account #03 — used for address derivation."),
derivationInfo = AccountCreateEditUM.DerivationInfo.Content(
text = resourceReference(id = R.string.account_form_account_index, formatArgs = wrappedList(1)),
index = 1,
),
),
colorsState = AccountCreateEditUM.Colors(
selected = portfolioIcon.color,
@ -340,7 +340,10 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountC
name = "Main account",
inputPlaceholder = resourceReference(R.string.account_form_placeholder_edit_account),
onNameChange = {},
derivationInfo = stringReference("Account #03 — used for address derivation."),
derivationInfo = AccountCreateEditUM.DerivationInfo.Content(
text = resourceReference(id = R.string.account_form_account_index, formatArgs = wrappedList(1)),
index = 1,
),
),
colorsState = AccountCreateEditUM.Colors(
selected = portfolioIcon.color,