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

@ -8,6 +8,7 @@ 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.account.ArchivedAccountListComponent
import com.tangem.features.account.AccountCreateEditComponent
import com.tangem.features.account.AccountDetailsComponent
import com.tangem.features.createwalletselection.CreateWalletSelectionComponent
@ -94,6 +95,7 @@ internal class ChildFactory @Inject constructor(
private val redesignedWalletConnectComponentFactory: WalletConnectEntryComponent.Factory,
private val accountCreateEditComponentFactory: AccountCreateEditComponent.Factory,
private val accountDetailsComponentFactory: AccountDetailsComponent.Factory,
private val archivedAccountListComponentFactory: ArchivedAccountListComponent.Factory,
private val nftComponentFactory: NFTComponent.Factory,
private val nftSendComponentFactory: NFTSendComponent.Factory,
private val usedeskComponentFactory: UsedeskComponent.Factory,
@ -565,6 +567,15 @@ internal class ChildFactory @Inject constructor(
componentFactory = accountDetailsComponentFactory,
)
}
is AppRoute.ArchivedAccountList -> {
createComponentChild(
context = context,
params = ArchivedAccountListComponent.Params(
userWalletId = route.userWalletId,
),
componentFactory = archivedAccountListComponentFactory,
)
}
}
}
}

View file

@ -346,4 +346,9 @@ sealed class AppRoute(val path: String) : Route {
data class AccountDetails(
val account: Account,
) : AppRoute(path = "/account_details/${account.accountId.value}")
@Serializable
data class ArchivedAccountList(
val userWalletId: UserWalletId,
) : AppRoute(path = "/archived_account/${userWalletId.stringValue}")
}

View file

@ -1408,6 +1408,7 @@
<string name="wc_alert_unknown_error_description">Fehlercode: %s. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support.</string>
<string name="wc_alert_unknown_error_description_no_error_code">Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support.</string>
<string name="wc_alert_unknown_error_title">Wir haben einen unbekannten Fehler festgestellt.</string>
<string name="wc_alert_unsupported_dapps_description">Tangem Wallet unterstützt derzeit nicht %s</string>
<string name="wc_alert_unsupported_method_description">Fehlercode: 8 005. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support.</string>
<string name="wc_alert_unsupported_method_title">Wir haben einen unbekannten Fehler festgestellt.</string>
<string name="wc_alert_unsupported_networks_description">Tangem unterstützt derzeit das erforderliches Netzwerk von %s nicht.</string>
@ -1460,7 +1461,7 @@
<string name="wc_transaction_request">Transaktionsanfrage</string>
<string name="wc_transaction_request_title">Transaktionsanfrage</string>
<string name="wc_unlimited_amount">Unbegrenzte Menge</string>
<string name="wc_wallet_connect">Wallet verbinden</string>
<string name="wc_wallet_connect">WalletConnect</string>
<string name="welcome_interrupted_backup_alert_discard">Verwerfen</string>
<string name="welcome_interrupted_backup_alert_message">Du hast eine unterbrochene Sicherung. Möchtest du diese fortsetzen?</string>
<string name="welcome_interrupted_backup_alert_resume">Ja, fortsetzen</string>

View file

@ -1355,6 +1355,7 @@
<string name="wc_transaction_request">Demande de transaction</string>
<string name="wc_transaction_request_title">Demande de transaction</string>
<string name="wc_unlimited_amount">Montant illimité</string>
<string name="wc_wallet_connect">WalletConnect</string>
<string name="welcome_interrupted_backup_alert_discard">Ignorer</string>
<string name="welcome_interrupted_backup_alert_message">Vous avez une sauvegarde interrompue. Voulez-vous la reprendre ?</string>
<string name="welcome_interrupted_backup_alert_resume">Oui, reprendre</string>

View file

@ -1359,7 +1359,7 @@
<string name="wc_errors_no_blockchains_title">Нет сетей</string>
<string name="wc_errors_proposal_expired_subtitle">Пожалуйста, сгенерируйте новый URI и попробуйте подключиться снова</string>
<string name="wc_errors_proposal_expired_title">Предложение подключения истекло</string>
<string name="wc_estimated_wallet_changes">Предварительные изменения</string>
<string name="wc_estimated_wallet_changes">Прогнозируемые изменения</string>
<string name="wc_estimated_wallet_changes_not_simulated">Не удалось выполнить симуляцию транзакции. Пожалуйста, действуйте с осторожностью.</string>
<string name="wc_estimation_is_not_supported">Оценка не поддерживается для %s</string>
<string name="wc_fee_suggested">Предложено %s</string>

