Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-05 14:55:31 +05:00
commit 3ae32901d4
30 changed files with 992 additions and 282 deletions

View file

@ -79,7 +79,7 @@ private fun DropdownMenuContent(
content: @Composable ColumnScope.() -> Unit,
) {
// Menu open/close animation.
val transition = updateTransition(expandedStates, "DropDownMenu")
val transition = rememberTransition(expandedStates, "DropDownMenu")
val scale by transition.animateFloat(
transitionSpec = {

View file

@ -48,9 +48,9 @@ fun TangemCheckbox(
isEnabled: Boolean = true,
) {
val shape = if (isRounded) {
RoundedCornerShape(TangemTheme.dimens2.x1)
} else {
CircleShape
} else {
RoundedCornerShape(TangemTheme.dimens2.x1)
}
Box(
modifier = modifier

View file

@ -0,0 +1,288 @@
package com.tangem.core.ui.ds.contextmenu
import android.content.res.Configuration
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import androidx.compose.ui.window.PopupProperties
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.components.haze.hazeSourceTangem
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.PopUpMenuTestTags
import dev.chrisbanes.haze.rememberHazeState
import kotlin.math.max
import kotlin.math.min
/**
* Just copy paste [DropdownMenu] from material3 with deleting vertical paddings.
*/
@Composable
fun TangemContextMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset.Zero,
properties: PopupProperties = PopupProperties(focusable = true),
content: @Composable ColumnScope.() -> Unit,
) {
val expandedStates = remember { MutableTransitionState(false) }
expandedStates.targetState = expanded
if (expandedStates.currentState || expandedStates.targetState) {
val transformOriginState = remember { mutableStateOf(TransformOrigin.Center) }
val density = LocalDensity.current
val popupPositionProvider = DropdownMenuPositionProvider(
offset,
density,
) { parentBounds, menuBounds ->
transformOriginState.value = calculateTransformOrigin(parentBounds, menuBounds)
}
Popup(
onDismissRequest = onDismissRequest,
popupPositionProvider = popupPositionProvider,
properties = properties,
) {
DropdownMenuContent(
expandedStates = expandedStates,
transformOriginState = transformOriginState,
modifier = modifier,
content = content,
)
}
}
}
private const val IN_TRANSITION_DURATION = 120
private const val OUT_TRANSITION_DURATION = 75
@Suppress("ReusedModifierInstance", "MagicNumber")
@Composable
private fun DropdownMenuContent(
expandedStates: MutableTransitionState<Boolean>,
transformOriginState: MutableState<TransformOrigin>,
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit,
) {
// Menu open/close animation.
val transition = rememberTransition(expandedStates, "DropDownMenu")
val scale by transition.animateFloat(
transitionSpec = {
if (false isTransitioningTo true) {
// Dismissed to expanded
tween(
durationMillis = IN_TRANSITION_DURATION,
easing = LinearOutSlowInEasing,
)
} else {
// Expanded to dismissed.
tween(
durationMillis = 1,
delayMillis = OUT_TRANSITION_DURATION - 1,
)
}
},
label = "",
) { isExpanded ->
if (isExpanded) {
// Menu is expanded.
1f
} else {
// Menu is dismissed.
0.8f
}
}
val alpha by transition.animateFloat(
transitionSpec = {
if (false isTransitioningTo true) {
// Dismissed to expanded
tween(durationMillis = 30)
} else {
// Expanded to dismissed.
tween(durationMillis = OUT_TRANSITION_DURATION)
}
},
label = "",
) { isExpanded ->
if (isExpanded) {
// Menu is expanded.
1f
} else {
// Menu is dismissed.
0f
}
}
Card(
modifier = Modifier
.clip(RoundedCornerShape(TangemTheme.dimens2.x5))
.graphicsLayer {
scaleX = scale
scaleY = scale
this.alpha = alpha
transformOrigin = transformOriginState.value
},
elevation = CardDefaults.cardElevation(),
) {
Column(
modifier = modifier
.width(IntrinsicSize.Max)
.verticalScroll(rememberScrollState())
.clip(RoundedCornerShape(TangemTheme.dimens2.x5))
.background(TangemTheme.colors2.contextMenu.background)
.testTag(PopUpMenuTestTags.CONTAINER),
content = content,
)
}
}
private fun calculateTransformOrigin(parentBounds: IntRect, menuBounds: IntRect): TransformOrigin {
val pivotX = when {
menuBounds.left >= parentBounds.right -> 0f
menuBounds.right <= parentBounds.left -> 1f
menuBounds.width == 0 -> 0f
else -> {
val intersectionCenter =
(max(parentBounds.left, menuBounds.left) + min(parentBounds.right, menuBounds.right)) / 2
(intersectionCenter - menuBounds.left).toFloat() / menuBounds.width
}
}
val pivotY = when {
menuBounds.top >= parentBounds.bottom -> 0f
menuBounds.bottom <= parentBounds.top -> 1f
menuBounds.height == 0 -> 0f
else -> {
val intersectionCenter =
(max(parentBounds.top, menuBounds.top) + min(parentBounds.bottom, menuBounds.bottom)) / 2
(intersectionCenter - menuBounds.top).toFloat() / menuBounds.height
}
}
return TransformOrigin(pivotX, pivotY)
}
private val MenuVerticalMargin = 48.dp
@Immutable
internal data class DropdownMenuPositionProvider(
val contentOffset: DpOffset,
val density: Density,
val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> },
) : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize,
): IntOffset {
// The min margin above and below the menu, relative to the screen.
val verticalMargin = with(density) { MenuVerticalMargin.roundToPx() }
// The content offset specified using the dropdown offset parameter.
val contentOffsetX = with(density) { contentOffset.x.roundToPx() }
val contentOffsetY = with(density) { contentOffset.y.roundToPx() }
// Compute horizontal position.
val toRight = anchorBounds.left + contentOffsetX
val toLeft = anchorBounds.right - contentOffsetX - popupContentSize.width
val toDisplayRight = windowSize.width - popupContentSize.width
val toDisplayLeft = 0
val x = if (layoutDirection == LayoutDirection.Ltr) {
sequenceOf(
toRight,
toLeft,
// If the anchor gets outside of the window on the left, we want to position
// toDisplayLeft for proximity to the anchor. Otherwise, toDisplayRight.
if (anchorBounds.left >= 0) toDisplayRight else toDisplayLeft,
)
} else {
sequenceOf(
toLeft,
toRight,
// If the anchor gets outside of the window on the right, we want to position
// toDisplayRight for proximity to the anchor. Otherwise, toDisplayLeft.
if (anchorBounds.right <= windowSize.width) toDisplayLeft else toDisplayRight,
)
}.firstOrNull {
it >= 0 && it + popupContentSize.width <= windowSize.width
} ?: toLeft
// Compute vertical position.
val toBottom = maxOf(anchorBounds.bottom + contentOffsetY, verticalMargin)
val toTop = anchorBounds.top - contentOffsetY - popupContentSize.height
val toCenter = anchorBounds.top - popupContentSize.height / 2
val toDisplayBottom = windowSize.height - popupContentSize.height - verticalMargin
val y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull { element ->
element >= verticalMargin &&
element + popupContentSize.height <= windowSize.height - verticalMargin
} ?: toTop
onPositionCalculated(
anchorBounds,
IntRect(
left = x,
top = y,
right = x + popupContentSize.width,
bottom = y + popupContentSize.height,
),
)
return IntOffset(x, y)
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun TangemContextMenu_Preview() {
TangemThemePreviewRedesign {
val hazeState = rememberHazeState()
Column(
modifier = Modifier
.fillMaxSize()
.background(TangemTheme.colors2.surface.level1)
.hazeSourceTangem(state = hazeState, zIndex = -1f),
) {
TangemContextMenu(
expanded = true,
onDismissRequest = { },
modifier = Modifier.hazeEffectTangem(state = hazeState),
) {
TangemContextMenuCheckboxItem(
title = stringReference("Sort by balance"),
isChecked = true,
onClick = {},
)
HorizontalDivider(
thickness = 0.5.dp,
color = TangemTheme.colors2.border.neutral.quaternary,
)
TangemContextMenuCheckboxItem(
title = stringReference("Group tokens"),
isChecked = false,
onClick = {},
)
}
}
}
}
// endregion

View file

@ -0,0 +1,46 @@
package com.tangem.core.ui.ds.contextmenu
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds.checkbox.TangemCheckbox
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.res.TangemTheme
/**
* Item with checkbox for [TangemContextMenu].
*/
@Composable
fun TangemContextMenuCheckboxItem(title: TextReference, isChecked: Boolean, onClick: () -> Unit) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.width(238.dp)
.clickableSingle(onClick = onClick)
.padding(
vertical = TangemTheme.dimens2.x5,
horizontal = TangemTheme.dimens2.x4,
),
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography2.headingSemibold17,
color = TangemTheme.colors2.text.neutral.primary,
)
TangemCheckbox(
modifier = Modifier,
isRounded = true,
isChecked = isChecked,
onCheckedChange = { /* no-op */ },
)
}
}

