Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-09 13:33:25 +03:00
commit a651da10e5
143 changed files with 3655 additions and 899 deletions

View file

@ -148,6 +148,13 @@ internal class FeedEntryChildFactory @Inject constructor(
appComponentContext = appComponentContext,
params = DefaultSearchComponent.Params(
onBackClick = onBackClicked,
onMarketTokenClick = { token, currency ->
feedEntryClickIntents.onMarketItemClick(
token = token,
appCurrency = currency,
source = AnalyticsParam.ScreensSources.Market.value,
)
},
),
)
}

View file

@ -15,6 +15,8 @@ import com.tangem.core.ui.ds.field.search.TangemFieldShape
import com.tangem.core.ui.ds.field.search.TangemSearchField
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.features.feed.model.search.SearchModel
import com.tangem.features.feed.ui.search.SearchContent
import com.tangem.features.feed.ui.search.state.SearchCallbacks
@ -74,6 +76,7 @@ internal class DefaultSearchComponent(
onClearHintsClick = model::clearSearchHistory,
onTextHintClick = model::onTextHintClick,
onResultMarketTokenClick = model::onResultMarketTokenClick,
onHistoryTokenClick = model::onHistoryTokenClick,
)
}
SearchContent(
@ -86,5 +89,6 @@ internal class DefaultSearchComponent(
data class Params(
val onBackClick: () -> Unit,
val onMarketTokenClick: ((TokenMarketParams, AppCurrency) -> Unit),
)
}

View file

@ -1,19 +1,21 @@
package com.tangem.features.feed.model.search
import arrow.core.getOrElse
import com.tangem.common.ui.charts.state.MarketChartData
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
import com.tangem.common.ui.charts.state.sorted
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.common.ui.charts.state.MarketChartData
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
import com.tangem.common.ui.charts.state.sorted
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
import com.tangem.domain.markets.GetTokenPriceChartUseCase
import com.tangem.domain.markets.PriceChangeInterval
import com.tangem.domain.models.account.AccountName
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.markets.toSerializableParam
import com.tangem.domain.search.usecase.ClearSearchHistoryUseCase
import com.tangem.domain.search.usecase.GetSearchResultsUseCase
import com.tangem.domain.search.usecase.SaveRecentSearchTokenUseCase
@ -26,6 +28,7 @@ import com.tangem.features.feed.model.search.converter.MarketsListItemUMToRecent
import com.tangem.features.feed.model.search.converter.MarketsListItemUMWithAppCurrency
import com.tangem.features.feed.model.search.converter.RecentSearchTokenToMarketsListItemUMConverter
import com.tangem.features.feed.model.search.converter.RecentSearchTokenWithAppCurrency
import com.tangem.features.feed.model.search.converter.UserAssetSearchItemConverter
import com.tangem.features.feed.model.search.state.SearchStateController
import com.tangem.features.feed.model.search.state.transformers.*
import com.tangem.features.feed.ui.search.state.*
@ -35,13 +38,8 @@ import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
private const val UPDATE_QUOTES_TIMER_MILLIS = 60000L
@ -54,6 +52,7 @@ internal class SearchModel @Inject constructor(
paramsContainer: ParamsContainer,
getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getSearchResultsUseCase: GetSearchResultsUseCase,
private val saveSearchQueryUseCase: SaveSearchQueryUseCase,
private val saveRecentSearchTokenUseCase: SaveRecentSearchTokenUseCase,
@ -77,6 +76,13 @@ internal class SearchModel @Inject constructor(
initialValue = AppCurrency.Default,
)
private val isBalanceHidden = getBalanceHidingSettingsUseCase.isBalanceHidden()
.stateIn(
scope = modelScope,
started = SharingStarted.Eagerly,
initialValue = false,
)
private val marketsListItemToRecentSearchTokenConverter by lazy {
MarketsListItemUMToRecentSearchTokenConverter()
}
@ -143,17 +149,38 @@ internal class SearchModel @Inject constructor(
)
saveRecentSearchTokenUseCase(marketsListItemToRecentSearchTokenConverter.convert(input))
saveSearchQueryUseCase(stateController.value.searchBar.query)
withContext(dispatchers.mainImmediate) {
searchMarketsListManager.getTokenById(item.id)?.let { found ->
params.onMarketTokenClick(found.toSerializableParam(), appCurrency)
}
}
}
}
fun onHistoryTokenClick(item: MarketsListItemUM) {
val tokenMarketParams = TokenMarketParams(
id = item.id,
name = item.name,
symbol = item.currencySymbol,
tokenQuotes = TokenMarketParams.Quotes(
currentPrice = item.price.fiatPrice,
h24Percent = null,
weekPercent = null,
monthPercent = null,
),
imageUrl = item.iconUrl,
)
params.onMarketTokenClick(tokenMarketParams, currentAppCurrency.value)
}
private fun initCallbacks() {
stateController.update(object : SearchUMTransformer {
override fun transform(prevState: SearchUM): SearchUM {
return prevState.copy(
searchBar = prevState.searchBar.copy(
onQueryChange = ::onQueryChange,
onActiveChange = ::onActiveChange,
onClearClick = ::onClearClick,
onCancelClick = params.onBackClick,
),
)
}
@ -164,10 +191,6 @@ internal class SearchModel @Inject constructor(
stateController.update(UpdateSearchBarQueryTransformer(query))
}
private fun onActiveChange(isActive: Boolean) {
if (!isActive) params.onBackClick()
}
private fun onClearClick() {
stateController.update(UpdateSearchBarQueryTransformer(""))
}
@ -200,20 +223,19 @@ internal class SearchModel @Inject constructor(
private fun subscribeToSearchResults(query: String) {
modelScope.launch {
getSearchResultsUseCase(query = query).collectLatest { searchResult ->
val userAssets = searchResult.userAssets.map { entry ->
UserAssetItemUM(
id = "${entry.userWalletId.stringValue}_${entry.accountId.value}" +
"_${entry.currencyStatus.currency.id.value}",
tokenIconUrl = entry.currencyStatus.currency.iconUrl,
tokenName = entry.currencyStatus.currency.name,
tokenSymbol = entry.currencyStatus.currency.symbol,
accountName = entry.accountName.toDisplayString(),
onClick = {
// TODO in [REDACTED_TASK_KEY] while just a stub item. Will be handled in next task.
},
)
}.toImmutableList()
combine(
getSearchResultsUseCase(query = query),
currentAppCurrency,
isBalanceHidden,
) { searchResult, appCurrency, balanceHidden ->
val converter = UserAssetSearchItemConverter(
appCurrency = appCurrency,
isBalanceHidden = balanceHidden,
)
searchResult.userAssets
.map(converter::convert)
.toImmutableList()
}.collectLatest { userAssets ->
stateController.update(UpdateUserAssetsTransformer(userAssets))
}
}.saveIn(searchResultsJob)
@ -334,13 +356,6 @@ internal class SearchModel @Inject constructor(
}
}
private fun AccountName.toDisplayString(): String {
return when (this) {
is AccountName.DefaultMain -> "Main" // TODO [REDACTED_TASK_KEY] localize
is AccountName.Custom -> value
}
}
private fun CoroutineScope.loadQuotesWithTimer(timeMillis: Long) {
launch {
while (true) {

View file

@ -0,0 +1,92 @@
package com.tangem.features.feed.model.search.converter
import com.tangem.common.ui.account.toUM
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.search.model.UserAssetSearchEntry
import com.tangem.domain.search.model.UserAssetSearchItem
import com.tangem.features.feed.ui.search.state.UserAssetItemUM
import com.tangem.utils.StringsSigns
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
internal class UserAssetSearchItemConverter(
private val appCurrency: AppCurrency,
private val isBalanceHidden: Boolean,
) : Converter<UserAssetSearchItem, UserAssetItemUM> {
override fun convert(value: UserAssetSearchItem): UserAssetItemUM {
return when (value) {
is UserAssetSearchItem.Single -> convertSingle(value.entry)
is UserAssetSearchItem.Grouped -> convertGrouped(value)
}
}
private fun convertSingle(entry: UserAssetSearchEntry): UserAssetItemUM.Single {
val currency = entry.currencyStatus.currency
val value = entry.currencyStatus.value
return UserAssetItemUM.Single(
id = "${entry.userWalletId.stringValue}_${entry.accountId.value}_${currency.id.value}",
icon = TangemIconUM.Currency(
currencyIconState = CryptoCurrencyToIconStateConverter().convert(entry.currencyStatus),
),
tokenName = currency.name,
tokenSymbol = currency.symbol,
fiatRate = value.fiatRate?.format { fiat(appCurrency.code, appCurrency.symbol) },
cryptoBalance = formatCryptoAmount(value.amount, currency.symbol, currency.decimals),
fiatBalance = formatFiatAmount(value.fiatAmount),
isBalanceHidden = isBalanceHidden,
onClick = {},
)
}
private fun convertGrouped(item: UserAssetSearchItem.Grouped): UserAssetItemUM.Grouped {
val totalFiat = item.entries.sumOf { it.currencyStatus.value.fiatAmount ?: BigDecimal.ZERO }
val totalCrypto = item.entries.sumOf { it.currencyStatus.value.amount ?: BigDecimal.ZERO }
val firstCurrency = item.entries.first().currencyStatus.currency
val children = item.entries.map { entry ->
UserAssetItemUM.GroupedChild(
walletName = entry.userWalletName,
accountName = entry.accountName.toUM(),
accountIcon = entry.accountIcon.value,
accountColor = entry.accountIcon.color,
cryptoBalance = formatCryptoAmount(
entry.currencyStatus.value.amount,
entry.currencyStatus.currency.symbol,
entry.currencyStatus.currency.decimals,
),
fiatBalance = formatFiatAmount(entry.currencyStatus.value.fiatAmount),
)
}.toImmutableList()
return UserAssetItemUM.Grouped(
id = "grouped_${item.tokenName}_${item.tokenSymbol}",
icon = TangemIconUM.Currency(
currencyIconState = CryptoCurrencyToIconStateConverter().convert(item.entries.first().currencyStatus),
),
tokenName = item.tokenName,
tokenSymbol = item.tokenSymbol,
tokensCount = item.entries.size,
totalCryptoBalance = formatCryptoAmount(totalCrypto, firstCurrency.symbol, firstCurrency.decimals),
totalFiatBalance = formatFiatAmount(totalFiat),
isBalanceHidden = isBalanceHidden,
children = children,
onClick = {},
)
}
private fun formatCryptoAmount(amount: BigDecimal?, symbol: String, decimals: Int): String {
return amount?.format { crypto(symbol, decimals) } ?: StringsSigns.DASH_SIGN
}
private fun formatFiatAmount(fiatAmount: BigDecimal?): String {
return fiatAmount?.format { fiat(appCurrency.code, appCurrency.symbol) } ?: StringsSigns.DASH_SIGN
}
}

View file

@ -3,20 +3,16 @@ package com.tangem.features.feed.ui.search
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.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity
@ -32,7 +28,6 @@ import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.list.InfiniteListHandler
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
@ -54,6 +49,15 @@ internal fun SearchContent(
val lazyListState = rememberLazyListState()
val background = LocalMainBottomSheetColor.current.value
val contentStructureKey = when (content) {
is SearchContentUM.InitialEmpty -> "empty"
is SearchContentUM.History -> "history"
is SearchContentUM.Results -> "results_${content.userAssets.isNotEmpty()}"
}
LaunchedEffect(contentStructureKey) {
lazyListState.scrollToItem(0)
}
LazyColumn(
state = lazyListState,
modifier = modifier
@ -72,6 +76,7 @@ internal fun SearchContent(
history = content,
onClearAllClick = searchCallbacks.onClearHintsClick,
onHintClick = searchCallbacks.onTextHintClick,
onHistoryTokenClick = searchCallbacks.onHistoryTokenClick,
)
is SearchContentUM.Results -> searchResultsItems(
results = content,
@ -98,6 +103,7 @@ private fun LazyListScope.searchHistoryItems(
history: SearchContentUM.History,
onClearAllClick: (() -> Unit),
onHintClick: (String) -> Unit,
onHistoryTokenClick: (MarketsListItemUM) -> Unit,
) {
if (!history.textHints.isEmpty() || !history.recentTokens.isEmpty()) {
item(key = "recents") {
@ -107,15 +113,20 @@ private fun LazyListScope.searchHistoryItems(
)
}
}
items(
itemsIndexed(
items = history.textHints,
key = { "hint_${it.text}" },
) { hint ->
key = { _, item -> "hint_${item.text}" },
) { index, hint ->
TextHintItem(hint = hint, onHintClick = { onHintClick(hint.text) })
HorizontalDivider(
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x2),
color = TangemTheme.colors2.border.neutral.primary,
)
if (index < history.textHints.size - 1) {
HorizontalDivider(
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x2),
color = TangemTheme.colors2.border.neutral.primary,
)
}
}
item {
SpacerH(TangemTheme.dimens2.x2)
}
items(
items = history.recentTokens,
@ -129,7 +140,7 @@ private fun LazyListScope.searchHistoryItems(
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
),
model = token,
onClick = {}, // TODO in [REDACTED_TASK_KEY]
onClick = { onHistoryTokenClick(token) },
)
}
}
@ -264,9 +275,16 @@ private fun TextHintItem(hint: TextHintItemUM, onHintClick: () -> Unit) {
}
}
// TODO in [REDACTED_TASK_KEY] while just a stub item. Will be handled in next task.
@Composable
private fun UserAssetItem(asset: UserAssetItemUM) {
when (asset) {
is UserAssetItemUM.Single -> SingleUserAssetItem(asset)
is UserAssetItemUM.Grouped -> GroupedUserAssetItem(asset)
}
}
@Composable
private fun SingleUserAssetItem(asset: UserAssetItemUM.Single) {
Row(
modifier = Modifier
.fillMaxWidth()
@ -276,10 +294,8 @@ private fun UserAssetItem(asset: UserAssetItemUM) {
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
TangemIcon(
tangemIconUM = TangemIconUM.Url(asset.tokenIconUrl, fallbackRes = R.drawable.ic_custom_token_44),
modifier = Modifier
.size(40.dp)
.clip(CircleShape),
modifier = Modifier.size(40.dp),
tangemIconUM = asset.icon,
)
Column(modifier = Modifier.weight(1f)) {
Text(
@ -289,12 +305,79 @@ private fun UserAssetItem(asset: UserAssetItemUM) {
maxLines = 1,
)
Text(
text = "${asset.tokenSymbol} · ${asset.accountName}",
text = asset.tokenSymbol,
style = TangemTheme.typography2.captionRegular13,
color = TangemTheme.colors2.text.neutral.tertiary,
maxLines = 1,
)
}
if (!asset.isBalanceHidden) {
Column(horizontalAlignment = Alignment.End) {
Text(
text = asset.fiatBalance,
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
)
Text(
text = asset.cryptoBalance,
style = TangemTheme.typography2.captionRegular13,
color = TangemTheme.colors2.text.neutral.tertiary,
maxLines = 1,
)
}
}
}
}
// TODO [REDACTED_JIRA] update ui item to Portfolio block item
@Composable
private fun GroupedUserAssetItem(asset: UserAssetItemUM.Grouped) {
Column(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = asset.onClick)
.padding(horizontal = 12.dp, vertical = 14.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
TangemIcon(
modifier = Modifier.size(40.dp),
tangemIconUM = asset.icon,
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = asset.tokenName,
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
)
Text(
text = "${asset.tokenSymbol} · ${asset.tokensCount}",
style = TangemTheme.typography2.captionRegular13,
color = TangemTheme.colors2.text.neutral.tertiary,
maxLines = 1,
)
}
if (!asset.isBalanceHidden) {
Column(horizontalAlignment = Alignment.End) {
Text(
text = asset.totalFiatBalance,
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
)
Text(
text = asset.totalCryptoBalance,
style = TangemTheme.typography2.captionRegular13,
color = TangemTheme.colors2.text.neutral.tertiary,
maxLines = 1,
)
}
}
}
}
}

View file

@ -2,11 +2,7 @@ package com.tangem.features.feed.ui.search.preview
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
@ -15,7 +11,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -206,18 +204,22 @@ internal object SearchContentPreviewFixtures {
updateTimestamp = updateTimestamp,
)
private fun userAsset(
id: String,
name: String,
symbol: String,
accountName: String,
iconUrl: String? = null,
): UserAssetItemUM = UserAssetItemUM(
private fun userAsset(id: String, name: String, symbol: String): UserAssetItemUM = UserAssetItemUM.Single(
id = id,
tokenIconUrl = iconUrl,
icon = TangemIconUM.Currency(
CurrencyIconState.CoinIcon(
url = null,
fallbackResId = com.tangem.core.ui.R.drawable.ic_ethereumpow_22,
isGrayscale = false,
shouldShowCustomBadge = false,
),
),
tokenName = name,
tokenSymbol = symbol,
accountName = accountName,
fiatRate = "$98,765.43",
cryptoBalance = "1.234 $symbol",
fiatBalance = "$121,876.50",
isBalanceHidden = false,
onClick = {},
)
@ -251,13 +253,8 @@ internal object SearchContentPreviewFixtures {
)
private fun portfolioTwo(): ImmutableList<UserAssetItemUM> = persistentListOf(
userAsset(id = "p1", name = "Ethereum", symbol = "ETH", accountName = "Main wallet"),
userAsset(
id = "p2",
name = "Polygon",
symbol = "POL",
accountName = "Account with a long label for preview",
),
userAsset(id = "p1", name = "Ethereum", symbol = "ETH"),
userAsset(id = "p2", name = "Polygon", symbol = "POL"),
)
private fun marketListShort(): ImmutableList<MarketsListItemUM> = persistentListOf(
@ -390,6 +387,7 @@ private val SearchContentPreviewCallbacks = SearchCallbacks(
onClearHintsClick = {},
onTextHintClick = { _ -> },
onResultMarketTokenClick = { _ -> },
onHistoryTokenClick = { _ -> },
)
/** All [SearchContentPreviewScenario] values for the Preview Parameter dropdown in Android Studio. */

View file

@ -7,4 +7,5 @@ internal data class SearchCallbacks(
val onClearHintsClick: () -> Unit,
val onTextHintClick: (hint: String) -> Unit,
val onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
val onHistoryTokenClick: (MarketsListItemUM) -> Unit,
)

View file

@ -1,8 +1,11 @@
package com.tangem.features.feed.ui.search.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.account.AccountNameUM
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.domain.models.account.CryptoPortfolioIcon
import kotlinx.collections.immutable.ImmutableList
data class SearchUM(
@ -42,11 +45,45 @@ sealed interface MarketSearchResultUM {
data class TextHintItemUM(val text: String)
data class UserAssetItemUM(
val id: String,
val tokenIconUrl: String?,
val tokenName: String,
val tokenSymbol: String,
val accountName: String,
val onClick: () -> Unit,
)
@Immutable
sealed interface UserAssetItemUM {
val id: String
val icon: TangemIconUM
val tokenName: String
val tokenSymbol: String
val onClick: () -> Unit
data class Single(
override val id: String,
override val icon: TangemIconUM,
override val tokenName: String,
override val tokenSymbol: String,
val fiatRate: String?,
val cryptoBalance: String,
val fiatBalance: String,
val isBalanceHidden: Boolean,
override val onClick: () -> Unit,
) : UserAssetItemUM
data class Grouped(
override val id: String,
override val icon: TangemIconUM,
override val tokenName: String,
override val tokenSymbol: String,
val tokensCount: Int,
val totalCryptoBalance: String,
val totalFiatBalance: String,
val isBalanceHidden: Boolean,
val children: ImmutableList<GroupedChild>,
override val onClick: () -> Unit,
) : UserAssetItemUM
data class GroupedChild(
val walletName: String,
val accountName: AccountNameUM,
val accountIcon: CryptoPortfolioIcon.Icon,
val accountColor: CryptoPortfolioIcon.Color,
val cryptoBalance: String,
val fiatBalance: String,
)
}