Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-15 13:34:06 +03:00
parent ddba5fe3cf
commit 5ce84d1500
14 changed files with 2832 additions and 0 deletions

View file

@ -84,6 +84,7 @@ dependencies {
api(projects.libs.blockchainSdk)
implementation(projects.common)
implementation(projects.common.ui)
implementation(projects.common.uiCharts)
implementation(projects.libs.auth)
implementation(projects.libs.crypto)
implementation(projects.libs.tangemSdkApi)

View file

@ -16,6 +16,7 @@ import com.tangem.core.ui.ds2.row.TangemRowContentLead
import com.tangem.core.ui.ds2.row.TangemRowVerticalAlignment
import com.tangem.core.ui.ds2.tokenicon.TangemTokenIcon
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
import com.tangem.core.ui.ds2.util.TangemPriceChange
internal sealed interface StoryBookPage
@ -446,6 +447,64 @@ internal data class TextStyleStory(
}
}
@Suppress("BooleanPropertyNaming")
internal data class TangemTokenRowV2Story(
val variant: Variant,
val direction: TangemPriceChange.Direction,
val hasBadge: Boolean,
val isBadgeFilled: Boolean,
val hasPending: Boolean,
val hasQuote: Boolean,
val hasPriceChange: Boolean,
val hasCryptoBalance: Boolean,
val hasContractWarning: Boolean,
val hasUpdateWarning: Boolean,
val hasMessageBubble: Boolean,
val isBalanceHidden: Boolean,
val isQuoteFlickering: Boolean,
val isBalanceFlickering: Boolean,
val onVariantChange: (Variant) -> Unit,
val onDirectionChange: (TangemPriceChange.Direction) -> Unit,
val onBadgeToggle: () -> Unit,
val onBadgeFilledToggle: () -> Unit,
val onPendingToggle: () -> Unit,
val onQuoteToggle: () -> Unit,
val onPriceChangeToggle: () -> Unit,
val onCryptoBalanceToggle: () -> Unit,
val onContractWarningToggle: () -> Unit,
val onUpdateWarningToggle: () -> Unit,
val onMessageBubbleToggle: () -> Unit,
val onBalanceHiddenToggle: () -> Unit,
val onQuoteFlickeringToggle: () -> Unit,
val onBalanceFlickeringToggle: () -> Unit,
) : DsStoryBookPage {
/** Which [com.tangem.core.ui.ds2.tokenrow.TangemTokenRow] variant/preset is rendered. */
enum class Variant { Default, Organize, Unreachable, NoAddress, Shimmer }
}
@Suppress("BooleanPropertyNaming")
internal data class TangemTokenRowMarketStory(
val direction: TangemPriceChange.Direction,
val isShimmer: Boolean,
val hasTicker: Boolean,
val hasPosition: Boolean,
val hasCapitalization: Boolean,
val hasPrice: Boolean,
val hasPriceChange: Boolean,
val hasChart: Boolean,
val longTitle: Boolean,
val onDirectionChange: (TangemPriceChange.Direction) -> Unit,
val onShimmerToggle: () -> Unit,
val onTickerToggle: () -> Unit,
val onPositionToggle: () -> Unit,
val onCapitalizationToggle: () -> Unit,
val onPriceToggle: () -> Unit,
val onPriceChangeToggle: () -> Unit,
val onChartToggle: () -> Unit,
val onLongTitleToggle: () -> Unit,
) : DsStoryBookPage
internal data class TangemBadgeV2Story(
val variant: TangemBadge.Variant,
val status: TangemBadge.Status,

View file

@ -28,6 +28,8 @@ import com.tangem.feature.tester.presentation.storybook.page.ds.search.tangemSea
import com.tangem.feature.tester.presentation.storybook.page.ds.shimmer.tangemShimmerStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.textstyle.textStyleStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.tokenicon.tangemTokenIconStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.tokenrowmarket.tangemTokenRowMarketStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.tokenrowv2.tangemTokenRowV2StoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.topnavigation.tangemTopNavigationStoryFactory
private data class DsStoryItem(val title: String, val factory: StoryPageFactory)
@ -40,6 +42,8 @@ private fun buildDsStories() = listOf(
DsStoryItem(title = "⭕ TangemCheckmark", factory = tangemCheckmarkStoryFactory),
DsStoryItem(title = "🎨 TangemTokenIcon", factory = tangemTokenIconStoryFactory),
DsStoryItem(title = "📋 TangemRow", factory = tangemRowStoryFactory),
DsStoryItem(title = "🪙 TangemTokenRow", factory = tangemTokenRowV2StoryFactory),
DsStoryItem(title = "📈 TangemTokenRowMarket", factory = tangemTokenRowMarketStoryFactory),
DsStoryItem(title = "🔎 TangemSearch", factory = tangemSearchStoryFactory),
DsStoryItem(title = "✨ TangemShimmer", factory = tangemShimmerStoryFactory),
DsStoryItem(title = "🌫️ TangemFade", factory = tangemFadeStoryFactory),

View file

@ -0,0 +1,32 @@
package com.tangem.feature.tester.presentation.storybook.page.ds.tokenrowmarket
import com.tangem.core.ui.ds2.util.TangemPriceChange
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowMarketStory
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemTokenRowMarketStory>.build(): TangemTokenRowMarketStory {
return TangemTokenRowMarketStory(
direction = TangemPriceChange.Direction.Up,
isShimmer = false,
hasTicker = true,
hasPosition = true,
hasCapitalization = true,
hasPrice = true,
hasPriceChange = true,
hasChart = true,
longTitle = false,
onDirectionChange = { direction -> updateStory { it.copy(direction = direction) } },
onShimmerToggle = { updateStory { it.copy(isShimmer = !it.isShimmer) } },
onTickerToggle = { updateStory { it.copy(hasTicker = !it.hasTicker) } },
onPositionToggle = { updateStory { it.copy(hasPosition = !it.hasPosition) } },
onCapitalizationToggle = { updateStory { it.copy(hasCapitalization = !it.hasCapitalization) } },
onPriceToggle = { updateStory { it.copy(hasPrice = !it.hasPrice) } },
onPriceChangeToggle = { updateStory { it.copy(hasPriceChange = !it.hasPriceChange) } },
onChartToggle = { updateStory { it.copy(hasChart = !it.hasChart) } },
onLongTitleToggle = { updateStory { it.copy(longTitle = !it.longTitle) } },
)
}
internal val tangemTokenRowMarketStoryFactory
get() = storyPageFactory(StateUpdater<TangemTokenRowMarketStory>::build)

View file

@ -0,0 +1,243 @@
@file:Suppress("MagicNumber")
package com.tangem.feature.tester.presentation.storybook.page.ds.tokenrowmarket
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.charts.MarketChartMini
import com.tangem.common.ui.charts.state.MarketChartLook
import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.core.ui.ds2.tokenicon.TangemTokenIcon
import com.tangem.core.ui.ds2.tokenrow.Shimmer
import com.tangem.core.ui.ds2.tokenrow.TangemTokenRowMarket
import com.tangem.core.ui.ds2.util.TangemPriceChange
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowMarketStory
import kotlinx.collections.immutable.persistentListOf
private const val SAMPLE_ICON_URL = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/bitcoin.png"
@Composable
internal fun TangemTokenRowMarketStory(state: TangemTokenRowMarketStory, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.background(TangemTheme.colors.background.primary)
.padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
ComponentPreview(state = state)
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
DirectionSelector(selected = state.direction, onSelect = state.onDirectionChange)
Toggles(state = state)
}
}
}
@Composable
private fun ComponentPreview(state: TangemTokenRowMarketStory) {
val icon = TangemTokenIcon.UiState.Token(TangemTokenIcon.State(url = SAMPLE_ICON_URL))
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors3.bg.primary)
.padding(vertical = 16.dp),
) {
if (state.isShimmer) {
TangemTokenRowMarket.Shimmer()
} else {
TangemTokenRowMarket(
icon = icon,
title = if (state.longTitle) {
stringReference("Very Long Token Name Coin")
} else {
stringReference("Bitcoin")
},
ticker = if (state.hasTicker) stringReference("BTC") else null,
position = if (state.hasPosition) stringReference("2") else null,
capitalization = if (state.hasCapitalization) stringReference("1.196T") else null,
price = if (state.hasPrice) stringReference("$59,723.24") else null,
priceChange = if (state.hasPriceChange) {
TangemPriceChange.State(value = stringReference("2.08%"), direction = state.direction)
} else {
null
},
chart = if (state.hasChart) {
{ MarketChart(direction = state.direction) }
} else {
null
},
onClick = {},
)
}
}
}
// The graph slot is filled with the real MarketChartMini from :common:ui-charts, sized 24x32
// (collapsed) per the Figma spec, showing how a feature wires the chart into the DS slot.
@Composable
private fun MarketChart(direction: TangemPriceChange.Direction) {
val type = when (direction) {
TangemPriceChange.Direction.Up -> MarketChartLook.Type.Growing
TangemPriceChange.Direction.Down -> MarketChartLook.Type.Falling
TangemPriceChange.Direction.Neutral -> MarketChartLook.Type.Neutral
}
MarketChartMini(
rawData = PreviewChartData,
type = type,
modifier = Modifier.size(width = 24.dp, height = 32.dp),
)
}
private val PreviewChartData = MarketChartRawData(
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
)
@Composable
private fun DirectionSelector(selected: TangemPriceChange.Direction, onSelect: (TangemPriceChange.Direction) -> Unit) {
Section(label = "Price change") {
ChipGrid(
items = TangemPriceChange.Direction.entries,
label = { it.name },
isSelected = { it == selected },
onSelect = onSelect,
)
}
}
@Composable
private fun Toggles(state: TangemTokenRowMarketStory) {
Section(label = "Flags") {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
ToggleRow(label = "shimmer", checked = state.isShimmer, onToggle = state.onShimmerToggle)
ToggleRow(label = "ticker", checked = state.hasTicker, onToggle = state.onTickerToggle)
ToggleRow(label = "position", checked = state.hasPosition, onToggle = state.onPositionToggle)
ToggleRow(
label = "capitalization",
checked = state.hasCapitalization,
onToggle = state.onCapitalizationToggle,
)
ToggleRow(label = "price", checked = state.hasPrice, onToggle = state.onPriceToggle)
ToggleRow(label = "priceChange", checked = state.hasPriceChange, onToggle = state.onPriceChangeToggle)
ToggleRow(label = "chart", checked = state.hasChart, onToggle = state.onChartToggle)
ToggleRow(label = "longTitle", checked = state.longTitle, onToggle = state.onLongTitleToggle)
}
}
}
@Composable
private fun Section(label: String, content: @Composable () -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = label,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
content()
}
}
@Composable
private fun <T> ChipGrid(items: List<T>, label: (T) -> String, isSelected: (T) -> Boolean, onSelect: (T) -> Unit) {
val shape = RoundedCornerShape(50)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(shape)
.background(TangemTheme.colors2.surface.level2)
.border(
width = 1.dp,
color = TangemTheme.colors2.border.neutral.secondary,
shape = shape,
)
.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
items.forEach { item ->
Chip(
label = label(item),
selected = isSelected(item),
onClick = { onSelect(item) },
modifier = Modifier.weight(1f),
)
}
}
}
@Composable
private fun Chip(label: String, selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
val chipShape = RoundedCornerShape(50)
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.clip(chipShape)
.background(
if (selected) TangemTheme.colors2.surface.level3 else TangemTheme.colors2.surface.level2,
)
.clickable(onClick = onClick)
.padding(vertical = 8.dp, horizontal = 4.dp),
) {
Text(
text = label,
style = TangemTheme.typography.caption2,
color = if (selected) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.secondary,
)
}
}
@Composable
private fun ToggleRow(label: String, checked: Boolean, onToggle: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(12.dp))
.background(TangemTheme.colors2.surface.level2)
.clickable(onClick = onToggle)
.padding(horizontal = 12.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = label,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = if (checked) "ON" else "OFF",
style = TangemTheme.typography.caption2,
color = if (checked) TangemTheme.colors.text.accent else TangemTheme.colors.text.secondary,
)
}
}

