diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 7d7c0cc50d..ba64c1cfc6 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -8,13 +8,15 @@ import com.tangem.feature.referral.api.ReferralComponent import com.tangem.feature.stories.api.StoriesComponent import com.tangem.feature.usedesk.api.UsedeskComponent import com.tangem.feature.walletsettings.component.WalletSettingsComponent -import com.tangem.features.hotwallet.WalletBackupComponent +import com.tangem.features.account.AccountCreateEditComponent import com.tangem.features.createwalletselection.CreateWalletSelectionComponent import com.tangem.features.details.component.DetailsComponent import com.tangem.features.disclaimer.api.components.DisclaimerComponent +import com.tangem.features.home.api.HomeComponent import com.tangem.features.hotwallet.AddExistingWalletComponent import com.tangem.features.hotwallet.CreateMobileWalletComponent import com.tangem.features.hotwallet.WalletActivationComponent +import com.tangem.features.hotwallet.WalletBackupComponent import com.tangem.features.managetokens.component.ChooseManagedTokensComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource @@ -43,7 +45,6 @@ import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCo import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent -import com.tangem.features.home.api.HomeComponent import com.tangem.tap.features.welcome.component.WelcomeComponent import com.tangem.tap.routing.component.RoutingComponent.Child import dagger.hilt.android.scopes.ActivityScoped @@ -85,6 +86,7 @@ internal class ChildFactory @Inject constructor( private val walletComponentFactory: WalletEntryComponent.Factory, private val sendComponentFactoryV2: SendComponent.Factory, private val redesignedWalletConnectComponentFactory: WalletConnectEntryComponent.Factory, + private val accountCreateEditComponentFactory: AccountCreateEditComponent.Factory, private val nftComponentFactory: NFTComponent.Factory, private val nftSendComponentFactory: NFTSendComponent.Factory, private val usedeskComponentFactory: UsedeskComponent.Factory, @@ -497,6 +499,24 @@ internal class ChildFactory @Inject constructor( componentFactory = sendWithSwapComponentFactory, ) } + is AppRoute.CreateAccount -> { + createComponentChild( + context = context, + params = AccountCreateEditComponent.Params.Create( + userWalletId = route.userWalletId, + ), + componentFactory = accountCreateEditComponentFactory, + ) + } + is AppRoute.EditAccount -> { + createComponentChild( + context = context, + params = AccountCreateEditComponent.Params.Edit( + account = route.account, + ), + componentFactory = accountCreateEditComponentFactory, + ) + } } } } \ No newline at end of file diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index de65bd8448..3b71b7518e 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -9,6 +9,7 @@ import com.tangem.core.decompose.navigation.Route import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.feedback.models.CardInfo import com.tangem.domain.markets.TokenMarketParams +import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.wallet.UserWalletId @@ -320,4 +321,14 @@ sealed class AppRoute(val path: String) : Route { val userWalletId: UserWalletId, val currency: CryptoCurrency, ) : AppRoute(path = "/send_with_swap/${userWalletId.stringValue}/${currency.symbol}") + + @Serializable + data class CreateAccount( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/create_account/${userWalletId.stringValue}") + + @Serializable + data class EditAccount( + val account: Account, + ) : AppRoute(path = "/edit_account/${account.accountId.value}") } \ No newline at end of file diff --git a/features/account/api/build.gradle.kts b/features/account/api/build.gradle.kts index 349aee77c0..e3e35ac4cd 100644 --- a/features/account/api/build.gradle.kts +++ b/features/account/api/build.gradle.kts @@ -13,4 +13,7 @@ dependencies { /* Project - Core */ implementation(projects.core.decompose) implementation(projects.core.ui) + + /* Project - Domain */ + implementation(projects.domain.models) } \ No newline at end of file diff --git a/features/account/api/src/main/java/com/tangem/features/account/AccountCreateEditComponent.kt b/features/account/api/src/main/java/com/tangem/features/account/AccountCreateEditComponent.kt index 1f6de5f0b2..b18a7db430 100644 --- a/features/account/api/src/main/java/com/tangem/features/account/AccountCreateEditComponent.kt +++ b/features/account/api/src/main/java/com/tangem/features/account/AccountCreateEditComponent.kt @@ -2,13 +2,20 @@ package com.tangem.features.account import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.wallet.UserWalletId interface AccountCreateEditComponent : ComposableContentComponent { interface Factory : ComponentFactory sealed interface Params { - data object Create : Params - data object Edit : Params + data class Create( + val userWalletId: UserWalletId, + ) : Params + + data class Edit( + val account: Account, + ) : Params } } \ No newline at end of file diff --git a/features/account/impl/build.gradle.kts b/features/account/impl/build.gradle.kts index 55714f3021..e68d20b144 100644 --- a/features/account/impl/build.gradle.kts +++ b/features/account/impl/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { /** Domain */ implementation(projects.domain.models) + implementation(projects.domain.account) /** Common */ implementation(projects.common.ui) 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 872b7af23f..77818706a6 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 @@ -9,11 +9,22 @@ 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.domain.account.usecase.AddCryptoPortfolioUseCase +import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase +import com.tangem.domain.models.account.AccountName +import com.tangem.domain.models.account.CryptoPortfolioIcon import com.tangem.features.account.AccountCreateEditComponent import com.tangem.features.account.createedit.entity.AccountCreateEditUM +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.updateIconSelect +import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateName import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch import javax.inject.Inject @ModelScoped @@ -22,24 +33,24 @@ internal class AccountCreateEditModel @Inject constructor( private val messageSender: UiMessageSender, private val router: Router, override val dispatchers: CoroutineDispatcherProvider, + private val updateCryptoPortfolioUseCase: UpdateCryptoPortfolioUseCase, + private val addCryptoPortfolioUseCase: AddCryptoPortfolioUseCase, ) : Model() { private val params = paramsContainer.require() + private val umBuilder = AccountCreateEditUMBuilder(params) - val uiState: StateFlow - field = MutableStateFlow(TODO()) + val uiState: StateFlow get() = _uiState + private val _uiState = MutableStateFlow(value = getInitialState()) - init { - params - } - - fun unsaveChangeDialog() { - val firstAction = EventMessageAction( + private fun unsaveChangeDialog() { + val secondAction = EventMessageAction( title = resourceReference(R.string.account_unsaved_dialog_action_first), onClick = {}, ) - val secondAction = EventMessageAction( + val firstAction = EventMessageAction( title = resourceReference(R.string.account_unsaved_dialog_action_second), + warning = true, onClick = { router.pop() }, ) messageSender.send( @@ -51,4 +62,80 @@ internal class AccountCreateEditModel @Inject constructor( ), ) } + + private fun onConfirmClick() = modelScope.launch { + when (params) { + is AccountCreateEditComponent.Params.Create -> createNewCryptoPortfolio(params) + is AccountCreateEditComponent.Params.Edit -> editCryptoPortfolio(params) + } + } + + private suspend fun createNewCryptoPortfolio(params: AccountCreateEditComponent.Params.Create) { + val state = uiState.value + val name = AccountName(state.account.name).getOrNull() ?: return + val icon = state.account.portfolioIcon + addCryptoPortfolioUseCase( + userWalletId = params.userWalletId, + accountName = name, + icon = icon, + derivationIndex = 0, // todo account + ) + } + + private suspend fun editCryptoPortfolio(params: AccountCreateEditComponent.Params.Edit) { + val state = uiState.value + val name = AccountName(state.account.name).getOrNull() ?: return + val icon = state.account.portfolioIcon + val isNewName = name != params.account.name + val isNewIcon = icon != params.account.portfolioIcon + updateCryptoPortfolioUseCase( + icon = if (isNewIcon) icon else null, + name = if (isNewName) name else null, + accountId = params.account.accountId, + ) + } + + private fun onCloseClick() = unsaveChangeDialog() + + private fun onIconSelect(icon: CryptoPortfolioIcon.Icon) { + _uiState.value = uiState.value + .updateIconSelect(icon) + .validateNewState() + } + + private fun onColorSelect(color: CryptoPortfolioIcon.Color) { + _uiState.value = uiState.value + .updateColorSelect(color) + .validateNewState() + } + + private fun onNameChange(name: String) { + _uiState.value = uiState.value + .updateName(name) + .validateNewState() + } + + private fun AccountCreateEditUM.validateNewState(): AccountCreateEditUM { + val isValidName = AccountName(this.account.name).isRight() + val isAvailableForConfirm = when (params) { + is AccountCreateEditComponent.Params.Create -> isValidName + is AccountCreateEditComponent.Params.Edit -> { + val isNewName = this.account.name != params.account.name.value + val isNewIcon = this.account.portfolioIcon != params.account.portfolioIcon + isValidName && (isNewName || isNewIcon) + } + } + return this.updateButton(isButtonEnabled = isAvailableForConfirm) + } + + private fun getInitialState(): AccountCreateEditUM { + return AccountCreateEditUM( + title = umBuilder.toolbarTitle, + account = umBuilder.initAccountUM(::onNameChange), + colorsState = umBuilder.initColorsUM(::onColorSelect), + iconsState = umBuilder.initIconsUM(::onIconSelect), + buttonState = umBuilder.initButtonUM(::onConfirmClick), + onCloseClick = ::onCloseClick, + ) + } } \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/DefaultAccountCreateEditComponent.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/DefaultAccountCreateEditComponent.kt index 06725717ad..e920c6cbbe 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/DefaultAccountCreateEditComponent.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/DefaultAccountCreateEditComponent.kt @@ -1,5 +1,6 @@ package com.tangem.features.account.createedit +import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -26,6 +27,7 @@ internal class DefaultAccountCreateEditComponent @AssistedInject constructor( modifier = modifier, state = state, ) + BackHandler(onBack = state.onCloseClick) } @AssistedFactory 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 4ef4877b97..2c7fd2d0f0 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 @@ -3,40 +3,39 @@ package com.tangem.features.account.createedit.entity import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.models.account.CryptoPortfolioIcon import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf data class AccountCreateEditUM( - val title: TextReference = TextReference.EMPTY, - val account: Account = Account(), + val title: TextReference, + val account: Account, val colorsState: Colors, val iconsState: Icons, - val buttonState: Button = Button(), - val onCloseClick: () -> Unit = {}, + val buttonState: Button, + val onCloseClick: () -> Unit, ) { data class Account( - val name: TextReference = TextReference.EMPTY, - val portfolioIcon: CryptoPortfolioIcon = CryptoPortfolioIcon.ofDefaultCustomAccount(), - val derivationInfo: TextReference = TextReference.EMPTY, - val inputPlaceholder: TextReference = TextReference.EMPTY, - val onNameChange: (String) -> Unit = {}, + val name: String, + val portfolioIcon: CryptoPortfolioIcon, + val derivationInfo: TextReference, + val inputPlaceholder: TextReference, + val onNameChange: (String) -> Unit, ) data class Colors( val selected: CryptoPortfolioIcon.Color, - val list: ImmutableList = persistentListOf(), - val onColorSelect: (CryptoPortfolioIcon.Color) -> Unit = {}, + val list: ImmutableList, + val onColorSelect: (CryptoPortfolioIcon.Color) -> Unit, ) data class Icons( val selected: CryptoPortfolioIcon.Icon, - val list: ImmutableList = persistentListOf(), - val onIconSelect: (CryptoPortfolioIcon.Icon) -> Unit = {}, + val list: ImmutableList, + val onIconSelect: (CryptoPortfolioIcon.Icon) -> Unit, ) data class Button( - val isButtonEnabled: Boolean = false, - val onConfirmClick: () -> Unit = {}, - val text: TextReference = TextReference.EMPTY, + val isButtonEnabled: Boolean, + val onConfirmClick: () -> Unit, + val text: TextReference, ) } \ No newline at end of file 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 new file mode 100644 index 0000000000..75f31f86ac --- /dev/null +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt @@ -0,0 +1,118 @@ +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.domain.models.account.Account +import com.tangem.domain.models.account.CryptoPortfolioIcon +import com.tangem.features.account.AccountCreateEditComponent +import kotlinx.collections.immutable.toImmutableList +import javax.inject.Inject + +internal class AccountCreateEditUMBuilder @Inject constructor( + val params: AccountCreateEditComponent.Params, +) { + + private val accountColors = CryptoPortfolioIcon.Color.entries.toImmutableList() + private val accountIcons = CryptoPortfolioIcon.Icon.entries.toImmutableList() + private val createIcon = CryptoPortfolioIcon.ofDefaultCustomAccount() + + val toolbarTitle: TextReference + get() = when (params) { + is AccountCreateEditComponent.Params.Create -> resourceReference(R.string.account_form_title_create) + is AccountCreateEditComponent.Params.Edit -> resourceReference(R.string.account_form_title_edit) + } + + fun initAccountUM(onNameChange: (String) -> Unit): AccountCreateEditUM.Account { + return when (params) { + is AccountCreateEditComponent.Params.Create -> AccountCreateEditUM.Account( + name = "", + portfolioIcon = createIcon, + derivationInfo = TextReference.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, + derivationInfo = TextReference.EMPTY, // todo account use Account.CryptoPortfolio.derivationIndex ? + inputPlaceholder = resourceReference(R.string.account_form_placeholder_edit_account), + onNameChange = onNameChange, + ) + } + } + + fun initColorsUM(onColorSelect: (CryptoPortfolioIcon.Color) -> Unit): AccountCreateEditUM.Colors { + val selected: CryptoPortfolioIcon.Color = when (params) { + is AccountCreateEditComponent.Params.Create -> createIcon.color + is AccountCreateEditComponent.Params.Edit -> params.account.portfolioIcon.color + } + return AccountCreateEditUM.Colors( + selected = selected, + list = accountColors, + onColorSelect = onColorSelect, + ) + } + + fun initIconsUM(onIconSelect: (CryptoPortfolioIcon.Icon) -> Unit): AccountCreateEditUM.Icons { + val selected: CryptoPortfolioIcon.Icon = when (params) { + is AccountCreateEditComponent.Params.Create -> createIcon.value + is AccountCreateEditComponent.Params.Edit -> params.account.portfolioIcon.value + } + return AccountCreateEditUM.Icons( + selected = selected, + list = accountIcons, + onIconSelect = onIconSelect, + ) + } + + fun initButtonUM(onConfirmClick: () -> Unit): AccountCreateEditUM.Button { + val text: TextReference = when (params) { + is AccountCreateEditComponent.Params.Create -> resourceReference(R.string.account_form_create_button) + is AccountCreateEditComponent.Params.Edit -> resourceReference(R.string.account_form_edit_button) + } + return AccountCreateEditUM.Button( + isButtonEnabled = false, + onConfirmClick = onConfirmClick, + text = text, + ) + } + + internal companion object { + + val Account.portfolioIcon: CryptoPortfolioIcon + get() = when (this) { + is Account.CryptoPortfolio -> this.icon + } + + fun AccountCreateEditUM.updateColorSelect(color: CryptoPortfolioIcon.Color): AccountCreateEditUM { + val newIcon = CryptoPortfolioIcon.ofCustomAccount( + value = account.portfolioIcon.value, + color = color, + ) + return this.copy( + account = this.account.copy(portfolioIcon = newIcon), + colorsState = this.colorsState.copy(selected = color), + ) + } + + fun AccountCreateEditUM.updateIconSelect(icon: CryptoPortfolioIcon.Icon): AccountCreateEditUM { + val newIcon = CryptoPortfolioIcon.ofCustomAccount( + value = icon, + color = account.portfolioIcon.color, + ) + return this.copy( + account = this.account.copy(portfolioIcon = newIcon), + iconsState = this.iconsState.copy(selected = icon), + ) + } + + fun AccountCreateEditUM.updateName(name: String): AccountCreateEditUM { + return this.copy(account = this.account.copy(name = name)) + } + + fun AccountCreateEditUM.updateButton(isButtonEnabled: Boolean): AccountCreateEditUM { + return this.copy(buttonState = this.buttonState.copy(isButtonEnabled = isButtonEnabled)) + } + } +} \ No newline at end of file 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 dd927df0f0..725964567d 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 @@ -33,6 +33,7 @@ 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.res.TangemTheme @@ -66,7 +67,7 @@ internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modi .weight(1f), ) { - AccountSummary(state.account, state.account.onNameChange) + AccountSummary(state.account) SpacerH24() AccountColor(state.colorsState) SpacerH24() @@ -91,7 +92,7 @@ internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modi } @Composable -private fun AccountSummary(account: AccountCreateEditUM.Account, onNameChange: (String) -> Unit) { +private fun AccountSummary(account: AccountCreateEditUM.Account) { Column( modifier = Modifier .clip(RoundedCornerShape(16.dp)) @@ -115,9 +116,9 @@ private fun AccountSummary(account: AccountCreateEditUM.Account, onNameChange: ( centered = true, textStyle = TangemTheme.typography.head, placeholder = account.inputPlaceholder, - value = account.name.resolveReference(), + value = account.name, singleLine = true, - onValueChange = onNameChange, + onValueChange = account.onNameChange, ) SpacerH(20.dp) } @@ -133,9 +134,11 @@ private fun AccountIcon(account: AccountCreateEditUM.Account) { .background(account.portfolioIcon.color.getUiColor()), ) { val icon = account.portfolioIcon.value + val letter = account.name.firstOrNull() + ?: account.inputPlaceholder.resolveReference().first() when { icon == CryptoPortfolioIcon.Icon.Letter -> Text( - text = account.name.resolveReference().first().uppercase(), + text = letter.uppercase(), style = TangemTheme.typography.head, color = TangemTheme.colors.text.constantWhite, ) @@ -298,19 +301,27 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider