Updated on 2026-08-14
This commit is contained in:
parent
2e857d5e3e
commit
45fa120411
49 changed files with 669 additions and 92 deletions
|
|
@ -35,10 +35,11 @@ internal class EarnTokenWithCurrencyToListItemUMConverter(
|
|||
formatArgs = wrappedList(convertPercent(value.earnToken.apy)),
|
||||
)
|
||||
},
|
||||
earnType = when (value.earnToken.type) {
|
||||
earnTypeTitle = when (value.earnToken.type) {
|
||||
EarnType.STAKING -> TextReference.Res(R.string.common_staking)
|
||||
EarnType.YIELD -> TextReference.Res(R.string.common_yield_mode)
|
||||
},
|
||||
earnType = value.earnToken.type,
|
||||
onItemClick = { onItemClick(value) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
|
|||
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.common.ui.markets.toMarketsListItemPriceAnnotated
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
|
|
@ -110,7 +111,12 @@ internal class MarketsTokenItemConverter(
|
|||
|
||||
return MarketsListItemUM.Price(
|
||||
text = priceText,
|
||||
annotated = tokenQuotesShort.currentPrice.toMarketsListItemPriceAnnotated(
|
||||
appCurrencyCode = appCurrency.code,
|
||||
appCurrencySymbol = appCurrency.symbol,
|
||||
),
|
||||
changeType = changeType,
|
||||
fiatPrice = tokenQuotesShort.currentPrice,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -230,6 +230,9 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
fiatCurrencySymbol = currentAppCurrency.value.symbol,
|
||||
).price()
|
||||
},
|
||||
priceAnnotated = params.token.tokenQuotes.currentPrice.toMarketsTokenDetailsPriceAnnotated(
|
||||
currentAppCurrency.value,
|
||||
),
|
||||
dateTimeText = resourceReference(R.string.common_today),
|
||||
priceChangePercentText = params.token.tokenQuotes.h24Percent?.format { percent() },
|
||||
priceChangeType = params.token.tokenQuotes.h24Percent.percentChangeType(),
|
||||
|
|
@ -505,6 +508,9 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
fiatCurrencyCode = currentAppCurrency.value.code,
|
||||
).price()
|
||||
},
|
||||
priceAnnotated = newInfo.quotes.currentPrice.toMarketsTokenDetailsPriceAnnotated(
|
||||
currentAppCurrency.value,
|
||||
),
|
||||
priceChangePercentText = newInfo.quotes.getFormattedPercentByInterval(
|
||||
interval = marketsTokenDetailsUM.selectedInterval,
|
||||
),
|
||||
|
|
@ -604,7 +610,8 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
)
|
||||
} ?: getDefaultDateTimeString(currentState.selectedInterval)
|
||||
|
||||
val priceText = (price ?: currentQuotes.value.currentPrice).format {
|
||||
val amountForPrice = price ?: currentQuotes.value.currentPrice
|
||||
val priceText = amountForPrice.format {
|
||||
fiat(
|
||||
fiatCurrencySymbol = currentAppCurrency.value.symbol,
|
||||
fiatCurrencyCode = currentAppCurrency.value.code,
|
||||
|
|
@ -625,6 +632,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
isMarkerSet = markerTimestamp != null,
|
||||
dateTimeText = dateTimeText,
|
||||
priceText = priceText,
|
||||
priceAnnotated = amountForPrice.toMarketsTokenDetailsPriceAnnotated(currentAppCurrency.value),
|
||||
priceChangePercentText = percentText,
|
||||
priceChangeType = percent.percentChangeType(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
package com.tangem.features.feed.model.market.details.converter
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.compact
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
|
|
@ -188,6 +185,7 @@ internal class MetricsConverter(
|
|||
formatArgs = wrappedList(formatted),
|
||||
)
|
||||
},
|
||||
fullyDilutedValuationChange24 = fullyDilutedValuationChange24?.convertChange(),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
|
|
@ -280,6 +278,17 @@ internal class MetricsConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal?.convertChange(): TextReference? {
|
||||
if (this == null) return null
|
||||
val amount = this.abs().formatAmount()
|
||||
val value = when {
|
||||
this > BigDecimal.ZERO -> StringsSigns.PLUS + amount
|
||||
this < BigDecimal.ZERO -> StringsSigns.MINUS + amount
|
||||
else -> amount
|
||||
}
|
||||
return value?.let(::stringReference)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getLiquidity(volume24h: BigDecimal?, marketCap: BigDecimal?): Float? {
|
||||
if (volume24h == null || marketCap == null || marketCap == BigDecimal.ZERO) return null
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.features.feed.model.market.details.formatter
|
||||
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import com.tangem.common.ui.charts.state.MarketChartLook
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.getFiatPriceAmountWithScale
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.PriceChangeInterval
|
||||
import com.tangem.domain.markets.TokenQuotes
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -73,4 +75,14 @@ internal fun PriceChangeType.toChartType(): MarketChartLook.Type {
|
|||
PriceChangeType.DOWN -> MarketChartLook.Type.Falling
|
||||
PriceChangeType.NEUTRAL -> MarketChartLook.Type.Neutral
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BigDecimal.toMarketsTokenDetailsPriceAnnotated(appCurrency: AppCurrency): TextReference {
|
||||
return formatStyled {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.tertiary) },
|
||||
).price()
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ internal class QuotesStateUpdater(
|
|||
fiatCurrencyCode = currentAppCurrency().code,
|
||||
).price()
|
||||
},
|
||||
priceAnnotated = newQuotes.currentPrice.toMarketsTokenDetailsPriceAnnotated(currentAppCurrency()),
|
||||
priceChangePercentText = newQuotes.getFormattedPercentByInterval(
|
||||
interval = stateToUpdate.selectedInterval,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
|
|||
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.common.ui.markets.toMarketsListItemPriceAnnotated
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost
|
||||
|
|
@ -68,7 +69,12 @@ internal class RelatedTokenConverter(private val appCurrency: AppCurrency) :
|
|||
|
||||
return MarketsListItemUM.Price(
|
||||
text = priceText,
|
||||
annotated = tokenInfo.quotes.currentPrice.toMarketsListItemPriceAnnotated(
|
||||
appCurrencyCode = appCurrency.code,
|
||||
appCurrencySymbol = appCurrency.symbol,
|
||||
),
|
||||
changeType = null,
|
||||
fiatPrice = tokenInfo.quotes.currentPrice,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,19 +5,27 @@ 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.core.ui.components.marketprice.PriceChangeType
|
||||
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.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.search.model.RecentSearchToken
|
||||
import com.tangem.domain.search.usecase.ClearSearchHistoryUseCase
|
||||
import com.tangem.domain.search.usecase.GetSearchResultsUseCase
|
||||
import com.tangem.domain.search.usecase.SaveRecentSearchTokenUseCase
|
||||
import com.tangem.domain.search.usecase.SaveSearchQueryUseCase
|
||||
import com.tangem.features.feed.components.search.DefaultSearchComponent
|
||||
import com.tangem.features.feed.model.market.list.state.MarketsListUM
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.model.market.list.statemanager.MarketsListBatchFlowManager
|
||||
import com.tangem.features.feed.model.search.converter.MarketsListItemUMToRecentSearchTokenConverter
|
||||
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.state.SearchStateController
|
||||
import com.tangem.features.feed.model.search.state.transformers.*
|
||||
import com.tangem.features.feed.ui.search.state.*
|
||||
|
|
@ -28,6 +36,9 @@ 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.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -45,7 +56,9 @@ internal class SearchModel @Inject constructor(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getSearchResultsUseCase: GetSearchResultsUseCase,
|
||||
private val saveSearchQueryUseCase: SaveSearchQueryUseCase,
|
||||
private val saveRecentSearchTokenUseCase: SaveRecentSearchTokenUseCase,
|
||||
private val clearSearchHistoryUseCase: ClearSearchHistoryUseCase,
|
||||
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
|
||||
private val stateController: SearchStateController,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -64,6 +77,18 @@ internal class SearchModel @Inject constructor(
|
|||
initialValue = AppCurrency.Default,
|
||||
)
|
||||
|
||||
private val marketsListItemToRecentSearchTokenConverter by lazy {
|
||||
MarketsListItemUMToRecentSearchTokenConverter()
|
||||
}
|
||||
|
||||
private val recentSearchTokenToMarketsListItemConverter by lazy {
|
||||
RecentSearchTokenToMarketsListItemUMConverter()
|
||||
}
|
||||
|
||||
private val priceAndTimePointValuesConverter by lazy {
|
||||
PriceAndTimePointValuesConverter(shouldFormatAxis = false)
|
||||
}
|
||||
|
||||
private val searchMarketsListManager by lazy {
|
||||
MarketsListBatchFlowManager(
|
||||
getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase,
|
||||
|
|
@ -108,8 +133,15 @@ internal class SearchModel @Inject constructor(
|
|||
stateController.update(UpdateSearchBarQueryTransformer(text))
|
||||
}
|
||||
|
||||
fun onResultMarketTokenClick() {
|
||||
fun onResultMarketTokenClick(item: MarketsListItemUM) {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
val appCurrency = currentAppCurrency.value
|
||||
val input = MarketsListItemUMWithAppCurrency(
|
||||
item = item,
|
||||
appCurrencyCode = appCurrency.code,
|
||||
appCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
saveRecentSearchTokenUseCase(marketsListItemToRecentSearchTokenConverter.convert(input))
|
||||
saveSearchQueryUseCase(stateController.value.searchBar.query)
|
||||
}
|
||||
}
|
||||
|
|
@ -263,31 +295,43 @@ internal class SearchModel @Inject constructor(
|
|||
TextHintItemUM(text = hint.text)
|
||||
}.toImmutableList()
|
||||
|
||||
val appCurrency = currentAppCurrency.value
|
||||
val recentTokens = searchResult.recentTokens.map { token ->
|
||||
token.toMarketsListItemUM()
|
||||
recentSearchTokenToMarketsListItemConverter.convert(
|
||||
RecentSearchTokenWithAppCurrency(token = token, appCurrency = appCurrency),
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
stateController.update(UpdateHistoryTransformer(textHints, recentTokens))
|
||||
|
||||
loadRecentTokenCharts(recentTokens, appCurrency)
|
||||
}
|
||||
}.saveIn(searchResultsJob)
|
||||
}
|
||||
|
||||
private fun RecentSearchToken.toMarketsListItemUM(): MarketsListItemUM {
|
||||
return MarketsListItemUM(
|
||||
id = id,
|
||||
name = name,
|
||||
currencySymbol = symbol,
|
||||
iconUrl = imageUrl,
|
||||
ratingPosition = null,
|
||||
marketCap = null,
|
||||
price = MarketsListItemUM.Price(text = ""),
|
||||
trendPercentText = "",
|
||||
trendType = PriceChangeType.NEUTRAL,
|
||||
chartData = null,
|
||||
isUnder100kMarketCap = false,
|
||||
stakingRate = null,
|
||||
updateTimestamp = timestamp,
|
||||
)
|
||||
private suspend fun loadRecentTokenCharts(tokens: ImmutableList<MarketsListItemUM>, appCurrency: AppCurrency) {
|
||||
coroutineScope {
|
||||
tokens.map { token ->
|
||||
async(dispatchers.io) {
|
||||
val chart = getTokenPriceChartUseCase(
|
||||
appCurrency = appCurrency,
|
||||
interval = PriceChangeInterval.H24,
|
||||
tokenId = token.id,
|
||||
tokenSymbol = token.currencySymbol,
|
||||
preview = true,
|
||||
).getOrElse { return@async }
|
||||
|
||||
val chartData = priceAndTimePointValuesConverter.convert(
|
||||
MarketChartData.Data(
|
||||
y = chart.priceY.toImmutableList(),
|
||||
x = chart.timeStamps.map { it.toBigDecimal() }.toImmutableList(),
|
||||
).sorted(),
|
||||
)
|
||||
|
||||
stateController.update(UpdateRecentTokenChartTransformer(token.id, chartData))
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private fun AccountName.toDisplayString(): String {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.features.feed.model.search.converter
|
||||
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.search.model.RecentSearchToken
|
||||
import com.tangem.domain.search.model.TokenPriceChangeDirection
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class MarketsListItemUMToRecentSearchTokenConverter :
|
||||
Converter<MarketsListItemUMWithAppCurrency, RecentSearchToken> {
|
||||
|
||||
override fun convert(value: MarketsListItemUMWithAppCurrency): RecentSearchToken {
|
||||
val item = value.item
|
||||
val direction = when (item.trendType) {
|
||||
PriceChangeType.UP -> TokenPriceChangeDirection.UP
|
||||
PriceChangeType.DOWN -> TokenPriceChangeDirection.DOWN
|
||||
PriceChangeType.NEUTRAL -> TokenPriceChangeDirection.NEUTRAL
|
||||
}
|
||||
val stakingText = when (val rate = item.stakingRate) {
|
||||
is TextReference.Str -> rate.value
|
||||
is TextReference.StyledStr -> rate.value
|
||||
else -> null
|
||||
}
|
||||
return RecentSearchToken(
|
||||
id = item.id,
|
||||
name = item.name,
|
||||
symbol = item.currencySymbol,
|
||||
imageUrl = item.iconUrl,
|
||||
timestamp = item.updateTimestamp ?: System.currentTimeMillis(),
|
||||
appCurrencyCode = value.appCurrencyCode,
|
||||
appCurrencySymbol = value.appCurrencySymbol,
|
||||
price = item.price.fiatPrice,
|
||||
priceChangePercent = item.trendPercentText,
|
||||
priceChangeDirection = direction,
|
||||
marketCap = item.marketCap,
|
||||
ratingPosition = item.ratingPosition,
|
||||
isUnder100kMarketCap = item.isUnder100kMarketCap,
|
||||
stakingRateText = stakingText,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.feed.model.search.converter
|
||||
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
|
||||
internal data class MarketsListItemUMWithAppCurrency(
|
||||
val item: MarketsListItemUM,
|
||||
val appCurrencyCode: String,
|
||||
val appCurrencySymbol: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.features.feed.model.search.converter
|
||||
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.price
|
||||
import com.tangem.domain.search.model.TokenPriceChangeDirection
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class RecentSearchTokenToMarketsListItemUMConverter :
|
||||
Converter<RecentSearchTokenWithAppCurrency, MarketsListItemUM> {
|
||||
|
||||
override fun convert(value: RecentSearchTokenWithAppCurrency): MarketsListItemUM {
|
||||
val token = value.token
|
||||
val currencyCode = token.appCurrencyCode.ifEmpty { value.appCurrency.code }
|
||||
val currencySymbol = token.appCurrencySymbol.ifEmpty { value.appCurrency.symbol }
|
||||
val trendType = when (token.priceChangeDirection) {
|
||||
TokenPriceChangeDirection.UP -> PriceChangeType.UP
|
||||
TokenPriceChangeDirection.DOWN -> PriceChangeType.DOWN
|
||||
TokenPriceChangeDirection.NEUTRAL -> PriceChangeType.NEUTRAL
|
||||
}
|
||||
val priceText = token.price.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = currencyCode,
|
||||
fiatCurrencySymbol = currencySymbol,
|
||||
).price()
|
||||
}
|
||||
return MarketsListItemUM(
|
||||
id = token.id,
|
||||
name = token.name,
|
||||
currencySymbol = token.symbol,
|
||||
iconUrl = token.imageUrl,
|
||||
ratingPosition = token.ratingPosition,
|
||||
marketCap = token.marketCap,
|
||||
price = MarketsListItemUM.Price(
|
||||
text = priceText,
|
||||
annotated = token.price.toMarketsListItemPriceAnnotated(
|
||||
appCurrencyCode = currencyCode,
|
||||
appCurrencySymbol = currencySymbol,
|
||||
),
|
||||
changeType = null,
|
||||
fiatPrice = token.price,
|
||||
),
|
||||
trendPercentText = token.priceChangePercent,
|
||||
trendType = trendType,
|
||||
chartData = null,
|
||||
isUnder100kMarketCap = token.isUnder100kMarketCap,
|
||||
stakingRate = token.stakingRateText?.let { TextReference.Str(it) },
|
||||
updateTimestamp = token.timestamp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.feed.model.search.converter
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.search.model.RecentSearchToken
|
||||
|
||||
internal data class RecentSearchTokenWithAppCurrency(
|
||||
val token: RecentSearchToken,
|
||||
val appCurrency: AppCurrency,
|
||||
)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.features.feed.model.search.state.transformers
|
||||
|
||||
import com.tangem.common.ui.charts.state.MarketChartRawData
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.feed.ui.search.state.SearchContentUM
|
||||
import com.tangem.features.feed.ui.search.state.SearchUM
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class UpdateRecentTokenChartTransformer(
|
||||
private val tokenId: CryptoCurrency.RawID,
|
||||
private val chartData: MarketChartRawData,
|
||||
) : SearchUMTransformer {
|
||||
|
||||
override fun transform(prevState: SearchUM): SearchUM {
|
||||
val content = prevState.content
|
||||
if (content !is SearchContentUM.History) return prevState
|
||||
|
||||
val updatedTokens = content.recentTokens.map { token ->
|
||||
if (token.id == tokenId) {
|
||||
token.copy(chartData = chartData)
|
||||
} else {
|
||||
token
|
||||
}
|
||||
}.toImmutableList()
|
||||
|
||||
return prevState.copy(
|
||||
content = content.copy(recentTokens = updatedTokens),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import com.tangem.core.ui.components.list.InfiniteListHandler
|
|||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.domain.models.earn.EarnType
|
||||
import com.tangem.features.feed.ui.earn.components.*
|
||||
import com.tangem.features.feed.ui.earn.state.*
|
||||
import com.tangem.features.feed.ui.feed.state.FeedListSearchBar
|
||||
|
|
@ -641,7 +642,8 @@ private fun previewEarnListItemUM(
|
|||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 6.54%"),
|
||||
earnType = stringReference("Yield"),
|
||||
earnTypeTitle = stringReference("Yield"),
|
||||
earnType = EarnType.YIELD,
|
||||
onItemClick = {},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.domain.models.earn.EarnType
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
|
||||
|
||||
@Composable
|
||||
|
|
@ -95,7 +96,7 @@ private fun EarnListItemV1(item: EarnListItemUM, modifier: Modifier = Modifier)
|
|||
)
|
||||
SpacerH(2.dp)
|
||||
Text(
|
||||
text = item.earnType.resolveReference(),
|
||||
text = item.earnTypeTitle.resolveReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
maxLines = 1,
|
||||
|
|
@ -147,6 +148,7 @@ private fun EarnListItemV2(item: EarnListItemUM, modifier: Modifier = Modifier)
|
|||
ModeBlock(
|
||||
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM),
|
||||
earnType = item.earnType,
|
||||
earnTypeTitle = item.earnTypeTitle,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -200,19 +202,24 @@ private fun TokenTitle(name: String, symbol: String, modifier: Modifier = Modifi
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ModeBlock(earnType: TextReference, modifier: Modifier = Modifier) {
|
||||
private fun ModeBlock(earnType: EarnType, earnTypeTitle: TextReference, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_staking_new_16),
|
||||
imageVector = ImageVector.vectorResource(
|
||||
id = when (earnType) {
|
||||
EarnType.STAKING -> R.drawable.ic_staking_new_16
|
||||
EarnType.YIELD -> R.drawable.ic_yield_mode_16
|
||||
},
|
||||
),
|
||||
tint = TangemTheme.colors2.markers.iconGray,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = earnType.resolveReference(),
|
||||
text = earnTypeTitle.resolveReference(),
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
)
|
||||
|
|
@ -239,7 +246,8 @@ private fun EarnListItemPreviewV1() {
|
|||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 8.50%"),
|
||||
earnType = stringReference("Yield"),
|
||||
earnTypeTitle = stringReference("Yield"),
|
||||
earnType = EarnType.YIELD,
|
||||
onItemClick = {},
|
||||
),
|
||||
)
|
||||
|
|
@ -257,7 +265,8 @@ private fun EarnListItemPreviewV1() {
|
|||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 8.50%"),
|
||||
earnType = stringReference("Yield"),
|
||||
earnTypeTitle = stringReference("Yield"),
|
||||
earnType = EarnType.YIELD,
|
||||
onItemClick = {},
|
||||
),
|
||||
)
|
||||
|
|
@ -286,7 +295,8 @@ private fun EarnListItemPreviewV2() {
|
|||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 8.50%"),
|
||||
earnType = stringReference("Yield"),
|
||||
earnTypeTitle = stringReference("Yield"),
|
||||
earnType = EarnType.YIELD,
|
||||
onItemClick = {},
|
||||
),
|
||||
)
|
||||
|
|
@ -305,7 +315,8 @@ private fun EarnListItemPreviewV2() {
|
|||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 8.50%"),
|
||||
earnType = stringReference("Yield"),
|
||||
earnTypeTitle = stringReference("Yield"),
|
||||
earnType = EarnType.YIELD,
|
||||
onItemClick = {},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.core.ui.ds.image.TangemIconUM
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.domain.models.earn.EarnType
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
|
||||
|
||||
@Composable
|
||||
|
|
@ -161,7 +162,8 @@ private fun EarnListItemPreviewV1() {
|
|||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 6.54%"),
|
||||
earnType = stringReference("Yield"),
|
||||
earnTypeTitle = stringReference("Yield"),
|
||||
earnType = EarnType.YIELD,
|
||||
onItemClick = {},
|
||||
),
|
||||
onClick = {},
|
||||
|
|
@ -187,7 +189,8 @@ private fun EarnListItemPreviewV2() {
|
|||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 6.54%"),
|
||||
earnType = stringReference("Yield"),
|
||||
earnTypeTitle = stringReference("Yield"),
|
||||
earnType = EarnType.YIELD,
|
||||
onItemClick = {},
|
||||
),
|
||||
onClick = {},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.feed.ui.earn.state
|
|||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.models.earn.EarnType
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
|
|
@ -32,6 +33,7 @@ internal data class EarnListItemUM(
|
|||
val tokenName: TextReference,
|
||||
val currencyIconState: CurrencyIconState,
|
||||
val earnValue: TextReference,
|
||||
val earnType: TextReference,
|
||||
val earnType: EarnType,
|
||||
val earnTypeTitle: TextReference,
|
||||
val onItemClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -11,12 +11,14 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.earn.EarnType
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
|
||||
import com.tangem.features.feed.ui.feed.state.*
|
||||
import kotlinx.collections.immutable.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
internal object FeedListPreviewDataProvider {
|
||||
|
|
@ -246,7 +248,11 @@ internal object FeedListPreviewDataProvider {
|
|||
iconUrl = null,
|
||||
ratingPosition = rating,
|
||||
marketCap = marketCap,
|
||||
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
||||
price = MarketsListItemUM.Price(
|
||||
text = "31 285.72$",
|
||||
fiatPrice = BigDecimal(123123),
|
||||
annotated = stringReference("31 285.72$"),
|
||||
),
|
||||
trendPercentText = percent,
|
||||
trendType = trendType,
|
||||
chartData = MarketChartRawData(
|
||||
|
|
@ -273,7 +279,8 @@ internal object FeedListPreviewDataProvider {
|
|||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 6.54%"),
|
||||
earnType = stringReference("Yield"),
|
||||
earnTypeTitle = stringReference("Yield"),
|
||||
earnType = EarnType.YIELD,
|
||||
onItemClick = {},
|
||||
)
|
||||
}.toPersistentList()
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker
|
|||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
|
|
@ -182,6 +183,7 @@ private fun Header(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier)
|
|||
Column(modifier = Modifier.weight(1f)) {
|
||||
TokenPriceText(
|
||||
price = state.priceText,
|
||||
priceAnnotated = state.priceAnnotated,
|
||||
triggerPriceChange = state.triggerPriceChange,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
|
||||
|
|
@ -212,6 +214,28 @@ private fun Header(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier)
|
|||
|
||||
@Composable
|
||||
private fun TokenPriceText(
|
||||
price: String,
|
||||
triggerPriceChange: StateEvent<PriceChangeType>,
|
||||
priceAnnotated: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
TokenPriceTextV2(
|
||||
priceAnnotated = priceAnnotated,
|
||||
triggerPriceChange = triggerPriceChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
TokenPriceTextV1(
|
||||
price = price,
|
||||
triggerPriceChange = triggerPriceChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenPriceTextV1(
|
||||
price: String,
|
||||
triggerPriceChange: StateEvent<PriceChangeType>,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -244,6 +268,40 @@ private fun TokenPriceText(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenPriceTextV2(
|
||||
priceAnnotated: TextReference,
|
||||
triggerPriceChange: StateEvent<PriceChangeType>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val growColor = TangemTheme.colors2.graphic.status.accent
|
||||
val fallColor = TangemTheme.colors2.graphic.status.warning
|
||||
val generalColor = TangemTheme.colors2.text.neutral.primary
|
||||
|
||||
val color = remember(generalColor) { Animatable(generalColor) }
|
||||
|
||||
EventEffect(triggerPriceChange) { priceChangeType ->
|
||||
val nextColor = when (priceChangeType) {
|
||||
PriceChangeType.UP,
|
||||
-> growColor
|
||||
PriceChangeType.DOWN -> fallColor
|
||||
PriceChangeType.NEUTRAL -> return@EventEffect
|
||||
}
|
||||
|
||||
color.animateTo(nextColor, snap())
|
||||
color.animateTo(generalColor, tween(durationMillis = 500))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = priceAnnotated.resolveAnnotatedReference(),
|
||||
modifier = modifier,
|
||||
color = color.value,
|
||||
autoSize = TextAutoSize.StepBased(maxFontSize = TangemTheme.typography2.headingBold34.fontSize),
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography2.headingBold34,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IntervalSelector(
|
||||
trendInterval: PriceChangeInterval,
|
||||
|
|
|
|||
|
|
@ -156,12 +156,42 @@ internal fun FDVCard(item: InfoPointUMV2.FullyDilutedValuation) {
|
|||
modifier = Modifier
|
||||
.heightIn(120.dp)
|
||||
.fillMaxWidth(),
|
||||
title = { MetricValueText(value = item.value) },
|
||||
title = {
|
||||
if (item.fullyDilutedValuationChange24 != null) {
|
||||
Row {
|
||||
MetricValueText(value = item.fullyDilutedValuationChange24)
|
||||
Text(
|
||||
modifier = Modifier.padding(TangemTheme.dimens2.x1),
|
||||
text = stringResourceSafe(R.string.markets_token_details_trading_interval),
|
||||
style = TangemTheme.typography2.captionSemibold11,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
MetricValueText(value = item.value)
|
||||
}
|
||||
},
|
||||
content = {
|
||||
InformationTextBlock(
|
||||
text = resourceReference(R.string.markets_token_details_fully_diluted_valuation),
|
||||
onInfoClick = item.onInfoClick,
|
||||
)
|
||||
Column {
|
||||
if (item.fullyDilutedValuationChange24 != null) {
|
||||
Text(
|
||||
text = item.value?.resolveReference()
|
||||
?: stringResourceSafe(R.string.token_market_metrics_no_data),
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
SpacerH(4.dp)
|
||||
}
|
||||
|
||||
InformationTextBlock(
|
||||
text = resourceReference(R.string.markets_token_details_fully_diluted_valuation),
|
||||
onInfoClick = item.onInfoClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -445,6 +475,7 @@ private fun MetricsCardsPreview() {
|
|||
FDVCard(
|
||||
item = InfoPointUMV2.FullyDilutedValuation(
|
||||
value = stringReference("$ 1.5 T"),
|
||||
fullyDilutedValuationChange24 = stringReference("$ 2.44 M in total"),
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ internal object MarketsTokenDetailsPreview {
|
|||
onScroll = {},
|
||||
),
|
||||
onShareClick = {},
|
||||
priceAnnotated = stringReference("$0.00000000324"),
|
||||
)
|
||||
|
||||
val contentState = MarketsTokenDetailsUM(
|
||||
|
|
@ -144,5 +145,6 @@ internal object MarketsTokenDetailsPreview {
|
|||
onScroll = {},
|
||||
),
|
||||
onShareClick = {},
|
||||
priceAnnotated = stringReference("$0.00000000324"),
|
||||
)
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import java.math.BigDecimal
|
|||
internal data class MarketsTokenDetailsUM(
|
||||
val tokenName: String,
|
||||
val priceText: String,
|
||||
val priceAnnotated: TextReference,
|
||||
val iconUrl: String?,
|
||||
val dateTimeText: TextReference,
|
||||
val priceChangePercentText: String?,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ internal sealed interface InfoPointUMV2 {
|
|||
@Immutable
|
||||
data class FullyDilutedValuation(
|
||||
val value: TextReference?,
|
||||
val fullyDilutedValuationChange24: TextReference?,
|
||||
val onInfoClick: () -> Unit,
|
||||
) : InfoPointUMV2
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.markets.MarketsListItem
|
||||
import com.tangem.common.ui.markets.MarketsListItemPlaceholder
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
|
|
@ -133,7 +134,10 @@ private fun LazyListScope.searchHistoryItems(
|
|||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.searchResultsItems(results: SearchContentUM.Results, onResultMarketTokenClick: () -> Unit) {
|
||||
private fun LazyListScope.searchResultsItems(
|
||||
results: SearchContentUM.Results,
|
||||
onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
|
||||
) {
|
||||
if (results.userAssets.isNotEmpty()) {
|
||||
item(key = "header_portfolio") {
|
||||
SectionHeader(title = stringResourceSafe(R.string.markets_search_portfolio_header))
|
||||
|
|
@ -163,7 +167,7 @@ private fun LazyListScope.searchResultsItems(results: SearchContentUM.Results, o
|
|||
private fun LazyListScope.marketSearchResultItems(
|
||||
market: MarketSearchResultUM.Content,
|
||||
hasUserAssetsSection: Boolean,
|
||||
onResultMarketTokenClick: () -> Unit,
|
||||
onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
|
||||
) {
|
||||
if (hasUserAssetsSection) {
|
||||
item(key = "spacer_between_sections") {
|
||||
|
|
@ -185,7 +189,7 @@ private fun LazyListScope.marketSearchResultItems(
|
|||
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
|
||||
),
|
||||
model = token,
|
||||
onClick = onResultMarketTokenClick,
|
||||
onClick = { onResultMarketTokenClick(token) },
|
||||
)
|
||||
}
|
||||
if (market.shouldShowUnder100kNotification) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.tangem.features.feed.ui.search.state.*
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
/** Labeled UI state for [SearchContent] previews. */
|
||||
internal data class SearchContentPreviewScenario(
|
||||
|
|
@ -192,7 +193,11 @@ internal object SearchContentPreviewFixtures {
|
|||
iconUrl = null,
|
||||
ratingPosition = rating,
|
||||
marketCap = marketCap,
|
||||
price = MarketsListItemUM.Price(text = priceText),
|
||||
price = MarketsListItemUM.Price(
|
||||
text = priceText,
|
||||
annotated = stringReference(priceText),
|
||||
fiatPrice = BigDecimal(123123),
|
||||
),
|
||||
trendPercentText = trendText,
|
||||
trendType = trend,
|
||||
chartData = chart,
|
||||
|
|
@ -384,7 +389,7 @@ private val SearchContentPreviewCallbacks = SearchCallbacks(
|
|||
onLoadMore = {},
|
||||
onClearHintsClick = {},
|
||||
onTextHintClick = { _ -> },
|
||||
onResultMarketTokenClick = {},
|
||||
onResultMarketTokenClick = { _ -> },
|
||||
)
|
||||
|
||||
/** All [SearchContentPreviewScenario] values for the Preview Parameter dropdown in Android Studio. */
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.features.feed.ui.search.state
|
||||
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
|
||||
internal data class SearchCallbacks(
|
||||
val onLoadMore: () -> Unit,
|
||||
val onClearHintsClick: () -> Unit,
|
||||
val onTextHintClick: (hint: String) -> Unit,
|
||||
val onResultMarketTokenClick: () -> Unit,
|
||||
val onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue