Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-13 14:37:42 +07:00
parent a443332da8
commit 40993944ee
14 changed files with 407 additions and 4 deletions

View file

@ -0,0 +1,11 @@
package com.tangem.features.account
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.wallet.UserWalletId
interface ArchivedAccountListComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Params, ArchivedAccountListComponent>
data class Params(val userWalletId: UserWalletId)
}

View file

@ -0,0 +1,68 @@
package com.tangem.features.account.archived
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
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.TextReference
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.domain.account.usecase.RecoverCryptoPortfolioUseCase
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.features.account.ArchivedAccountListComponent
import com.tangem.features.account.archived.entity.AccountArchivedUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("UnusedPrivateMember") // todo account
internal class ArchivedAccountListModel @Inject constructor(
paramsContainer: ParamsContainer,
private val messageSender: UiMessageSender,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
private val recoverCryptoPortfolioUseCase: RecoverCryptoPortfolioUseCase,
) : Model() {
private val params = paramsContainer.require<ArchivedAccountListComponent.Params>()
val uiState: StateFlow<AccountArchivedUM> get() = _uiState
private val _uiState: MutableStateFlow<AccountArchivedUM> = MutableStateFlow(getInitialState())
private fun confirmRecoverDialog(accountId: AccountId) {
val account: Account? = null // todo account find
account ?: return
val secondAction = EventMessageAction(
title = resourceReference(R.string.common_cancel),
onClick = {},
)
val firstAction = EventMessageAction(
title = resourceReference(R.string.account_archived_recover),
onClick = { recoverCryptoPortfolio(account.accountId) },
)
messageSender.send(
DialogMessage(
title = stringReference(account.name.value),
message = TextReference.EMPTY,
firstActionBuilder = { firstAction },
secondActionBuilder = { secondAction },
),
)
}
private fun recoverCryptoPortfolio(accountId: AccountId) = modelScope.launch {
recoverCryptoPortfolioUseCase(accountId)
}
private fun getInitialState(): AccountArchivedUM {
return AccountArchivedUM.Loading(
onCloseClick = { router.pop() },
)
}
}

View file

