Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-16 22:35:36 +03:00
parent a94db85dfd
commit 648ceb9946
9 changed files with 94 additions and 60 deletions

View file

@ -4,8 +4,13 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.*
@Composable
fun InfiniteListHandler(listState: LazyListState, onLoadMore: () -> Boolean, buffer: Int = 2) {
val loadMore by remember {
fun InfiniteListHandler(
listState: LazyListState,
onLoadMore: () -> Boolean,
buffer: Int = 2,
triggerLoadMoreCheckOnItemsCountChange: Boolean = false,
) {
val loadMore by remember(buffer, listState) {
derivedStateOf {
val layoutInfo = listState.layoutInfo
val totalItemsNumber = layoutInfo.totalItemsCount
@ -15,12 +20,18 @@ fun InfiniteListHandler(listState: LazyListState, onLoadMore: () -> Boolean, buf
}
}
val totalItemsCount by remember { derivedStateOf { listState.layoutInfo.totalItemsCount } }
var emitted by remember(totalItemsCount) { mutableStateOf(false) }
val totalItemsCount by remember(listState) { derivedStateOf { listState.layoutInfo.totalItemsCount } }
var emitted by remember(totalItemsCount, buffer, listState) { mutableStateOf(false) }
LaunchedEffect(loadMore) {
if (loadMore && !emitted) {
emitted = onLoadMore()
}
}
LaunchedEffect(totalItemsCount) {
if (triggerLoadMoreCheckOnItemsCountChange && loadMore && !emitted) {
emitted = onLoadMore()
}
}
}

View file

@ -9,6 +9,7 @@ import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.markets.TangemTechMarketsApi
import com.tangem.datasource.api.markets.models.response.TokenMarketChartListResponse
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.TangemTechApi.Companion.marketsQuoteFields
import com.tangem.domain.markets.TokenMarket
import com.tangem.domain.markets.TokenMarketUpdateRequest
import com.tangem.pagination.Batch
@ -70,7 +71,7 @@ internal class MarketsBatchUpdateFetcher(
tangemTechApi.getQuotes(
currencyId = updateRequest.currencyId,
coinIds = idsToUpdate.map { it.second }.flatten().joinToString(separator = ","),
fields = quoteFields.joinToString(separator = ","),
fields = marketsQuoteFields.joinToString(separator = ","),
).getOrThrow()
}
}
@ -120,13 +121,4 @@ internal class MarketsBatchUpdateFetcher(
throw e
}
}
companion object {
private val quoteFields = listOf(
"price",
"priceChange24h",
"priceChange1w",
"priceChange30d",
)
}
}

View file

