Updated on 2026-08-14
This commit is contained in:
parent
25a8807f60
commit
7467c8336b
10 changed files with 149 additions and 23 deletions
|
|
@ -63,4 +63,10 @@ object MarketsDomainModule {
|
|||
networksRepository = networksRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTokenExchangesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenExchangesUseCase {
|
||||
return GetTokenExchangesUseCase(marketsTokenRepository = marketsTokenRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
},
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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 ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue