Updated on 2026-08-14

This commit is contained in:
Tangem 2024-12-03 11:43:31 +04:00
parent da0a117861
commit 26419a5754
12 changed files with 85 additions and 135 deletions

View file

@ -29,7 +29,6 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor(
is OnrampOperationComponent.Params.Buy -> OnrampOperation.BUY
is OnrampOperationComponent.Params.Sell -> OnrampOperation.SELL
},
hasSearchBar = true,
userWalletId = params.userWalletId,
onTokenClick = { _, status -> model.onTokenClick(status) },
),

View file

@ -34,7 +34,6 @@ internal class DefaultSwapSelectTokensComponent @AssistedInject constructor(
context = child(key = "select_from_token_list"),
params = OnrampTokenListComponent.Params(
filterOperation = OnrampOperation.SWAP,
hasSearchBar = true,
userWalletId = params.userWalletId,
onTokenClick = model::selectFromToken,
),

View file

@ -26,6 +26,7 @@ import com.tangem.features.onramp.tokenlist.entity.transformer.SetNothingToFound
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer
import com.tangem.features.onramp.utils.InputManager
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -62,6 +63,13 @@ internal class AvailableSwapPairsModel @Inject constructor(
private val availablePairsByNetworkFlow = MutableStateFlow<Map<LeastTokenInfo, List<SwapPairLeast>>>(emptyMap())
init {
tokenListUMController.update(
transformer = UpdateSearchBarCallbacksTransformer(
onQueryChange = ::onSearchQueryChange,
onActiveChange = ::onSearchBarActiveChange,
),
)
subscribeOnUpdateState()
subscribeOnAvailablePairsUpdates()
}
@ -88,12 +96,9 @@ internal class AvailableSwapPairsModel @Inject constructor(
if (query.isNotEmpty() && filterByQueryTokenList.isEmpty()) {
SetNothingToFoundStateTransformer(
isBalanceHidden = isBalanceHidden,
hasSearchBar = currencies.isNotEmpty(),
emptySearchMessageReference = resourceReference(
id = R.string.action_buttons_swap_empty_search_message,
),
onQueryChange = ::onSearchQueryChange,
onActiveChange = ::onSearchBarActiveChange,
)
} else {
UpdateTokenItemsTransformer(
@ -101,13 +106,10 @@ internal class AvailableSwapPairsModel @Inject constructor(
onItemClick = params.onTokenClick,
statuses = filterByQueryTokenList.filterByAvailability(availablePairs = availablePairs),
isBalanceHidden = isBalanceHidden,
hasSearchBar = currencies.isNotEmpty(),
unavailableTokensHeaderReference = resourceReference(
id = R.string.tokens_list_unavailable_to_swap_header,
wrappedList(selectedStatus?.currency?.name?.capitalize() ?: ""),
),
onQueryChange = ::onSearchQueryChange,
onActiveChange = ::onSearchBarActiveChange,
)
}
}
@ -151,8 +153,7 @@ internal class AvailableSwapPairsModel @Inject constructor(
}
private fun onSearchQueryChange(newQuery: String) {
val searchBar = tokenListUMController.getSearchBar()
if (searchBar?.searchBarUM?.query == newQuery) return
if (state.value.searchBarUM.query == newQuery) return
modelScope.launch {
tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery))

View file

@ -19,13 +19,11 @@ internal interface OnrampTokenListComponent : ComposableContentComponent {
* Params
*
* @property filterOperation operation that is used to filter tokens by availability
* @property hasSearchBar flag that indicates if search bar should be shown
* @property userWalletId id of multi-currency wallet
* @property onTokenClick callback for token click
*/
data class Params(
val filterOperation: OnrampOperation,
val hasSearchBar: Boolean,
val userWalletId: UserWalletId,
val onTokenClick: (TokenItemState, CryptoCurrencyStatus) -> Unit,
)

View file

@ -1,12 +1,13 @@
package com.tangem.features.onramp.tokenlist.entity
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
/**
* Token list UM
*
* @property searchBarUM search bar UI model
* @property availableItems available items (search bar, header, tokens)
* @property unavailableItems unavailable items (header, tokens)
* @property isBalanceHidden flag that indicates if balance should be hidden
@ -14,24 +15,8 @@ import kotlinx.collections.immutable.persistentListOf
[REDACTED_AUTHOR]
*/
internal data class TokenListUM(
val searchBarUM: SearchBarUM,
val availableItems: ImmutableList<TokensListItemUM>,
val unavailableItems: ImmutableList<TokensListItemUM>,
val isBalanceHidden: Boolean,
) {
/** Get search bar if it exists */
fun getSearchBar(): TokensListItemUM.SearchBar? {
return availableItems.firstOrNull() as? TokensListItemUM.SearchBar
}
/** Get tokens */
fun getTokens(): ImmutableList<TokensListItemUM> {
if (getSearchBar() == null) return availableItems
return if (availableItems.size > 1) {
availableItems.subList(fromIndex = 1, toIndex = availableItems.size)
} else {
persistentListOf()
}
}
}
)

View file

@ -1,6 +1,8 @@
package com.tangem.features.onramp.tokenlist.entity
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.utils.SearchBarUMTransformer
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
@ -20,6 +22,13 @@ internal class TokenListUMController @Inject constructor() {
private val _state: MutableStateFlow<TokenListUM> = MutableStateFlow(
value = TokenListUM(
searchBarUM = SearchBarUM(
placeholderText = resourceReference(id = R.string.common_search),
query = "",
onQueryChange = {},
isActive = false,
onActiveChange = {},
),
availableItems = persistentListOf(),
unavailableItems = persistentListOf(),
isBalanceHidden = false,
@ -39,27 +48,9 @@ internal class TokenListUMController @Inject constructor() {
fun update(transformer: SearchBarUMTransformer) {
Timber.d("Applying ${transformer::class.simpleName}")
_state.update { prevState ->
val searchBarItem = prevState.getSearchBar()
if (searchBarItem != null) {
val updatedSearchBar = searchBarItem.copy(
searchBarUM = transformer.transform(searchBarItem.searchBarUM),
)
prevState.copy(
availableItems = persistentListOf(
updatedSearchBar,
*prevState.getTokens().toTypedArray(),
),
)
} else {
prevState
}
prevState.copy(
searchBarUM = transformer.transform(prevState.searchBarUM),
)
}
}
/** Get search bar if it exists */
fun getSearchBar(): TokensListItemUM.SearchBar? {
return _state.value.getSearchBar()
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.features.onramp.tokenlist.entity.transformer
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
@ -12,25 +11,12 @@ import kotlinx.collections.immutable.toImmutableList
internal class SetNothingToFoundStateTransformer(
private val isBalanceHidden: Boolean,
private val hasSearchBar: Boolean,
private val emptySearchMessageReference: TextReference,
private val onQueryChange: (String) -> Unit,
private val onActiveChange: (Boolean) -> Unit,
) : TokenListUMTransformer {
override fun transform(prevState: TokenListUM): TokenListUM {
val searchBarItem = if (hasSearchBar) {
prevState.getSearchBar() ?: createSearchBarItem()
} else {
null
}
return prevState.copy(
availableItems = buildList {
if (searchBarItem != null) {
add(searchBarItem)
}
createGroupTitle(
textReference = resourceReference(id = R.string.exchange_tokens_available_tokens_header),
)
@ -47,18 +33,6 @@ internal class SetNothingToFoundStateTransformer(
)
}
private fun createSearchBarItem(): TokensListItemUM.SearchBar {
return TokensListItemUM.SearchBar(
searchBarUM = SearchBarUM(
placeholderText = resourceReference(id = R.string.common_search),
query = "",
onQueryChange = onQueryChange,
isActive = false,
onActiveChange = onActiveChange,
),
)
}
private fun createGroupTitle(textReference: TextReference): TokensListItemUM.GroupTitle {
return TokensListItemUM.GroupTitle(id = textReference.hashCode(), text = textReference)
}

View file

@ -4,7 +4,6 @@ import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.common.ui.tokens.TokenItemStateConverter.Companion.getFormattedCryptoAmount
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
@ -23,10 +22,7 @@ internal class UpdateTokenItemsTransformer(
private val onItemClick: (TokenItemState, CryptoCurrencyStatus) -> Unit,
private val statuses: Map<Boolean, List<CryptoCurrencyStatus>>,
private val isBalanceHidden: Boolean,
private val hasSearchBar: Boolean,
private val unavailableTokensHeaderReference: TextReference,
private val onQueryChange: (String) -> Unit,
private val onActiveChange: (Boolean) -> Unit,
) : TokenListUMTransformer {
override fun transform(prevState: TokenListUM): TokenListUM {
@ -40,18 +36,8 @@ internal class UpdateTokenItemsTransformer(
statuses = statuses[false].orEmpty(),
)
val searchBarItem = if (hasSearchBar) {
prevState.getSearchBar() ?: createSearchBarItem()
} else {
null
}
return prevState.copy(
availableItems = buildList {
if (searchBarItem != null) {
add(searchBarItem)
}
if (availableItems.isNotEmpty()) {
createGroupTitle(
textReference = resourceReference(id = R.string.exchange_tokens_available_tokens_header),
@ -68,7 +54,8 @@ internal class UpdateTokenItemsTransformer(
}
addAll(unavailableItems)
}.toImmutableList(),
}
.toImmutableList(),
isBalanceHidden = isBalanceHidden,
)
}
@ -158,18 +145,6 @@ internal class UpdateTokenItemsTransformer(
}
}
private fun createSearchBarItem(): TokensListItemUM.SearchBar {
return TokensListItemUM.SearchBar(
searchBarUM = SearchBarUM(
placeholderText = resourceReference(id = R.string.common_search),
query = "",
onQueryChange = onQueryChange,
isActive = false,
onActiveChange = onActiveChange,
),
)
}
private fun createGroupTitle(textReference: TextReference): TokensListItemUM.GroupTitle {
return TokensListItemUM.GroupTitle(id = textReference.hashCode(), text = textReference)
}

View file

@ -22,6 +22,7 @@ import com.tangem.features.onramp.tokenlist.entity.transformer.SetNothingToFound
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer
import com.tangem.features.onramp.utils.InputManager
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -49,6 +50,13 @@ internal class OnrampTokenListModel @Inject constructor(
}
init {
tokenListUMController.update(
transformer = UpdateSearchBarCallbacksTransformer(
onQueryChange = ::onSearchQueryChange,
onActiveChange = ::onSearchBarActiveChange,
),
)
subscribeOnUpdateState()
}
@ -71,15 +79,12 @@ internal class OnrampTokenListModel @Inject constructor(
if (query.isNotEmpty() && filterByQueryTokenList.isEmpty()) {
SetNothingToFoundStateTransformer(
isBalanceHidden = isBalanceHidden,
hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(),
emptySearchMessageReference = when (params.filterOperation) {
OnrampOperation.BUY -> R.string.action_buttons_buy_empty_search_message
OnrampOperation.SELL -> R.string.action_buttons_sell_empty_search_message
OnrampOperation.SWAP -> R.string.action_buttons_swap_empty_search_message
}
.let(::resourceReference),
onQueryChange = ::onSearchQueryChange,
onActiveChange = ::onSearchBarActiveChange,
)
} else {
UpdateTokenItemsTransformer(
@ -87,14 +92,11 @@ internal class OnrampTokenListModel @Inject constructor(
onItemClick = params.onTokenClick,
statuses = filterByQueryTokenList.filterByAvailability(),
isBalanceHidden = isBalanceHidden,
hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(),
unavailableTokensHeaderReference = when (params.filterOperation) {
OnrampOperation.BUY -> R.string.tokens_list_unavailable_to_purchase_header
OnrampOperation.SELL -> R.string.tokens_list_unavailable_to_sell_header
OnrampOperation.SWAP -> R.string.tokens_list_unavailable_to_swap_source_header
}.let(::resourceReference),
onQueryChange = ::onSearchQueryChange,
onActiveChange = ::onSearchBarActiveChange,
)
}
}
@ -104,8 +106,8 @@ internal class OnrampTokenListModel @Inject constructor(
}
private fun onSearchQueryChange(newQuery: String) {
val searchBar = tokenListUMController.getSearchBar()
if (searchBar?.searchBarUM?.query == newQuery) return
val searchBar = state.value.searchBarUM
if (searchBar.query == newQuery) return
modelScope.launch {
tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery))

View file

@ -5,14 +5,18 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.fields.SearchBar
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.tokenlist.TokenListItem
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
@ -33,6 +37,10 @@ import kotlinx.collections.immutable.ImmutableList
@Composable
internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) {
Column(modifier) {
SearchBar(searchBarUM = state.searchBarUM)
SpacerH12()
ItemsBlock(items = state.availableItems, isBalanceHidden = state.isBalanceHidden)
SpacerH12()
@ -41,6 +49,23 @@ internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) {
}
}
@Composable
private fun SearchBar(searchBarUM: SearchBarUM) {
SearchBar(
state = searchBarUM,
colors = TextFieldDefaults.colors().copy(
focusedContainerColor = TangemTheme.colors.field.focused,
unfocusedContainerColor = TangemTheme.colors.field.focused,
focusedTextColor = TangemTheme.colors.text.primary1,
unfocusedTextColor = TangemTheme.colors.text.primary1,
cursorColor = TangemTheme.colors.icon.primary1,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
),
)
}
@Composable
private fun ItemsBlock(items: ImmutableList<TokensListItemUM>, isBalanceHidden: Boolean) {
items.fastForEachIndexed { index, item ->

View file

@ -11,44 +11,32 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM> {
override val values: Sequence<TokenListUM> = sequenceOf(
createTokensList(
hasSearchBar = false,
createHeader(),
createDefaultTokenItem(),
createDefaultTokenItem(),
),
createTokensList(
hasSearchBar = true,
createHeader(),
createDefaultTokenItem(),
createDefaultTokenItem(),
),
)
private fun createTokensList(hasSearchBar: Boolean, vararg items: TokensListItemUM): TokenListUM {
private fun createTokensList(vararg items: TokensListItemUM): TokenListUM {
return TokenListUM(
availableItems = buildList {
if (hasSearchBar) {
TokensListItemUM.SearchBar(
searchBarUM = SearchBarUM(
placeholderText = resourceReference(id = R.string.common_search),
query = "",
onQueryChange = {},
isActive = false,
onActiveChange = {},
),
)
.let { add(element = it) }
}
addAll(items)
}
.toImmutableList(),
searchBarUM = SearchBarUM(
placeholderText = resourceReference(id = R.string.common_search),
query = "",
onQueryChange = {},
isActive = false,
onActiveChange = {},
),
availableItems = persistentListOf(*items),
unavailableItems = persistentListOf(
createHeader(),
createUnavailableTokenItem(),

View file

@ -0,0 +1,13 @@
package com.tangem.features.onramp.utils
import com.tangem.core.ui.components.fields.entity.SearchBarUM
internal class UpdateSearchBarCallbacksTransformer(
private val onQueryChange: (String) -> Unit,
private val onActiveChange: (Boolean) -> Unit,
) : SearchBarUMTransformer() {
override fun transform(prevState: SearchBarUM): SearchBarUM {
return prevState.copy(onQueryChange = onQueryChange, onActiveChange = onActiveChange)
}
}