View file

@ -1,22 +1,18 @@
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
@ -25,10 +21,13 @@ 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.ds.row.internal.TangemRowTail
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenElementsTestTags
import org.burnoutcrew.reorderable.ReorderableLazyListState
/**
* UI model for header row component
@ -37,13 +36,19 @@ import com.tangem.core.ui.test.TokenElementsTestTags
* @param modifier Modifier for the composable
*/
@Composable
fun TangemHeaderRow(headerRowUM: TangemHeaderRowUM, modifier: Modifier = Modifier, isBalanceHidden: Boolean = false) {
fun TangemHeaderRow(
headerRowUM: TangemHeaderRowUM,
modifier: Modifier = Modifier,
reorderableState: ReorderableLazyListState? = null,
isBalanceHidden: Boolean = false,
) {
TangemHeaderRow(
headTangemIconUM = headerRowUM.startIconUM,
footerTangemIconRes = headerRowUM.endIconRes,
tailUM = headerRowUM.tailUM,
title = headerRowUM.title,
subtitle = headerRowUM.subtitle,
isBalanceHidden = isBalanceHidden,
reorderableState = reorderableState,
modifier = modifier,
)
}
@ -54,7 +59,7 @@ fun TangemHeaderRow(headerRowUM: TangemHeaderRowUM, modifier: Modifier = Modifie
* @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 tailUM Optional TailUM for the tail content
* @param titleContent Composable lambda for the title content
* @param headContent Composable lambda for the head content
*/
@ -64,7 +69,7 @@ fun TangemHeaderRow(
isBalanceHidden: Boolean = false,
subtitle: TextReference? = null,
onItemClick: (() -> Unit)? = null,
@DrawableRes footerTangemIconRes: Int? = null,
tailUM: TangemRowTailUM = TangemRowTailUM.Empty,
titleContent: @Composable (Modifier) -> Unit,
headContent: @Composable (Modifier) -> Unit,
) {
@ -101,17 +106,7 @@ fun TangemHeaderRow(
)
}
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),
)
}
TangemRowTail(tangemRowTailUM = tailUM)
}
}
@ -133,7 +128,8 @@ fun TangemHeaderRow(
isBalanceHidden: Boolean = false,
subtitle: TextReference? = null,
headTangemIconUM: TangemIconUM? = null,
@DrawableRes footerTangemIconRes: Int? = null,
tailUM: TangemRowTailUM = TangemRowTailUM.Empty,
reorderableState: ReorderableLazyListState? = null,
isEnabled: Boolean = false,
onItemClick: (() -> Unit)? = null,
) {
@ -182,17 +178,10 @@ fun TangemHeaderRow(
)
}
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),
)
}
TangemRowTail(
tangemRowTailUM = tailUM,
reorderableState = reorderableState,
)
}
}
@ -217,7 +206,25 @@ private class PreviewProvider : PreviewParameterProvider<TangemHeaderRowUM> {
startIconUM = TangemIconUM.Currency(
currencyIconState = CurrencyIconState.Locked,
),
endIconRes = R.drawable.ic_minimize_24,
tailUM = TangemRowTailUM.Empty,
title = stringReference("Account"),
subtitle = stringReference("\$ 42,900.17"),
),
TangemHeaderRowUM(
id = "1",
startIconUM = TangemIconUM.Currency(
currencyIconState = CurrencyIconState.Locked,
),
tailUM = TangemRowTailUM.Icon(R.drawable.ic_arrow_collapse_24),
title = stringReference("Account"),
subtitle = stringReference("\$ 42,900.17"),
),
TangemHeaderRowUM(
id = "1",
startIconUM = TangemIconUM.Currency(
currencyIconState = CurrencyIconState.Locked,
),
tailUM = TangemRowTailUM.Icon(R.drawable.ic_group_drop_24),
title = stringReference("Account"),
subtitle = stringReference("\$ 42,900.17"),
),

View file

