Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-05 16:51:25 +05:00
parent 52e7b5e58c
commit aa1f4deb24
25 changed files with 810 additions and 108 deletions

View file

@ -4,8 +4,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.ds.message.TangemMessage
import com.tangem.core.ui.ds.message.TangemMessageUM
import com.tangem.core.ui.res.TangemTheme
import kotlinx.collections.immutable.ImmutableList
@ -44,4 +48,66 @@ fun LazyListScope.notifications(
)
},
)
}
/**
* Displays a list of notifications using TangemMessage composables.
*
* @param notifications List of NotificationConfig objects to be displayed.
* @param contentColor Color to be used for the content of the notifications.
* @param modifier Optional Modifier for the notifications.
* @param hasPaddingAbove Boolean indicating whether to add padding above the first notification.
*/
fun LazyListScope.notifications2(
notifications: ImmutableList<NotificationConfig>,
contentColor: Color,
modifier: Modifier = Modifier,
hasPaddingAbove: Boolean = false,
) {
itemsIndexed(
items = notifications,
key = { index, item -> item.title?.hashCode()?.plus(index) ?: index },
contentType = { _, item -> item::class.java },
itemContent = { i, item ->
val topPadding = if (i == 0 && hasPaddingAbove) 0.dp else 12.dp
TangemMessage(
config = item,
contentColor = contentColor,
modifier = modifier
.padding(top = topPadding)
.animateItem(),
)
},
)
}
/**
* Displays a list of notifications using TangemMessage composables.
*
* @param notifications List of TangemMessageUM objects to be displayed.
* @param contentColor Color to be used for the content of the notifications.
* @param modifier Optional Modifier for the notifications.
* @param hasPaddingAbove Boolean indicating whether to add padding above the first notification.
*/
fun LazyListScope.notifications(
notifications: ImmutableList<TangemMessageUM>,
contentColor: Color,
modifier: Modifier = Modifier,
hasPaddingAbove: Boolean = false,
) {
itemsIndexed(
items = notifications,
key = { _, item -> item.id },
contentType = { _, item -> item::class.java },
itemContent = { i, item ->
val topPadding = if (i == 0 && hasPaddingAbove) TangemTheme.dimens2.x0 else TangemTheme.dimens2.x2
TangemMessage(
messageUM = item,
contentColor = contentColor,
modifier = modifier
.padding(top = topPadding)
.animateItem(),
)
},
)
}

View file

