Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-15 21:10:37 +07:00
parent 5ee276974f
commit 9ebc1ac551
12 changed files with 224 additions and 31 deletions

View file

@ -43,6 +43,12 @@ internal object AccountDomainModule {
return RecoverCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
}
@Provides
@Singleton
fun provideGetArchivedAccountsUseCase(accountsCRUDRepository: AccountsCRUDRepository): GetArchivedAccountsUseCase {
return GetArchivedAccountsUseCase(crudRepository = accountsCRUDRepository)
}
@Provides
@Singleton
fun provideGetUnoccupiedAccountIndexUseCase(

View file

@ -10,34 +10,78 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.account.models.ArchivedAccount
import com.tangem.domain.account.usecase.ArchivedAccountList
import com.tangem.domain.account.usecase.GetArchivedAccountsUseCase
import com.tangem.domain.account.usecase.RecoverCryptoPortfolioUseCase
import com.tangem.domain.models.account.Account
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.models.account.AccountId
import com.tangem.features.account.ArchivedAccountListComponent
import com.tangem.features.account.archived.entity.AccountArchivedUM
import com.tangem.features.account.archived.entity.AccountArchivedUMBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Suppress("UnusedPrivateMember") // todo account
@Suppress("LongParameterList")
internal class ArchivedAccountListModel @Inject constructor(
paramsContainer: ParamsContainer,
private val messageSender: UiMessageSender,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
private val recoverCryptoPortfolioUseCase: RecoverCryptoPortfolioUseCase,
private val getArchivedAccountsUseCase: GetArchivedAccountsUseCase,
private val umBuilder: AccountArchivedUMBuilder,
) : Model() {
private val params = paramsContainer.require<ArchivedAccountListComponent.Params>()
private val onCloseClick = { router.pop() }
val uiState: StateFlow<AccountArchivedUM> get() = _uiState
private val _uiState: MutableStateFlow<AccountArchivedUM> = MutableStateFlow(getInitialState())
private var getArchivedAccountsJob = JobHolder()
private fun confirmRecoverDialog(accountId: AccountId) {
val account: Account? = null // todo account find
account ?: return
init {
getArchivedAccounts()
}
private fun getArchivedAccounts() {
getArchivedAccountsUseCase(params.userWalletId)
.conflate()
.distinctUntilChanged()
.onEach { lce ->
val newState = when (lce) {
is Lce.Content<ArchivedAccountList> -> umBuilder.mapContent(
accounts = lce.content,
onCloseClick = onCloseClick,
confirmRecoverDialog = { confirmRecoverDialog(it) },
)
is Lce.Error<Throwable> -> umBuilder.mapError(
throwable = lce.error,
onCloseClick = onCloseClick,
getArchivedAccounts = { getArchivedAccounts() },
)
is Lce.Loading<ArchivedAccountList> -> lce.partialContent?.let { content ->
umBuilder.mapContent(
accounts = content,
onCloseClick = onCloseClick,
confirmRecoverDialog = { confirmRecoverDialog(it) },
)
}
}
newState?.let { _uiState.value = newState }
}
.flowOn(dispatchers.default)
.launchIn(modelScope)
.saveIn(getArchivedAccountsJob)
}
private fun confirmRecoverDialog(account: ArchivedAccount) {
val secondAction = EventMessageAction(
title = resourceReference(R.string.common_cancel),
onClick = {},
@ -51,7 +95,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.toUM().value),
formatArgs = wrappedList(account.name.toUM().value),
),
firstActionBuilder = { firstAction },
secondActionBuilder = { secondAction },
@ -61,11 +105,19 @@ internal class ArchivedAccountListModel @Inject constructor(
private fun recoverCryptoPortfolio(accountId: AccountId) = modelScope.launch {
recoverCryptoPortfolioUseCase(accountId)
.onLeft { Timber.e(it.toString()) }
.onRight { showSuccessRecoverMessage() }
router.pop()
}
private fun showSuccessRecoverMessage() {
val message = resourceReference(R.string.account_recover_success_message)
messageSender.send(ToastMessage(message = message))
}
private fun getInitialState(): AccountArchivedUM {
return AccountArchivedUM.Loading(
onCloseClick = { router.pop() },
onCloseClick = onCloseClick,
)
}
}

View file

@ -23,5 +23,6 @@ internal data class ArchivedAccountUM(
val accountName: TextReference,
val accountIconUM: CryptoPortfolioIconUM,
val tokensInfo: TextReference,
val onClick: (accountId: String) -> Unit,
val networksInfo: TextReference,
val onClick: () -> Unit,
)

View file

@ -0,0 +1,55 @@
package com.tangem.features.account.archived.entity
import com.tangem.common.ui.account.toUM
import com.tangem.core.res.R
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.account.models.ArchivedAccount
import com.tangem.domain.account.usecase.ArchivedAccountList
import kotlinx.collections.immutable.toImmutableList
import timber.log.Timber
import javax.inject.Inject
internal class AccountArchivedUMBuilder @Inject constructor() {
fun mapContent(
accounts: ArchivedAccountList,
onCloseClick: () -> Unit,
confirmRecoverDialog: (account: ArchivedAccount) -> Unit,
) = AccountArchivedUM.Content(
onCloseClick = onCloseClick,
accounts = accounts
.map { account -> account.mapArchivedAccountUM(confirmRecoverDialog) }
.toImmutableList(),
)
fun ArchivedAccount.mapArchivedAccountUM(confirmRecoverDialog: (account: ArchivedAccount) -> Unit) =
ArchivedAccountUM(
accountId = accountId.value,
accountName = name.toUM().value,
accountIconUM = icon.toUM(),
tokensInfo = pluralReference(
R.plurals.common_tokens_count,
count = tokensCount,
formatArgs = wrappedList(tokensCount),
),
networksInfo = pluralReference(
R.plurals.common_networks_count,
count = networksCount,
formatArgs = wrappedList(networksCount),
),
onClick = { confirmRecoverDialog(this) },
)
fun mapError(
throwable: Throwable,
onCloseClick: () -> Unit,
getArchivedAccounts: () -> Unit,
): AccountArchivedUM.Error {
Timber.e(throwable)
return AccountArchivedUM.Error(
onCloseClick = onCloseClick,
onRetryClick = { getArchivedAccounts() },
)
}
}

View file

@ -21,6 +21,7 @@ import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
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
@ -125,14 +126,21 @@ private fun ArchivedAccountRow(item: ArchivedAccountUM, modifier: Modifier = Mod
Row(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = { item.onClick(item.accountId) })
.clickable(onClick = item.onClick)
.padding(all = TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
val subtitle = stringResourceSafe(
id = R.string.account_label_tokens_info,
formatArgs = arrayOf(
item.tokensInfo.resolveReference(),
item.networksInfo.resolveReference(),
),
)
AccountRow(
title = item.accountName,
subtitle = item.tokensInfo,
subtitle = stringReference(subtitle),
icon = item.accountIconUM,
modifier = Modifier.weight(1f),
)
@ -140,7 +148,7 @@ private fun ArchivedAccountRow(item: ArchivedAccountUM, modifier: Modifier = Mod
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(R.string.account_archived_recover),
onClick = { item.onClick(item.accountId) },
onClick = item.onClick,
),
)
}
@ -166,7 +174,8 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountA
accountId = it.toString(),
accountName = accountName,
accountIconUM = portfolioIcon(),
tokensInfo = stringReference("10 tokens in 2 networks"),
tokensInfo = stringReference("10 tokens"),
networksInfo = stringReference("2 networks"),
onClick = {},
)

View file

@ -2,6 +2,7 @@ package com.tangem.features.account.createedit
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
@ -12,8 +13,10 @@ import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.res.R
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
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.usecase.AddCryptoPortfolioUseCase
import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase
@ -25,6 +28,7 @@ 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.toggleProgress
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
@ -98,12 +102,20 @@ internal class AccountCreateEditModel @Inject constructor(
val index = state.account.derivationInfo.index ?: return
val derivationIndex = DerivationIndex(value = index).getOrNull() ?: return
addCryptoPortfolioUseCase(
uiState.value = uiState.value.toggleProgress(showProgress = true)
val result = addCryptoPortfolioUseCase(
userWalletId = params.userWalletId,
accountName = name,
icon = icon,
derivationIndex = derivationIndex,
)
uiState.value = uiState.value.toggleProgress(showProgress = false)
result
.onLeft { showMessage(it.toString()) }
.onRight {
showMessage(R.string.account_create_success_message)
router.pop()
}
}
private suspend fun editCryptoPortfolio(params: AccountCreateEditComponent.Params.Edit) {
@ -112,11 +124,28 @@ internal class AccountCreateEditModel @Inject constructor(
val icon = state.account.portfolioIcon.toDomain()
val isNewName = name != params.account.accountName
val isNewIcon = icon != params.account.portfolioIcon
updateCryptoPortfolioUseCase(
uiState.value = uiState.value.toggleProgress(showProgress = true)
val result = updateCryptoPortfolioUseCase(
icon = if (isNewIcon) icon else null,
accountName = if (isNewName) name else null,
accountId = params.account.accountId,
)
uiState.value = uiState.value.toggleProgress(showProgress = false)
result
.onLeft { showMessage(it.toString()) }
.onRight {
showMessage(R.string.account_edit_success_message)
router.pop()
}
}
private fun showMessage(@StringRes id: Int) {
val message = resourceReference(id)
messageSender.send(ToastMessage(message = message))
}
private fun showMessage(text: String) {
messageSender.send(ToastMessage(message = stringReference(text)))
}
private fun onCloseClick() = unsaveChangeDialog()

View file

@ -49,6 +49,7 @@ internal data class AccountCreateEditUM(
data class Button(
val isButtonEnabled: Boolean,
val showProgress: Boolean,
val onConfirmClick: () -> Unit,
val text: TextReference,
)

View file

@ -77,6 +77,7 @@ internal class AccountCreateEditUMBuilder(
}
return AccountCreateEditUM.Button(
isButtonEnabled = false,
showProgress = false,
onConfirmClick = onConfirmClick,
text = text,
)
@ -117,6 +118,10 @@ internal class AccountCreateEditUMBuilder(
return this.copy(buttonState = this.buttonState.copy(isButtonEnabled = isButtonEnabled))
}
fun AccountCreateEditUM.toggleProgress(showProgress: Boolean): AccountCreateEditUM {
return this.copy(buttonState = this.buttonState.copy(showProgress = showProgress))
}
fun AccountCreateEditUM.updateDerivationIndex(derivationIndex: Int): AccountCreateEditUM {
return this.copy(
account = this.account.copy(

View file

@ -80,6 +80,7 @@ internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modi
.fillMaxWidth()
.padding(16.dp),
enabled = state.buttonState.isButtonEnabled,
showProgress = state.buttonState.showProgress,
text = state.buttonState.text.resolveReference(),
onClick = state.buttonState.onConfirmClick,
)
@ -309,6 +310,7 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountC
),
buttonState = AccountCreateEditUM.Button(
isButtonEnabled = false,
showProgress = false,
onConfirmClick = {},
text = stringReference("Add account"),
),
@ -342,6 +344,7 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountC
),
buttonState = AccountCreateEditUM.Button(
isButtonEnabled = false,
showProgress = false,
onConfirmClick = {},
text = stringReference("Save"),
),

View file

@ -11,7 +11,9 @@ 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.message.ToastMessage
import com.tangem.domain.account.usecase.ArchiveCryptoPortfolioUseCase
import com.tangem.domain.models.account.Account
import com.tangem.features.account.AccountDetailsComponent
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon
import com.tangem.features.account.details.entity.AccountDetailsUM
@ -70,16 +72,30 @@ internal class AccountDetailsModel @Inject constructor(
private fun archiveCryptoPortfolio() = modelScope.launch {
archiveCryptoPortfolioUseCase(params.account.accountId)
.onRight {
val message = resourceReference(R.string.account_archive_success_message)
messageSender.send(ToastMessage(message = message))
router.pop()
}
}
private fun getInitialState(): AccountDetailsUM {
val account = params.account
val archiveMode = when (account) {
is Account.CryptoPortfolio -> when (account.isMainAccount) {
true -> AccountDetailsUM.ArchiveMode.None
false -> AccountDetailsUM.ArchiveMode.Available(
onArchiveAccountClick = ::onArchiveAccountClick,
)
}
}
return AccountDetailsUM(
accountName = params.account.accountName.toUM().value,
accountIcon = params.account.portfolioIcon.toUM(),
onCloseClick = { router.pop() },
onAccountEditClick = ::onEditAccountClick,
onManageTokensClick = ::onManageTokensClick,
onArchiveAccountClick = ::onArchiveAccountClick,
archiveMode = archiveMode,
)
}
}

View file

@ -6,8 +6,16 @@ import com.tangem.core.ui.extensions.TextReference
internal data class AccountDetailsUM(
val accountName: TextReference,
val accountIcon: CryptoPortfolioIconUM,
val archiveMode: ArchiveMode,
val onCloseClick: () -> Unit,
val onAccountEditClick: () -> Unit,
val onManageTokensClick: () -> Unit,
val onArchiveAccountClick: () -> Unit,
)
) {
sealed interface ArchiveMode {
data object None : ArchiveMode
data class Available(
val onArchiveAccountClick: () -> Unit,
) : ArchiveMode
}
}

View file

@ -65,21 +65,26 @@ internal fun AccountDetailsContent(state: AccountDetailsUM, modifier: Modifier =
AccountRow(state)
SpacerH16()
ManageTokensRow(state)
SpacerH16()
ArchiveAccountRow(state)
SpacerH(8.dp)
Text(
modifier = Modifier.padding(horizontal = 12.dp),
text = stringResourceSafe(R.string.account_details_archive_description),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
when (state.archiveMode) {
is AccountDetailsUM.ArchiveMode.Available -> {
SpacerH16()
ArchiveAccountRow(state.archiveMode)
SpacerH(8.dp)
Text(
modifier = Modifier.padding(horizontal = 12.dp),
text = stringResourceSafe(R.string.account_details_archive_description),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
AccountDetailsUM.ArchiveMode.None -> Unit
}
}
}
}
@Composable
private fun ArchiveAccountRow(state: AccountDetailsUM) {
private fun ArchiveAccountRow(state: AccountDetailsUM.ArchiveMode.Available) {
Row(
modifier = Modifier
.fillMaxWidth()
@ -176,12 +181,15 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountD
onCloseClick = {},
onAccountEditClick = {},
onManageTokensClick = {},
onArchiveAccountClick = {},
accountName = stringReference(value = accountName),
archiveMode = AccountDetailsUM.ArchiveMode.Available(
onArchiveAccountClick = {},
),
accountName = stringReference(accountName),
accountIcon = portfolioIcon,
)
add(first)
portfolioIcon = AccountIconPreviewData.randomAccountIcon(letter = true)
add(first.copy(accountIcon = portfolioIcon))
add(first.copy(archiveMode = AccountDetailsUM.ArchiveMode.None))
},
)