diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt index bb9e8f3562..11d24c670b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt @@ -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, + val availableItems: ImmutableList, + val unavailableItems: ImmutableList, 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 { - 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() } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt index bb662a5b7a..d6263dba6d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt @@ -19,7 +19,8 @@ internal class TokenListUMController @Inject constructor() { private val _state: MutableStateFlow = MutableStateFlow( value = TokenListUM( - items = persistentListOf(), + availableItems = persistentListOf(), + unavailableItems = persistentListOf(), isBalanceHidden = false, ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt index db17cb19ea..29a5b2edab 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt @@ -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(), ), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index b6065912be..cdf9aedfaa 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -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, + private val appCurrency: AppCurrency, + private val onItemClick: (CryptoCurrencyStatus) -> Unit, + private val statuses: Map>, 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, + ): List { + 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, + ) + } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index fc6329296f..3ca0bd1482 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -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.filterByAvailability(): List { - return filter { - when (params.filterOperation) { + private fun List.filterByAvailability(): Map> { + 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 } } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt index c75b080c3b..eab5741ead 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt @@ -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, 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) { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt index ce2f3e4f88..19bb331def 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt @@ -20,7 +20,7 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider