Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-01 17:04:42 +04:00
parent 902d80d1f1
commit e6cb71265e
7 changed files with 155 additions and 45 deletions

View file

@ -7,27 +7,29 @@ import kotlinx.collections.immutable.persistentListOf
/**
* Token list UM
*
* @property items items (search bar, tokens, headers)
* @property isBalanceHidden flag that indicates if balance should be hidden
* @property availableItems available items (search bar, header, tokens)
* @property unavailableItems unavailable items (header, tokens)
* @property isBalanceHidden flag that indicates if balance should be hidden
*
[REDACTED_AUTHOR]
*/
internal data class TokenListUM(
val items: ImmutableList<TokensListItemUM>,
val availableItems: ImmutableList<TokensListItemUM>,
val unavailableItems: ImmutableList<TokensListItemUM>,
val isBalanceHidden: Boolean,
) {
/** Get search bar if it exists */
fun getSearchBar(): TokensListItemUM.SearchBar? {
return items.firstOrNull() as? TokensListItemUM.SearchBar
return availableItems.firstOrNull() as? TokensListItemUM.SearchBar
}
/** Get tokens */
fun getTokens(): ImmutableList<TokensListItemUM> {
if (getSearchBar() == null) return items
if (getSearchBar() == null) return availableItems
return if (items.size > 1) {
items.subList(fromIndex = 1, toIndex = items.size)
return if (availableItems.size > 1) {
availableItems.subList(fromIndex = 1, toIndex = availableItems.size)
} else {
persistentListOf()
}

View file

@ -19,7 +19,8 @@ internal class TokenListUMController @Inject constructor() {
private val _state: MutableStateFlow<TokenListUM> = MutableStateFlow(
value = TokenListUM(
items = persistentListOf(),
availableItems = persistentListOf(),
unavailableItems = persistentListOf(),
isBalanceHidden = false,
),
)

View file

@ -21,7 +21,7 @@ internal abstract class SearchBarUMTransformer : TokenListUMTransformer {
val updatedSearchBar = searchBarItem.copy(searchBarUM = transform(searchBarItem.searchBarUM))
prevState.copy(
items = persistentListOf(
availableItems = persistentListOf(
updatedSearchBar,
*prevState.getTokens().toTypedArray(),
),

View file

@ -1,18 +1,26 @@
package com.tangem.features.onramp.tokenlist.entity.transformer
import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.common.ui.tokens.TokenItemStateConverter.Companion.getFormattedFiatAmount
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
import kotlinx.collections.immutable.toImmutableList
@Suppress("LongParameterList")
internal class UpdateTokenItemsTransformer(
private val tokenItemStateConverter: TokenItemStateConverter,
private val statuses: List<CryptoCurrencyStatus>,
private val appCurrency: AppCurrency,
private val onItemClick: (CryptoCurrencyStatus) -> Unit,
private val statuses: Map<Boolean, List<CryptoCurrencyStatus>>,
private val isBalanceHidden: Boolean,
private val hasSearchBar: Boolean,
private val onQueryChange: (String) -> Unit,
@ -20,7 +28,15 @@ internal class UpdateTokenItemsTransformer(
) : TokenListUMTransformer {
override fun transform(prevState: TokenListUM): TokenListUM {
val items = tokenItemStateConverter.convertList(input = statuses).map(TokensListItemUM::Token)
val availableItems = convertStatuses(
converter = createDefaultTokenItemStateConverter(),
statuses = statuses[true].orEmpty(),
)
val unavailableItems = convertStatuses(
converter = createUnavailableTokenItemStateConverter(),
statuses = statuses[false].orEmpty(),
)
val searchBarItem = if (hasSearchBar) {
prevState.getSearchBar() ?: createSearchBarItem()
@ -29,11 +45,80 @@ internal class UpdateTokenItemsTransformer(
}
return prevState.copy(
items = (listOfNotNull(searchBarItem) + items).toImmutableList(),
availableItems = buildList {
if (searchBarItem != null) {
add(searchBarItem)
}
createGroupTitle(
textReference = resourceReference(id = R.string.exchange_tokens_available_tokens_header),
)
.let(::add)
addAll(availableItems)
}
.toImmutableList(),
unavailableItems = buildList {
if (unavailableItems.isNotEmpty()) {
// TODO: [REDACTED_JIRA]
createGroupTitle(
textReference = stringReference(value = "Unavailable tokens"),
)
.let(::add)
}
addAll(unavailableItems)
}.toImmutableList(),
isBalanceHidden = isBalanceHidden,
)
}
private fun convertStatuses(
converter: TokenItemStateConverter,
statuses: List<CryptoCurrencyStatus>,
): List<TokensListItemUM.Token> {
return converter.convertList(statuses)
.map(TokensListItemUM::Token)
}
private fun createDefaultTokenItemStateConverter(): TokenItemStateConverter {
return TokenItemStateConverter(appCurrency = appCurrency, onItemClick = onItemClick)
}
private fun createUnavailableTokenItemStateConverter(): TokenItemStateConverter {
return TokenItemStateConverter(
appCurrency = appCurrency,
iconStateProvider = { CryptoCurrencyToIconStateConverter(isAvailable = false).convert(it) },
titleStateProvider = { TokenItemState.TitleState.Content(text = it.currency.name, isAvailable = false) },
subtitleStateProvider = {
when (it.value) {
CryptoCurrencyStatus.Loading -> TokenItemState.SubtitleState.Loading
else -> TokenItemState.SubtitleState.TextContent(value = it.currency.symbol, isAvailable = false)
}
},
fiatAmountStateProvider = {
when (it.value) {
is CryptoCurrencyStatus.Loaded,
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.NoQuote,
is CryptoCurrencyStatus.NoAccount,
-> {
TokenItemState.FiatAmountState.TextContent(
text = it.getFormattedFiatAmount(appCurrency),
isAvailable = false,
)
}
is CryptoCurrencyStatus.Unreachable,
is CryptoCurrencyStatus.NoAmount,
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.Loading,
-> null
}
},
onItemClick = onItemClick,
)
}
private fun createSearchBarItem(): TokensListItemUM.SearchBar {
return TokensListItemUM.SearchBar(
searchBarUM = SearchBarUM(
@ -45,4 +130,11 @@ internal class UpdateTokenItemsTransformer(
),
)
}
private fun createGroupTitle(textReference: TextReference): TokensListItemUM.NetworkGroupTitle {
return TokensListItemUM.NetworkGroupTitle(
id = textReference.hashCode(),
name = textReference,
)
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.onramp.tokenlist.model
import arrow.core.getOrElse
import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -68,10 +67,8 @@ internal class OnrampTokenListModel @Inject constructor(
.filterByAvailability()
UpdateTokenItemsTransformer(
tokenItemStateConverter = TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = params.onTokenClick,
),
appCurrency = appCurrency,
onItemClick = params.onTokenClick,
statuses = filterTokenList,
isBalanceHidden = isBalanceHidden,
hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(),
@ -106,14 +103,18 @@ internal class OnrampTokenListModel @Inject constructor(
}
}
private fun List<CryptoCurrencyStatus>.filterByAvailability(): List<CryptoCurrencyStatus> {
return filter {
when (params.filterOperation) {
private fun List<CryptoCurrencyStatus>.filterByAvailability(): Map<Boolean, List<CryptoCurrencyStatus>> {
return groupBy { status ->
val isAvailable = when (params.filterOperation) {
OnrampOperation.BUY -> {
rampStateManager.availableForBuy(scanResponse = scanResponse, cryptoCurrency = it.currency)
rampStateManager.availableForBuy(scanResponse = scanResponse, cryptoCurrency = status.currency)
}
OnrampOperation.SELL -> rampStateManager.availableForSell(cryptoCurrency = it.currency)
OnrampOperation.SELL -> rampStateManager.availableForSell(cryptoCurrency = status.currency)
}
isAvailable &&
status.value !is CryptoCurrencyStatus.MissedDerivation &&
status.value !is CryptoCurrencyStatus.Unreachable
}
}
}

View file

@ -4,52 +4,65 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.tokenlist.TokenListItem
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
import com.tangem.features.onramp.tokenlist.ui.preview.PreviewTokenListUMProvider
import kotlinx.collections.immutable.ImmutableList
/**
* Token list
*
* @param state state
* @param modifier modifier
* @param state state
* @param contentPadding content padding
* @param modifier modifier
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun TokenList(state: TokenListUM, contentPadding: PaddingValues, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier, contentPadding = contentPadding) {
itemsIndexed(
items = state.items,
key = { _, item -> item.id },
contentType = { _, item -> item::class.java },
itemContent = { index, item ->
TokenListItem(
state = item,
isBalanceHidden = state.isBalanceHidden,
modifier = Modifier
.animateItem()
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = state.items.lastIndex,
addDefaultPadding = false,
backgroundColor = TangemTheme.colors.background.primary,
),
)
},
)
itemsBlock(items = state.availableItems, isBalanceHidden = state.isBalanceHidden)
item { SpacerH12() }
itemsBlock(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden)
}
}
private fun LazyListScope.itemsBlock(items: ImmutableList<TokensListItemUM>, isBalanceHidden: Boolean) {
return itemsIndexed(
items = items,
key = { _, item -> item.id },
contentType = { _, item -> item::class.java },
itemContent = { index, item ->
TokenListItem(
state = item,
isBalanceHidden = isBalanceHidden,
modifier = Modifier
.animateItem()
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = items.lastIndex,
addDefaultPadding = false,
backgroundColor = TangemTheme.colors.background.primary,
),
)
},
)
}
@Preview
@Composable
private fun Preview_TokenList(@PreviewParameter(PreviewTokenListUMProvider::class) state: TokenListUM) {

View file

@ -20,7 +20,7 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM
private fun createTokensList(hasSearchBar: Boolean, vararg items: TokensListItemUM): TokenListUM {
return TokenListUM(
items = buildList {
availableItems = buildList {
if (hasSearchBar) {
TokensListItemUM.SearchBar(
searchBarUM = SearchBarUM(
@ -37,6 +37,7 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM
addAll(items)
}
.toImmutableList(),
unavailableItems = items.toList().toImmutableList(),
isBalanceHidden = false,
)
}