@ -1,9 +1,9 @@
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.ds.row.internal.TangemRowTailUM
import com.tangem.core.ui.extensions.TextReference
/**
@ -13,7 +13,7 @@ import com.tangem.core.ui.extensions.TextReference
* @param title Title text reference
* @param subtitle Subtitle text reference (optional)
* @param startIconUM Icon UI model (optional)
* @param endIconRes Icon UI model (optional)
* @param tailUM Tail UI model (optional)
* @param isEnabled Flag indicating if click is enabled
* @param onItemClick Callback for item click (optional)
*/
@ -23,7 +23,7 @@ data class TangemHeaderRowUM(
val title: TextReference,
val subtitle: TextReference? = null,
val startIconUM: TangemIconUM? = null,
@DrawableRes val endIconRes: Int? = null,
val tailUM: TangemRowTailUM = TangemRowTailUM.Empty,
val isEnabled: Boolean = false,
val onItemClick: (() -> Unit)? = null,
) : TangemRowUM

View file

@ -1,7 +1,9 @@
package com.tangem.core.ui.ds.row.token.internal
package com.tangem.core.ui.ds.row.internal
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
@ -9,11 +11,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.R
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.res.TangemTheme
@ -22,37 +23,48 @@ import org.burnoutcrew.reorderable.ReorderableLazyListState
import org.burnoutcrew.reorderable.detectReorder
@Composable
internal fun TokenRowTail(
tailUM: TangemTokenRowUM.TailUM,
reorderableTokenListState: ReorderableLazyListState?,
internal fun TangemRowTail(
tangemRowTailUM: TangemRowTailUM,
modifier: Modifier = Modifier,
reorderableState: ReorderableLazyListState? = null,
) {
AnimatedContent(
targetState = tailUM,
targetState = tangemRowTailUM,
label = "Update non content fiat block",
modifier = modifier,
modifier = modifier.height(TangemTheme.dimens2.x4),
contentKey = { it::class.java },
) { animatedState ->
val innerModifier = Modifier.padding(start = TangemTheme.dimens2.x2)
when (animatedState) {
TangemTokenRowUM.TailUM.Empty -> Unit
is TangemTokenRowUM.TailUM.Draggable -> DraggableImage(
reorderableTokenListState = reorderableTokenListState,
TangemRowTailUM.Empty -> Unit
is TangemRowTailUM.Draggable -> DraggableImage(
iconRes = animatedState.iconRes,
reorderableState = reorderableState,
modifier = innerModifier,
)
is TangemTokenRowUM.TailUM.Text -> ContentText(text = animatedState.text, modifier = innerModifier)
is TangemRowTailUM.Text -> ContentText(text = animatedState.text, modifier = innerModifier)
is TangemRowTailUM.Icon -> Icon(
imageVector = ImageVector.vectorResource(animatedState.iconRes),
contentDescription = null,
modifier = modifier,
tint = TangemTheme.colors2.graphic.neutral.tertiaryConstant,
)
}
}
}
@Composable
private fun DraggableImage(reorderableTokenListState: ReorderableLazyListState?, modifier: Modifier = Modifier) {
private fun DraggableImage(
@DrawableRes iconRes: Int,
reorderableState: ReorderableLazyListState?,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.size(size = TangemTheme.dimens2.x6)
.then(
other = if (reorderableTokenListState != null) {
Modifier.detectReorder(reorderableTokenListState)
other = if (reorderableState != null) {
Modifier.detectReorder(reorderableState)
} else {
Modifier
},
@ -61,7 +73,7 @@ private fun DraggableImage(reorderableTokenListState: ReorderableLazyListState?,
contentAlignment = Alignment.Center,
) {
Icon(
painter = painterResource(id = R.drawable.ic_drag_24),
imageVector = ImageVector.vectorResource(iconRes),
tint = TangemTheme.colors2.graphic.neutral.tertiaryConstant,
contentDescription = null,
)

View file

@ -0,0 +1,25 @@
package com.tangem.core.ui.ds.row.internal
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
/**
* Tail UI model for row components
*/
@Immutable
sealed class TangemRowTailUM {
data class Text(
val text: TextReference,
) : TangemRowTailUM()
data class Icon(
@DrawableRes val iconRes: Int,
) : TangemRowTailUM()
data class Draggable(
@DrawableRes val iconRes: Int,
) : TangemRowTailUM()
data object Empty : TangemRowTailUM()
}

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
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.internal.TangemRowTail
import com.tangem.core.ui.ds.row.token.internal.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -30,16 +31,16 @@ import org.burnoutcrew.reorderable.ReorderableLazyListState
*
* [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 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.
* @param tokenRowUM The user model containing the data for the token row.
* @param isBalanceHidden A boolean indicating whether the balance should be hidden.
* @param reorderableState 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?,
reorderableState: ReorderableLazyListState?,
modifier: Modifier = Modifier,
) {
TangemRowContainer(
@ -88,9 +89,9 @@ fun TangemTokenRow(
.testTag(tag = TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT),
)
TokenRowTail(
tailUM = tokenRowUM.tailUM,
reorderableTokenListState = reorderableTokenListState,
TangemRowTail(
tangemRowTailUM = tokenRowUM.tailUM,
reorderableState = reorderableState,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.TAIL)
.testTag(tag = TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK),
@ -114,18 +115,18 @@ fun TangemTokenRow(
*
* [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.
* @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 reorderableState 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?,
reorderableState: ReorderableLazyListState?,
modifier: Modifier = Modifier,
headComponent: @Composable (Modifier) -> Unit,
titleComponent: @Composable (Modifier) -> Unit,
@ -183,9 +184,9 @@ fun TangemTokenRow(
.testTag(tag = TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT),
)
TokenRowTail(
tailUM = tokenRowUM.tailUM,
reorderableTokenListState = reorderableTokenListState,
TangemRowTail(
tangemRowTailUM = tokenRowUM.tailUM,
reorderableState = reorderableState,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.TAIL)
.testTag(tag = TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK),
@ -232,7 +233,7 @@ private fun TangemTokenRow_Preview(
TangemTokenRow(
tokenRowUM = tokenRowUM,
isBalanceHidden = false,
reorderableTokenListState = null,
reorderableState = null,
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
)
}

View file

@ -6,6 +6,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeState
import com.tangem.core.ui.ds.badge.TangemBadgeUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowUM
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -32,7 +33,7 @@ sealed class TangemTokenRowUM : TangemRowUM {
abstract val bottomEndContentUM: EndContentUM
/** Token row tail UM */
abstract val tailUM: TailUM
abstract val tailUM: TangemRowTailUM
/** Promo banner UM */
abstract val promoBannerUM: PromoBannerUM
@ -54,7 +55,7 @@ sealed class TangemTokenRowUM : TangemRowUM {
override val topEndContentUM: EndContentUM,
override val bottomEndContentUM: EndContentUM,
override val promoBannerUM: PromoBannerUM = PromoBannerUM.Empty,
override val tailUM: TailUM = TailUM.Empty,
override val tailUM: TangemRowTailUM = TangemRowTailUM.Empty,
override val onItemClick: (() -> Unit)?,
override val onItemLongClick: (() -> Unit)?,
) : TangemTokenRowUM()
@ -71,7 +72,7 @@ sealed class TangemTokenRowUM : TangemRowUM {
override val topEndContentUM: EndContentUM = EndContentUM.Loading
override val bottomEndContentUM: EndContentUM = EndContentUM.Loading
override val promoBannerUM: PromoBannerUM = PromoBannerUM.Empty
override val tailUM: TailUM = TailUM.Empty
override val tailUM: TangemRowTailUM = TangemRowTailUM.Empty
override val onItemClick: (() -> Unit)? = null
override val onItemLongClick: (() -> Unit)? = null
}
@ -84,7 +85,7 @@ sealed class TangemTokenRowUM : TangemRowUM {
override val headIconUM: TangemIconUM.Currency,
override val titleUM: TitleUM,
override val subtitleUM: SubtitleUM,
override val tailUM: TailUM,
override val tailUM: TangemRowTailUM,
override val onItemClick: (() -> Unit)?,
override val onItemLongClick: (() -> Unit)?,
override val topEndContentUM: EndContentUM = EndContentUM.Empty,
@ -154,15 +155,4 @@ sealed class TangemTokenRowUM : TangemRowUM {
data object Empty : PromoBannerUM()
}
@Immutable
sealed class TailUM {
data class Text(
val text: TextReference,
) : TailUM()
data object Draggable : TailUM()
data object Empty : TailUM()
}
}

View file

@ -7,6 +7,7 @@ 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.internal.TangemRowTailUM
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.stringReference
@ -123,7 +124,7 @@ internal object TangemTokenRowPreviewData {
topEndContentUM = topEndContentUM,
bottomEndContentUM = bottomEndContentUM,
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
tailUM = TangemTokenRowUM.TailUM.Empty,
tailUM = TangemRowTailUM.Empty,
onItemClick = {},
onItemLongClick = {},
)
@ -150,7 +151,7 @@ internal object TangemTokenRowPreviewData {
),
bottomEndContentUM = bottomEndContentUM,
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
tailUM = TangemTokenRowUM.TailUM.Empty,
tailUM = TangemRowTailUM.Empty,
onItemClick = {},
onItemLongClick = {},
)
@ -164,7 +165,7 @@ internal object TangemTokenRowPreviewData {
topEndContentUM = topEndContentUM,
bottomEndContentUM = bottomEndContentUM,
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
tailUM = TangemTokenRowUM.TailUM.Empty,
tailUM = TangemRowTailUM.Empty,
onItemClick = {},
onItemLongClick = {},
)
@ -178,7 +179,7 @@ internal object TangemTokenRowPreviewData {
topEndContentUM = topEndContentUM,
bottomEndContentUM = bottomEndContentUM,
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
tailUM = TangemTokenRowUM.TailUM.Empty,
tailUM = TangemRowTailUM.Empty,
onItemClick = {},
onItemLongClick = {},
)
@ -189,7 +190,7 @@ internal object TangemTokenRowPreviewData {
headIconUM = coinIconState,
titleUM = titleUM,
subtitleUM = subtitleUM,
tailUM = TangemTokenRowUM.TailUM.Draggable,
tailUM = TangemRowTailUM.Draggable(R.drawable.ic_drag_24),
onItemClick = {},
onItemLongClick = {},
)
@ -202,7 +203,7 @@ internal object TangemTokenRowPreviewData {
subtitleUM = subtitleUM,
topEndContentUM = topEndContentUM,
bottomEndContentUM = bottomEndContentUM,
tailUM = TangemTokenRowUM.TailUM.Draggable,
tailUM = TangemRowTailUM.Draggable(R.drawable.ic_drag_24),
onItemClick = {},
onItemLongClick = {},
)

View file

@ -5,19 +5,23 @@ import androidx.annotation.DrawableRes
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
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
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
@ -30,11 +34,9 @@ import com.tangem.core.ui.res.TangemThemePreviewRedesign
* @param title The title text to be displayed in the center of the top bar.
* @param modifier Modifier to be applied to the top bar.
* @param subtitle Optional subtitle text to be displayed below the title.
* @param startIconRes Optional drawable resource ID for the start icon.
* @param onStartContentClick Optional click action for the start icon.
* @param endIconRes Optional drawable resource ID for the end icon.
* @param onEndContentClick Optional click action for the end icon.
* @param isGhostButtons Flag to determine if ghost button styling should be applied.
* @param startActionUM Optional action data for the start action icon.
* @param endActionUM Optional action data for the end action icon.
* @param titleIconRes Optional drawable resource ID for the icon to be displayed next to the title.
*
[REDACTED_AUTHOR]
*/
@ -43,56 +45,95 @@ fun TangemTopBar(
modifier: Modifier = Modifier,
title: TextReference? = null,
subtitle: TextReference? = null,
@DrawableRes startIconRes: Int? = null,
onStartContentClick: (() -> Unit)? = null,
@DrawableRes endIconRes: Int? = null,
onEndContentClick: (() -> Unit)? = null,
startActionUM: TangemTopBarActionUM? = null,
endActionUM: TangemTopBarActionUM? = null,
@DrawableRes titleIconRes: Int? = null,
titleStyle: TextStyle = TangemTheme.typography2.headingSemibold17,
isGhostButtons: Boolean = false,
) {
TangemTopBarInner(
TangemTopBar(
title = title,
subtitle = subtitle,
titleIconRes = titleIconRes,
modifier = modifier,
content = {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5),
) {
TangemTopBarTitle(title = title, titleIconRes = titleIconRes, titleStyle = titleStyle)
AnimatedVisibility(
visible = subtitle != null,
label = "Subtitle Visibility",
) {
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
Text(
text = wrappedSubtitle.resolveAnnotatedReference(),
color = TangemTheme.colors2.text.neutral.secondary,
style = TangemTheme.typography2.bodyRegular15,
textAlign = TextAlign.Center,
maxLines = 1,
)
}
}
},
startContent = if (startIconRes != null) {
{ TangemTopBarIcon(iconRes = startIconRes) }
startContent = if (startActionUM != null) {
{ TangemTopBarActionContent(startActionUM) }
} else {
null
},
onStartContentClick = onStartContentClick,
endContent = if (endIconRes != null) {
{ TangemTopBarIcon(iconRes = endIconRes) }
endContent = if (endActionUM != null) {
{ TangemTopBarActionContent(endActionUM) }
} else {
null
},
onEndContentClick = onEndContentClick,
isGhostButtons = isGhostButtons,
)
}
/**
* A top bar composable that displays a title and optional start and end icons.
* [Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8435-74860&m=dev)
*
* @param title The title text to be displayed in the center of the top bar.
* @param modifier Modifier to be applied to the top bar.
* @param subtitle Optional subtitle text to be displayed below the title.
*
[REDACTED_AUTHOR]
*/
@Composable
private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes: Int?, titleStyle: TextStyle) {
fun TangemTopBar(
modifier: Modifier = Modifier,
title: TextReference? = null,
subtitle: TextReference? = null,
@DrawableRes titleIconRes: Int? = null,
startContent: @Composable (() -> Unit)? = null,
endContent: @Composable (() -> Unit)? = null,
) {
Box(
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens2.x16)
.padding(TangemTheme.dimens2.x4, TangemTheme.dimens2.x3),
) {
AnimatedVisibility(
visible = startContent != null,
modifier = Modifier.align(Alignment.CenterStart),
label = "Start Content Visibility",
) {
startContent?.invoke()
}
Column(
modifier = Modifier
.align(Alignment.Center)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5),
) {
TangemTopBarTitle(title = title, titleIconRes = titleIconRes)
AnimatedVisibility(
visible = subtitle != null,
label = "Subtitle Visibility",
) {
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
Text(
text = wrappedSubtitle.resolveAnnotatedReference(),
color = TangemTheme.colors2.text.neutral.secondary,
style = TangemTheme.typography2.bodyRegular15,
textAlign = TextAlign.Center,
maxLines = 1,
)
}
}
AnimatedVisibility(
visible = endContent != null,
modifier = Modifier.align(Alignment.CenterEnd),
label = "End Content Visibility",
) {
endContent?.invoke()
}
}
}
@Composable
private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes: Int?) {
AnimatedVisibility(
visible = title != null,
label = "Title Visibility",
@ -127,7 +168,7 @@ private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes:
Text(
text = wrappedTitle.resolveAnnotatedReference(),
color = TangemTheme.colors2.text.neutral.primary,
style = titleStyle,
style = TangemTheme.typography2.headingSemibold17,
textAlign = TextAlign.Center,
maxLines = 1,
)
@ -136,12 +177,29 @@ private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes:
}
@Composable
private fun TangemTopBarIcon(@DrawableRes iconRes: Int) {
fun TangemTopBarActionContent(
actionUM: TangemTopBarActionUM,
modifier: Modifier = Modifier,
iconSize: Dp = TangemTheme.dimens2.x8,
) {
val background = lerp(
start = Color.Transparent,
stop = TangemTheme.colors2.button.backgroundSecondary,
fraction = actionUM.ghostModeProgress,
)
val padding = (TangemTheme.dimens2.x10 - iconSize) / 2
Icon(
imageVector = ImageVector.vectorResource(id = iconRes),
imageVector = ImageVector.vectorResource(id = actionUM.iconRes),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier.fillMaxSize(),
modifier = modifier
.size(TangemTheme.dimens2.x10)
.clip(CircleShape)
.conditional(actionUM.isActionable) { background(background) }
.conditionalCompose(actionUM.isActionable && actionUM.onClick != null) {
clickableSingle(onClick = requireNotNull(actionUM.onClick))
}
.padding(padding),
)
}
@ -154,13 +212,10 @@ private fun TangemTopBar_Preview(@PreviewParameter(PreviewProvider::class) param
TangemTopBar(
title = params.title,
subtitle = params.subtitle,
startIconRes = params.startIconRes,
endIconRes = params.endIconRes,
titleIconRes = params.titleIconRes,
isGhostButtons = params.isGhostButtons,
onStartContentClick = {},
onEndContentClick = {},
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
startContent = params.startActionUM?.let { { TangemTopBarActionContent(it) } },
endContent = params.endActionUM?.let { { TangemTopBarActionContent(it) } },
)
}
}
@ -168,10 +223,9 @@ private fun TangemTopBar_Preview(@PreviewParameter(PreviewProvider::class) param
private class TangemTopBarPreviewData(
val title: TextReference? = null,
val subtitle: TextReference? = null,
val isGhostButtons: Boolean = false,
val titleIconRes: Int? = null,
val startIconRes: Int? = null,
val endIconRes: Int? = null,
val startActionUM: TangemTopBarActionUM? = null,
val endActionUM: TangemTopBarActionUM? = null,
)
private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData> {
@ -179,41 +233,79 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
get() = sequenceOf(
TangemTopBarPreviewData(
title = stringReference("Title"),
startIconRes = R.drawable.ic_tangem_24,
endIconRes = R.drawable.ic_more_vertical_24,
isGhostButtons = true,
startActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_tangem_24,
onClick = {},
isActionable = false,
),
endActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
),
),
TangemTopBarPreviewData(
title = stringReference("Title"),
subtitle = stringReference("Subtitle"),
startIconRes = R.drawable.ic_tangem_24,
endIconRes = R.drawable.ic_more_vertical_24,
isGhostButtons = true,
startActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_tangem_24,
onClick = {},
isActionable = true,
ghostModeProgress = 1f,
),
endActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = true,
ghostModeProgress = 1f,
),
),
TangemTopBarPreviewData(
title = stringReference("Title"),
subtitle = stringReference("Subtitle"),
titleIconRes = R.drawable.ic_tangem_24,
startIconRes = R.drawable.ic_tangem_24,
endIconRes = R.drawable.ic_more_vertical_24,
isGhostButtons = true,
startActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_tangem_24,
onClick = {},
isActionable = false,
),
endActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = true,
ghostModeProgress = 1f,
),
),
TangemTopBarPreviewData(
subtitle = stringReference("Subtitle"),
titleIconRes = R.drawable.ic_tangem_24,
startIconRes = R.drawable.ic_tangem_24,
endIconRes = R.drawable.ic_more_vertical_24,
isGhostButtons = true,
startActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_tangem_24,
onClick = {},
isActionable = true,
),
endActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
),
),
TangemTopBarPreviewData(
title = stringReference("Title"),
endIconRes = R.drawable.ic_more_vertical_24,
isGhostButtons = true,
endActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
),
),
TangemTopBarPreviewData(
title = stringReference("Title"),
startIconRes = R.drawable.ic_tangem_24,
isGhostButtons = true,
startActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_tangem_24,
onClick = {},
isActionable = true,
ghostModeProgress = 1f,
),
),
TangemTopBarPreviewData(
title = combinedReference(
@ -238,8 +330,16 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
},
),
),
startIconRes = R.drawable.ic_tangem_24,
endIconRes = R.drawable.ic_more_vertical_24,
startActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_tangem_24,
onClick = {},
isActionable = true,
),
endActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
),
),
)
}

