Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-05 18:33:40 +03:00
parent 1006477fd7
commit 2d753e14af
30 changed files with 189 additions and 55 deletions

View file

@ -7,6 +7,7 @@ import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.repository.* import com.tangem.domain.tokens.repository.*
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.features.markets.MarketsFeatureToggles
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
@ -230,6 +231,7 @@ internal object TokensDomainModule {
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository, stakingRepository: StakingRepository,
stakingFeatureToggles: StakingFeatureToggles, stakingFeatureToggles: StakingFeatureToggles,
marketsFeatureToggles: MarketsFeatureToggles,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetCryptoCurrencyActionsUseCase { ): GetCryptoCurrencyActionsUseCase {
return GetCryptoCurrencyActionsUseCase( return GetCryptoCurrencyActionsUseCase(
@ -241,6 +243,7 @@ internal object TokensDomainModule {
networksRepository = networksRepository, networksRepository = networksRepository,
stakingRepository = stakingRepository, stakingRepository = stakingRepository,
stakingFeatureToggles = stakingFeatureToggles, stakingFeatureToggles = stakingFeatureToggles,
marketsFeatureToggles = marketsFeatureToggles,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }

View file

@ -193,6 +193,7 @@ internal class ChildFactory @Inject constructor(
params = MarketsTokenDetailsComponent.Params( params = MarketsTokenDetailsComponent.Params(
token = route.token, token = route.token,
appCurrency = route.appCurrency, appCurrency = route.appCurrency,
showPortfolio = route.showPortfolio,
), ),
componentFactory = marketsTokenDetailsComponentFactory, componentFactory = marketsTokenDetailsComponentFactory,
) )

View file

@ -271,5 +271,6 @@ sealed class AppRoute(val path: String) : Route {
data class MarketsTokenDetails( data class MarketsTokenDetails(
val token: TokenMarketParams, val token: TokenMarketParams,
val appCurrency: AppCurrency, val appCurrency: AppCurrency,
) : AppRoute(path = "/markets_token_details/${token.id}") val showPortfolio: Boolean,
) : AppRoute(path = "/markets_token_details/${token.id}/$showPortfolio")
} }

View file

@ -125,6 +125,8 @@ fun MarketChart(
// Sometimes the chart is not drawn correctly (ex. in LazyLayout), so we need to force the redraw // Sometimes the chart is not drawn correctly (ex. in LazyLayout), so we need to force the redraw
.drawBehind { .drawBehind {
state.markerFraction state.markerFraction
state.chartColor
state.markerHighlightRightSide
}, },
chart = chart, chart = chart,
modelProducer = state.modelProducer, modelProducer = state.modelProducer,

View file

@ -22,10 +22,13 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.PrimarySmallButton import com.tangem.core.ui.components.buttons.PrimarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.buttons.chip.Chip
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.* import com.tangem.core.ui.res.*
import com.valentinilk.shimmer.* import com.valentinilk.shimmer.*
@ -123,6 +126,22 @@ fun SmallButtonShimmer(
) )
} }
/**
* Shimmer for Chip
* Height and min width will be set automatically
*/
@Composable
fun ChipShimmer(modifier: Modifier = Modifier) {
Chip(
modifier = modifier
.clip(RoundedCornerShape(size = 100.dp))
.shimmer(LocalTangemShimmer.current),
text = TextReference.EMPTY,
iconResId = R.drawable.ic_plus_24,
onClick = {},
)
}
internal val TangemShimmer: Shimmer internal val TangemShimmer: Shimmer
@Composable @Composable
get() = rememberShimmer( get() = rememberShimmer(
@ -195,6 +214,7 @@ private fun ShimmersPreview() {
) )
SmallButtonShimmer(withIcon = true) SmallButtonShimmer(withIcon = true)
SmallButtonShimmer(withIcon = false) SmallButtonShimmer(withIcon = false)
ChipShimmer()
} }
} }
} }

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M3,3V19C3,19.53 3.211,20.039 3.586,20.414C3.961,20.789 4.47,21 5,21H21"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#1E1E1E"/>
<path
android:pathData="M19,9L14,14L10,10L7,13"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#1E1E1E"/>
</vector>