View file

@ -0,0 +1,42 @@
package com.tangem.feature.tester.presentation.storybook.page.ds.tokenrowv2
import com.tangem.core.ui.ds2.util.TangemPriceChange
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowV2Story
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemTokenRowV2Story>.build(): TangemTokenRowV2Story {
return TangemTokenRowV2Story(
variant = TangemTokenRowV2Story.Variant.Default,
direction = TangemPriceChange.Direction.Up,
hasBadge = true,
isBadgeFilled = false,
hasPending = false,
hasQuote = true,
hasPriceChange = true,
hasCryptoBalance = true,
hasContractWarning = false,
hasUpdateWarning = false,
hasMessageBubble = true,
isBalanceHidden = false,
isQuoteFlickering = false,
isBalanceFlickering = false,
onVariantChange = { variant -> updateStory { it.copy(variant = variant) } },
onDirectionChange = { direction -> updateStory { it.copy(direction = direction) } },
onBadgeToggle = { updateStory { it.copy(hasBadge = !it.hasBadge) } },
onBadgeFilledToggle = { updateStory { it.copy(isBadgeFilled = !it.isBadgeFilled) } },
onPendingToggle = { updateStory { it.copy(hasPending = !it.hasPending) } },
onQuoteToggle = { updateStory { it.copy(hasQuote = !it.hasQuote) } },
onPriceChangeToggle = { updateStory { it.copy(hasPriceChange = !it.hasPriceChange) } },
onCryptoBalanceToggle = { updateStory { it.copy(hasCryptoBalance = !it.hasCryptoBalance) } },
onContractWarningToggle = { updateStory { it.copy(hasContractWarning = !it.hasContractWarning) } },
onUpdateWarningToggle = { updateStory { it.copy(hasUpdateWarning = !it.hasUpdateWarning) } },
onMessageBubbleToggle = { updateStory { it.copy(hasMessageBubble = !it.hasMessageBubble) } },
onBalanceHiddenToggle = { updateStory { it.copy(isBalanceHidden = !it.isBalanceHidden) } },
onQuoteFlickeringToggle = { updateStory { it.copy(isQuoteFlickering = !it.isQuoteFlickering) } },
onBalanceFlickeringToggle = { updateStory { it.copy(isBalanceFlickering = !it.isBalanceFlickering) } },
)
}
internal val tangemTokenRowV2StoryFactory
get() = storyPageFactory(StateUpdater<TangemTokenRowV2Story>::build)