@ -24,6 +24,7 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.ds.badge.TangemBadgeSize.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
@ -48,6 +49,7 @@ fun TangemBadge(badgeUM: TangemBadgeUM, modifier: Modifier = Modifier) {
color = badgeUM.color,
type = badgeUM.type,
iconPosition = badgeUM.iconPosition,
onClick = badgeUM.onClick,
modifier = modifier,
)
}
@ -64,6 +66,7 @@ fun TangemBadge(badgeUM: TangemBadgeUM, modifier: Modifier = Modifier) {
* @param color [TangemBadgeColor] defining the color scheme of the badge.
* @param type [TangemBadgeType] defining the style of the badge.
* @param iconPosition [TangemBadgeIconPosition] defining icon position of the badge.
* @param onClick Lambda to be invoked when the badge is clicked (optional).
*
[REDACTED_AUTHOR]
*/
@ -77,6 +80,7 @@ fun TangemBadge(
color: TangemBadgeColor = TangemBadgeColor.Gray,
type: TangemBadgeType = TangemBadgeType.Solid,
iconPosition: TangemBadgeIconPosition = TangemBadgeIconPosition.Start,
onClick: (() -> Unit)? = null,
) {
val iconColor = getIconColor(type = type, color = color)
Row(
@ -86,7 +90,8 @@ fun TangemBadge(
.heightIn(min = size.toHeightDp())
.clip(shape.toShape(size))
.getBackgroundColor(type = type, color = color, shape = shape.toShape(size))
.padding(size.toPaddingDp(position = iconPosition)),
.padding(size.toPaddingDp(position = iconPosition))
.clickableSingle(enabled = onClick != null, onClick = { onClick?.invoke() }),
) {
AnimatedVisibility(
visible = iconRes != null && iconPosition == TangemBadgeIconPosition.Start,

View file

@ -8,13 +8,13 @@ import com.tangem.core.ui.extensions.TextReference
* UI model for [TangemBadge] component
*
* @param text TextReference for the badge label.
* @param modifier Modifier to be applied to the badge.
* @param iconRes Drawable resource ID for the icon to be displayed in the badge.
* @param size [TangemBadgeSize] defining the size of the badge.
* @param shape [TangemBadgeShape] defining the shape of the badge.
* @param color [TangemBadgeColor] defining the color scheme of the badge.
* @param type [TangemBadgeType] defining the style of the badge.
* @param iconPosition [TangemBadgeIconPosition] defining icon position of the badge.
* @param onClick Lambda to be invoked when the badge is clicked (optional).
*/
class TangemBadgeUM(
val text: TextReference,
@ -24,4 +24,5 @@ class TangemBadgeUM(
val color: TangemBadgeColor = TangemBadgeColor.Gray,
val type: TangemBadgeType = TangemBadgeType.Solid,
val iconPosition: TangemBadgeIconPosition = TangemBadgeIconPosition.Start,
val onClick: (() -> Unit)? = null,
)

View file

@ -0,0 +1,76 @@
package com.tangem.core.ui.ds.image
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.icons.identicon.IdentIcon
import com.tangem.core.ui.extensions.ColorReference2
import com.tangem.core.ui.res.TangemTheme
/**
* Model representing different types of icons that can be displayed in the UI.
*/
@Immutable
sealed interface TangemIconUM {
/** Icon representing a currency. */
data class Currency(
val currencyIconState: CurrencyIconState,
) : TangemIconUM
/** Icon represented by a drawable resource. */
data class Icon(
@DrawableRes val iconRes: Int,
val tintReference: ColorReference2 = ColorReference2 { TangemTheme.colors2.graphic.neutral.primary },
) : TangemIconUM
/** Image represented by a drawable resource. */
data class Image(
@DrawableRes val imageRes: Int,
) : TangemIconUM
/** Identicon represented by a text string (e.g., an address). */
data class Ident(
val text: String,
) : TangemIconUM
}
/**
* Composable function to display an icon based on the provided [TangemIconUM] type.
*
* @param tangemIconUM The [TangemIconUM] instance representing the icon to be displayed.
* @param modifier The [Modifier] to be applied to the icon.
*/
@Composable
fun TangemIcon(tangemIconUM: TangemIconUM, modifier: Modifier = Modifier) {
when (tangemIconUM) {
is TangemIconUM.Currency -> {
CurrencyIcon(
state = tangemIconUM.currencyIconState,
modifier = modifier,
)
}
is TangemIconUM.Icon -> Icon(
imageVector = ImageVector.vectorResource(tangemIconUM.iconRes),
contentDescription = null,
modifier = modifier,
tint = tangemIconUM.tintReference(),
)
is TangemIconUM.Image -> Image(
imageVector = ImageVector.vectorResource(tangemIconUM.imageRes),
contentDescription = null,
modifier = modifier,
)
is TangemIconUM.Ident -> IdentIcon(
address = tangemIconUM.text,
modifier = modifier,
)
}
}

View file

@ -11,9 +11,11 @@ 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.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -24,6 +26,7 @@ import com.tangem.core.ui.components.flicker
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -35,21 +38,31 @@ import kotlinx.collections.immutable.persistentListOf
*
* @param messageUM Data model containing message properties.
* @param modifier Modifier to be applied to the message component.
* @param content Optional composable content to be displayed alongside the title and subtitle.
*/
@Composable
fun TangemMessage(
messageUM: TangemMessageUM,
modifier: Modifier = Modifier,
content: @Composable (RowScope.() -> Unit)? = null,
contentColor: Color = TangemTheme.colors2.surface.level3,
) {
TangemMessage(
modifier = modifier,
modifier = modifier
.conditional(messageUM.onClick != null) {
clickableSingle(onClick = requireNotNull(messageUM.onClick))
},
title = messageUM.title,
subtitle = messageUM.subtitle,
messageEffect = messageUM.messageEffect,
isCentered = messageUM.isCentered,
content = content,
content = {
if (messageUM.iconUM != null) {
TangemIcon(
tangemIconUM = messageUM.iconUM,
modifier = Modifier.size(TangemTheme.dimens2.x8),
)
}
},
contentColor = contentColor,
onCloseClick = messageUM.onCloseClick,
buttons = {
messageUM.buttonsUM.fastForEach { buttonUM ->
@ -73,7 +86,11 @@ fun TangemMessage(
* @see com.tangem.core.ui.components.notifications.Notification for legacy component.
*/
@Composable
fun TangemMessage(config: NotificationConfig, modifier: Modifier = Modifier) {
fun TangemMessage(
config: NotificationConfig,
modifier: Modifier = Modifier,
contentColor: Color = TangemTheme.colors2.surface.level3,
) {
val buttonState = config.buttonsState
TangemMessage(
title = config.title,
@ -101,6 +118,7 @@ fun TangemMessage(config: NotificationConfig, modifier: Modifier = Modifier) {
)
}
},
contentColor = contentColor,
buttons = if (buttonState != null) {
{
TangemMessageLegacyButtons(buttonState = buttonState)
@ -129,10 +147,11 @@ fun TangemMessage(
title: TextReference? = null,
subtitle: TextReference? = null,
messageEffect: TangemMessageEffect = TangemMessageEffect.None,
content: (@Composable RowScope.() -> Unit)? = null,
buttons: (@Composable RowScope.() -> Unit)? = null,
onCloseClick: (() -> Unit)? = null,
isCentered: Boolean = false,
contentColor: Color = TangemTheme.colors2.surface.level3,
content: (@Composable RowScope.() -> Unit)? = null,
buttons: (@Composable RowScope.() -> Unit)? = null,
) {
val alignment = if (isCentered) {
Alignment.CenterHorizontally
@ -146,6 +165,7 @@ fun TangemMessage(
.messageEffectBackground(
messageEffect = messageEffect,
radius = TangemTheme.dimens2.x6,
contentColor = contentColor,
),
)
Column(
@ -160,8 +180,9 @@ fun TangemMessage(
subtitle = subtitle,
alignment = alignment,
content = content,
isCentered = isCentered,
)
if (buttons != null && !isCentered) {
if (buttons != null) {
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier
@ -191,8 +212,14 @@ private fun TangemMessageContent(
title: TextReference? = null,
subtitle: TextReference? = null,
alignment: Alignment.Horizontal = Alignment.Start,
isCentered: Boolean = false,
content: (@Composable RowScope.() -> Unit)? = null,
) {
val textAlign = if (isCentered) {
TextAlign.Center
} else {
TextAlign.Start
}
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
modifier = Modifier.padding(TangemTheme.dimens2.x1),
@ -208,10 +235,12 @@ private fun TangemMessageContent(
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
textAlign = textAlign,
)
}
if (subtitle != null) {
Text(
textAlign = textAlign,
text = subtitle.resolveAnnotatedReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
@ -310,12 +339,14 @@ private class TangemMessagePreviewProvider : PreviewParameterProvider<TangemMess
override val values: Sequence<TangemMessageUM>
get() = sequenceOf(
TangemMessageUM(
id = "1",
title = stringReference("Title text"),
subtitle = stringReference("Subtext"),
messageEffect = TangemMessageEffect.None,
isCentered = true,
),
TangemMessageUM(
id = "2",
title = stringReference("Title text"),
subtitle = stringReference("Subtext"),
messageEffect = TangemMessageEffect.Magic,
@ -335,6 +366,7 @@ private class TangemMessagePreviewProvider : PreviewParameterProvider<TangemMess
),
),
TangemMessageUM(
id = "3",
title = stringReference("Title text"),
subtitle = stringReference("Subtext"),
messageEffect = TangemMessageEffect.Card,
@ -348,6 +380,7 @@ private class TangemMessagePreviewProvider : PreviewParameterProvider<TangemMess
),
),
TangemMessageUM(
id = "4",
title = stringReference("Title text"),
subtitle = stringReference("Subtext"),
messageEffect = TangemMessageEffect.Warning,

View file

@ -1,18 +1,24 @@
package com.tangem.core.ui.ds.message
import android.content.res.Configuration
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@ -21,6 +27,7 @@ import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.utils.toPx
import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.HazeTint
import kotlinx.collections.immutable.ImmutableList
@ -29,15 +36,15 @@ import kotlinx.collections.immutable.persistentListOf
/**
* Different visual effects for [Tangem message component].
*/
enum class TangemMessageEffect {
enum class TangemMessageEffect(val isAnimatable: Boolean) {
/** Magic effect with vibrant colors */
Magic,
Magic(true),
/** Card effect with bright colors */
Card,
Card(true),
/** Warning effect with alert colors */
Warning,
Warning(false),
/** No special effect */
None,
None(false),
;
/** Gets the color gradient based on the effect type and [isInDarkTheme] */
@ -223,11 +230,19 @@ enum class TangemMessageEffect {
/** Applies a message effect background to the [Modifier] based on the provided [messageEffect] and [radius] */
@Composable
internal fun Modifier.messageEffectBackground(messageEffect: TangemMessageEffect, radius: Dp): Modifier {
internal fun Modifier.messageEffectBackground(
messageEffect: TangemMessageEffect,
radius: Dp,
contentColor: Color,
): Modifier {
val isInDarkTheme = LocalIsInDarkTheme.current
val borderGradientColors = remember { messageEffect.getBorderGradient(isInDarkTheme) }
val gradientColors = remember { messageEffect.getColorGradient(isInDarkTheme) }
val angle by rememberAnimationAngle(messageEffect.isAnimatable)
val brush = Brush.sweepGradient(messageEffect.getColorGradient(isInDarkTheme))
val padding = 1.dp.toPx()
return this
.clip(RoundedCornerShape(radius))
.border(
@ -255,14 +270,39 @@ internal fun Modifier.messageEffectBackground(messageEffect: TangemMessageEffect
blurRadius = 25.dp
}
.conditionalCompose(gradientColors.isNotEmpty()) {
border(
width = 0.dp,
brush = Brush.sweepGradient(messageEffect.getColorGradient(isInDarkTheme)),
shape = RoundedCornerShape(radius),
)
drawWithContent {
rotate(angle) {
drawCircle(
brush = brush,
radius = size.width,
blendMode = BlendMode.SrcIn,
)
}
drawRect(
color = contentColor,
topLeft = Offset(padding, padding),
size = Size(size.width - 2 * padding, size.height - 2 * padding),
)
drawContent()
}
}
}
@Composable
private fun rememberAnimationAngle(isAnimatable: Boolean) = if (isAnimatable) {
val infiniteTransition = rememberInfiniteTransition()
infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 3000, easing = LinearEasing),
repeatMode = RepeatMode.Restart,
),
)
} else {
remember { mutableFloatStateOf(0f) }
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@ -281,6 +321,7 @@ private fun TangemMessageEffect_Preview() {
.messageEffectBackground(
messageEffect = messageEffect,
radius = 16.dp,
contentColor = TangemTheme.colors2.surface.level1,
)
.fillMaxWidth()
.height(height = 100.dp),

View file

@ -2,6 +2,7 @@ package com.tangem.core.ui.ds.message
import androidx.annotation.DrawableRes
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -17,11 +18,14 @@ import kotlinx.collections.immutable.persistentListOf
* @param onCloseClick Lambda to be invoked when the close button is clicked (optional
*/
data class TangemMessageUM(
val id: String,
val title: TextReference,
val subtitle: TextReference,
val messageEffect: TangemMessageEffect,
val isCentered: Boolean,
val messageEffect: TangemMessageEffect = TangemMessageEffect.None,
val iconUM: TangemIconUM? = null,
val isCentered: Boolean = false,
val buttonsUM: ImmutableList<TangemMessageButtonUM> = persistentListOf(),
val onClick: (() -> Unit)? = null,
val onCloseClick: (() -> Unit)? = null,
)

View file

@ -0,0 +1,12 @@
package com.tangem.core.ui.ds.row
import androidx.compose.runtime.Immutable
/**
* Base interface for all row UI models in the Tangem application. Each row UI model must implement this interface
*/
@Immutable
interface TangemRowUM {
val id: String
}

View file

@ -0,0 +1,230 @@
package com.tangem.core.ui.ds.row.header
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenElementsTestTags
/**
* UI model for header row component
*
* @param headerRowUM UI model for the header row
* @param modifier Modifier for the composable
*/
@Composable
fun TangemHeaderRow(headerRowUM: TangemHeaderRowUM, modifier: Modifier = Modifier) {
TangemHeaderRow(
headTangemIconUM = headerRowUM.startIconUM,
footerTangemIconRes = headerRowUM.endIconRes,
title = headerRowUM.title,
subtitle = headerRowUM.subtitle,
modifier = modifier,
)
}
/**
* Composable function that represents a header row with customizable title and head content.
*
* @param modifier Modifier for the composable
* @param subtitle Optional subtitle as a TextReference
* @param onItemClick Optional click callback for the row
* @param footerTangemIconRes Optional drawable resource ID for the footer icon
* @param titleContent Composable lambda for the title content
* @param headContent Composable lambda for the head content
*/
@Composable
fun TangemHeaderRow(
modifier: Modifier = Modifier,
subtitle: TextReference? = null,
onItemClick: (() -> Unit)? = null,
@DrawableRes footerTangemIconRes: Int? = null,
titleContent: @Composable (Modifier) -> Unit,
headContent: @Composable (Modifier) -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.fillMaxWidth()
.clickableSingle(enabled = onItemClick != null, onClick = { onItemClick?.invoke() })
.padding(
top = TangemTheme.dimens2.x4,
bottom = TangemTheme.dimens2.x3,
start = TangemTheme.dimens2.x4,
end = TangemTheme.dimens2.x4,
),
) {
headContent(
Modifier
.padding(end = TangemTheme.dimens2.x2)
.size(TangemTheme.dimens2.x4),
)
titleContent(Modifier)
AnimatedVisibility(
visible = subtitle != null,
) {
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
Text(
text = wrappedSubtitle.resolveAnnotatedReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
modifier = Modifier
.testTag(tag = TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT)
.padding(start = TangemTheme.dimens2.x1),
)
}
SpacerWMax()
AnimatedVisibility(
visible = footerTangemIconRes != null,
) {
val wrappedIconUM = remember(this) { requireNotNull(footerTangemIconRes) }
Icon(
imageVector = ImageVector.vectorResource(id = wrappedIconUM),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.secondary,
modifier = Modifier.size(TangemTheme.dimens2.x4),
)
}
}
}
/**
* Composable function that represents a header row with title, optional subtitle, and optional icons.
*
* @param title Title as a TextReference
* @param modifier Modifier for the composable
* @param subtitle Optional subtitle as a TextReference
* @param headTangemIconUM Optional TangemIconUM for the head icon
* @param footerTangemIconRes Optional drawable resource ID for the footer icon
* @param isEnabled Boolean indicating if the row is clickable
* @param onItemClick Optional click callback for the row
*/
@Composable
fun TangemHeaderRow(
title: TextReference,
modifier: Modifier = Modifier,
subtitle: TextReference? = null,
headTangemIconUM: TangemIconUM? = null,
@DrawableRes footerTangemIconRes: Int? = null,
isEnabled: Boolean = false,
onItemClick: (() -> Unit)? = null,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.fillMaxWidth()
.clickableSingle(enabled = isEnabled && onItemClick != null, onClick = { onItemClick?.invoke() })
.padding(
top = TangemTheme.dimens2.x4,
bottom = TangemTheme.dimens2.x3,
start = TangemTheme.dimens2.x4,
end = TangemTheme.dimens2.x4,
),
) {
AnimatedVisibility(
visible = headTangemIconUM != null,
) {
val wrappedIconUM = remember(this) { requireNotNull(headTangemIconUM) }
TangemIcon(
tangemIconUM = wrappedIconUM,
modifier = Modifier
.padding(end = TangemTheme.dimens2.x2)
.size(TangemTheme.dimens2.x4),
)
}
Text(
text = title.resolveAnnotatedReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
modifier = Modifier.testTag(tag = TokenElementsTestTags.TOKEN_TITLE),
)
AnimatedVisibility(
visible = subtitle != null,
) {
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
Text(
text = wrappedSubtitle.resolveAnnotatedReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
modifier = Modifier
.testTag(tag = TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT)
.padding(start = TangemTheme.dimens2.x1),
)
}
SpacerWMax()
AnimatedVisibility(
visible = footerTangemIconRes != null,
) {
val wrappedIconUM = remember(this) { requireNotNull(footerTangemIconRes) }
Icon(
imageVector = ImageVector.vectorResource(id = wrappedIconUM),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.secondary,
modifier = Modifier.size(TangemTheme.dimens2.x4),
)
}
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun TangemHeaderRow_Preview(@PreviewParameter(PreviewProvider::class) params: TangemHeaderRowUM) {
TangemThemePreviewRedesign {
TangemHeaderRow(
headerRowUM = params,
modifier = Modifier.background(TangemTheme.colors2.surface.level3),
)
}
}
private class PreviewProvider : PreviewParameterProvider<TangemHeaderRowUM> {
override val values: Sequence<TangemHeaderRowUM>
get() = sequenceOf(
TangemHeaderRowUM(
id = "1",
startIconUM = TangemIconUM.Currency(
currencyIconState = CurrencyIconState.Locked,
),
endIconRes = R.drawable.ic_minimize_24,
title = stringReference("Account"),
subtitle = stringReference("\$ 42,900.17"),
),
TangemHeaderRowUM(
id = "2",
title = stringReference("Account"),
),
)
}
// endregion

View file

@ -0,0 +1,29 @@
package com.tangem.core.ui.ds.row.header
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowUM
import com.tangem.core.ui.extensions.TextReference
/**
* UI model for header row component
*
* @param id Unique id
* @param title Title text reference
* @param subtitle Subtitle text reference (optional)
* @param startIconUM Icon UI model (optional)
* @param endIconRes Icon UI model (optional)
* @param isEnabled Flag indicating if click is enabled
* @param onItemClick Callback for item click (optional)
*/
@Immutable
data class TangemHeaderRowUM(
override val id: String,
val title: TextReference,
val subtitle: TextReference? = null,
val startIconUM: TangemIconUM? = null,
@DrawableRes val endIconRes: Int? = null,
val isEnabled: Boolean = false,
val onItemClick: (() -> Unit)? = null,
) : TangemRowUM

View file

@ -16,7 +16,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.ds.row.token.internal.*
@ -44,19 +44,19 @@ fun TangemTokenRow(
) {
TangemRowContainer(
content = {
CurrencyIcon(
state = tokenRowUM.iconState,
TangemIcon(
tangemIconUM = tokenRowUM.headIconUM,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.HEAD)
.padding(end = TangemTheme.dimens2.x2)
.testTag(TokenElementsTestTags.TOKEN_ICON),
.testTag(tag = TokenElementsTestTags.TOKEN_ICON),
)
TokenRowPromoBanner(
promoBannerUM = tokenRowUM.promoBannerUM,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.EXTRA_TOP)
.testTag(TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER)
.testTag(tag = TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER)
.padding(horizontal = TangemTheme.dimens2.x3)
.fillMaxWidth(),
)
@ -98,7 +98,89 @@ fun TangemTokenRow(
reorderableTokenListState = reorderableTokenListState,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.TAIL)
.testTag(TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK),
.testTag(tag = TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK),
)
},
modifier = modifier.tokenClickable(tokenRowUM = tokenRowUM),
)
}
/**
* Composable function that represents a Tangem token row in a list.
*
* [Token Row](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8207-17583&t=k8dyaykorsNocGVq-4)
*
* @param tokenRowUM The user model containing the data for the token row.
* @param headComponent The composable function representing the head component.
* @param titleComponent The composable function representing the title component.
* @param isBalanceHidden A boolean indicating whether the balance should be hidden.
* @param reorderableTokenListState The state of the reorderable lazy list, if applicable.
* @param modifier The modifier to be applied to the row.
*/
@Composable
fun TangemTokenRow(
tokenRowUM: TangemTokenRowUM,
isBalanceHidden: Boolean,
reorderableTokenListState: ReorderableLazyListState?,
modifier: Modifier = Modifier,
headComponent: @Composable (Modifier) -> Unit,
titleComponent: @Composable (Modifier) -> Unit,
) {
TangemRowContainer(
content = {
headComponent(
Modifier
.layoutId(layoutId = TangemRowLayoutId.HEAD)
.padding(end = TangemTheme.dimens2.x2)
.testTag(tag = TokenElementsTestTags.TOKEN_ICON),
)
TokenRowPromoBanner(
promoBannerUM = tokenRowUM.promoBannerUM,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.EXTRA_TOP)
.testTag(tag = TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER)
.padding(horizontal = TangemTheme.dimens2.x3)
.fillMaxWidth(),
)
titleComponent(
Modifier
.layoutId(layoutId = TangemRowLayoutId.START_TOP)
.padding(end = TangemTheme.dimens2.x2)
.testTag(tag = TokenElementsTestTags.TOKEN_TITLE),
)
TokenRowSubtitle(
subtitleUM = tokenRowUM.subtitleUM,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.START_BOTTOM)
.padding(end = TangemTheme.dimens2.x2)
.testTag(tag = TokenElementsTestTags.TOKEN_PRICE),
)
TokenRowEndTopContent(
endContentUM = tokenRowUM.topEndContentUM,
isBalanceHidden = isBalanceHidden,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.END_TOP)
.testTag(tag = TokenElementsTestTags.TOKEN_FIAT_AMOUNT),
)
TokenRowEndBottomContent(
endContentUM = tokenRowUM.bottomEndContentUM,
isBalanceHidden = isBalanceHidden,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM)
.testTag(tag = TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT),
)
TokenRowTail(
tailUM = tokenRowUM.tailUM,
reorderableTokenListState = reorderableTokenListState,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.TAIL)
.testTag(tag = TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK),
)
},
modifier = modifier.tokenClickable(tokenRowUM = tokenRowUM),
@ -114,7 +196,7 @@ private fun Modifier.tokenClickable(tokenRowUM: TangemTokenRowUM): Modifier = co
val onHapticLongClick = if (onLongClick != null) {
{
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onLongClick(tokenRowUM)
onLongClick()
}
} else {
null
@ -123,9 +205,9 @@ private fun Modifier.tokenClickable(tokenRowUM: TangemTokenRowUM): Modifier = co
when {
onClick == null && onLongClick == null -> this
onClick == null && onLongClick != null -> combinedClickable(onClick = {}, onLongClick = onHapticLongClick)
onClick != null && onLongClick == null -> combinedClickable(onClick = { onClick(tokenRowUM) })
onClick != null && onLongClick == null -> combinedClickable(onClick = onClick)
onClick != null && onLongClick != null -> {
combinedClickable(onClick = { onClick(tokenRowUM) }, onLongClick = onHapticLongClick)
combinedClickable(onClick = onClick, onLongClick = onHapticLongClick)
}
else -> this
}

View file

@ -4,20 +4,20 @@ import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeState
import com.tangem.core.ui.ds.badge.TangemBadgeUM
import com.tangem.core.ui.extensions.ColorReference2
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Immutable
sealed class TangemTokenRowUM {
sealed class TangemTokenRowUM : TangemRowUM {
/** Unique id */
abstract val id: String
abstract override val id: String
/** Token icon state */
abstract val iconState: CurrencyIconState
abstract val headIconUM: TangemIconUM.Currency
/** Token title UM (in one row with [topEndContentUM]) */
abstract val titleUM: TitleUM
@ -38,25 +38,25 @@ sealed class TangemTokenRowUM {
abstract val promoBannerUM: PromoBannerUM
/** Callback which will be called when an item is clicked */
abstract val onItemClick: ((TangemTokenRowUM) -> Unit)?
abstract val onItemClick: (() -> Unit)?
/** Callback which will be called when an item is long clicked */
abstract val onItemLongClick: ((TangemTokenRowUM) -> Unit)?
abstract val onItemLongClick: (() -> Unit)?
/**
* Content state of [TangemTokenRowUM]
*/
data class Content(
override val id: String,
override val iconState: CurrencyIconState,
override val headIconUM: TangemIconUM.Currency,
override val titleUM: TitleUM,
override val subtitleUM: SubtitleUM,
override val topEndContentUM: EndContentUM,
override val bottomEndContentUM: EndContentUM,
override val promoBannerUM: PromoBannerUM = PromoBannerUM.Empty,
override val tailUM: TailUM = TailUM.Empty,
override val onItemClick: ((TangemTokenRowUM) -> Unit)?,
override val onItemLongClick: ((TangemTokenRowUM) -> Unit)?,
override val onItemClick: (() -> Unit)?,
override val onItemLongClick: (() -> Unit)?,
) : TangemTokenRowUM()
/**
@ -64,7 +64,7 @@ sealed class TangemTokenRowUM {
*/
data class Loading(
override val id: String,
override val iconState: CurrencyIconState,
override val headIconUM: TangemIconUM.Currency = TangemIconUM.Currency(CurrencyIconState.Loading),
override val titleUM: TitleUM = TitleUM.Loading,
override val subtitleUM: SubtitleUM = SubtitleUM.Loading,
) : TangemTokenRowUM() {
@ -72,8 +72,8 @@ sealed class TangemTokenRowUM {
override val bottomEndContentUM: EndContentUM = EndContentUM.Loading
override val promoBannerUM: PromoBannerUM = PromoBannerUM.Empty
override val tailUM: TailUM = TailUM.Empty
override val onItemClick: ((TangemTokenRowUM) -> Unit)? = null
override val onItemLongClick: ((TangemTokenRowUM) -> Unit)? = null
override val onItemClick: (() -> Unit)? = null
override val onItemLongClick: (() -> Unit)? = null
}
/**
@ -81,12 +81,12 @@ sealed class TangemTokenRowUM {
*/
data class Actionable(
override val id: String,
override val iconState: CurrencyIconState,
override val headIconUM: TangemIconUM.Currency,
override val titleUM: TitleUM,
override val subtitleUM: SubtitleUM,
override val tailUM: TailUM,
override val onItemClick: ((TangemTokenRowUM) -> Unit)?,
override val onItemLongClick: ((TangemTokenRowUM) -> Unit)?,
override val onItemClick: (() -> Unit)?,
override val onItemLongClick: (() -> Unit)?,
override val topEndContentUM: EndContentUM = EndContentUM.Empty,
override val bottomEndContentUM: EndContentUM = EndContentUM.Empty,
) : TangemTokenRowUM() {
@ -116,7 +116,7 @@ sealed class TangemTokenRowUM {
val text: TextReference,
val isAvailable: Boolean = true,
val isFlickering: Boolean = false,
val icons: ImmutableList<IconUM> = persistentListOf(),
val icons: ImmutableList<TangemIconUM> = persistentListOf(),
val priceChangeUM: PriceChangeState = PriceChangeState.Unknown,
val badge: TangemBadgeUM? = null,
) : SubtitleUM()
@ -133,7 +133,7 @@ sealed class TangemTokenRowUM {
val text: TextReference,
val isAvailable: Boolean = true,
val isFlickering: Boolean = false,
val icons: ImmutableList<IconUM> = persistentListOf(),
val icons: ImmutableList<TangemIconUM.Icon> = persistentListOf(),
val priceChangeUM: PriceChangeState = PriceChangeState.Unknown,
) : EndContentUM()
@ -164,9 +164,4 @@ sealed class TangemTokenRowUM {
data object Empty : TailUM()
}
data class IconUM(
val iconRes: Int,
val tintReference: ColorReference2 = ColorReference2 { TangemTheme.colors2.graphic.neutral.primary },
)
}

View file

@ -6,6 +6,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.stringReference
@ -63,49 +64,60 @@ internal object TangemTokenRowPreviewData {
text = stringReference("Title"),
)
private val accountResIcon: CurrencyIconState.CryptoPortfolio.Icon
get() = CurrencyIconState.CryptoPortfolio.Icon(
resId = R.drawable.ic_rounded_star_24,
color = Color.Blue,
isGrayscale = false,
)
private val accountLetterIcon: CurrencyIconState.CryptoPortfolio.Letter
get() = CurrencyIconState.CryptoPortfolio.Letter(
char = stringReference("A"),
color = Color.Blue,
isGrayscale = false,
private val accountResIcon: TangemIconUM.Currency
get() = TangemIconUM.Currency(
CurrencyIconState.CryptoPortfolio.Icon(
resId = R.drawable.ic_rounded_star_24,
color = Color.Blue,
isGrayscale = false,
),
)
private val coinIconState
get() = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_polygon_22,
isGrayscale = false,
shouldShowCustomBadge = false,
private val accountLetterIcon: TangemIconUM.Currency
get() = TangemIconUM.Currency(
CurrencyIconState.CryptoPortfolio.Letter(
char = stringReference("A"),
color = Color.Blue,
isGrayscale = false,
),
)
private val tokenIconState
get() = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_polygon_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
private val coinIconState: TangemIconUM.Currency
get() = TangemIconUM.Currency(
CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_polygon_22,
isGrayscale = false,
shouldShowCustomBadge = false,
),
)
private val customTokenIconState
get() = CurrencyIconState.CustomTokenIcon(
tint = TangemColorPalette.Black,
background = TangemColorPalette.Meadow,
topBadgeIconResId = R.drawable.img_polygon_22,
isGrayscale = false,
private val tokenIconState: TangemIconUM.Currency
get() = TangemIconUM.Currency(
CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_polygon_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
),
)
private val customTokenIconState: TangemIconUM.Currency
get() = TangemIconUM.Currency(
CurrencyIconState.CustomTokenIcon(
tint = TangemColorPalette.Black,
background = TangemColorPalette.Meadow,
topBadgeIconResId = R.drawable.img_polygon_22,
isGrayscale = false,
),
)
val defaultState: TangemTokenRowUM.Content
get() = TangemTokenRowUM.Content(
id = UUID.randomUUID().toString(),
iconState = coinIconState,
headIconUM = coinIconState,
titleUM = titleUM,
subtitleUM = subtitleUM,
topEndContentUM = topEndContentUM,
@ -131,9 +143,9 @@ internal object TangemTokenRowPreviewData {
}),
),
icons = persistentListOf(
TangemTokenRowUM.IconUM(R.drawable.ic_staking_mini_10),
TangemTokenRowUM.IconUM(R.drawable.ic_attention_12),
TangemTokenRowUM.IconUM(R.drawable.ic_error_sync_24),
TangemIconUM.Icon(R.drawable.ic_staking_mini_10),
TangemIconUM.Icon(R.drawable.ic_attention_12),
TangemIconUM.Icon(R.drawable.ic_error_sync_24),
),
),
bottomEndContentUM = bottomEndContentUM,
@ -146,7 +158,7 @@ internal object TangemTokenRowPreviewData {
val tokenState: TangemTokenRowUM.Content
get() = TangemTokenRowUM.Content(
id = UUID.randomUUID().toString(),
iconState = tokenIconState,
headIconUM = tokenIconState,
titleUM = titleUM,
subtitleUM = subtitleUM,
topEndContentUM = topEndContentUM,
@ -160,7 +172,7 @@ internal object TangemTokenRowPreviewData {
val customTokenState: TangemTokenRowUM.Content
get() = TangemTokenRowUM.Content(
id = UUID.randomUUID().toString(),
iconState = customTokenIconState,
headIconUM = customTokenIconState,
titleUM = titleUM,
subtitleUM = subtitleUM,
topEndContentUM = topEndContentUM,
@ -174,7 +186,7 @@ internal object TangemTokenRowPreviewData {
val draggableState: TangemTokenRowUM.Actionable
get() = TangemTokenRowUM.Actionable(
id = UUID.randomUUID().toString(),
iconState = coinIconState,
headIconUM = coinIconState,
titleUM = titleUM,
subtitleUM = subtitleUM,
tailUM = TangemTokenRowUM.TailUM.Draggable,
@ -185,7 +197,7 @@ internal object TangemTokenRowPreviewData {
val draggableStateV2: TangemTokenRowUM.Actionable
get() = TangemTokenRowUM.Actionable(
id = UUID.randomUUID().toString(),
iconState = coinIconState,
headIconUM = coinIconState,
titleUM = titleUM,
subtitleUM = subtitleUM,
topEndContentUM = topEndContentUM,
@ -198,12 +210,12 @@ internal object TangemTokenRowPreviewData {
val loadingState: TangemTokenRowUM.Loading
get() = TangemTokenRowUM.Loading(
id = UUID.randomUUID().toString(),
iconState = coinIconState,
headIconUM = coinIconState,
titleUM = TangemTokenRowUM.TitleUM.Loading,
subtitleUM = TangemTokenRowUM.SubtitleUM.Loading,
)
val unreachableState: TangemTokenRowUM.Content
val unreachableState: TangemTokenRowUM
get() = defaultState.copy(
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
text = stringReference(StringsSigns.DASH_SIGN),
@ -219,7 +231,7 @@ internal object TangemTokenRowPreviewData {
val accountState: TangemTokenRowUM.Content
get() = TangemTokenRowUM.Content(
id = UUID.randomUUID().toString(),
iconState = accountResIcon,
headIconUM = accountResIcon,
titleUM = TangemTokenRowUM.TitleUM.Content(
text = stringReference(value = "Portfolio"),
),
@ -239,7 +251,7 @@ internal object TangemTokenRowPreviewData {
val accountLetterState: TangemTokenRowUM.Content
get() = accountState.copy(
iconState = accountLetterIcon,
headIconUM = accountLetterIcon,
)
val accountEllipsisState: TangemTokenRowUM.Content

View file

@ -65,9 +65,9 @@ private fun Content(
),
)
when (endContentUM.priceChangeUM) {
when (val priceChangeUM = endContentUM.priceChangeUM) {
is PriceChangeState.Content -> TokenRowPriceChangeContent(
priceChangeState = endContentUM.priceChangeUM,
priceChangeState = priceChangeUM,
isFlickering = endContentUM.isFlickering,
isAvailable = endContentUM.isAvailable,
)

View file

@ -65,9 +65,9 @@ private fun SubtitleContent(subtitleUM: TangemTokenRowUM.SubtitleUM.Content, mod
),
)
when (subtitleUM.priceChangeUM) {
when (val priceChangeUM = subtitleUM.priceChangeUM) {
is PriceChangeState.Content -> TokenRowPriceChangeContent(
priceChangeState = subtitleUM.priceChangeUM,
priceChangeState = priceChangeUM,
isFlickering = subtitleUM.isFlickering,
isAvailable = subtitleUM.isAvailable,
)

View file

@ -21,12 +21,12 @@ import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.ds.badge.TangemBadge
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
internal fun TokenRowTitle(titleUM: TangemTokenRowUM.TitleUM, modifier: Modifier = Modifier) {
fun TokenRowTitle(titleUM: TangemTokenRowUM.TitleUM, modifier: Modifier = Modifier) {
when (titleUM) {
is TangemTokenRowUM.TitleUM.Content -> ContentTitle(titleUM = titleUM, modifier = modifier)
TangemTokenRowUM.TitleUM.Loading -> TextShimmer(
@ -42,7 +42,7 @@ internal fun TokenRowTitle(titleUM: TangemTokenRowUM.TitleUM, modifier: Modifier
private fun ContentTitle(titleUM: TangemTokenRowUM.TitleUM.Content, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens2.x4),
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens2.x1),
verticalAlignment = Alignment.CenterVertically,
) {
/*
@ -50,7 +50,7 @@ private fun ContentTitle(titleUM: TangemTokenRowUM.TitleUM.Content, modifier: Mo
* So we need to use [weight] to avoid displacement.
*/
Text(
text = titleUM.text.resolveReference(),
text = titleUM.text.resolveAnnotatedReference(),
modifier = Modifier.weight(weight = 1f, fill = false),
color = if (titleUM.isAvailable) {
TangemTheme.colors2.text.neutral.primary

View file

@ -8,6 +8,7 @@ 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.platform.LocalDensity
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.conditionalCompose
@ -37,16 +38,20 @@ internal fun TangemTopBarInner(
onEndContentClick: (() -> Unit)? = null,
isGhostButtons: Boolean = false,
) {
val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(density = this).toDp() }
Box(
modifier = modifier
.height(TangemTheme.dimens2.x16)
.height(TangemTheme.dimens2.x16 + statusBarHeight)
.fillMaxWidth()
.padding(top = statusBarHeight)
.padding(TangemTheme.dimens2.x4, TangemTheme.dimens2.x3),
) {
val iconModifier = Modifier
.size(TangemTheme.dimens2.x10)
.clip(RoundedCornerShape(TangemTheme.dimens2.x25))
.background(TangemTheme.colors2.button.backgroundSecondary)
.conditionalCompose(isGhostButtons) {
background(TangemTheme.colors2.button.backgroundSecondary)
}
AnimatedVisibility(
visible = startContent != null,

View file

@ -384,6 +384,13 @@ fun TextReference.orMaskWithStars(maskWithStars: Boolean): TextReference {
return if (maskWithStars) stringReference(THREE_STARS) else this
}
/**
* Returns the TextReference itself if it's not null, otherwise returns an empty TextReference.
*/
fun TextReference?.orEmpty(): TextReference {
return this ?: TextReference.EMPTY
}
@ReadOnlyComposable
@Composable
private fun createStyledText(

View file

@ -0,0 +1,10 @@
<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="M19.395,21C21.017,21 22,19.882 22,18.433C22,17.987 21.866,17.523 21.628,17.106L14.223,4.298C13.727,3.436 12.878,3 12,3C11.122,3 10.263,3.436 9.777,4.298L2.372,17.106C2.115,17.533 2,17.987 2,18.433C2,19.882 2.983,21 4.605,21H19.395ZM12.009,14.672C11.513,14.672 11.237,14.387 11.227,13.885L11.103,8.732C11.094,8.229 11.466,7.869 12,7.869C12.515,7.869 12.916,8.239 12.906,8.741L12.763,13.885C12.754,14.397 12.477,14.672 12.009,14.672ZM12.009,17.845C11.437,17.845 10.941,17.39 10.941,16.832C10.941,16.263 11.427,15.808 12.009,15.808C12.582,15.808 13.069,16.254 13.069,16.832C13.069,17.4 12.573,17.845 12.009,17.845Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,12 @@
<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="M12.5,18H14.5C16.157,18 17.5,16.657 17.5,15C17.5,13.343 16.157,12 14.5,12H10C8.343,12 7,10.657 7,9C7,7.343 8.343,6 10,6L12.5,6M12.5,18L7,18M12.5,18V21M17.5,6L12.5,6M12.5,6V3"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,12 @@
<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="M16.769,18.101C18.179,17.943 21,16.677 21,13.83C21,11.484 19.163,9.602 15.943,9.558C15.876,9.557 15.817,9.515 15.797,9.451C15.078,7.202 11.191,2.872 5.957,6.235C0.785,9.558 3.136,17.152 7.367,18.101M12.068,15.728V12.406M12.068,19.905V20"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,12 @@
<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="M20.777,10.615L20.777,13.115C20.777,15.876 18.539,18.115 15.777,18.115L6.777,18.115M11.277,14.115L6.789,17.705C6.531,17.911 6.54,18.306 6.807,18.5L11.777,22.115M3.134,13.5L3.134,11C3.134,8.239 5.372,6 8.134,6L17.134,6M12.634,10L17.122,6.409C17.38,6.203 17.371,5.809 17.104,5.615L12.134,2"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,12 @@
<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="M4,7.078L11.278,7.078M11.278,7.078C11.278,8.806 12.671,10.207 14.389,10.207C16.107,10.207 17.5,8.806 17.5,7.078M11.278,7.078C11.278,5.351 12.671,3.95 14.389,3.95C16.107,3.95 17.5,5.351 17.5,7.078M17.5,7.078L20,7.078M20,16.822H12.5M12.5,16.822C12.5,18.549 11.107,19.95 9.389,19.95C7.671,19.95 6.278,18.549 6.278,16.822M12.5,16.822C12.5,15.094 11.107,13.693 9.389,13.693C7.671,13.693 6.278,15.094 6.278,16.822M6.278,16.822H4"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,33 @@
<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="M11.264,12.05C11.264,11.636 11.6,11.3 12.014,11.3C12.428,11.3 12.764,11.636 12.764,12.05C12.764,12.464 12.428,12.8 12.014,12.8C11.6,12.8 11.264,12.464 11.264,12.05Z"
android:fillColor="#000000"/>
<path
android:pathData="M18.5,12.05C18.5,11.636 18.836,11.3 19.25,11.3C19.664,11.3 20,11.636 20,12.05C20,12.464 19.664,12.8 19.25,12.8C18.836,12.8 18.5,12.464 18.5,12.05Z"
android:fillColor="#000000"/>
<path
android:pathData="M4,12.05C4,11.636 4.336,11.3 4.75,11.3C5.164,11.3 5.5,11.636 5.5,12.05C5.5,12.464 5.164,12.8 4.75,12.8C4.336,12.8 4,12.464 4,12.05Z"
android:fillColor="#000000"/>
<path
android:pathData="M11.264,12.05C11.264,11.636 11.6,11.3 12.014,11.3C12.428,11.3 12.764,11.636 12.764,12.05C12.764,12.464 12.428,12.8 12.014,12.8C11.6,12.8 11.264,12.464 11.264,12.05Z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M18.5,12.05C18.5,11.636 18.836,11.3 19.25,11.3C19.664,11.3 20,11.636 20,12.05C20,12.464 19.664,12.8 19.25,12.8C18.836,12.8 18.5,12.464 18.5,12.05Z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M4,12.05C4,11.636 4.336,11.3 4.75,11.3C5.164,11.3 5.5,11.636 5.5,12.05C5.5,12.464 5.164,12.8 4.75,12.8C4.336,12.8 4,12.464 4,12.05Z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,13 @@
<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="M12.495,3.5V20.5M21,11.995L4,11.995"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>