View file

@ -143,7 +143,6 @@ internal class DefaultQuotesRepository(
block = { acc.add(rawCurrencyId) }, block = { acc.add(rawCurrencyId) },
) )
} }
acc acc
} }
} }

View file

@ -15,7 +15,7 @@ data class TokenMarketParams(
@Serializable @Serializable
data class Quotes( data class Quotes(
val currentPrice: SerializedBigDecimal, val currentPrice: SerializedBigDecimal,
val h24Percent: SerializedBigDecimal, val h24Percent: SerializedBigDecimal?,
val weekPercent: SerializedBigDecimal?, val weekPercent: SerializedBigDecimal?,
val monthPercent: SerializedBigDecimal?, val monthPercent: SerializedBigDecimal?,
) )

View file

@ -28,6 +28,7 @@ dependencies {
/** Project - Api */ /** Project - Api */
implementation(projects.features.send.api) implementation(projects.features.send.api)
implementation(projects.features.staking.api) implementation(projects.features.staking.api)
implementation(projects.features.markets.api)
/** Project - Other */ /** Project - Other */
implementation(projects.core.utils) implementation(projects.core.utils)

View file

@ -12,6 +12,7 @@ import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.markets.MarketsFeatureToggles
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.isNullOrZero import com.tangem.utils.isNullOrZero
@ -33,6 +34,7 @@ class GetCryptoCurrencyActionsUseCase(
private val networksRepository: NetworksRepository, private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository, private val stakingRepository: StakingRepository,
private val stakingFeatureToggles: StakingFeatureToggles, private val stakingFeatureToggles: StakingFeatureToggles,
private val marketsFeatureToggles: MarketsFeatureToggles,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) { ) {
@ -112,6 +114,12 @@ class GetCryptoCurrencyActionsUseCase(
val activeList = mutableListOf<TokenActionsState.ActionState>() val activeList = mutableListOf<TokenActionsState.ActionState>()
val disabledList = mutableListOf<TokenActionsState.ActionState>() val disabledList = mutableListOf<TokenActionsState.ActionState>()
// markets
// not a custom token
if (marketsFeatureToggles.isFeatureEnabled && cryptoCurrencyStatus.currency.id.rawCurrencyId != null) {
activeList.add(TokenActionsState.ActionState.Analytics(ScenarioUnavailabilityReason.None))
}
// copy address // copy address
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
activeList.add(TokenActionsState.ActionState.CopyAddress(ScenarioUnavailabilityReason.None)) activeList.add(TokenActionsState.ActionState.CopyAddress(ScenarioUnavailabilityReason.None))

View file

@ -30,6 +30,8 @@ data class TokenActionsState(
data class Send(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState() data class Send(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
data class Analytics(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
data class HideToken(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState() data class HideToken(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
} }
} }

View file

@ -53,6 +53,8 @@ import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.LocalSnackbarHostState import com.tangem.core.ui.res.LocalSnackbarHostState
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
@ -369,6 +371,8 @@ private fun NetworksList(
isEditable: Boolean, isEditable: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val hapticManager = LocalHapticManager.current
AnimatedVisibility( AnimatedVisibility(
modifier = modifier, modifier = modifier,
visible = networks is NetworksUM.Expanded, visible = networks is NetworksUM.Expanded,
@ -403,7 +407,14 @@ private fun NetworksList(
if (isEditable) { if (isEditable) {
TangemSwitch( TangemSwitch(
checked = network.isSelected, checked = network.isSelected,
onCheckedChange = network.onSelectedStateChange, onCheckedChange = { checked ->
if (checked) {
hapticManager.perform(TangemHapticEffect.View.ToggleOn)
} else {
hapticManager.perform(TangemHapticEffect.View.ToggleOff)
}
network.onSelectedStateChange(checked)
},
) )
} }
}, },

View file

@ -19,6 +19,7 @@ interface MarketsTokenDetailsComponent : ComposableContentComponent {
data class Params( data class Params(
val token: TokenMarketParams, val token: TokenMarketParams,
val appCurrency: AppCurrency, val appCurrency: AppCurrency,
val showPortfolio: Boolean,
) )
@Composable @Composable

View file

@ -46,6 +46,7 @@ dependencies {
implementation(deps.compose.ui.utils) implementation(deps.compose.ui.utils)
implementation(deps.lifecycle.compose) implementation(deps.lifecycle.compose)
implementation(deps.androidx.activity.compose) implementation(deps.androidx.activity.compose)
implementation(deps.markdown.composeview)
/* DI */ /* DI */
implementation(deps.hilt.android) implementation(deps.hilt.android)

View file

@ -33,17 +33,21 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
private val model: MarketsTokenDetailsModel = getOrCreateModel(params) private val model: MarketsTokenDetailsModel = getOrCreateModel(params)
private val portfolioComponent = portfolioComponentFactory.create( private val portfolioComponent: MarketsPortfolioComponent? = if (params.showPortfolio) {
context = child("my_portfolio"), portfolioComponentFactory.create(
params = MarketsPortfolioComponent.Params(params.token), context = child("my_portfolio"),
) params = MarketsPortfolioComponent.Params(params.token),
)
} else {
null
}
init { init {
componentScope.launch { componentScope.launch {
model.networksState.collectLatest { model.networksState.collectLatest {
when (it) { when (it) {
is TokenNetworksState.NetworksAvailable -> portfolioComponent.setTokenNetworks(it.networks) is TokenNetworksState.NetworksAvailable -> portfolioComponent?.setTokenNetworks(it.networks)
TokenNetworksState.NoNetworksAvailable -> portfolioComponent.setNoNetworksAvailable() TokenNetworksState.NoNetworksAvailable -> portfolioComponent?.setNoNetworksAvailable()
else -> {} else -> {}
} }
} }
@ -79,10 +83,16 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
backgroundColor = LocalMainBottomSheetColor.current.value, backgroundColor = LocalMainBottomSheetColor.current.value,
addTopBarStatusBarPadding = false, addTopBarStatusBarPadding = false,
state = state, state = state,
onBackClick = ::navigateBack, onBackClick = {
if (bsState == BottomSheetState.EXPANDED) {
navigateBack()
}
},
onHeaderSizeChange = onHeaderSizeChange, onHeaderSizeChange = onHeaderSizeChange,
portfolioBlock = { blockModifier -> portfolioBlock = portfolioComponent?.let { component ->
portfolioComponent.Content(blockModifier) { blockModifier ->
component.Content(blockModifier)
}
}, },
) )
} }
@ -109,8 +119,10 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
state = state, state = state,
onBackClick = ::navigateBack, onBackClick = ::navigateBack,
onHeaderSizeChange = {}, onHeaderSizeChange = {},
portfolioBlock = { blockModifier -> portfolioBlock = portfolioComponent?.let { component ->
portfolioComponent.Content(blockModifier) { blockModifier ->
component.Content(blockModifier)
}
}, },
) )
} }

View file

@ -4,7 +4,6 @@ import androidx.compose.runtime.Stable
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.common.ui.charts.state.MarketChartData import com.tangem.common.ui.charts.state.MarketChartData
import com.tangem.common.ui.charts.state.MarketChartDataProducer import com.tangem.common.ui.charts.state.MarketChartDataProducer
import com.tangem.common.ui.charts.state.MarketChartLook
import com.tangem.common.ui.charts.state.sorted import com.tangem.common.ui.charts.state.sorted
import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
@ -135,15 +134,16 @@ internal class MarketsTokenDetailsModel @Inject constructor(
fiatCurrencySymbol = currentAppCurrency.value.symbol, fiatCurrencySymbol = currentAppCurrency.value.symbol,
), ),
dateTimeText = resourceReference(R.string.common_today), dateTimeText = resourceReference(R.string.common_today),
priceChangePercentText = BigDecimalFormatter.formatPercent( priceChangePercentText = params.token.tokenQuotes.h24Percent?.let {
percent = params.token.tokenQuotes.h24Percent, BigDecimalFormatter.formatPercent(
useAbsoluteValue = true, percent = it,
), useAbsoluteValue = true,
)
},
priceChangeType = params.token.tokenQuotes.h24Percent.percentChangeType(), priceChangeType = params.token.tokenQuotes.h24Percent.percentChangeType(),
iconUrl = params.token.imageUrl, iconUrl = params.token.imageUrl,
chartState = MarketsTokenDetailsUM.ChartState( chartState = MarketsTokenDetailsUM.ChartState(
dataProducer = chartDataProducer, dataProducer = chartDataProducer,
chartLook = MarketChartLook(),
onLoadRetryClick = ::onLoadRetryClicked, onLoadRetryClick = ::onLoadRetryClicked,
status = MarketsTokenDetailsUM.ChartState.Status.LOADING, status = MarketsTokenDetailsUM.ChartState.Status.LOADING,
onMarkerPointSelected = ::onMarkerPointSelected, onMarkerPointSelected = ::onMarkerPointSelected,
@ -320,6 +320,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
currentQuotes.value = newInfo.quotes currentQuotes.value = newInfo.quotes
val percent = newInfo.quotes.getPercentByInterval(interval = state.value.selectedInterval) val percent = newInfo.quotes.getPercentByInterval(interval = state.value.selectedInterval)
state.update { state.update {
it.copy( it.copy(
priceText = newInfo.quotes.currentPrice.formatAsPrice(currentAppCurrency.value), priceText = newInfo.quotes.currentPrice.formatAsPrice(currentAppCurrency.value),

View file

@ -18,7 +18,6 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import com.tangem.common.ui.charts.state.MarketChartDataProducer import com.tangem.common.ui.charts.state.MarketChartDataProducer
import com.tangem.common.ui.charts.state.MarketChartLook
import com.tangem.core.ui.components.* import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
@ -54,7 +53,7 @@ internal fun MarketsTokenDetailsContent(
addTopBarStatusBarPadding: Boolean, addTopBarStatusBarPadding: Boolean,
onBackClick: () -> Unit, onBackClick: () -> Unit,
onHeaderSizeChange: (Dp) -> Unit, onHeaderSizeChange: (Dp) -> Unit,
portfolioBlock: @Composable (Modifier) -> Unit, portfolioBlock: @Composable ((Modifier) -> Unit)?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Content( Content(
@ -78,7 +77,7 @@ private fun Content(
addTopBarStatusBarInsets: Boolean, addTopBarStatusBarInsets: Boolean,
onBackClick: () -> Unit, onBackClick: () -> Unit,
onHeaderSizeChange: (Dp) -> Unit, onHeaderSizeChange: (Dp) -> Unit,
portfolioBlock: @Composable (Modifier) -> Unit, portfolioBlock: @Composable ((Modifier) -> Unit)?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val density = LocalDensity.current val density = LocalDensity.current
@ -129,9 +128,7 @@ private fun Content(
) )
} }
item { SpacerH32() } item { SpacerH32() }
item( item("chart") {
contentType = "chart",
) {
MarketTokenDetailsChart( MarketTokenDetailsChart(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
@ -167,11 +164,13 @@ private fun Header(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier)
style = TangemTheme.typography.caption2, style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary, color = TangemTheme.colors.text.tertiary,
) )
PriceChangeInPercent( if (state.priceChangePercentText != null) {
valueInPercent = state.priceChangePercentText, PriceChangeInPercent(
type = state.priceChangeType, valueInPercent = state.priceChangePercentText,
textStyle = TangemTheme.typography.caption2, type = state.priceChangeType,
) textStyle = TangemTheme.typography.caption2,
)
}
} }
} }
SpacerW4() SpacerW4()
@ -290,7 +289,6 @@ private fun Preview() {
priceChangeType = PriceChangeType.UP, priceChangeType = PriceChangeType.UP,
chartState = MarketsTokenDetailsUM.ChartState( chartState = MarketsTokenDetailsUM.ChartState(
dataProducer = MarketChartDataProducer.build { }, dataProducer = MarketChartDataProducer.build { },
chartLook = MarketChartLook(),
onLoadRetryClick = {}, onLoadRetryClick = {},
status = MarketsTokenDetailsUM.ChartState.Status.LOADING, status = MarketsTokenDetailsUM.ChartState.Status.LOADING,
onMarkerPointSelected = { _, _ -> }, onMarkerPointSelected = { _, _ -> },

View file

@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
@ -17,6 +16,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent
import dev.jeziellago.compose.markdowntext.MarkdownText
@Composable @Composable
internal fun InfoBottomSheet(config: TangemBottomSheetConfig) { internal fun InfoBottomSheet(config: TangemBottomSheetConfig) {
@ -35,10 +35,14 @@ internal fun InfoBottomSheet(config: TangemBottomSheetConfig) {
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(horizontal = TangemTheme.dimens.spacing28), .padding(horizontal = TangemTheme.dimens.spacing28),
) { ) {
Text( MarkdownText(
text = it.body.resolveReference(), markdown = it.body.resolveReference(),
style = TangemTheme.typography.body2, disableLinkMovementMethod = true,
color = TangemTheme.colors.text.secondary, linkifyMask = 0,
syntaxHighlightColor = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.body2.copy(
color = TangemTheme.colors.text.secondary,
),
) )
SpacerH(bottomBarHeight) SpacerH(bottomBarHeight)
} }

View file

@ -10,7 +10,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.components.SmallButtonShimmer import com.tangem.core.ui.components.ChipShimmer
import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.InformationBlock import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.buttons.chip.Chip import com.tangem.core.ui.components.buttons.chip.Chip
@ -145,9 +145,8 @@ private fun SubBlockPlaceholder(modifier: Modifier = Modifier, lastBlock: Boolea
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) { ) {
repeat(times = 3) { repeat(times = 3) {
SmallButtonShimmer( ChipShimmer(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
withIcon = true,
) )
} }
} }

View file

@ -161,7 +161,7 @@ internal fun PricePerformanceBlockPlaceholder(modifier: Modifier = Modifier) {
}, },
content = { content = {
Column( Column(
modifier = modifier.padding(vertical = TangemTheme.dimens.spacing8), modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) { ) {
Row( Row(

View file

@ -13,7 +13,7 @@ import com.tangem.features.markets.tokenlist.impl.ui.components.UnableToLoadData
internal fun LazyListScope.tokenMarketDetailsBody( internal fun LazyListScope.tokenMarketDetailsBody(
state: MarketsTokenDetailsUM.Body, state: MarketsTokenDetailsUM.Body,
portfolioBlock: @Composable (Modifier) -> Unit, portfolioBlock: @Composable ((Modifier) -> Unit)?,
) { ) {
when (state) { when (state) {
MarketsTokenDetailsUM.Body.Loading -> { MarketsTokenDetailsUM.Body.Loading -> {
@ -21,8 +21,10 @@ internal fun LazyListScope.tokenMarketDetailsBody(
DescriptionPlaceholder(modifier = Modifier.blockPaddings()) DescriptionPlaceholder(modifier = Modifier.blockPaddings())
} }
item(key = "portfolio") { if (portfolioBlock != null) {
portfolioBlock(Modifier.blockPaddings()) item(key = "portfolio") {
portfolioBlock(Modifier.blockPaddings())
}
} }
loadingInfoBlocks() loadingInfoBlocks()
@ -32,8 +34,10 @@ internal fun LazyListScope.tokenMarketDetailsBody(
description(state.description) description(state.description)
} }
item(key = "portfolio") { if (portfolioBlock != null) {
portfolioBlock(Modifier.blockPaddings()) item(key = "portfolio") {
portfolioBlock(Modifier.blockPaddings())
}
} }
infoBlocksList(state.infoBlocks) infoBlocksList(state.infoBlocks)

View file

@ -2,7 +2,6 @@ package com.tangem.features.markets.details.impl.ui.state
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.tangem.common.ui.charts.state.MarketChartDataProducer import com.tangem.common.ui.charts.state.MarketChartDataProducer
import com.tangem.common.ui.charts.state.MarketChartLook
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.event.StateEvent
@ -15,7 +14,7 @@ internal data class MarketsTokenDetailsUM(
val priceText: String, val priceText: String,
val iconUrl: String?, val iconUrl: String?,
val dateTimeText: TextReference, val dateTimeText: TextReference,
val priceChangePercentText: String, val priceChangePercentText: String?,
val priceChangeType: PriceChangeType, val priceChangeType: PriceChangeType,
val selectedInterval: PriceChangeInterval, val selectedInterval: PriceChangeInterval,
val markerSet: Boolean, val markerSet: Boolean,
@ -29,7 +28,6 @@ internal data class MarketsTokenDetailsUM(
data class ChartState( data class ChartState(
val status: Status, val status: Status,
val dataProducer: MarketChartDataProducer, val dataProducer: MarketChartDataProducer,
val chartLook: MarketChartLook,
val onLoadRetryClick: () -> Unit, val onLoadRetryClick: () -> Unit,
val onMarkerPointSelected: (time: BigDecimal?, price: BigDecimal?) -> Unit, val onMarkerPointSelected: (time: BigDecimal?, price: BigDecimal?) -> Unit,
) { ) {

View file

@ -70,6 +70,7 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
params = MarketsTokenDetailsComponent.Params( params = MarketsTokenDetailsComponent.Params(
token = token.toSerializableParam(), token = token.toSerializableParam(),
appCurrency = appCurrency, appCurrency = appCurrency,
showPortfolio = true,
), ),
), ),
) )

View file

@ -182,13 +182,12 @@ internal class TokenActionsHandler @AssistedInject constructor(
is TokenActionsState.ActionState.HideToken -> null is TokenActionsState.ActionState.HideToken -> null
is TokenActionsState.ActionState.Receive -> TokenActionsBSContentUM.Action.Receive is TokenActionsState.ActionState.Receive -> TokenActionsBSContentUM.Action.Receive
is TokenActionsState.ActionState.Sell -> TokenActionsBSContentUM.Action.Sell is TokenActionsState.ActionState.Sell -> TokenActionsBSContentUM.Action.Sell
is TokenActionsState.ActionState.Send -> { is TokenActionsState.ActionState.Send -> TokenActionsBSContentUM.Action.Send.takeIf {
TokenActionsBSContentUM.Action.Send.takeIf { cryptoCurrencyData.status.value.amount.isNullOrZero().not()
cryptoCurrencyData.status.value.amount.isNullOrZero().not()
}
} }
is TokenActionsState.ActionState.Stake -> TokenActionsBSContentUM.Action.Stake is TokenActionsState.ActionState.Stake -> TokenActionsBSContentUM.Action.Stake
is TokenActionsState.ActionState.Swap -> TokenActionsBSContentUM.Action.Exchange is TokenActionsState.ActionState.Swap -> TokenActionsBSContentUM.Action.Exchange
is TokenActionsState.ActionState.Analytics -> null
} }
.takeIf { this.unavailabilityReason == ScenarioUnavailabilityReason.None } .takeIf { this.unavailabilityReason == ScenarioUnavailabilityReason.None }

View file

@ -36,6 +36,8 @@ import com.tangem.core.ui.components.currency.icon.CoinIcon
import com.tangem.core.ui.components.rows.ArrowRow import com.tangem.core.ui.components.rows.ArrowRow
import com.tangem.core.ui.components.rows.BlockchainRow import com.tangem.core.ui.components.rows.BlockchainRow
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.markets.impl.R import com.tangem.features.markets.impl.R
@ -168,6 +170,8 @@ private fun ContinueButton(
@Suppress("LongMethod") @Suppress("LongMethod")
@Composable @Composable
private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifier) { private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifier) {
val hapticManager = LocalHapticManager.current
InformationBlock( InformationBlock(
modifier = modifier, modifier = modifier,
title = { title = {
@ -229,7 +233,15 @@ private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifi
} else { } else {
TangemTheme.colors.icon.inactive TangemTheme.colors.icon.inactive
}, },
onCheckedChange = { state.onNetworkSwitchClick(network, it) }, onCheckedChange = { checked ->
if (checked) {
hapticManager.perform(TangemHapticEffect.View.ToggleOn)
} else {
hapticManager.perform(TangemHapticEffect.View.ToggleOff)
}
state.onNetworkSwitchClick(network, checked)
},
enabled = network.isEnabled, enabled = network.isEnabled,
) )
}, },

View file

@ -143,6 +143,7 @@ internal class TokenMarketBlockModel @Inject constructor(
AppRoute.MarketsTokenDetails( AppRoute.MarketsTokenDetails(
token = tokenParam, token = tokenParam,
appCurrency = currentAppCurrency.value, appCurrency = currentAppCurrency.value,
showPortfolio = false,
), ),
) )
} }

View file

@ -79,6 +79,7 @@ dependencies {
implementation(projects.domain.analytics) implementation(projects.domain.analytics)
implementation(projects.domain.visa) implementation(projects.domain.visa)
implementation(projects.domain.staking.models) implementation(projects.domain.staking.models)
implementation(projects.domain.markets.models)
//TODO: Create api/impl modules for onboarding [REDACTED_JIRA] //TODO: Create api/impl modules for onboarding [REDACTED_JIRA]
implementation(projects.features.onboarding) implementation(projects.features.onboarding)

View file

@ -95,6 +95,11 @@ internal class MultiWalletCurrencyActionsConverter(
icon = R.drawable.ic_hide_24 icon = R.drawable.ic_hide_24
action = { clickIntents.onHideTokensClick(cryptoCurrencyStatus) } action = { clickIntents.onHideTokensClick(cryptoCurrencyStatus) }
} }
is TokenActionsState.ActionState.Analytics -> {
title = resourceReference(R.string.common_analytics)
icon = R.drawable.ic_analytics_24
action = { clickIntents.onAnalyticsClick(cryptoCurrencyStatus) }
}
} }
return TokenActionButtonConfig( return TokenActionButtonConfig(

View file

@ -20,6 +20,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.* import com.tangem.domain.tokens.*
@ -48,6 +49,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.math.BigDecimal
import javax.inject.Inject import javax.inject.Inject
interface WalletCurrencyActionsClickIntents { interface WalletCurrencyActionsClickIntents {
@ -77,6 +79,8 @@ interface WalletCurrencyActionsClickIntents {
fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus) fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onExploreClick() fun onExploreClick()
fun onAnalyticsClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
} }
@Suppress("LongParameterList", "LargeClass") @Suppress("LongParameterList", "LargeClass")
@ -378,6 +382,31 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
showErrorIfDemoModeOrElse(action = ::openExplorer) showErrorIfDemoModeOrElse(action = ::openExplorer)
} }
override fun onAnalyticsClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
viewModelScope.launch {
val rawId = cryptoCurrencyStatus.currency.id.rawCurrencyId ?: return@launch
appRouter.push(
AppRoute.MarketsTokenDetails(
TokenMarketParams(
id = rawId,
name = cryptoCurrencyStatus.currency.name,
symbol = cryptoCurrencyStatus.currency.symbol,
tokenQuotes = TokenMarketParams.Quotes(
currentPrice = cryptoCurrencyStatus.value.fiatRate ?: BigDecimal.ZERO,
h24Percent = cryptoCurrencyStatus.value.priceChange,
weekPercent = null,
monthPercent = null,
),
imageUrl = cryptoCurrencyStatus.currency.iconUrl,
),
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
showPortfolio = true,
),
)
}
}
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) { override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId)) stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId))

View file

@ -85,6 +85,7 @@ leakcanary = "2.13"
decompose = "2.2.2" decompose = "2.2.2"
room = "2.6.1" room = "2.6.1"
markdown = "0.7.2" markdown = "0.7.2"
markdownComposeView = "0.5.4"
# endregion Other libraries # endregion Other libraries
# region Tangem # region Tangem
@ -270,4 +271,5 @@ room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" } markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" }
markdown-composeview = { module = "com.github.jeziellago:compose-markdown", version.ref = "markdownComposeView" }
# endregion Other # endregion Other