View file

@ -0,0 +1,308 @@
@file:Suppress("MagicNumber")
package com.tangem.feature.tester.presentation.storybook.page.ds.tokenrowv2
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds2.badge.TangemBadge
import com.tangem.core.ui.ds2.messagebubble.TangemMessageBubble
import com.tangem.core.ui.ds2.tokenicon.TangemTokenIcon
import com.tangem.core.ui.ds2.tokenrow.NoAddress
import com.tangem.core.ui.ds2.tokenrow.Organize
import com.tangem.core.ui.ds2.tokenrow.Shimmer
import com.tangem.core.ui.ds2.tokenrow.TangemTokenRow
import com.tangem.core.ui.ds2.tokenrow.Unreachable
import com.tangem.core.ui.ds2.util.TangemPriceChange
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_chart_bar_vertical_16
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowV2Story
private const val SAMPLE_ICON_URL = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/bitcoin.png"
@Composable
internal fun TangemTokenRowV2Story(state: TangemTokenRowV2Story, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.background(TangemTheme.colors.background.primary)
.padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
ComponentPreview(state = state)
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
VariantSelector(selected = state.variant, onSelect = state.onVariantChange)
DirectionSelector(selected = state.direction, onSelect = state.onDirectionChange)
Toggles(state = state)
}
}
}
@Composable
private fun ComponentPreview(state: TangemTokenRowV2Story) {
val icon = TangemTokenIcon.UiState.Token(TangemTokenIcon.State(url = SAMPLE_ICON_URL))
val priceChange = if (state.hasPriceChange) {
TangemPriceChange.State(value = stringReference("2.08%"), direction = state.direction)
} else {
null
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors3.bg.primary)
.padding(vertical = 16.dp),
) {
when (state.variant) {
TangemTokenRowV2Story.Variant.Default -> DefaultRowPreview(
state = state,
icon = icon,
priceChange = priceChange,
)
TangemTokenRowV2Story.Variant.Organize -> TangemTokenRow.Organize(
icon = icon,
title = stringReference("Bitcoin"),
ticker = stringReference("BTC"),
fiatBalance = stringReference("$583.00"),
isBalanceHidden = state.isBalanceHidden,
)
TangemTokenRowV2Story.Variant.Unreachable -> TangemTokenRow.Unreachable(
icon = icon,
title = stringReference("Bitcoin"),
badge = stringReference("Unreachable"),
quote = if (state.hasQuote) stringReference("$1.00") else null,
priceChange = priceChange,
onClick = {},
)
TangemTokenRowV2Story.Variant.NoAddress -> TangemTokenRow.NoAddress(
icon = icon,
title = stringReference("Bitcoin"),
message = stringReference("No address"),
quote = if (state.hasQuote) stringReference("$1.00") else null,
priceChange = priceChange,
onClick = {},
)
TangemTokenRowV2Story.Variant.Shimmer -> TangemTokenRow.Shimmer()
}
}
}
@Composable
private fun DefaultRowPreview(
state: TangemTokenRowV2Story,
icon: TangemTokenIcon.UiState,
priceChange: TangemPriceChange.State?,
) {
TangemTokenRow(
icon = icon,
title = stringReference("Bitcoin"),
badge = if (state.hasBadge) {
TangemTokenRow.Badge(
text = stringReference("APY 5.47%"),
variant = if (state.isBadgeFilled) TangemBadge.Variant.Solid else TangemBadge.Variant.Tinted,
status = if (state.isBadgeFilled) TangemBadge.Status.Success else TangemBadge.Status.Neutral,
)
} else {
null
},
hasPending = state.hasPending,
quote = if (state.hasQuote) stringReference("$1.00") else null,
priceChange = priceChange,
fiatBalance = stringReference("$583.00"),
cryptoBalance = if (state.hasCryptoBalance) stringReference("0,000015 BTC") else null,
showContractWarning = state.hasContractWarning,
showUpdateWarning = state.hasUpdateWarning,
isBalanceHidden = state.isBalanceHidden,
isQuoteFlickering = state.isQuoteFlickering,
isBalanceFlickering = state.isBalanceFlickering,
messageBubble = if (state.hasMessageBubble) {
{
TangemMessageBubble(
text = stringReference("Enable 5.47% APY on your balance"),
variant = TangemMessageBubble.Variant.Success,
icon = Icons.ic_chart_bar_vertical_16,
onClick = {},
onClose = {},
closeContentDescription = "Dismiss",
)
}
} else {
null
},
onClick = {},
)
}
@Composable
private fun VariantSelector(
selected: TangemTokenRowV2Story.Variant,
onSelect: (TangemTokenRowV2Story.Variant) -> Unit,
) {
Section(label = "Variant") {
ChipGrid(
items = TangemTokenRowV2Story.Variant.entries,
label = { it.name },
isSelected = { it == selected },
onSelect = onSelect,
)
}
}
@Composable
private fun DirectionSelector(selected: TangemPriceChange.Direction, onSelect: (TangemPriceChange.Direction) -> Unit) {
Section(label = "Price change") {
ChipGrid(
items = TangemPriceChange.Direction.entries,
label = { it.name },
isSelected = { it == selected },
onSelect = onSelect,
)
}
}
@Composable
private fun Toggles(state: TangemTokenRowV2Story) {
Section(label = "Flags") {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
ToggleRow(label = "badge", checked = state.hasBadge, onToggle = state.onBadgeToggle)
ToggleRow(label = "badgeFilled", checked = state.isBadgeFilled, onToggle = state.onBadgeFilledToggle)
ToggleRow(label = "pending", checked = state.hasPending, onToggle = state.onPendingToggle)
ToggleRow(label = "quote", checked = state.hasQuote, onToggle = state.onQuoteToggle)
ToggleRow(label = "priceChange", checked = state.hasPriceChange, onToggle = state.onPriceChangeToggle)
ToggleRow(label = "cryptoBalance", checked = state.hasCryptoBalance, onToggle = state.onCryptoBalanceToggle)
ToggleRow(
label = "contractWarning",
checked = state.hasContractWarning,
onToggle = state.onContractWarningToggle,
)
ToggleRow(label = "updateWarning", checked = state.hasUpdateWarning, onToggle = state.onUpdateWarningToggle)
ToggleRow(label = "messageBubble", checked = state.hasMessageBubble, onToggle = state.onMessageBubbleToggle)
ToggleRow(label = "balanceHidden", checked = state.isBalanceHidden, onToggle = state.onBalanceHiddenToggle)
ToggleRow(
label = "quoteFlickering",
checked = state.isQuoteFlickering,
onToggle = state.onQuoteFlickeringToggle,
)
ToggleRow(
label = "balanceFlickering",
checked = state.isBalanceFlickering,
onToggle = state.onBalanceFlickeringToggle,
)
}
}
}
@Composable
private fun Section(label: String, content: @Composable () -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = label,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
content()
}
}
@Composable
private fun <T> ChipGrid(items: List<T>, label: (T) -> String, isSelected: (T) -> Boolean, onSelect: (T) -> Unit) {
val shape = RoundedCornerShape(50)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(shape)
.background(TangemTheme.colors2.surface.level2)
.border(
width = 1.dp,
color = TangemTheme.colors2.border.neutral.secondary,
shape = shape,
)
.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
items.forEach { item ->
Chip(
label = label(item),
selected = isSelected(item),
onClick = { onSelect(item) },
modifier = Modifier.weight(1f),
)
}
}
}
@Composable
private fun Chip(label: String, selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
val chipShape = RoundedCornerShape(50)
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.clip(chipShape)
.background(
if (selected) TangemTheme.colors2.surface.level3 else TangemTheme.colors2.surface.level2,
)
.clickable(onClick = onClick)
.padding(vertical = 8.dp, horizontal = 4.dp),
) {
Text(
text = label,
style = TangemTheme.typography.caption2,
color = if (selected) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.secondary,
)
}
}
@Composable
private fun ToggleRow(label: String, checked: Boolean, onToggle: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(12.dp))
.background(TangemTheme.colors2.surface.level2)
.clickable(onClick = onToggle)
.padding(horizontal = 12.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = label,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = if (checked) "ON" else "OFF",
style = TangemTheme.typography.caption2,
color = if (checked) TangemTheme.colors.text.accent else TangemTheme.colors.text.secondary,
)
}
}

