Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-04 12:13:45 +04:00
parent 25a8807f60
commit 7467c8336b
10 changed files with 149 additions and 23 deletions

View file

@ -63,4 +63,10 @@ object MarketsDomainModule {
networksRepository = networksRepository,
)
}
@Provides
@Singleton
fun provideGetTokenExchangesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenExchangesUseCase {
return GetTokenExchangesUseCase(marketsTokenRepository = marketsTokenRepository)
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.domain.markets
import arrow.core.Either
import com.tangem.domain.markets.repositories.MarketsTokenRepository
/**
* Get token exchanges use case
*
* @property marketsTokenRepository markets token repository
*/
class GetTokenExchangesUseCase(
private val marketsTokenRepository: MarketsTokenRepository,
) {
suspend operator fun invoke(tokenId: String): Either<Throwable, List<TokenMarketExchange>> {
return Either.catch { marketsTokenRepository.getTokenExchanges(tokenId = tokenId) }
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.markets.details.impl.model
import androidx.compose.runtime.Stable
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.common.ui.charts.state.MarketChartData
import com.tangem.common.ui.charts.state.MarketChartDataProducer
@ -23,11 +24,12 @@ import com.tangem.domain.markets.*
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.impl.analytics.MarketDetailsAnalyticsEvent
import com.tangem.features.markets.details.impl.model.converters.DescriptionConverter
import com.tangem.features.markets.details.impl.model.converters.ExchangeItemStateConverter
import com.tangem.features.markets.details.impl.model.converters.TokenMarketInfoConverter
import com.tangem.features.markets.details.impl.model.formatter.*
import com.tangem.features.markets.details.impl.model.state.QuotesStateUpdater
import com.tangem.features.markets.details.impl.model.state.TokenNetworksState
import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent
import com.tangem.features.markets.details.impl.ui.state.ExchangesBottomSheetContent
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
import com.tangem.features.markets.impl.R
import com.tangem.lib.crypto.BlockchainUtils
@ -54,6 +56,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase,
private val getTokenExchangesUseCase: GetTokenExchangesUseCase,
private val urlOpener: UrlOpener,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
@ -73,10 +76,8 @@ internal class MarketsTokenDetailsModel @Inject constructor(
private val infoConverter = TokenMarketInfoConverter(
appCurrency = Provider { currentAppCurrency.value },
onInfoClick = { showInfoBottomSheet(it) },
onListedOnClick = {
// TODO: [REDACTED_JIRA]
},
onInfoClick = { showBottomSheet(it) },
onListedOnClick = ::onListedOnClick,
onLinkClick = { link ->
urlOpener.openUrl(link.url)
// === Analytics ===
@ -104,7 +105,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
private val descriptionConverter = DescriptionConverter(
onReadModeClicked = {
showInfoBottomSheet(it)
showBottomSheet(it)
// === Analytics ===
analyticsEventHandler.send(analyticsEventBuilder.readMoreClicked())
},
@ -177,7 +178,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
markerSet = false,
body = MarketsTokenDetailsUM.Body.Loading,
triggerPriceChange = consumedEvent(),
infoBottomSheet = TangemBottomSheetConfig(
bottomSheetConfig = TangemBottomSheetConfig(
isShow = false,
onDismissRequest = {},
content = TangemBottomSheetConfigContent.Empty,
@ -480,24 +481,22 @@ internal class MarketsTokenDetailsModel @Inject constructor(
}
}
private fun showInfoBottomSheet(content: InfoBottomSheetContent) {
private fun showBottomSheet(content: TangemBottomSheetConfigContent) {
state.update { stateToUpdate ->
stateToUpdate.copy(
infoBottomSheet = stateToUpdate.infoBottomSheet.copy(
bottomSheetConfig = stateToUpdate.bottomSheetConfig.copy(
isShow = true,
onDismissRequest = ::hideInfoBottomSheet,
onDismissRequest = ::hideBottomSheet,
content = content,
),
)
}
}
private fun hideInfoBottomSheet() {
private fun hideBottomSheet() {
state.update { stateToUpdate ->
stateToUpdate.copy(
infoBottomSheet = stateToUpdate.infoBottomSheet.copy(
isShow = false,
),
bottomSheetConfig = stateToUpdate.bottomSheetConfig.copy(isShow = false),
)
}
}
@ -517,6 +516,39 @@ internal class MarketsTokenDetailsModel @Inject constructor(
}
}
private fun onListedOnClick(exchangesCount: Int) {
showBottomSheet(content = ExchangesBottomSheetContent.Loading(exchangesCount))
modelScope.launch {
val maybeExchanges = getTokenExchangesUseCase(tokenId = params.token.id)
updateExchangeBSContent(maybeExchanges = maybeExchanges, exchangesCount = exchangesCount)
}
}
private fun updateExchangeBSContent(
maybeExchanges: Either<Throwable, List<TokenMarketExchange>>,
exchangesCount: Int,
) {
val content = maybeExchanges
.fold(
ifLeft = {
ExchangesBottomSheetContent.Error(onRetryClick = { onListedOnClick(exchangesCount) })
},
ifRight = {
ExchangesBottomSheetContent.Content(
exchangeItems = ExchangeItemStateConverter.convertList(it).toImmutableList(),
)
},
)
state.update { stateToUpdate ->
stateToUpdate.copy(
bottomSheetConfig = stateToUpdate.bottomSheetConfig.copy(content = content),
)
}
}
private fun CoroutineScope.loadQuotesWithTimer(timeMillis: Long) {
launch {
while (true) {

View file

@ -0,0 +1,64 @@
package com.tangem.features.markets.details.impl.model.converters
import com.tangem.core.ui.components.audits.AuditLabelUM
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.markets.TokenMarketExchange
import com.tangem.domain.markets.TokenMarketExchange.TrustScore
import com.tangem.features.markets.impl.R
import com.tangem.utils.converter.Converter
/**
* Converter from [TokenMarketExchange] to [TokenItemState]
*
[REDACTED_AUTHOR]
*/
internal object ExchangeItemStateConverter : Converter<TokenMarketExchange, TokenItemState> {
override fun convert(value: TokenMarketExchange): TokenItemState {
return TokenItemState.Content(
id = value.id,
iconState = CurrencyIconState.CoinIcon(
url = value.imageUrl,
fallbackResId = R.drawable.ic_alert_24,
isGrayscale = false,
showCustomBadge = false,
),
titleState = TokenItemState.TitleState.Content(text = value.name),
fiatAmountState = TokenItemState.FiatAmountState.Content(
text = BigDecimalFormatter.formatFiatPriceUncapped(
fiatAmount = value.volumeInUsd,
fiatCurrencyCode = "USD",
fiatCurrencySymbol = "$",
),
),
subtitleState = TokenItemState.SubtitleState.TextContent(
value = if (value.isCentralized) "CEX" else "DEX",
),
subtitle2State = TokenItemState.Subtitle2State.LabelContent(
auditLabelUM = value.trustScore.toAuditLabelUM(),
),
onItemClick = null,
onItemLongClick = null,
)
}
private fun TrustScore.toAuditLabelUM(): AuditLabelUM {
return when (this) {
TrustScore.Risky -> AuditLabelUM(
text = resourceReference(id = R.string.markets_token_details_exchange_trust_score_risky),
type = AuditLabelUM.Type.Prohibition,
)
TrustScore.Caution -> AuditLabelUM(
text = resourceReference(id = R.string.markets_token_details_exchange_trust_score_caution),
type = AuditLabelUM.Type.Warning,
)
TrustScore.Trusted -> AuditLabelUM(
text = resourceReference(id = R.string.markets_token_details_exchange_trust_score_trusted),
type = AuditLabelUM.Type.Permit,
)
}
}
}

View file

@ -15,7 +15,7 @@ import com.tangem.utils.converter.Converter
internal class TokenMarketInfoConverter(
private val appCurrency: Provider<AppCurrency>,
private val onInfoClick: (InfoBottomSheetContent) -> Unit,
private val onListedOnClick: () -> Unit,
private val onListedOnClick: (Int) -> Unit,
onLinkClick: (LinksUM.Link) -> Unit,
onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit,
onInsightsIntervalChanged: (PriceChangeInterval) -> Unit,
@ -55,7 +55,7 @@ internal class TokenMarketInfoConverter(
)
},
listedOn = if (exchangesAmount != null && exchangesAmount > 0) {
ListedOnUM.Content(onClick = onListedOnClick, amount = exchangesAmount)
ListedOnUM.Content(onClick = { onListedOnClick(exchangesAmount) }, amount = exchangesAmount)
} else {
ListedOnUM.Empty
},

View file

@ -37,9 +37,12 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.markets.PriceChangeInterval
import com.tangem.features.markets.details.impl.ui.components.ExchangesBottomSheet
import com.tangem.features.markets.details.impl.ui.components.InfoBottomSheet
import com.tangem.features.markets.details.impl.ui.components.MarketTokenDetailsChart
import com.tangem.features.markets.details.impl.ui.components.tokenMarketDetailsBody
import com.tangem.features.markets.details.impl.ui.state.ExchangesBottomSheetContent
import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
import com.tangem.features.markets.impl.R
import kotlinx.collections.immutable.persistentListOf
@ -67,7 +70,10 @@ internal fun MarketsTokenDetailsContent(
addTopBarStatusBarInsets = addTopBarStatusBarPadding,
)
InfoBottomSheet(config = state.infoBottomSheet)
when (state.bottomSheetConfig.content) {
is InfoBottomSheetContent -> InfoBottomSheet(config = state.bottomSheetConfig)
is ExchangesBottomSheetContent -> ExchangesBottomSheet(config = state.bottomSheetConfig)
}
}
@Suppress("LongParameterList")
@ -299,7 +305,7 @@ private fun Preview() {
selectedInterval = PriceChangeInterval.H24,
onSelectedIntervalChange = { },
body = MarketsTokenDetailsUM.Body.Loading,
infoBottomSheet = TangemBottomSheetConfig(
bottomSheetConfig = TangemBottomSheetConfig(
isShow = false,
onDismissRequest = {},
content = TangemBottomSheetConfigContent.Empty,

View file

@ -32,7 +32,7 @@ import kotlinx.coroutines.delay
[REDACTED_AUTHOR]
*/
@Composable
internal fun ListedOnBlock(state: ListedOnUM) {
internal fun ListedOnBlock(state: ListedOnUM, modifier: Modifier = Modifier) {
Box {
InformationBlock(
title = {
@ -44,7 +44,7 @@ internal fun ListedOnBlock(state: ListedOnUM) {
overflow = TextOverflow.Ellipsis,
)
},
modifier = Modifier.clickable(enabled = state is ListedOnUM.Content) {
modifier = modifier.clickable(enabled = state is ListedOnUM.Content) {
(state as? ListedOnUM.Content)?.onClick?.invoke()
},
) {

View file

@ -116,7 +116,7 @@ internal fun LazyListScope.infoBlocksList(state: MarketsTokenDetailsUM.Informati
}
item(key = "listedOn") {
ListedOnBlock(state = state.listedOn)
ListedOnBlock(state = state.listedOn, modifier = Modifier.blockPaddings())
}
if (state.links != null) {

View file

@ -20,7 +20,7 @@ internal data class MarketsTokenDetailsUM(
val markerSet: Boolean,
val chartState: ChartState,
val onSelectedIntervalChange: (PriceChangeInterval) -> Unit,
val infoBottomSheet: TangemBottomSheetConfig,
val bottomSheetConfig: TangemBottomSheetConfig,
val triggerPriceChange: StateEvent<PriceChangeType>,
val body: Body,
) {

View file

@ -89,7 +89,7 @@ markdownComposeView = "0.5.4"
# endregion Other libraries
# region Tangem
tangemBlockchainSdk = "develop-811"
tangemBlockchainSdk = "develop-814"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-387"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^