From 7467c8336b06869565b36db4002577142504dd34 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 4 Oct 2024 12:13:45 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/MarketsDomainModule.kt | 6 ++ .../markets/GetTokenExchangesUseCase.kt | 18 ++++++ .../impl/model/MarketsTokenDetailsModel.kt | 60 +++++++++++++---- .../converters/ExchangeItemStateConverter.kt | 64 +++++++++++++++++++ .../converters/TokenMarketInfoConverter.kt | 4 +- .../impl/ui/MarketsTokenDetailsContent.kt | 10 ++- .../impl/ui/components/ListedOnBlock.kt | 4 +- .../ui/components/TokenMarketDetailsBody.kt | 2 +- .../impl/ui/state/MarketsTokenDetailsUM.kt | 2 +- gradle/dependencies.toml | 2 +- 10 files changed, 149 insertions(+), 23 deletions(-) create mode 100644 domain/markets/src/main/java/com/tangem/domain/markets/GetTokenExchangesUseCase.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/ExchangeItemStateConverter.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index 59b0f45dc5..8adbb68381 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -63,4 +63,10 @@ object MarketsDomainModule { networksRepository = networksRepository, ) } + + @Provides + @Singleton + fun provideGetTokenExchangesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenExchangesUseCase { + return GetTokenExchangesUseCase(marketsTokenRepository = marketsTokenRepository) + } } \ No newline at end of file diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenExchangesUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenExchangesUseCase.kt new file mode 100644 index 0000000000..a2862b339f --- /dev/null +++ b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenExchangesUseCase.kt @@ -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> { + return Either.catch { marketsTokenRepository.getTokenExchanges(tokenId = tokenId) } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt index 0924fa6e79..08813a2998 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt @@ -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>, + 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) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/ExchangeItemStateConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/ExchangeItemStateConverter.kt new file mode 100644 index 0000000000..f6f4d3ba8a --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/ExchangeItemStateConverter.kt @@ -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 { + + 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, + ) + } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt index b58a1e48d5..3ab8df1094 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt @@ -15,7 +15,7 @@ import com.tangem.utils.converter.Converter internal class TokenMarketInfoConverter( private val appCurrency: Provider, 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 }, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt index 26639bd472..4e46c48655 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt @@ -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, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ListedOnBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ListedOnBlock.kt index 29550713b7..2ba168b352 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ListedOnBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ListedOnBlock.kt @@ -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() }, ) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt index df869b4c73..521ced4a30 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt @@ -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) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MarketsTokenDetailsUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MarketsTokenDetailsUM.kt index 1293a56b43..a4b669739a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MarketsTokenDetailsUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MarketsTokenDetailsUM.kt @@ -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, val body: Body, ) { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ba4ad15a6f..a8df604cbf 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -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 ^