Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-13 18:09:01 +05:00
parent 1443cbb961
commit fafb6ed65c
5 changed files with 630 additions and 5 deletions

View file

@ -0,0 +1,62 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.wallet.UserWalletId
/** Wallet card state */
@Immutable
internal sealed interface WalletBalanceUM {
/** Wallet Id */
val id: UserWalletId
/** Wallet Name */
val name: String
/**
* Wallet card content state
*
* @property id wallet id
* @property name wallet name
* @property balance wallet balance
*/
data class Content(
override val id: UserWalletId,
override val name: String,
val balance: TextReference,
val isBalanceFlickering: Boolean,
val isZeroBalance: Boolean?,
) : WalletBalanceUM
/**
* Wallet card error state
*
* @property id wallet id
* @property name wallet name
*/
data class Error(
override val id: UserWalletId,
override val name: String,
) : WalletBalanceUM
/**
* Wallet card loading state
*
* @property id wallet id
* @property name wallet name
*/
data class Loading(
override val id: UserWalletId,
override val name: String,
) : WalletBalanceUM
fun copySealed(name: String): WalletBalanceUM {
return when (this) {
is Content -> copy(name = name)
is Error -> copy(name = name)
is Loading -> copy(name = name)
}
}
}

View file

@ -0,0 +1,434 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.annotation.DrawableRes
import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.message.TangemMessageButtonUM
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.core.ui.ds.message.TangemMessageUM
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import kotlinx.collections.immutable.persistentListOf
/**
* Wallet notification types
*/
internal enum class WalletNotificationType {
Status,
Critical,
Warning,
Promo,
Survey,
Informational,
}
/**
* Wallet notification UI model
*
* @property messageUM - message to show in notification
* @property type - type of notification, affects design and priority
*/
internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val type: WalletNotificationType) {
data object DevCard : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "DevCardNotification",
title = resourceReference(id = R.string.warning_developer_card_title),
subtitle = resourceReference(id = R.string.warning_developer_card_message),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Warning,
)
data object FailedCardValidation : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "FailedCardValidationNotification",
title = resourceReference(id = R.string.warning_failed_to_verify_card_title),
subtitle = resourceReference(id = R.string.warning_failed_to_verify_card_message),
messageEffect = TangemMessageEffect.Warning,
),
type = WalletNotificationType.Status,
)
data class BackupError(val onClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "BackupErrorNotification",
title = resourceReference(id = R.string.warning_backup_errors_title),
subtitle = resourceReference(id = R.string.warning_backup_errors_message),
messageEffect = TangemMessageEffect.Warning,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.common_contact_support),
type = TangemButtonType.PrimaryInverse,
onClick = onClick,
),
),
),
type = WalletNotificationType.Warning,
)
data class SeedPhraseNotification(
val onDeclineClick: () -> Unit,
val onConfirmClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "SeedPhraseIssueNotification",
title = resourceReference(id = R.string.warning_seedphrase_issue_title),
subtitle = resourceReference(id = R.string.warning_seedphrase_issue_message),
messageEffect = TangemMessageEffect.Warning,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.common_no),
type = TangemButtonType.PrimaryInverse,
onClick = onDeclineClick,
),
TangemMessageButtonUM(
text = resourceReference(id = R.string.common_yes),
type = TangemButtonType.PrimaryInverse,
onClick = onConfirmClick,
),
),
),
type = WalletNotificationType.Critical,
)
data class SeedPhraseSecondNotification(
val onDeclineClick: () -> Unit,
val onConfirmClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "SeedPhraseSecondIssueNotification",
title = resourceReference(id = R.string.warning_seedphrase_action_required_title),
subtitle = resourceReference(id = R.string.warning_seedphrase_contacted_support),
messageEffect = TangemMessageEffect.Warning,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.seed_warning_no),
type = TangemButtonType.PrimaryInverse,
onClick = onDeclineClick,
),
TangemMessageButtonUM(
text = resourceReference(id = R.string.seed_warning_yes),
type = TangemButtonType.PrimaryInverse,
onClick = onConfirmClick,
),
),
),
type = WalletNotificationType.Critical,
)
data class MissingBackup(val onClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "MissingBackupNotification",
title = resourceReference(id = R.string.warning_no_backup_title),
subtitle = resourceReference(id = R.string.warning_no_backup_message),
messageEffect = TangemMessageEffect.Warning,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.button_start_backup_process),
type = TangemButtonType.PrimaryInverse,
onClick = onClick,
),
),
),
type = WalletNotificationType.Critical,
)
data object SomeNetworksUnreachable : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "SomeNetworksUnreachableNotification",
title = resourceReference(id = R.string.warning_some_networks_unreachable_title),
subtitle = resourceReference(id = R.string.warning_some_networks_unreachable_message),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Status,
)
data class NumberOfSignedHashesIncorrect(val onCloseClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "NumberOfSignedHashesIncorrectNotification",
title = resourceReference(id = R.string.warning_number_of_signed_hashes_incorrect_title),
subtitle = resourceReference(id = R.string.warning_number_of_signed_hashes_incorrect_message),
messageEffect = TangemMessageEffect.Warning,
onCloseClick = onCloseClick,
),
type = WalletNotificationType.Warning,
)
data object TestnetCard : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "TestnetCardNotification",
title = resourceReference(id = R.string.warning_testnet_card_title),
subtitle = resourceReference(id = R.string.warning_testnet_card_message),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Warning,
)
data class LowSignatures(val count: Int) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "LowSignaturesNotification",
title = resourceReference(id = R.string.warning_low_signatures_title),
subtitle = resourceReference(
id = R.string.warning_low_signatures_message,
formatArgs = wrappedList(count.toString()),
),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Critical,
)
data class MissingAddresses(
@DrawableRes val tangemIcon: Int?,
val missingAddressesCount: Int,
val onGenerateClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "MissingAddressesNotification",
title = resourceReference(id = R.string.warning_missing_derivation_title),
subtitle = pluralReference(
id = R.plurals.warning_missing_derivation_message,
count = missingAddressesCount,
formatArgs = wrappedList(missingAddressesCount),
),
isCentered = true,
iconUM = tangemIcon?.let { TangemIconUM.Icon(it) },
messageEffect = TangemMessageEffect.Magic,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.common_generate_addresses),
type = TangemButtonType.PrimaryInverse,
iconRes = tangemIcon,
onClick = onGenerateClick,
),
),
),
type = WalletNotificationType.Warning,
)
data class NoAccount(val network: String, val symbol: String, val amount: String) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "NoAccountNotification",
title = resourceReference(id = R.string.warning_no_account_title),
subtitle = resourceReference(
id = R.string.no_account_generic,
wrappedList(network, amount, symbol),
),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Warning,
)
data object DemoCard : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "DemoCardNotification",
title = resourceReference(id = R.string.warning_demo_mode_title),
subtitle = resourceReference(id = R.string.warning_demo_mode_title),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Warning,
)
data class NoteMigration(val onClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "NoteMigrationNotification",
title = resourceReference(R.string.wallet_promo_banner_title),
subtitle = resourceReference(R.string.wallet_promo_banner_description),
messageEffect = TangemMessageEffect.None,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.wallet_promo_banner_button_title),
onClick = onClick,
type = TangemButtonType.PrimaryInverse,
),
),
),
type = WalletNotificationType.Promo,
)
data class UnlockWallets(val onClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "UnlockWalletsNotification",
title = resourceReference(id = R.string.common_access_denied),
subtitle = resourceReference(
id = R.string.warning_access_denied_message,
formatArgs = wrappedList(
resourceReference(R.string.common_biometrics),
),
),
onClick = onClick,
messageEffect = TangemMessageEffect.Magic,
isCentered = true,
),
type = WalletNotificationType.Warning,
)
data class RateApp(
val onLikeClick: () -> Unit,
val onDislikeClick: () -> Unit,
val onCloseClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "RateAppNotification",
title = resourceReference(id = R.string.warning_rate_app_title),
subtitle = resourceReference(id = R.string.warning_rate_app_message),
isCentered = true,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.warning_button_could_be_better),
type = TangemButtonType.PrimaryInverse,
onClick = onDislikeClick,
),
TangemMessageButtonUM(
text = resourceReference(id = R.string.warning_button_like_it),
type = TangemButtonType.Primary,
onClick = onLikeClick,
),
),
messageEffect = TangemMessageEffect.None,
onCloseClick = onCloseClick,
),
type = WalletNotificationType.Survey,
)
data object UsedOutdatedData : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "UsedOutdatedDataNotification",
title = stringReference("Missing some token balances"), // todo redesign main lokalise
subtitle = stringReference("Will be updated as soon as possible"),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Status,
)
data class FinishWalletActivation(
val messageEffect: TangemMessageEffect,
val isBackupExists: Boolean,
val onClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "FinishWalletActivationNotification",
title = resourceReference(R.string.hw_activation_need_title),
subtitle = if (isBackupExists) {
resourceReference(R.string.hw_activation_need_warning_description)
} else {
resourceReference(R.string.hw_activation_need_description)
},
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.img_knight_shield_32,
tintReference = { TangemTheme.colors2.graphic.status.attention },
),
messageEffect = messageEffect,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.hw_activation_need_finish),
type = TangemButtonType.PrimaryInverse,
onClick = onClick,
),
),
),
type = when (messageEffect) {
TangemMessageEffect.Card -> WalletNotificationType.Critical
else -> WalletNotificationType.Warning
},
)
data class PushNotifications(
val onCloseClick: () -> Unit,
val onEnabledClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "PushNotificationsNotification",
title = resourceReference(R.string.user_push_notification_banner_title),
subtitle = resourceReference(R.string.user_push_notification_banner_subtitle),
onCloseClick = onCloseClick,
messageEffect = TangemMessageEffect.Card,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.common_later),
type = TangemButtonType.PrimaryInverse,
onClick = onCloseClick,
),
TangemMessageButtonUM(
text = resourceReference(R.string.common_enable),
type = TangemButtonType.Primary,
onClick = onEnabledClick,
),
),
),
type = WalletNotificationType.Informational,
)
data class CloreMigration(
val onStartMigrationClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "CloreMigrationNotification",
title = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_title),
subtitle = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_description),
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.status.attention },
),
messageEffect = TangemMessageEffect.None,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_button),
onClick = onStartMigrationClick,
type = TangemButtonType.PrimaryInverse,
),
),
),
type = WalletNotificationType.Informational,
)
data class OnePlusOnePromo(
val onCloseClick: () -> Unit,
val onClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "OnePlusOnePromoNotification",
title = resourceReference(R.string.notification_one_plus_one_title),
subtitle = resourceReference(R.string.notification_one_plus_one_text),
messageEffect = TangemMessageEffect.Magic,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.notification_one_plus_one_button),
type = TangemButtonType.PrimaryInverse,
onClick = onCloseClick,
),
TangemMessageButtonUM(
text = resourceReference(R.string.notification_one_plus_one_button),
type = TangemButtonType.Primary,
onClick = onClick,
),
),
),
type = WalletNotificationType.Promo,
)
data class YieldPromo(
val onCloseClick: () -> Unit,
val onTermsAndConditionsClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "YieldPromoNotification",
title = resourceReference(R.string.notification_yield_promo_title),
subtitle = resourceReference(R.string.notification_yield_promo_text),
onCloseClick = onCloseClick,
messageEffect = TangemMessageEffect.Magic,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.notification_yield_promo_button),
type = TangemButtonType.Primary,
onClick = onTermsAndConditionsClick,
),
),
),
type = WalletNotificationType.Promo,
)
}