View file

@ -1302,6 +1302,7 @@
<string name="wc_alert_session_disconnected_title">Сеанс Wallet Connect було завершено</string>
<string name="wc_alert_unknown_error_description">Код помилки: %s. Якщо проблема зберігається, зверніться до нашої служби підтримки.</string>
<string name="wc_alert_unknown_error_title">Ми зіткнулися з невідомою помилкою</string>
<string name="wc_alert_unsupported_dapps_description">Tangem Wallet наразі не підтримує %s</string>
<string name="wc_alert_unsupported_networks_description">Tangem наразі не підтримує необхідну мережу для %s.</string>
<string name="wc_alert_unsupported_networks_title">Непідтримувані мережі</string>
<string name="wc_alert_verified_domain_description">Цей домен пройшов перевірку та вважається безпечним, надійним і вільним від відомих загроз чи підозрілої активності. %s</string>
@ -1354,7 +1355,7 @@
<string name="wc_transaction_info_to_title">До</string>
<string name="wc_transaction_request">Запит транзакції</string>
<string name="wc_transaction_request_title">Запит транзакції</string>
<string name="wc_wallet_connect">Підключення гаманця</string>
<string name="wc_wallet_connect">WalletConnect</string>
<string name="welcome_interrupted_backup_alert_discard">Відмовитися</string>
<string name="welcome_interrupted_backup_alert_message">Ви не завершили резервне копіювання. Бажаєте продовжити?</string>
<string name="welcome_interrupted_backup_alert_resume">Так, поновити</string>

View file

@ -495,6 +495,7 @@
<string name="hw_create_seed_description">Stay up to date with the latest features and news</string>
<string name="hw_create_seed_title">Seed phrase backup</string>
<string name="hw_create_title">Create Mobile Wallet</string>
<string name="hw_import_seed_phrase_already_imported">This recovery phrase has already been imported</string>
<string name="hw_mobile_wallet">Mobile Wallet</string>
<string name="information_generated_with_ai">This information was generated with AI.\nTap here, if you find any errors.</string>
<string name="initial_message_change_access_code_body">To change the access code tap the card or ring as shown above and do not remove until the end of the operation</string>
@ -1504,12 +1505,18 @@
<string name="warning_token_trustline_button_title">Enable Trustline</string>
<string name="warning_token_trustline_subtitle">A Trustline must be enabled to receive this token. The network requires a %1$s %2$s reserve.</string>
<string name="warning_token_trustline_title">Trustline Required</string>
<string name="wc_alert_add_network_to_portfolio_description">The required network %s is not added to your portfolio. Add it first, then proceed with the connection.</string>
<string name="wc_alert_add_network_to_portfolio_title">Add network to portfolio</string>
<string name="wc_alert_audit_malicious_domain">Malicious domain</string>
<string name="wc_alert_audit_unknown_domain">Unknown domain</string>
<string name="wc_alert_connect_anyway">Connect anyway</string>
<string name="wc_alert_connection_timeout_description">Timeout error. Please, try again later.</string>
<string name="wc_alert_connection_timeout_title">Failed to establish WalletConnect</string>
<string name="wc_alert_domain_issues_description">This domain cannot be verified. Check the request carefully approving.</string>
<string name="wc_alert_network_not_connected_description">To continue, please reconnect your dApp session with the required network %s.</string>
<string name="wc_alert_network_not_connected_title">Network not connected</string>
<string name="wc_alert_request_timeout_description">Check your network connection</string>
<string name="wc_alert_request_timeout_title">Request timeout</string>
<string name="wc_alert_session_disconnected_description">Please return to your browser and reconnect via WalletConnect.</string>
<string name="wc_alert_session_disconnected_title">Wallet Connect session was disconnected</string>
<string name="wc_alert_sign_anyway">Sign anyway</string>
@ -1520,6 +1527,8 @@
<string name="wc_alert_unsupported_dapps_title">Unsupported dApp</string>
<string name="wc_alert_unsupported_method_description">Error code: 8 005. If the problem persists — feel free to contact our support.</string>
<string name="wc_alert_unsupported_method_title">We\'ve encountered unknown error</string>
<string name="wc_alert_unsupported_network_description">This network %s is not supported by Tangem Wallet and cannot be connected.</string>
<string name="wc_alert_unsupported_network_title">Unsupported network</string>
<string name="wc_alert_unsupported_networks_description">Tangem does not currently support a required network by %s.</string>
<string name="wc_alert_unsupported_networks_title">Unsupported networks</string>
<string name="wc_alert_verified_domain_description">This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. %s</string>

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()),