@ -0,0 +1,40 @@
package com.tangem.features.account.archived
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.account.ArchivedAccountListComponent
import com.tangem.features.account.archived.ui.ArchivedAccountListContent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultArchivedAccountListComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: ArchivedAccountListComponent.Params,
) : AppComponentContext by appComponentContext, ArchivedAccountListComponent {
private val model: ArchivedAccountListModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
ArchivedAccountListContent(
modifier = modifier,
state = state,
)
BackHandler(onBack = state.onCloseClick)
}
@AssistedFactory
interface Factory : ArchivedAccountListComponent.Factory {
override fun create(
context: AppComponentContext,
params: ArchivedAccountListComponent.Params,
): DefaultArchivedAccountListComponent
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.features.account.archived.di
import com.tangem.core.decompose.model.Model
import com.tangem.features.account.ArchivedAccountListComponent
import com.tangem.features.account.archived.ArchivedAccountListModel
import com.tangem.features.account.archived.DefaultArchivedAccountListComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface AccountArchivedModule {
@Binds
fun bindArchivedAccountListComponentFactory(
impl: DefaultArchivedAccountListComponent.Factory,
): ArchivedAccountListComponent.Factory
@Binds
@IntoMap
@ClassKey(ArchivedAccountListModel::class)
fun bindArchivedAccountListModel(model: ArchivedAccountListModel): Model
}

View file

@ -0,0 +1,27 @@
package com.tangem.features.account.archived.entity
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.account.common.CryptoPortfolioIconUM
import kotlinx.collections.immutable.ImmutableList
internal sealed interface AccountArchivedUM {
val onCloseClick: () -> Unit
data class Loading(override val onCloseClick: () -> Unit) : AccountArchivedUM
data class Error(
override val onCloseClick: () -> Unit,
val onRetryClick: () -> Unit,
) : AccountArchivedUM
data class Content(
override val onCloseClick: () -> Unit,
val accounts: ImmutableList<ArchivedAccountUM>,
) : AccountArchivedUM
}
internal data class ArchivedAccountUM(
val accountId: String,
val accountName: String,
val accountIcon: CryptoPortfolioIconUM,
val tokensInfo: TextReference,
val onClick: (accountId: String) -> Unit,
)

View file

@ -0,0 +1,202 @@
package com.tangem.features.account.archived.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.res.R
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
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.models.account.CryptoPortfolioIcon
import com.tangem.features.account.archived.entity.AccountArchivedUM
import com.tangem.features.account.archived.entity.ArchivedAccountUM
import com.tangem.features.account.common.toUM
import com.tangem.features.account.details.ui.AccountIcon
import kotlinx.collections.immutable.toImmutableList
@Composable
internal fun ArchivedAccountListContent(state: AccountArchivedUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.background(color = TangemTheme.colors.background.secondary)
.fillMaxSize()
.imePadding()
.systemBarsPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarWithBackButton(
text = stringResourceSafe(R.string.account_archived_title),
onBackClick = state.onCloseClick,
modifier = Modifier.height(TangemTheme.dimens.size56),
)
Column(
modifier = Modifier
.fillMaxSize()
.weight(1f),
) {
when (state) {
is AccountArchivedUM.Content -> ArchiveAccountContent(state)
is AccountArchivedUM.Error -> ArchiveAccountError(state)
is AccountArchivedUM.Loading -> ArchiveAccountLoading()
}
}
}
}
@Composable
private fun ArchiveAccountLoading(modifier: Modifier = Modifier) {
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = TangemTheme.colors.icon.primary1,
modifier = Modifier,
)
}
}
@Composable
private fun ArchiveAccountError(state: AccountArchivedUM.Error, modifier: Modifier = Modifier) {
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
text = stringResourceSafe(R.string.common_unable_to_load),
)
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(R.string.try_to_load_data_again_button_title),
onClick = state.onRetryClick,
),
)
}
}
}
@Composable
private fun ArchiveAccountContent(state: AccountArchivedUM.Content, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier) {
itemsIndexed(
items = state.accounts,
key = { index, item -> item.accountId },
) { index, account ->
ArchivedAccountRow(
item = account,
modifier = Modifier.roundedShapeItemDecoration(
backgroundColor = TangemTheme.colors.background.primary,
radius = TangemTheme.dimens.radius20,
currentIndex = index,
addDefaultPadding = true,
lastIndex = state.accounts.lastIndex,
),
)
}
}
}
@Composable
private fun ArchivedAccountRow(item: ArchivedAccountUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = { item.onClick(item.accountId) })
.padding(all = TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
AccountIcon(
modifier = Modifier
.size(36.dp)
.clip(RoundedCornerShape(9.dp)),
accountName = item.accountName,
accountIcon = item.accountIcon,
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
) {
Text(
text = item.accountName,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
text = item.tokensInfo.resolveReference(),
)
}
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(R.string.account_archived_recover),
onClick = { item.onClick(item.accountId) },
),
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun WcConnectionsContentPreview(@PreviewParameter(PreviewStateProvider::class) params: AccountArchivedUM) {
TangemThemePreview {
ArchivedAccountListContent(state = params)
}
}
@Suppress("MagicNumber")
private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountArchivedUM>(
buildList {
fun portfolioIcon() = CryptoPortfolioIcon.ofDefaultCustomAccount().toUM()
val firstList = List(10) {
ArchivedAccountUM(
accountId = it.toString(),
accountName = "Account name",
accountIcon = portfolioIcon(),
tokensInfo = stringReference("10 tokens in 2 networks"),
onClick = {},
)
}.toImmutableList()
val first = AccountArchivedUM.Content(
onCloseClick = {},
accounts = firstList,
)
add(first)
add(AccountArchivedUM.Loading {})
add(AccountArchivedUM.Error({}, {}))
},
)

View file

@ -183,7 +183,7 @@ private fun AccountRow(state: AccountDetailsUM) {
// todo account make reusable
@Composable
private fun AccountIcon(accountName: String, accountIcon: CryptoPortfolioIconUM, modifier: Modifier = Modifier) {
internal fun AccountIcon(accountName: String, accountIcon: CryptoPortfolioIconUM, modifier: Modifier = Modifier) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier.background(accountIcon.color.getUiColor()),