View file

@ -55,11 +55,6 @@ internal sealed interface WalletState : WalletStateHolder {
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
override val tangemPayState: TangemPayState = TangemPayState.Empty
}
enum class WalletType {
Hot,
Cold,
}
}
sealed class SingleCurrency : WalletState, TxHistoryStateHolder {
@ -96,4 +91,9 @@ internal sealed interface WalletState : WalletStateHolder {
override val marketPriceBlockState: MarketPriceBlockState? = null
}
}
}
enum class WalletType {
Hot,
Cold,
}

View file

@ -0,0 +1,79 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.row.TangemRowUM
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
/**
* State of the tokens list in the wallet screen
*
* @property tokenList list of tokens to display
* @property organizeButtonUM configuration for the "Organize Tokens" button, if it should
*/
@Immutable
internal sealed class WalletTokensListUM {
abstract val tokenList: ImmutableList<TokensListItemUM2>
abstract val organizeButtonUM: TangemButtonUM?
data object Empty : WalletTokensListUM() {
override val tokenList: ImmutableList<TokensListItemUM2.Portfolio> = persistentListOf()
override val organizeButtonUM: TangemButtonUM? = null
}
data object Loading : WalletTokensListUM() {
override val tokenList: ImmutableList<TokensListItemUM2> = persistentListOf(
TokensListItemUM2.Portfolio(
tokenRowUM = TangemTokenRowUM.Loading(id = "0"),
tokenList = persistentListOf(),
isExpanded = false,
isCollapsable = true,
),
TokensListItemUM2.Portfolio(
tokenRowUM = TangemTokenRowUM.Loading(id = "1"),
tokenList = persistentListOf(),
isExpanded = false,
isCollapsable = true,
),
TokensListItemUM2.Portfolio(
tokenRowUM = TangemTokenRowUM.Loading(id = "2"),
tokenList = persistentListOf(),
isExpanded = false,
isCollapsable = true,
),
)
override val organizeButtonUM: TangemButtonUM? = null
}
data class Content(
override val tokenList: ImmutableList<TokensListItemUM2>,
override val organizeButtonUM: TangemButtonUM?,
) : WalletTokensListUM()
}
/**
* State of token list item in the wallet screen
*/
@Immutable
internal sealed interface TokensListItemUM2 {
val tokenRowUM: TangemRowUM
data class GroupTitle(
override val tokenRowUM: TangemHeaderRowUM,
) : TokensListItemUM2
data class Token(
override val tokenRowUM: TangemTokenRowUM,
) : TokensListItemUM2
data class Portfolio(
override val tokenRowUM: TangemTokenRowUM,
val tokenList: ImmutableList<TokensListItemUM2>,
val isExpanded: Boolean,
val isCollapsable: Boolean,
) : TokensListItemUM2
}