View file

@ -0,0 +1,19 @@
package com.tangem.core.ui.ds.topbar
import androidx.annotation.DrawableRes
import androidx.annotation.FloatRange
/**
* User model for top bar action
*
* @property iconRes resource id of action icon
* @property isActionable if true, action will be clickable, otherwise - not
* @property onClick lambda be invoked when action component is clicked. If null, action will not be clickable
* @property ghostModeProgress progress of ghost mode animation, from 0f to 1f.
*/
data class TangemTopBarActionUM(
@param:DrawableRes val iconRes: Int,
val isActionable: Boolean = true,
val onClick: (() -> Unit)? = null,
@param:FloatRange(0.0, 1.0) val ghostModeProgress: Float = 0f,
)

View file

@ -1,95 +0,0 @@
package com.tangem.core.ui.ds.topbar
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
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
import com.tangem.core.ui.res.TangemTheme
/**
* Internal top bar composable that arranges optional start, center, and end content.
* [Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8435-74860&m=dev)
*
* @param modifier Modifier to be applied to the top bar.
* @param content Center content of the top bar.
* @param startContent Optional start content of the top bar.
* @param onStartContentClick Optional click action for the start content.
* @param endContent Optional end content of the top bar.
* @param onEndContentClick Optional click action for the end content.
* @param isGhostButtons Flag to determine if ghost button styling should be applied.
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun TangemTopBarInner(
modifier: Modifier = Modifier,
content: (@Composable () -> Unit)? = null,
startContent: (@Composable () -> Unit)? = null,
onStartContentClick: (() -> Unit)? = null,
endContent: (@Composable () -> Unit)? = null,
onEndContentClick: (() -> Unit)? = null,
isGhostButtons: Boolean = false,
) {
val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(density = this).toDp() }
Box(
modifier = modifier
.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))
.conditionalCompose(isGhostButtons) {
background(TangemTheme.colors2.button.backgroundSecondary)
}
AnimatedVisibility(
visible = startContent != null,
modifier = Modifier.align(Alignment.CenterStart),
label = "Start Content Visibility",
) {
Box(
modifier = iconModifier
.conditional(onStartContentClick != null) {
clickableSingle { onStartContentClick?.invoke() }
}
.conditionalCompose(isGhostButtons) { padding(TangemTheme.dimens2.x1) },
) {
startContent?.invoke()
}
}
AnimatedVisibility(
visible = content != null,
modifier = Modifier.align(Alignment.Center),
) {
content?.invoke()
}
AnimatedVisibility(
visible = endContent != null,
modifier = Modifier.align(Alignment.CenterEnd),
label = "End Content Visibility",
) {
Box(
modifier = iconModifier
.conditional(onEndContentClick != null) {
clickableSingle { onEndContentClick?.invoke() }
}
.conditionalCompose(isGhostButtons) { padding(TangemTheme.dimens2.x1) },
) {
endContent?.invoke()
}
}
}
}

View file

@ -20,6 +20,7 @@ object TangemColorPalette {
// endregion Dark
// region Dark Alpha
val Dark_05 = Color(0x0D1E1E1E)
val Dark_10 = Color(0x1A1E1E1E)
val Dark_20 = Color(0x331E1E1E)
val Dark_30 = Color(0x4D1E1E1E)

View file

@ -22,6 +22,7 @@ class TangemColors2 internal constructor(
val skeleton: Skeleton,
val markers: Markers,
val tabs: Tabs,
val contextMenu: ContextMenu,
) {
@Stable
@ -335,15 +336,23 @@ class TangemColors2 internal constructor(
class Neutral internal constructor(
primary: Color,
secondary: Color,
tertiary: Color,
quaternary: Color,
) {
var primary by mutableStateOf(primary)
private set
var secondary by mutableStateOf(secondary)
private set
var tertiary by mutableStateOf(tertiary)
private set
var quaternary by mutableStateOf(quaternary)
private set
fun update(other: Neutral) {
primary = other.primary
secondary = other.secondary
tertiary = other.tertiary
quaternary = other.quaternary
}
}
@ -616,6 +625,18 @@ class TangemColors2 internal constructor(
}
}
@Stable
class ContextMenu internal constructor(
background: Color,
) {
var background by mutableStateOf(background)
private set
fun update(other: ContextMenu) {
background = other.background
}
}
fun update(other: TangemColors2) {
text.update(other.text)
graphic.update(other.graphic)
@ -629,5 +650,6 @@ class TangemColors2 internal constructor(
skeleton.update(other.skeleton)
markers.update(other.markers)
tabs.update(other.tabs)
contextMenu.update(other.contextMenu)
}
}

View file

@ -79,6 +79,8 @@ private fun lightThemeColors2(): TangemColors2 {
neutral = TangemColors2.Border.Neutral(
primary = TangemColorPalette.Light3,
secondary = TangemColorPalette.Light5,
tertiary = TangemColorPalette.Light_10,
quaternary = TangemColorPalette.Dark_10,
),
status = TangemColors2.Border.Status(
accent = TangemColorPalette.Azure,
@ -187,6 +189,9 @@ private fun lightThemeColors2(): TangemColors2 {
backgroundTertiary = TangemColorPalette.White,
backgroundQuaternary = TangemColorPalette.Dark_20,
)
val contextMenu = TangemColors2.ContextMenu(
background = TangemColorPalette.Dark_05,
)
return TangemColors2(
text = text,
graphic = graphic,
@ -200,6 +205,7 @@ private fun lightThemeColors2(): TangemColors2 {
skeleton = skeleton,
markers = markers,
tabs = tabs,
contextMenu = contextMenu,
)
}
@ -242,6 +248,8 @@ private fun darkThemeColors2(): TangemColors2 {
neutral = TangemColors2.Border.Neutral(
primary = TangemColorPalette.Dark4,
secondary = TangemColorPalette.Dark4,
tertiary = TangemColorPalette.Light_10,
quaternary = TangemColorPalette.Light_10,
),
status = TangemColors2.Border.Status(
accent = TangemColorPalette.Azure,
@ -350,6 +358,9 @@ private fun darkThemeColors2(): TangemColors2 {
backgroundTertiary = TangemColorPalette.Light_10,
backgroundQuaternary = TangemColorPalette.Light_10,
)
val contextMenu = TangemColors2.ContextMenu(
background = TangemColorPalette.Light_10,
)
return TangemColors2(
text = text,
graphic = graphic,
@ -363,5 +374,6 @@ private fun darkThemeColors2(): TangemColors2 {
skeleton = skeleton,
markers = markers,
tabs = tabs,
contextMenu = contextMenu,
)
}

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.333,14H9.825C9.922,14 10,14.078 10,14.175V20.667M9.949,14.051L3,21"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M20.667,10H14.175C14.078,10 14,9.922 14,9.825V3.333M14.051,9.949L21,3"
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="M7,18.759L7,5.5M11.5,10L7.185,5.207C7.086,5.096 6.913,5.096 6.814,5.207L2.5,10M17.314,5L17.314,18.759M12.814,14.259L17.128,19.052C17.228,19.163 17.4,19.163 17.5,19.052L21.814,14.259"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -43,4 +43,14 @@ internal data class NorthernLightsStory(
internal data class TangemTokenRowStory(
val isBalanceHidden: Boolean,
val onBalanceHiddenToggle: () -> Unit,
) : StoryBookPage
internal data class TangemContextMenuStory(
val isExpanded: Boolean,
val onExpandedChange: (Boolean) -> Unit,
) : StoryBookPage
internal data class TangemHeaderRowStory(
val isBalanceHidden: Boolean,
val onBalanceHiddenToggle: () -> Unit,
) : StoryBookPage

View file

@ -0,0 +1,17 @@
package com.tangem.feature.tester.presentation.storybook.page.contextmenu
import com.tangem.feature.tester.presentation.storybook.entity.TangemContextMenuStory
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemContextMenuStory>.build(): TangemContextMenuStory {
return TangemContextMenuStory(
isExpanded = false,
onExpandedChange = { expanded ->
updateStory { it.copy(isExpanded = expanded) }
},
)
}
internal val tangemContextMenuStoryFactory
get() = storyPageFactory(StateUpdater<TangemContextMenuStory>::build)

View file

@ -0,0 +1,80 @@
package com.tangem.feature.tester.presentation.storybook.page.contextmenu
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.ds.contextmenu.TangemContextMenu
import com.tangem.core.ui.ds.contextmenu.TangemContextMenuCheckboxItem
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import dev.chrisbanes.haze.rememberHazeState
import com.tangem.feature.tester.presentation.storybook.entity.TangemContextMenuStory as TangemContextMenuStoryState
@Composable
internal fun TangemContextMenuStory(state: TangemContextMenuStoryState, modifier: Modifier = Modifier) {
val hazeState = rememberHazeState()
LazyColumn(
contentPadding = PaddingValues(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier
.statusBarsPadding()
.fillMaxSize()
.background(TangemTheme.colors2.surface.level1)
.hazeSourceTangem(state = hazeState, zIndex = -1f),
) {
item("context_menu") {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
) {
Text(
text = "TangemContextMenu",
style = TangemTheme.typography2.headingSemibold17,
color = TangemTheme.colors2.text.neutral.primary,
)
Box {
PrimaryButton(
text = "Show Context Menu",
onClick = { state.onExpandedChange(true) },
)
TangemContextMenu(
expanded = state.isExpanded,
onDismissRequest = { state.onExpandedChange(false) },
offset = DpOffset(0.dp, 4.dp),
modifier = Modifier.hazeEffectTangem(hazeState),
) {
TangemContextMenuCheckboxItem(
title = TextReference.Str("Sort by balance"),
isChecked = true,
onClick = {},
)
HorizontalDivider(
thickness = 0.5.dp,
color = TangemTheme.colors2.border.neutral.quaternary,
)
TangemContextMenuCheckboxItem(
title = TextReference.Str("Group tokens"),
isChecked = false,
onClick = {},
)
}
}
}
}
}
}

View file

@ -0,0 +1,17 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.headerrow
import com.tangem.feature.tester.presentation.storybook.entity.TangemHeaderRowStory
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemHeaderRowStory>.build(): TangemHeaderRowStory {
return TangemHeaderRowStory(
isBalanceHidden = false,
onBalanceHiddenToggle = { updateStory { it.copy(isBalanceHidden = !it.isBalanceHidden) } },
)
}
internal val tangemHeaderRowStoryFactory
get() = storyPageFactory(StateUpdater<TangemHeaderRowStory>::build)

View file

@ -0,0 +1,111 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.headerrow
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
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.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Switch
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.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.header.TangemHeaderRow
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemHeaderRowStory
@Composable
internal fun TangemHeaderRowStory(state: TangemHeaderRowStory, modifier: Modifier = Modifier) {
val rows = remember { buildSampleRows() }
LazyColumn(
contentPadding = PaddingValues(bottom = 16.dp),
modifier = modifier
.statusBarsPadding()
.fillMaxSize()
.background(TangemTheme.colors2.surface.level1),
) {
stickyHeader("balance_toggle") {
BalanceToggle(
isHidden = state.isBalanceHidden,
onToggle = state.onBalanceHiddenToggle,
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level1)
.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
items(rows, key = { it.id }) { um ->
TangemHeaderRow(
headerRowUM = um,
isBalanceHidden = state.isBalanceHidden,
modifier = Modifier.background(TangemTheme.colors2.surface.level3),
)
HorizontalDivider(
color = TangemTheme.colors2.border.neutral.secondary,
modifier = Modifier.padding(start = 16.dp),
)
}
}
}
private fun buildSampleRows(): List<TangemHeaderRowUM> = listOf(
TangemHeaderRowUM(
id = "icon_subtitle_no_tail",
startIconUM = TangemIconUM.Currency(currencyIconState = CurrencyIconState.Locked),
tailUM = TangemRowTailUM.Empty,
title = stringReference("Account"),
subtitle = stringReference("\$ 42,900.17"),
),
TangemHeaderRowUM(
id = "icon_subtitle_collapse",
startIconUM = TangemIconUM.Currency(currencyIconState = CurrencyIconState.Locked),
tailUM = TangemRowTailUM.Icon(R.drawable.ic_arrow_collapse_24),
title = stringReference("Account"),
subtitle = stringReference("\$ 42,900.17"),
),
TangemHeaderRowUM(
id = "icon_subtitle_group_drop",
startIconUM = TangemIconUM.Currency(currencyIconState = CurrencyIconState.Locked),
tailUM = TangemRowTailUM.Icon(R.drawable.ic_group_drop_24),
title = stringReference("Account"),
subtitle = stringReference("\$ 42,900.17"),
),
TangemHeaderRowUM(
id = "title_only",
title = stringReference("Account"),
),
)
@Composable
private fun BalanceToggle(isHidden: Boolean, onToggle: () -> Unit, modifier: Modifier = Modifier) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier,
) {
Text(
text = "isBalanceHidden",
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
Switch(checked = isHidden, onCheckedChange = { onToggle() })
}
}

View file

@ -1,4 +1,5 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.tokenrow
import androidx.compose.foundation.background
@ -44,7 +45,7 @@ internal fun TangemTokenRowStory(state: TangemTokenRowStory, modifier: Modifier
TangemTokenRow(
tokenRowUM = um,
isBalanceHidden = state.isBalanceHidden,
reorderableTokenListState = null,
reorderableState = null,
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
)
HorizontalDivider(

View file

@ -18,9 +18,11 @@ import com.tangem.feature.tester.presentation.storybook.entity.StoryPageFactory
import com.tangem.feature.tester.presentation.storybook.page.background.northernLightsStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.badge.tangemBadgeStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.buttons.buttonsStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.opportunities.opportunitiesBGStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.message.tangemMessageStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.checkbox.tangemCheckboxStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.contextmenu.tangemContextMenuStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.headerrow.tangemHeaderRowStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.message.tangemMessageStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.opportunities.opportunitiesBGStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.tabs.tangemSegmentedPickerStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.tangemTokenRowStoryFactory
@ -35,6 +37,8 @@ private fun buildStories() = listOf(
StoryItem(title = "🗂️ Segmented Picker", factory = tangemSegmentedPickerStoryFactory),
StoryItem(title = "☑️ Checkbox", factory = tangemCheckboxStoryFactory),
StoryItem(title = "🪙 Token Row", factory = tangemTokenRowStoryFactory),
StoryItem(title = "📑 Header Row", factory = tangemHeaderRowStoryFactory),
StoryItem(title = "📋 Context Menu", factory = tangemContextMenuStoryFactory),
)
@Composable

View file

@ -11,6 +11,8 @@ import com.tangem.feature.tester.presentation.storybook.entity.TangemBadgeStory
import com.tangem.feature.tester.presentation.storybook.entity.StoryBookUM
import com.tangem.feature.tester.presentation.storybook.entity.StoryList
import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckboxStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemHeaderRowStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemContextMenuStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemMessageStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemSegmentedPickerStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowStory
@ -22,6 +24,8 @@ import com.tangem.feature.tester.presentation.storybook.page.checkbox.TangemChec
import com.tangem.feature.tester.presentation.storybook.page.message.TangemMessageStory
import com.tangem.feature.tester.presentation.storybook.page.tabs.TangemSegmentedPickerStory
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.TangemTokenRowStory
import com.tangem.feature.tester.presentation.storybook.page.headerrow.TangemHeaderRowStory
import com.tangem.feature.tester.presentation.storybook.page.contextmenu.TangemContextMenuStory
@Composable
internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier) {
@ -42,6 +46,8 @@ internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier)
is TangemCheckboxStory -> TangemCheckboxStory(state = storyState)
TangemSegmentedPickerStory -> TangemSegmentedPickerStory()
is TangemTokenRowStory -> TangemTokenRowStory(state = storyState)
is TangemHeaderRowStory -> TangemHeaderRowStory(state = storyState)
is TangemContextMenuStory -> TangemContextMenuStory(state = storyState)
}
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.resourceReference
@ -34,7 +35,7 @@ internal object WalletScreenPreviewData {
text = stringReference("0,12345678 BTC"),
),
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
tailUM = TangemTokenRowUM.TailUM.Empty,
tailUM = TangemRowTailUM.Empty,
onItemClick = {},
onItemLongClick = {},
)

View file

@ -1,23 +1,22 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import android.content.res.Configuration
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarActionUM
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.LocalPowerSavingState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -46,23 +45,27 @@ internal fun WalletTopBar(
color = Color.Unspecified,
contentColor = Color.Unspecified,
modifier = Modifier.hazeEffectTangem {
progressive =
HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f)
progressive = HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f)
},
) {
val isPowerSaving by LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsStateWithLifecycle()
val wrappedBalance = remember(behavior.state.collapsedFraction) {
if (behavior.state.collapsedFraction > VISIBILITY_THRESHOLD) walletBalance else null
}
TangemTopBar(
title = wrappedBalance,
startIconRes = R.drawable.ic_tangem_24,
endIconRes = R.drawable.ic_more_default_24,
onEndContentClick = topBarConfig.onDetailsClick,
isGhostButtons = !isPowerSaving,
startActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_tangem_24,
isActionable = false,
),
endActionUM = TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
isActionable = true,
onClick = topBarConfig.onDetailsClick,
ghostModeProgress = behavior.state.collapsedFraction,
),
modifier = Modifier
.statusBarsPadding()
.testTag(MainScreenTestTags.TOP_BAR),
)
}

View file

@ -31,6 +31,7 @@ import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.row.header.TangemHeaderRow
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
import com.tangem.core.ui.ds.row.token.TangemTokenRow
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.ds.row.token.internal.TokenRowTitle
@ -146,7 +147,7 @@ private fun LazyListScope.tokenItem(
is TangemTokenRowUM -> TangemTokenRow(
tokenRowUM = tokenRowUM,
isBalanceHidden = isBalanceHidden,
reorderableTokenListState = null,
reorderableState = null,
modifier = itemModifier,
)
is TangemHeaderRowUM -> TangemHeaderRow(
@ -199,7 +200,7 @@ private fun LazyListScope.portfolioItem(
is TangemTokenRowUM -> TangemTokenRow(
tokenRowUM = tokenRowUM,
isBalanceHidden = isBalanceHidden,
reorderableTokenListState = null,
reorderableState = null,
modifier = itemModifier,
)
is TangemHeaderRowUM -> TangemHeaderRow(
@ -368,7 +369,7 @@ internal fun PortfolioRowItem(
?.text?.orMaskWithStars(isBalanceHidden),
titleContent = composables.title,
headContent = composables.icon,
footerTangemIconRes = R.drawable.ic_minimize_24,
tailUM = TangemRowTailUM.Icon(R.drawable.ic_minimize_24),
onItemClick = item.tokenRowUM.onItemClick,
)
} else {
@ -377,7 +378,7 @@ internal fun PortfolioRowItem(
headComponent = composables.icon,
titleComponent = composables.title,
isBalanceHidden = isBalanceHidden,
reorderableTokenListState = null,
reorderableState = null,
)
}
}