@ -14,7 +14,6 @@ import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM
import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM
import com.tangem.utils.Provider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.*
@ -111,54 +110,55 @@ internal class MarketsListUMStateManager(
private fun MarketsListUM.updateItems(newItems: ImmutableList<MarketsListItemUM>): MarketsListUM {
val currentState = this
val isNextPageInSearch = isInSearchState && (this.list as? ListUM.Content)?.showUnder100kTokens == true
var searchUiItemsCached: ImmutableList<MarketsListItemUM> = persistentListOf()
val items = when {
isInSearchState && isNextPageInSearch.not() -> {
searchUiItemsCached = newItems
val filtered = newItems.filter { item -> item.isUnder100kMarketCap.not() }.toImmutableList()
if (isInSearchMode.not() || currentState.showUnder100kButtonAlreadyPressed()) {
val itemsWithFilteredPriceChange = newItems.filterPriceChangeByVisibility()
if (filtered.size == newItems.size) {
return currentState.copy(list = generalContentState(newItems))
} else {
filtered
}
}
else -> {
searchUiItemsCached = persistentListOf()
newItems
}
return currentState.copy(
list = generalContentState(itemsWithFilteredPriceChange)
.copy(
showUnder100kTokensNotificationWasHidden = currentState.showUnder100kButtonAlreadyPressed(),
),
)
}
val itemsWithFilteredPriceChange = items.filterPriceChangeByVisibility()
// Search state cases
return currentState.copy(
list = ListUM.Content(
items = itemsWithFilteredPriceChange,
loadMore = onLoadMoreUiItems,
visibleIdsChanged = visibleItemsChanged,
showUnder100kTokens = isInSearchState.not() || isNextPageInSearch,
onShowTokensUnder100kClicked = {
if (searchUiItemsCached.isNotEmpty()) {
val filtered = newItems.filter { item -> item.isUnder100kMarketCap.not() }
.toImmutableList()
.filterPriceChangeByVisibility()
if (filtered.size != newItems.size) {
val searchUiItemsCached = newItems.filterPriceChangeByVisibility()
return currentState.copy(
list = generalContentState(filtered).copy(
showUnder100kTokensNotificationWasHidden = false,
showUnder100kTokensNotification = true,
onShowTokensUnder100kClicked = {
state.update { s ->
if (s.list is ListUM.Content) {
(s.list as? ListUM.Content)?.let {
s.copy(
list = s.list.copy(
items = searchUiItemsCached,
showUnder100kTokens = true,
showUnder100kTokensNotification = false,
showUnder100kTokensNotificationWasHidden = true,
),
)
} else {
s
}
} ?: s
}
}
},
triggerScrollReset = consumedEvent(),
onItemClick = onTokenClick,
),
)
},
),
)
} else {
return currentState.copy(
list = generalContentState(newItems.filterPriceChangeByVisibility()),
)
}
}
private fun MarketsListUM.showUnder100kButtonAlreadyPressed(): Boolean {
return this.list is ListUM.Content && this.isInSearchMode && this.list.showUnder100kTokensNotificationWasHidden
}
// Show price change animation for visible items only
@ -182,10 +182,11 @@ internal class MarketsListUMStateManager(
items = newItems,
loadMore = onLoadMoreUiItems,
visibleIdsChanged = visibleItemsChanged,
showUnder100kTokens = true,
showUnder100kTokensNotification = false,
onShowTokensUnder100kClicked = {},
triggerScrollReset = consumedEvent(),
onItemClick = onTokenClick,
showUnder100kTokensNotificationWasHidden = false,
)
}

View file

@ -301,7 +301,8 @@ private fun Preview() {
item.copy(id = index.toString())
}
.toImmutableList(),
showUnder100kTokens = false,
showUnder100kTokensNotification = false,
showUnder100kTokensNotificationWasHidden = false,
loadMore = {},
visibleIdsChanged = {},
onShowTokensUnder100kClicked = {},

View file

@ -22,6 +22,7 @@ import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM
import kotlinx.coroutines.launch
private const val LOAD_NEXT_PAGE_ON_END_INDEX = 50
private const val LOAD_NEXT_PAGE_ON_END_INDEX_SEARCH = 25
private const val TOKEN_LAZY_LIST_ID_SEPARATOR = "***"
@Composable
@ -95,7 +96,7 @@ internal fun MarketsListLazyColumn(
)
}
if (isInSearchMode && state.showUnder100kTokens.not()) {
if (isInSearchMode && state.showUnder100kTokensNotification) {
item(key = "show tokens under 100k".hashCode()) {
ShowTokensUnder100kItem(
onShowTokensClick = state.onShowTokensUnder100kClicked,
@ -112,10 +113,15 @@ internal fun MarketsListLazyColumn(
InfiniteListHandler(
listState = lazyListState,
buffer = LOAD_NEXT_PAGE_ON_END_INDEX,
buffer = if (isInSearchMode) {
LOAD_NEXT_PAGE_ON_END_INDEX_SEARCH
} else {
LOAD_NEXT_PAGE_ON_END_INDEX
},
triggerLoadMoreCheckOnItemsCountChange = true,
onLoadMore = remember(state) {
{
if (state is ListUM.Content && state.showUnder100kTokens) {
if (state is ListUM.Content && state.showUnder100kTokensNotification.not()) {
state.loadMore()
true
} else {

View file

@ -22,6 +22,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
chardData = MarketChartRawData(
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
),
isUnder100kMarketCap = false,
),
MarketsListItemUM(
id = "1",
@ -34,6 +35,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
trendPercentText = "12.43%",
trendType = PriceChangeType.NEUTRAL,
chardData = null,
isUnder100kMarketCap = false,
),
MarketsListItemUM(
id = "1",
@ -48,6 +50,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
chardData = MarketChartRawData(
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
),
isUnder100kMarketCap = false,
),
MarketsListItemUM(
id = "1",
@ -62,6 +65,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
chardData = MarketChartRawData(
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
),
isUnder100kMarketCap = false,
),
MarketsListItemUM(
id = "1",
@ -76,6 +80,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
chardData = MarketChartRawData(
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
),
isUnder100kMarketCap = false,
),
MarketsListItemUM(
id = "1",
@ -90,6 +95,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
chardData = MarketChartRawData(
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
),
isUnder100kMarketCap = false,
),
),
)

View file

@ -17,7 +17,7 @@ data class MarketsListItemUM(
val trendPercentText: String,
val trendType: PriceChangeType,
val chardData: MarketChartRawData?,
val isUnder100kMarketCap: Boolean = false,
val isUnder100kMarketCap: Boolean,
) {
val chartType: MarketChartLook.Type = when (trendType) {
PriceChangeType.UP -> MarketChartLook.Type.Growing

View file

@ -41,7 +41,8 @@ sealed class ListUM {
data class Content(
val items: ImmutableList<MarketsListItemUM>,
val showUnder100kTokens: Boolean,
val showUnder100kTokensNotification: Boolean,
val showUnder100kTokensNotificationWasHidden: Boolean,
val loadMore: () -> Unit,
val visibleIdsChanged: (List<String>) -> Unit,
val onShowTokensUnder100kClicked: () -> Unit,

View file

@ -462,7 +462,9 @@ private inline fun BaseScaffoldWithMarkets(
color = BottomSheetDefaults.ScrimColor,
visible = bottomSheetState.targetValue == TangemSheetValue.Expanded ||
state.showMarketsOnboarding,
onDismissRequest = { coroutineScope.launch { bottomSheetState.partialExpand() } },
onDismissRequest = {
coroutineScope.launch { bottomSheetState.partialExpand() }
},
)
MarketsTooltip(
@ -486,7 +488,6 @@ private inline fun BaseScaffoldWithMarkets(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun MarketsTooltip(
availableHeight: Dp,
@ -644,6 +645,21 @@ private fun BottomSheetStateEffects(
val systemUiController = rememberSystemUiController()
val navigationBarColor = TangemTheme.colors.background.primary
LaunchedEffect(Unit) {
delay(timeMillis = 100)
when (bottomSheetState.currentValue) {
TangemSheetValue.Hidden,
TangemSheetValue.Expanded,
-> systemUiController.setNavigationBarColor(
color = Color.Transparent,
darkIcons = navigationBarColor.luminance() > 0.5f,
navigationBarContrastEnforced = true,
)
TangemSheetValue.PartiallyExpanded,
-> systemUiController.setNavigationBarColor(navigationBarColor)
}
}
LaunchedEffect(key1 = bottomSheetState.targetValue, navigationBarColor) {
when (bottomSheetState.targetValue) {
TangemSheetValue.Hidden,