View file

@ -0,0 +1,50 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.ds.button.TangemButtonUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
@Immutable
internal sealed interface WalletUM {
val pullToRefreshConfig: PullToRefreshConfig
val walletsBalanceUM: WalletBalanceUM
val buttons: PersistentList<TangemButtonUM>
val notifications: ImmutableList<WalletNotificationUM>
val tokensListUM: WalletTokensListUM
val nftState: WalletNFTItemUM
val type: WalletType
val tangemPayState: TangemPayState
data class Content(
override val pullToRefreshConfig: PullToRefreshConfig,
override val walletsBalanceUM: WalletBalanceUM,
override val buttons: PersistentList<TangemButtonUM>,
override val notifications: ImmutableList<WalletNotificationUM>,
override val tokensListUM: WalletTokensListUM,
override val nftState: WalletNFTItemUM,
override val type: WalletType,
override val tangemPayState: TangemPayState,
val stackableNotifications: ImmutableList<WalletNotificationUM>,
) : WalletUM
data class Locked(
override val walletsBalanceUM: WalletBalanceUM,
override val buttons: PersistentList<TangemButtonUM>,
override val type: WalletType,
override val notifications: ImmutableList<WalletNotificationUM> = persistentListOf(),
) : WalletUM {
override val pullToRefreshConfig = PullToRefreshConfig(false, {})
override val tokensListUM: WalletTokensListUM = WalletTokensListUM.Empty // todo redesign main locked state
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
override val tangemPayState: TangemPayState = TangemPayState.Empty
}
}