View file

@ -50,6 +50,8 @@ import com.tangem.feature.tester.presentation.storybook.page.ds.search.TangemSea
import com.tangem.feature.tester.presentation.storybook.page.ds.shimmer.TangemShimmerStory
import com.tangem.feature.tester.presentation.storybook.page.ds.textstyle.TextStyleStory
import com.tangem.feature.tester.presentation.storybook.page.ds.tokenicon.TangemTokenIconStory
import com.tangem.feature.tester.presentation.storybook.page.ds.tokenrowmarket.TangemTokenRowMarketStory
import com.tangem.feature.tester.presentation.storybook.page.ds.tokenrowv2.TangemTokenRowV2Story
import com.tangem.feature.tester.presentation.storybook.page.ds.topnavigation.TangemTopNavigationStory
import com.tangem.feature.tester.presentation.storybook.page.headerrow.TangemHeaderRowStory
import com.tangem.feature.tester.presentation.storybook.page.message.TangemMessageStory
@ -108,6 +110,8 @@ internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier)
is TangemFadeStory -> TangemFadeStory(state = storyState)
is TangemTopNavigationStory -> TangemTopNavigationStory(state = storyState)
is TangemMessageBannerStory -> TangemMessageBannerStory(state = storyState)
is TangemTokenRowV2Story -> TangemTokenRowV2Story(state = storyState)
is TangemTokenRowMarketStory -> TangemTokenRowMarketStory(state = storyState)
is TextStyleStory -> TextStyleStory(state = storyState)
}
}