Updated on 2026-08-14
This commit is contained in:
commit
866701cdd6
24 changed files with 1568 additions and 371 deletions
|
|
@ -1,51 +0,0 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Currency placeholder icon
|
||||
*
|
||||
* @param id currency id
|
||||
* @param modifier modifier
|
||||
*/
|
||||
@Composable
|
||||
fun CurrencyPlaceholderIcon(id: String, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier.background(color = TangemTheme.colors.icon.secondary, shape = CircleShape),
|
||||
) {
|
||||
Text(
|
||||
text = id.firstOrNull()?.titlecase().orEmpty(),
|
||||
modifier = Modifier.padding(TangemTheme.dimens.spacing4),
|
||||
color = TangemTheme.colors.text.primary2,
|
||||
textAlign = TextAlign.Center,
|
||||
// FIXME("Incorrect typography. Replace with typography from design system")
|
||||
style = TangemTheme.typography.h3.copy(fontWeight = FontWeight.Bold),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region preview
|
||||
|
||||
@Preview(showBackground = true, heightDp = 40, widthDp = 40)
|
||||
@Preview(showBackground = true, heightDp = 40, widthDp = 40, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_CurrencyPlaceholderIcon() {
|
||||
TangemThemePreview {
|
||||
CurrencyPlaceholderIcon(id = "DAI")
|
||||
}
|
||||
}
|
||||
// endregion preview
|
||||
|
|
@ -70,17 +70,6 @@ fun BasicDialog(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleOkDialog(message: String, onDismissDialog: () -> Unit) {
|
||||
TangemDialog(
|
||||
type = DialogType.Message(message),
|
||||
confirmButton = DialogButtonUM(onClick = onDismissDialog),
|
||||
onDismissDialog = onDismissDialog,
|
||||
title = null,
|
||||
dismissButton = null,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog with text field
|
||||
*
|
||||
|
|
@ -124,34 +113,6 @@ fun TextInputDialog(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextInputDialog(
|
||||
fieldValue: String,
|
||||
confirmButton: DialogButtonUM,
|
||||
onDismissDialog: () -> Unit,
|
||||
onValueChange: (String) -> Unit,
|
||||
textFieldParams: AdditionalTextInputDialogUM = remember { AdditionalTextInputDialogUM() },
|
||||
title: String? = null,
|
||||
dismissButton: DialogButtonUM? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) {
|
||||
TangemDialog(
|
||||
type = DialogType.SimpleTextInput(
|
||||
value = fieldValue,
|
||||
onValueChange = onValueChange,
|
||||
params = textFieldParams,
|
||||
),
|
||||
confirmButton = confirmButton,
|
||||
onDismissDialog = onDismissDialog,
|
||||
title = title,
|
||||
dismissButton = dismissButton,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = isDismissable,
|
||||
dismissOnClickOutside = isDismissable,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SelectorDialog(
|
||||
selectedItemIndex: Int,
|
||||
|
|
@ -441,14 +402,6 @@ private sealed class DialogType {
|
|||
// endregion Defaults
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun SimpleOkDialogPreview() {
|
||||
SimpleOkDialog(
|
||||
message = "All protected passwords will be deleted from the " +
|
||||
"secure storage, you must enter the wallet password to work with the app.",
|
||||
) {}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BasicDialogPreview() {
|
||||
BasicDialog(
|
||||
|
|
@ -461,15 +414,6 @@ private fun BasicDialogPreview() {
|
|||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_SimpleOkDialog() {
|
||||
TangemThemePreview {
|
||||
SimpleOkDialogPreview()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,142 +0,0 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import com.tangem.core.ui.components.atoms.text.BoundCounter
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/questions/69083061/how-to-make-middle-ellipsis-in-text-with-jetpack-compose
|
||||
*/
|
||||
@Deprecated("Use EllipsisText with TextEllipsis.Middle ellipsis instead")
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun MiddleEllipsisText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Color.Unspecified,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
fontStyle: FontStyle? = null,
|
||||
fontWeight: FontWeight? = null,
|
||||
fontFamily: FontFamily? = null,
|
||||
letterSpacing: TextUnit = TextUnit.Unspecified,
|
||||
textDecoration: TextDecoration? = null,
|
||||
textAlign: TextAlign? = null,
|
||||
lineHeight: TextUnit = TextUnit.Unspecified,
|
||||
softWrap: Boolean = true,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
) {
|
||||
// some letters, like "r", will have less width when placed right before "."
|
||||
// adding a space to prevent such case
|
||||
val layoutText = remember(text) { "$text $ellipsisText" }
|
||||
val textLayoutResultState = remember(layoutText) {
|
||||
mutableStateOf<TextLayoutResult?>(null)
|
||||
}
|
||||
SubcomposeLayout(modifier) { constraints ->
|
||||
// result is ignored - we only need to fill our textLayoutResult
|
||||
subcompose("measure") {
|
||||
Text(
|
||||
text = layoutText,
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
fontStyle = fontStyle,
|
||||
fontWeight = fontWeight,
|
||||
fontFamily = fontFamily,
|
||||
letterSpacing = letterSpacing,
|
||||
textDecoration = textDecoration,
|
||||
textAlign = textAlign,
|
||||
lineHeight = lineHeight,
|
||||
softWrap = softWrap,
|
||||
maxLines = 1,
|
||||
onTextLayout = { textLayoutResultState.value = it },
|
||||
style = style,
|
||||
)
|
||||
}.first().measure(Constraints())
|
||||
// to allow smart cast
|
||||
val textLayoutResult = textLayoutResultState.value
|
||||
?: // shouldn't happen - onTextLayout is called before subcompose finishes
|
||||
return@SubcomposeLayout layout(0, 0) {}
|
||||
val placeable = subcompose("visible") {
|
||||
val finalText = remember(text, textLayoutResult, constraints.maxWidth) {
|
||||
if (
|
||||
text.isEmpty() ||
|
||||
textLayoutResult.getBoundingBox(text.indices.last).right <= constraints.maxWidth
|
||||
) {
|
||||
// text not including ellipsis fits on the first line.
|
||||
return@remember text
|
||||
}
|
||||
|
||||
val ellipsisWidth = layoutText.indices.toList()
|
||||
.takeLast(ELLIPSIS_CHARACTERS_COUNT)
|
||||
.let widthLet@{ indices ->
|
||||
// fix this bug: https://issuetracker.google.com/issues/197146630
|
||||
// in this case width is invalid
|
||||
for (i in indices) {
|
||||
val width = textLayoutResult.getBoundingBox(i).width
|
||||
if (width > 0) {
|
||||
return@widthLet width * ELLIPSIS_CHARACTERS_COUNT
|
||||
}
|
||||
}
|
||||
// this should not happen, because
|
||||
// this error occurs only for the last character in the string
|
||||
error("all ellipsis chars have invalid width")
|
||||
}
|
||||
val availableWidth = constraints.maxWidth - ellipsisWidth
|
||||
val startCounter = BoundCounter(text, textLayoutResult) { it }
|
||||
val endCounter = BoundCounter(text, textLayoutResult) { text.indices.last - it }
|
||||
|
||||
while (availableWidth - startCounter.width - endCounter.width > 0) {
|
||||
val possibleEndWidth = endCounter.widthWithNextChar()
|
||||
if (
|
||||
startCounter.width >= possibleEndWidth &&
|
||||
availableWidth - startCounter.width - possibleEndWidth >= 0
|
||||
) {
|
||||
endCounter.addNextChar()
|
||||
} else if (availableWidth - startCounter.widthWithNextChar() - endCounter.width >= 0) {
|
||||
startCounter.addNextChar()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
startCounter.string.trimEnd() + ellipsisText + endCounter.string.reversed().trimStart()
|
||||
}
|
||||
Text(
|
||||
text = finalText,
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
fontStyle = fontStyle,
|
||||
fontWeight = fontWeight,
|
||||
fontFamily = fontFamily,
|
||||
letterSpacing = letterSpacing,
|
||||
textDecoration = textDecoration,
|
||||
textAlign = textAlign,
|
||||
lineHeight = lineHeight,
|
||||
softWrap = softWrap,
|
||||
onTextLayout = onTextLayout,
|
||||
style = style,
|
||||
)
|
||||
}[0].measure(constraints)
|
||||
layout(placeable.width, placeable.height) {
|
||||
placeable.place(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val ELLIPSIS_CHARACTERS_COUNT = 3
|
||||
private const val ELLIPSIS_CHARACTER = '.'
|
||||
private val ellipsisText = List(ELLIPSIS_CHARACTERS_COUNT) { ELLIPSIS_CHARACTER }.joinToString(separator = "")
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
package com.tangem.core.ui.components.rows
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH28
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* Simple clickable action row, without input and icon
|
||||
*
|
||||
* https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-807&mode=design&t=Ygv5sohTTHYAQcBS-4
|
||||
*/
|
||||
@Composable
|
||||
fun SimpleActionRow(title: String, description: String, modifier: Modifier = Modifier, isClickable: Boolean = true) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(color = TangemTheme.colors.background.action)
|
||||
.height(TangemTheme.dimens.size44)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(end = TangemTheme.dimens.spacing48)
|
||||
.align(Alignment.CenterStart),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
AnimatedContent(targetState = title, label = "") { title ->
|
||||
Text(
|
||||
text = title,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
AnimatedContent(targetState = description, label = "") { description ->
|
||||
Text(
|
||||
text = description,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isClickable) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterEnd)
|
||||
.padding(end = TangemTheme.dimens.spacing12),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SimpleActionRowPreview() {
|
||||
Column {
|
||||
TangemThemePreview(isDark = false) {
|
||||
SimpleActionRow(
|
||||
title = "Title",
|
||||
description = "Description",
|
||||
)
|
||||
}
|
||||
|
||||
SpacerH28()
|
||||
|
||||
TangemThemePreview(isDark = false) {
|
||||
SimpleActionRow(
|
||||
title = "Title",
|
||||
description = "Description",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.core.ui.components.states
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class SelectableItemsState<T>(
|
||||
val selectedItem: Item<T>,
|
||||
val items: ImmutableList<Item<T>>,
|
||||
)
|
||||
|
||||
data class Item<T>(
|
||||
val id: Int,
|
||||
val startText: TextReference,
|
||||
val endText: TextReference,
|
||||
val isSelected: Boolean,
|
||||
val data: T,
|
||||
)
|
||||
|
|
@ -29,6 +29,29 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
/**
|
||||
* Tangem badge component to display a small piece of information with optional icon.
|
||||
* [Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8441-83535&m=dev)
|
||||
*
|
||||
* @param badgeUM [TangemBadgeUM] containing all the badge parameters.
|
||||
* @param modifier Modifier to be applied to the badge.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TangemBadge(badgeUM: TangemBadgeUM, modifier: Modifier = Modifier) {
|
||||
TangemBadge(
|
||||
text = badgeUM.text,
|
||||
iconRes = badgeUM.iconRes,
|
||||
size = badgeUM.size,
|
||||
shape = badgeUM.shape,
|
||||
color = badgeUM.color,
|
||||
type = badgeUM.type,
|
||||
iconPosition = badgeUM.iconPosition,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tangem badge component to display a small piece of information with optional icon.
|
||||
* [Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8441-83535&m=dev)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.core.ui.ds.badge
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeSize.X9
|
||||
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.
|
||||
*/
|
||||
class TangemBadgeUM(
|
||||
val text: TextReference,
|
||||
@DrawableRes val iconRes: Int? = null,
|
||||
val size: TangemBadgeSize = X9,
|
||||
val shape: TangemBadgeShape = TangemBadgeShape.Default,
|
||||
val color: TangemBadgeColor = TangemBadgeColor.Gray,
|
||||
val type: TangemBadgeType = TangemBadgeType.Solid,
|
||||
val iconPosition: TangemBadgeIconPosition = TangemBadgeIconPosition.Start,
|
||||
)
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package com.tangem.core.ui.ds.row
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.Measurable
|
||||
import androidx.compose.ui.layout.Placeable
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* A custom layout composable that arranges its children in a row with specific layout IDs.
|
||||
*/
|
||||
internal enum class TangemRowLayoutId {
|
||||
HEAD, START_TOP, END_TOP, START_BOTTOM, END_BOTTOM, TAIL, EXTRA_TOP
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom layout composable that arranges its children in a row with specific layout IDs.
|
||||
*
|
||||
* @param modifier Modifier to be applied to the layout.
|
||||
* @param contentPadding Padding values to be applied around the content.
|
||||
* @param content Composable content to be laid out within the TangemRow.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun TangemRowContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues(TangemTheme.dimens2.x3),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val localDirection = LocalLayoutDirection.current
|
||||
val verticalPadding = with(density) { TangemTheme.dimens2.x1.roundToPx() }
|
||||
val contentTopPadding = with(density) { contentPadding.calculateTopPadding().roundToPx() }
|
||||
val contentBottomPadding = with(density) { contentPadding.calculateBottomPadding().roundToPx() }
|
||||
val contentStartPadding = with(density) { contentPadding.calculateLeftPadding(localDirection).roundToPx() }
|
||||
val contentEndPadding = with(density) { contentPadding.calculateRightPadding(localDirection).roundToPx() }
|
||||
Layout(
|
||||
content = content,
|
||||
modifier = modifier,
|
||||
) { measurables, constraints ->
|
||||
val layoutWidth = constraints.maxWidth - contentStartPadding - contentEndPadding
|
||||
|
||||
val startTopMinWidth = (layoutWidth * TITLE_MIN_WIDTH_COEFFICIENT).toInt()
|
||||
val startBottomMinWidth = (layoutWidth * PRICE_MIN_WIDTH_COEFFICIENT).toInt()
|
||||
|
||||
// Head composable measurement
|
||||
val headPlaceable = measurables.measure(
|
||||
layoutId = TangemRowLayoutId.HEAD,
|
||||
constraints = constraints,
|
||||
)
|
||||
|
||||
// Tail composable measurement
|
||||
val tailPlaceable = measurables.measure(
|
||||
layoutId = TangemRowLayoutId.TAIL,
|
||||
constraints = constraints,
|
||||
)
|
||||
|
||||
val availableWidthForBody = layoutWidth - headPlaceable.widthOrZero() - tailPlaceable.widthOrZero()
|
||||
|
||||
// End top composable width must take the whole free space but is not greater the Start top min size
|
||||
val endTopPlaceable = measurables.measure(
|
||||
layoutId = TangemRowLayoutId.END_TOP,
|
||||
constraints = constraints.copy(
|
||||
minWidth = 0,
|
||||
maxWidth = availableWidthForBody - startTopMinWidth,
|
||||
),
|
||||
)
|
||||
|
||||
// End bottom composable width must take the whole free space but is not greater the Start bottom min size
|
||||
val endBottomPlaceable = measurables.measure(
|
||||
layoutId = TangemRowLayoutId.END_BOTTOM,
|
||||
constraints = constraints.copy(
|
||||
minWidth = 0,
|
||||
maxWidth = availableWidthForBody - startBottomMinWidth,
|
||||
),
|
||||
)
|
||||
|
||||
/* Start top composable will take take the whole REMAINING width space width but no less than minimum */
|
||||
val startTopPlaceable = measurables.measure(
|
||||
layoutId = TangemRowLayoutId.START_TOP,
|
||||
constraints = constraints.copy(
|
||||
minWidth = startTopMinWidth,
|
||||
maxWidth = max(
|
||||
a = startTopMinWidth,
|
||||
b = availableWidthForBody - endTopPlaceable.widthOrZero(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
/* Start bottom composable will take take the whole REMAINING width space width but no less than minimum */
|
||||
val startBottomPlaceable = measurables.measure(
|
||||
layoutId = TangemRowLayoutId.START_BOTTOM,
|
||||
constraints = constraints.copy(
|
||||
minWidth = startBottomMinWidth,
|
||||
maxWidth = max(
|
||||
a = startBottomMinWidth,
|
||||
b = availableWidthForBody - endBottomPlaceable.widthOrZero(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val extraTopPlaceable = measurables.measure(
|
||||
layoutId = TangemRowLayoutId.EXTRA_TOP,
|
||||
constraints = constraints,
|
||||
)
|
||||
|
||||
val mainLayoutHeight = maxOf(
|
||||
headPlaceable.heightOrZero(),
|
||||
tailPlaceable.heightOrZero(),
|
||||
startTopPlaceable.heightOrZero() + startBottomPlaceable.heightOrZero() + verticalPadding,
|
||||
endTopPlaceable.heightOrZero() + endBottomPlaceable.heightOrZero() + verticalPadding,
|
||||
)
|
||||
|
||||
val mainContentTopPadding = if (extraTopPlaceable != null) {
|
||||
extraTopPlaceable.heightOrZero()
|
||||
} else {
|
||||
contentTopPadding
|
||||
}
|
||||
|
||||
val layoutHeight = mainLayoutHeight + mainContentTopPadding + contentBottomPadding
|
||||
|
||||
layout(width = constraints.maxWidth, height = layoutHeight) {
|
||||
extraTopPlaceable?.placeRelative(x = 0, y = 0)
|
||||
|
||||
headPlaceable?.placeRelative(
|
||||
x = contentStartPadding,
|
||||
y = mainContentTopPadding + (mainLayoutHeight - headPlaceable.height).div(other = 2),
|
||||
)
|
||||
|
||||
startTopPlaceable?.placeRelative(
|
||||
x = contentStartPadding + headPlaceable.widthOrZero(),
|
||||
y = mainContentTopPadding + if (startBottomPlaceable == null) {
|
||||
(mainLayoutHeight - startTopPlaceable.height).div(2)
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
|
||||
startBottomPlaceable?.placeRelative(
|
||||
x = contentStartPadding + headPlaceable.widthOrZero(),
|
||||
y = mainContentTopPadding + if (startTopPlaceable == null) {
|
||||
(mainLayoutHeight - startBottomPlaceable.height).div(2)
|
||||
} else {
|
||||
startTopPlaceable.heightOrZero() + verticalPadding
|
||||
},
|
||||
)
|
||||
|
||||
endTopPlaceable?.placeRelative(
|
||||
x = layoutWidth - endTopPlaceable.widthOrZero() - tailPlaceable.widthOrZero() + contentEndPadding,
|
||||
y = mainContentTopPadding + if (endBottomPlaceable == null) {
|
||||
(mainLayoutHeight - endTopPlaceable.height).div(2)
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
|
||||
endBottomPlaceable?.placeRelative(
|
||||
x = layoutWidth - endBottomPlaceable.widthOrZero() - tailPlaceable.widthOrZero() + contentEndPadding,
|
||||
y = mainContentTopPadding + if (endTopPlaceable == null) {
|
||||
(mainLayoutHeight - endBottomPlaceable.height).div(2)
|
||||
} else {
|
||||
endTopPlaceable.heightOrZero() + verticalPadding
|
||||
},
|
||||
)
|
||||
|
||||
tailPlaceable?.placeRelative(
|
||||
x = layoutWidth - tailPlaceable.width + contentEndPadding,
|
||||
y = mainContentTopPadding + (mainLayoutHeight - tailPlaceable.height).div(other = 2),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TITLE_MIN_WIDTH_COEFFICIENT = 0.3
|
||||
private const val PRICE_MIN_WIDTH_COEFFICIENT = 0.32
|
||||
|
||||
private fun List<Measurable>.measure(layoutId: TangemRowLayoutId, constraints: Constraints): Placeable? {
|
||||
return firstOrNull { it.layoutId == layoutId }?.measure(constraints)
|
||||
}
|
||||
|
||||
private fun Placeable?.widthOrZero(): Int = this?.width ?: 0
|
||||
private fun Placeable?.heightOrZero(): Int = this?.height ?: 0
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
package com.tangem.core.ui.ds.row.token
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
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.row.TangemRowContainer
|
||||
import com.tangem.core.ui.ds.row.TangemRowLayoutId
|
||||
import com.tangem.core.ui.ds.row.token.internal.*
|
||||
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
|
||||
|
||||
/**
|
||||
* 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 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,
|
||||
) {
|
||||
TangemRowContainer(
|
||||
content = {
|
||||
CurrencyIcon(
|
||||
state = tokenRowUM.iconState,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.HEAD)
|
||||
.padding(end = TangemTheme.dimens2.x2)
|
||||
.testTag(TokenElementsTestTags.TOKEN_ICON),
|
||||
)
|
||||
|
||||
TokenRowPromoBanner(
|
||||
promoBannerUM = tokenRowUM.promoBannerUM,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.EXTRA_TOP)
|
||||
.testTag(TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER)
|
||||
.padding(horizontal = TangemTheme.dimens2.x3)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
TokenRowTitle(
|
||||
titleUM = tokenRowUM.titleUM,
|
||||
modifier = 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(TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK),
|
||||
)
|
||||
},
|
||||
modifier = modifier
|
||||
.tokenClickable(tokenRowUM = tokenRowUM),
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun Modifier.tokenClickable(tokenRowUM: TangemTokenRowUM): Modifier = composed {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
val onClick = tokenRowUM.onItemClick
|
||||
val onLongClick = tokenRowUM.onItemLongClick
|
||||
val onHapticLongClick = if (onLongClick != null) {
|
||||
{
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onLongClick(tokenRowUM)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
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(tokenRowUM) }, onLongClick = onHapticLongClick)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TangemTokenRow_Preview(
|
||||
@PreviewParameter(TangemTokenRowPreviewProvider::class) tokenRowUM: TangemTokenRowUM,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TangemTokenRow(
|
||||
tokenRowUM = tokenRowUM,
|
||||
isBalanceHidden = false,
|
||||
reorderableTokenListState = null,
|
||||
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class TangemTokenRowPreviewProvider : CollectionPreviewParameterProvider<TangemTokenRowUM>(
|
||||
collection = listOf(
|
||||
TangemTokenRowPreviewData.defaultState,
|
||||
TangemTokenRowPreviewData.defaultEllipsisState,
|
||||
TangemTokenRowPreviewData.tokenState,
|
||||
TangemTokenRowPreviewData.customTokenState,
|
||||
TangemTokenRowPreviewData.draggableState,
|
||||
TangemTokenRowPreviewData.draggableStateV2,
|
||||
TangemTokenRowPreviewData.loadingState,
|
||||
TangemTokenRowPreviewData.unreachableState,
|
||||
TangemTokenRowPreviewData.accountState,
|
||||
TangemTokenRowPreviewData.accountLetterState,
|
||||
TangemTokenRowPreviewData.accountEllipsisState,
|
||||
TangemTokenRowPreviewData.promoBannerState,
|
||||
),
|
||||
)
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
package com.tangem.core.ui.ds.row.token
|
||||
|
||||
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.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Immutable
|
||||
sealed class TangemTokenRowUM {
|
||||
|
||||
/** Unique id */
|
||||
abstract val id: String
|
||||
|
||||
/** Token icon state */
|
||||
abstract val iconState: CurrencyIconState
|
||||
|
||||
/** Token title UM (in one row with [topEndContentUM]) */
|
||||
abstract val titleUM: TitleUM
|
||||
|
||||
/** Token subtitle UM (in one row with [bottomEndContentUM]) */
|
||||
abstract val subtitleUM: SubtitleUM
|
||||
|
||||
/** Top end content UM (e.i. fiat amount in one row with [titleUM]) */
|
||||
abstract val topEndContentUM: EndContentUM
|
||||
|
||||
/** Bottom end content UM (e.i. crypto amount in one row with [subtitleUM]) */
|
||||
abstract val bottomEndContentUM: EndContentUM
|
||||
|
||||
/** Token row tail UM */
|
||||
abstract val tailUM: TailUM
|
||||
|
||||
/** Promo banner UM */
|
||||
abstract val promoBannerUM: PromoBannerUM
|
||||
|
||||
/** Callback which will be called when an item is clicked */
|
||||
abstract val onItemClick: ((TangemTokenRowUM) -> Unit)?
|
||||
|
||||
/** Callback which will be called when an item is long clicked */
|
||||
abstract val onItemLongClick: ((TangemTokenRowUM) -> Unit)?
|
||||
|
||||
/**
|
||||
* Content state of [TangemTokenRowUM]
|
||||
*/
|
||||
data class Content(
|
||||
override val id: String,
|
||||
override val iconState: CurrencyIconState,
|
||||
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)?,
|
||||
) : TangemTokenRowUM()
|
||||
|
||||
/**
|
||||
* Loading state of [TangemTokenRowUM]
|
||||
*/
|
||||
data class Loading(
|
||||
override val id: String,
|
||||
override val iconState: CurrencyIconState,
|
||||
override val titleUM: TitleUM = TitleUM.Loading,
|
||||
override val subtitleUM: SubtitleUM = SubtitleUM.Loading,
|
||||
) : TangemTokenRowUM() {
|
||||
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 onItemClick: ((TangemTokenRowUM) -> Unit)? = null
|
||||
override val onItemLongClick: ((TangemTokenRowUM) -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Actionable state of [TangemTokenRowUM]
|
||||
*/
|
||||
data class Actionable(
|
||||
override val id: String,
|
||||
override val iconState: CurrencyIconState,
|
||||
override val titleUM: TitleUM,
|
||||
override val subtitleUM: SubtitleUM,
|
||||
override val tailUM: TailUM,
|
||||
override val onItemClick: ((TangemTokenRowUM) -> Unit)?,
|
||||
override val onItemLongClick: ((TangemTokenRowUM) -> Unit)?,
|
||||
override val topEndContentUM: EndContentUM = EndContentUM.Empty,
|
||||
override val bottomEndContentUM: EndContentUM = EndContentUM.Empty,
|
||||
) : TangemTokenRowUM() {
|
||||
override val promoBannerUM: PromoBannerUM = PromoBannerUM.Empty
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class TitleUM {
|
||||
|
||||
data class Content(
|
||||
val text: TextReference,
|
||||
val hasPending: Boolean = false,
|
||||
val isAvailable: Boolean = true,
|
||||
val badge: TangemBadgeUM? = null,
|
||||
val onBadgeClick: (() -> Unit)? = null,
|
||||
) : TitleUM()
|
||||
|
||||
data object Loading : TitleUM()
|
||||
|
||||
data object Empty : TitleUM()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class SubtitleUM {
|
||||
|
||||
data class Content(
|
||||
val text: TextReference,
|
||||
val isAvailable: Boolean = true,
|
||||
val isFlickering: Boolean = false,
|
||||
val icons: ImmutableList<IconUM> = persistentListOf(),
|
||||
val priceChangeUM: PriceChangeState = PriceChangeState.Unknown,
|
||||
val badge: TangemBadgeUM? = null,
|
||||
) : SubtitleUM()
|
||||
|
||||
data object Loading : SubtitleUM()
|
||||
|
||||
data object Empty : SubtitleUM()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class EndContentUM {
|
||||
|
||||
data class Content(
|
||||
val text: TextReference,
|
||||
val isAvailable: Boolean = true,
|
||||
val isFlickering: Boolean = false,
|
||||
val icons: ImmutableList<IconUM> = persistentListOf(),
|
||||
val priceChangeUM: PriceChangeState = PriceChangeState.Unknown,
|
||||
) : EndContentUM()
|
||||
|
||||
data object Loading : EndContentUM()
|
||||
|
||||
data object Empty : EndContentUM()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class PromoBannerUM {
|
||||
data class Content(
|
||||
val title: TextReference,
|
||||
val onPromoBannerClick: () -> Unit,
|
||||
val onCloseClick: () -> Unit,
|
||||
val onPromoShown: () -> Unit = {},
|
||||
) : PromoBannerUM()
|
||||
|
||||
data object Empty : PromoBannerUM()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class TailUM {
|
||||
data class Text(
|
||||
val text: TextReference,
|
||||
) : TailUM()
|
||||
|
||||
data object Draggable : TailUM()
|
||||
|
||||
data object Empty : TailUM()
|
||||
}
|
||||
|
||||
data class IconUM(
|
||||
val iconRes: Int,
|
||||
val tintReference: ColorReference2 = ColorReference2 { TangemTheme.colors2.graphic.neutral.primary },
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
package com.tangem.core.ui.ds.row.token.internal
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
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.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.styledResourceReference
|
||||
import com.tangem.core.ui.extensions.styledStringReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.utils.StringsSigns
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.util.UUID
|
||||
|
||||
internal object TangemTokenRowPreviewData {
|
||||
|
||||
private val priceChangeState: PriceChangeState.Content
|
||||
get() = PriceChangeState.Content(
|
||||
type = PriceChangeType.UP,
|
||||
valueInPercent = "2.45%",
|
||||
)
|
||||
|
||||
val promoBannerUM: TangemTokenRowUM.PromoBannerUM.Content
|
||||
get() = TangemTokenRowUM.PromoBannerUM.Content(
|
||||
title = stringReference("Earn yield by supplying your crypto assets"),
|
||||
onPromoBannerClick = {},
|
||||
onPromoShown = {},
|
||||
onCloseClick = {},
|
||||
)
|
||||
|
||||
val titleUM: TangemTokenRowUM.TitleUM.Content
|
||||
get() = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference(value = "Polygon"),
|
||||
hasPending = true,
|
||||
)
|
||||
|
||||
val subtitleUM: TangemTokenRowUM.SubtitleUM.Content
|
||||
get() = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference(value = "$ 0.6631"),
|
||||
priceChangeUM = priceChangeState,
|
||||
)
|
||||
|
||||
val topEndContentUM: TangemTokenRowUM.EndContentUM.Content
|
||||
get() = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = combinedReference(
|
||||
stringReference("$ 500"),
|
||||
styledStringReference(".17", {
|
||||
SpanStyle(
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
fontWeight = TangemTheme.typography2.bodyRegular16.fontWeight,
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
val bottomEndContentUM: TangemTokenRowUM.EndContentUM.Content
|
||||
get() = TangemTokenRowUM.EndContentUM.Content(
|
||||
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 coinIconState
|
||||
get() = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_polygon_22,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = 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 customTokenIconState
|
||||
get() = 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,
|
||||
titleUM = titleUM,
|
||||
subtitleUM = subtitleUM,
|
||||
topEndContentUM = topEndContentUM,
|
||||
bottomEndContentUM = bottomEndContentUM,
|
||||
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
|
||||
tailUM = TangemTokenRowUM.TailUM.Empty,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
|
||||
val defaultEllipsisState: TangemTokenRowUM.Content
|
||||
get() = defaultState.copy(
|
||||
titleUM = titleUM.copy(text = stringReference("Polygon Polygon Polygon Polygon")),
|
||||
subtitleUM = subtitleUM,
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = combinedReference(
|
||||
stringReference("$ 500"),
|
||||
styledStringReference(".11232131237", {
|
||||
SpanStyle(
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
fontWeight = TangemTheme.typography2.bodyRegular16.fontWeight,
|
||||
)
|
||||
}),
|
||||
),
|
||||
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),
|
||||
),
|
||||
),
|
||||
bottomEndContentUM = bottomEndContentUM,
|
||||
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
|
||||
tailUM = TangemTokenRowUM.TailUM.Empty,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
|
||||
val tokenState: TangemTokenRowUM.Content
|
||||
get() = TangemTokenRowUM.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleUM = titleUM,
|
||||
subtitleUM = subtitleUM,
|
||||
topEndContentUM = topEndContentUM,
|
||||
bottomEndContentUM = bottomEndContentUM,
|
||||
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
|
||||
tailUM = TangemTokenRowUM.TailUM.Empty,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
|
||||
val customTokenState: TangemTokenRowUM.Content
|
||||
get() = TangemTokenRowUM.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = customTokenIconState,
|
||||
titleUM = titleUM,
|
||||
subtitleUM = subtitleUM,
|
||||
topEndContentUM = topEndContentUM,
|
||||
bottomEndContentUM = bottomEndContentUM,
|
||||
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
|
||||
tailUM = TangemTokenRowUM.TailUM.Empty,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
|
||||
val draggableState: TangemTokenRowUM.Actionable
|
||||
get() = TangemTokenRowUM.Actionable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = coinIconState,
|
||||
titleUM = titleUM,
|
||||
subtitleUM = subtitleUM,
|
||||
tailUM = TangemTokenRowUM.TailUM.Draggable,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
|
||||
val draggableStateV2: TangemTokenRowUM.Actionable
|
||||
get() = TangemTokenRowUM.Actionable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = coinIconState,
|
||||
titleUM = titleUM,
|
||||
subtitleUM = subtitleUM,
|
||||
topEndContentUM = topEndContentUM,
|
||||
bottomEndContentUM = bottomEndContentUM,
|
||||
tailUM = TangemTokenRowUM.TailUM.Draggable,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
|
||||
val loadingState: TangemTokenRowUM.Loading
|
||||
get() = TangemTokenRowUM.Loading(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = coinIconState,
|
||||
titleUM = TangemTokenRowUM.TitleUM.Loading,
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Loading,
|
||||
)
|
||||
|
||||
val unreachableState: TangemTokenRowUM.Content
|
||||
get() = defaultState.copy(
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference(StringsSigns.DASH_SIGN),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = styledResourceReference(
|
||||
id = R.string.common_unreachable,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val accountState: TangemTokenRowUM.Content
|
||||
get() = TangemTokenRowUM.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = accountResIcon,
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference(value = "Portfolio"),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference("24 tokens"),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("22,129.65 $"),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("+ $ 1,245.32"),
|
||||
priceChangeUM = priceChangeState,
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
|
||||
val accountLetterState: TangemTokenRowUM.Content
|
||||
get() = accountState.copy(
|
||||
iconState = accountLetterIcon,
|
||||
)
|
||||
|
||||
val accountEllipsisState: TangemTokenRowUM.Content
|
||||
get() = accountState.copy(
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference(value = "Portfolio Portfolio Portfolio Portfolio"),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference("24 tokens 24 tokens 24 tokens 24 tokens"),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("22,129.6129387147653025 $"),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("+ $ 1,245.31093284302752"),
|
||||
priceChangeUM = priceChangeState,
|
||||
),
|
||||
)
|
||||
|
||||
val promoBannerState: TangemTokenRowUM.Content
|
||||
get() = defaultState.copy(
|
||||
promoBannerUM = promoBannerUM,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package com.tangem.core.ui.ds.row.token.internal
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
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.components.TextShimmer
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
@Composable
|
||||
internal fun TokenRowEndBottomContent(
|
||||
endContentUM: TangemTokenRowUM.EndContentUM,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (endContentUM) {
|
||||
is TangemTokenRowUM.EndContentUM.Content -> Content(
|
||||
modifier = modifier,
|
||||
endContentUM = endContentUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
TangemTokenRowUM.EndContentUM.Empty -> Unit
|
||||
TangemTokenRowUM.EndContentUM.Loading -> TextShimmer(
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
modifier = modifier.width(TangemTheme.dimens2.x10),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(
|
||||
endContentUM: TangemTokenRowUM.EndContentUM.Content,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = endContentUM.text.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography2.captionSemibold12.applyBladeBrush(
|
||||
isEnabled = endContentUM.isFlickering,
|
||||
textColor = if (endContentUM.isAvailable) {
|
||||
TangemTheme.colors2.text.neutral.secondary
|
||||
} else {
|
||||
TangemTheme.colors2.text.status.disabled
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
when (endContentUM.priceChangeUM) {
|
||||
is PriceChangeState.Content -> TokenRowPriceChangeContent(
|
||||
priceChangeState = endContentUM.priceChangeUM,
|
||||
isFlickering = endContentUM.isFlickering,
|
||||
isAvailable = endContentUM.isAvailable,
|
||||
)
|
||||
PriceChangeState.Unknown -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TokenRowEndBottomContent_Preview(
|
||||
@PreviewParameter(TokenRowEndBottomContentPreviewProvider::class) params: TangemTokenRowUM.EndContentUM,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TokenRowEndBottomContent(
|
||||
endContentUM = params,
|
||||
isBalanceHidden = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class TokenRowEndBottomContentPreviewProvider : PreviewParameterProvider<TangemTokenRowUM.EndContentUM> {
|
||||
override val values: Sequence<TangemTokenRowUM.EndContentUM>
|
||||
get() = sequenceOf(
|
||||
TangemTokenRowPreviewData.bottomEndContentUM,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.tangem.core.ui.ds.row.token.internal
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
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.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
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.util.fastForEach
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
@Composable
|
||||
internal fun TokenRowEndTopContent(
|
||||
endContentUM: TangemTokenRowUM.EndContentUM,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (endContentUM) {
|
||||
is TangemTokenRowUM.EndContentUM.Content -> Content(
|
||||
modifier = modifier,
|
||||
endContentUM = endContentUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
TangemTokenRowUM.EndContentUM.Empty -> Unit
|
||||
TangemTokenRowUM.EndContentUM.Loading -> TextShimmer(
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
modifier = modifier.width(TangemTheme.dimens2.x18),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(
|
||||
endContentUM: TangemTokenRowUM.EndContentUM.Content,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = endContentUM.icons.isNotEmpty(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x1),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
) {
|
||||
endContentUM.icons.fastForEach { icon ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x3),
|
||||
painter = rememberVectorPainter(image = ImageVector.vectorResource(icon.iconRes)),
|
||||
tint = icon.tintReference(),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
text = endContentUM.text.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography2.bodySemibold16.applyBladeBrush(
|
||||
isEnabled = endContentUM.isFlickering,
|
||||
textColor = if (endContentUM.isAvailable) {
|
||||
TangemTheme.colors2.text.neutral.primary
|
||||
} else {
|
||||
TangemTheme.colors2.text.status.disabled
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TokenRowEndTopContent_Preview(
|
||||
@PreviewParameter(TokenRowEndContentPreviewProvider::class) params: TangemTokenRowUM.EndContentUM,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TokenRowEndTopContent(
|
||||
endContentUM = params,
|
||||
isBalanceHidden = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class TokenRowEndContentPreviewProvider : PreviewParameterProvider<TangemTokenRowUM.EndContentUM> {
|
||||
override val values: Sequence<TangemTokenRowUM.EndContentUM>
|
||||
get() = sequenceOf(
|
||||
TangemTokenRowPreviewData.topEndContentUM,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.tangem.core.ui.ds.row.token.internal
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
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.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun RowScope.TokenRowPriceChangeContent(
|
||||
priceChangeState: PriceChangeState.Content,
|
||||
isFlickering: Boolean,
|
||||
isAvailable: Boolean = true,
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = priceChangeState.type,
|
||||
label = "Update the price change's arrow",
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens2.x1),
|
||||
) { animatedType ->
|
||||
Icon(
|
||||
painter = rememberVectorPainter(
|
||||
ImageVector.vectorResource(
|
||||
when (animatedType) {
|
||||
PriceChangeType.UP -> R.drawable.ic_up_dynamic_24
|
||||
PriceChangeType.DOWN -> R.drawable.ic_down_dynamic_24
|
||||
PriceChangeType.NEUTRAL -> R.drawable.ic_static_dynamic_24
|
||||
},
|
||||
),
|
||||
),
|
||||
tint = when (animatedType) {
|
||||
PriceChangeType.UP -> TangemTheme.colors2.graphic.status.accent
|
||||
PriceChangeType.DOWN -> TangemTheme.colors2.graphic.status.warning
|
||||
PriceChangeType.NEUTRAL -> TangemTheme.colors2.graphic.neutral.secondary
|
||||
},
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x3),
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedContent(
|
||||
targetState = priceChangeState.valueInPercent,
|
||||
label = "Update the price text",
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens2.x0_5),
|
||||
) { animatedText ->
|
||||
Text(
|
||||
text = animatedText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography2.captionSemibold12.applyBladeBrush(
|
||||
isEnabled = isFlickering,
|
||||
textColor = if (isAvailable) {
|
||||
TangemTheme.colors2.text.neutral.secondary
|
||||
} else {
|
||||
TangemTheme.colors2.text.status.disabled
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.tangem.core.ui.ds.row.token.internal
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
@Composable
|
||||
internal fun TokenRowPromoBanner(promoBannerUM: TangemTokenRowUM.PromoBannerUM, modifier: Modifier = Modifier) {
|
||||
when (promoBannerUM) {
|
||||
is TangemTokenRowUM.PromoBannerUM.Content -> TokenRowPromoBanner(
|
||||
promoBannerUM = promoBannerUM,
|
||||
modifier = modifier,
|
||||
)
|
||||
TangemTokenRowUM.PromoBannerUM.Empty -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TokenRowPromoBanner(promoBannerUM: TangemTokenRowUM.PromoBannerUM.Content, modifier: Modifier = Modifier) {
|
||||
LaunchedEffect(promoBannerUM) {
|
||||
promoBannerUM.onPromoShown()
|
||||
}
|
||||
val bgColor = TangemTheme.colors.control.default
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(color = bgColor, shape = RoundedCornerShape(TangemTheme.dimens2.x4))
|
||||
.clickable(onClick = promoBannerUM.onPromoBannerClick)
|
||||
.padding(horizontal = TangemTheme.dimens2.x3, vertical = TangemTheme.dimens2.x2)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_analytics_up_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier
|
||||
.padding(end = TangemTheme.dimens2.x2)
|
||||
.size(TangemTheme.dimens2.x4),
|
||||
)
|
||||
Text(
|
||||
text = promoBannerUM.title.resolveReference(),
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = TangemTheme.dimens2.x2),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors2.text.neutral.secondary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x4)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(bounded = false),
|
||||
onClick = { promoBannerUM.onCloseClick() },
|
||||
),
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_rectangle_bottom),
|
||||
contentDescription = null,
|
||||
tint = bgColor,
|
||||
modifier = Modifier
|
||||
.size(width = TangemTheme.dimens2.x3, height = TangemTheme.dimens2.x2),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_TokenRowPromoBanner() {
|
||||
TangemThemePreviewRedesign {
|
||||
TokenRowPromoBanner(
|
||||
promoBannerUM = TangemTokenRowPreviewData.promoBannerUM,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package com.tangem.core.ui.ds.row.token.internal
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.width
|
||||
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.text.style.TextOverflow
|
||||
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.components.TextShimmer
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.ds.badge.TangemBadge
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
@Composable
|
||||
internal fun TokenRowSubtitle(subtitleUM: TangemTokenRowUM.SubtitleUM, modifier: Modifier = Modifier) {
|
||||
when (subtitleUM) {
|
||||
is TangemTokenRowUM.SubtitleUM.Content -> SubtitleContent(
|
||||
subtitleUM = subtitleUM,
|
||||
modifier = modifier,
|
||||
)
|
||||
TangemTokenRowUM.SubtitleUM.Loading -> TextShimmer(
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
modifier = modifier.width(TangemTheme.dimens2.x8),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
TangemTokenRowUM.SubtitleUM.Empty -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SubtitleContent(subtitleUM: TangemTokenRowUM.SubtitleUM.Content, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = subtitleUM.badge != null,
|
||||
) {
|
||||
val wrappedBadge = remember(this) { requireNotNull(subtitleUM.badge) }
|
||||
TangemBadge(wrappedBadge)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = subtitleUM.text.resolveAnnotatedReference(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography2.captionSemibold12.applyBladeBrush(
|
||||
isEnabled = subtitleUM.isFlickering,
|
||||
textColor = if (subtitleUM.isAvailable) {
|
||||
TangemTheme.colors2.text.neutral.secondary
|
||||
} else {
|
||||
TangemTheme.colors2.text.status.disabled
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
when (subtitleUM.priceChangeUM) {
|
||||
is PriceChangeState.Content -> TokenRowPriceChangeContent(
|
||||
priceChangeState = subtitleUM.priceChangeUM,
|
||||
isFlickering = subtitleUM.isFlickering,
|
||||
isAvailable = subtitleUM.isAvailable,
|
||||
)
|
||||
PriceChangeState.Unknown -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TokenRowSubtitle_Preview(
|
||||
@PreviewParameter(TokenRowSubtitlePreviewProvider::class) params: TangemTokenRowUM.SubtitleUM,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TokenRowSubtitle(
|
||||
subtitleUM = params,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class TokenRowSubtitlePreviewProvider : PreviewParameterProvider<TangemTokenRowUM.SubtitleUM> {
|
||||
override val values: Sequence<TangemTokenRowUM.SubtitleUM>
|
||||
get() = sequenceOf(
|
||||
TangemTokenRowPreviewData.subtitleUM,
|
||||
TangemTokenRowUM.SubtitleUM.Loading,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.core.ui.ds.row.token.internal
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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
|
||||
import com.tangem.core.ui.test.OrganizeTokensScreenTestTags
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
|
||||
@Composable
|
||||
internal fun TokenRowTail(
|
||||
tailUM: TangemTokenRowUM.TailUM,
|
||||
reorderableTokenListState: ReorderableLazyListState?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = tailUM,
|
||||
label = "Update non content fiat block",
|
||||
modifier = modifier,
|
||||
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,
|
||||
modifier = innerModifier,
|
||||
)
|
||||
is TangemTokenRowUM.TailUM.Text -> ContentText(text = animatedState.text, modifier = innerModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DraggableImage(reorderableTokenListState: ReorderableLazyListState?, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(size = TangemTheme.dimens2.x6)
|
||||
.then(
|
||||
other = if (reorderableTokenListState != null) {
|
||||
Modifier.detectReorder(reorderableTokenListState)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.testTag(OrganizeTokensScreenTestTags.DRAGGABLE_IMAGE),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_drag_24),
|
||||
tint = TangemTheme.colors2.graphic.neutral.tertiaryConstant,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentText(text: TextReference, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = text.resolveAnnotatedReference(),
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography2.captionRegular12,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package com.tangem.core.ui.ds.row.token.internal
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.width
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
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.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
@Composable
|
||||
internal fun TokenRowTitle(titleUM: TangemTokenRowUM.TitleUM, modifier: Modifier = Modifier) {
|
||||
when (titleUM) {
|
||||
is TangemTokenRowUM.TitleUM.Content -> ContentTitle(titleUM = titleUM, modifier = modifier)
|
||||
TangemTokenRowUM.TitleUM.Loading -> TextShimmer(
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
modifier = modifier.width(TangemTheme.dimens2.x18),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
TangemTokenRowUM.TitleUM.Empty -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentTitle(titleUM: TangemTokenRowUM.TitleUM.Content, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens2.x4),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
/*
|
||||
* If currency name has a long width, then it will completely displace the image.
|
||||
* So we need to use [weight] to avoid displacement.
|
||||
*/
|
||||
Text(
|
||||
text = titleUM.text.resolveReference(),
|
||||
modifier = Modifier.weight(weight = 1f, fill = false),
|
||||
color = if (titleUM.isAvailable) {
|
||||
TangemTheme.colors2.text.neutral.primary
|
||||
} else {
|
||||
TangemTheme.colors2.text.status.disabled
|
||||
},
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = titleUM.hasPending,
|
||||
modifier = Modifier.align(alignment = Alignment.CenterVertically),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.img_loader_15),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = titleUM.badge != null,
|
||||
modifier = Modifier.conditional(
|
||||
condition = titleUM.onBadgeClick != null,
|
||||
modifier = { clickable(onClick = requireNotNull(titleUM.onBadgeClick)) },
|
||||
),
|
||||
) {
|
||||
val wrappedBadge = remember(this) { requireNotNull(titleUM.badge) }
|
||||
TangemBadge(wrappedBadge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TokenRowTitle_Preview() {
|
||||
TangemThemePreviewRedesign {
|
||||
TokenRowTitle(
|
||||
titleUM = TangemTokenRowPreviewData.titleUM,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.core.ui.extensions
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
|
|
@ -11,7 +13,7 @@ import androidx.compose.ui.graphics.Color
|
|||
*
|
||||
* @property value color provider from theme
|
||||
*/
|
||||
@Deprecated("Use TextReference with applied SpanStyleReference for colored text.")
|
||||
@Deprecated("Use TextReference with applied SpanStyleReference for colored text or ColorReference2 for color.")
|
||||
@Immutable
|
||||
data class ColorReference(val value: @Composable () -> Color)
|
||||
|
||||
|
|
@ -31,4 +33,17 @@ fun themedColor(value: @Composable () -> Color): ColorReference {
|
|||
@Composable
|
||||
fun ColorReference.resolveReference(): Color {
|
||||
return value()
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functional interface for keeping themed [Color] reference from app theme.
|
||||
* It is necessary to use [Stable] annotation for runtime stability.
|
||||
*/
|
||||
@Stable
|
||||
@FunctionalInterface
|
||||
fun interface ColorReference2 {
|
||||
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
operator fun invoke(): Color
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
|
||||
fun Modifier.disableNestedScroll(): Modifier = nestedScroll(DisableParentConnection)
|
||||
|
||||
private object DisableParentConnection : NestedScrollConnection {
|
||||
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
|
||||
return available.copy(x = 0f)
|
||||
}
|
||||
}
|
||||
9
core/ui/src/main/res/drawable/ic_down_dynamic_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_down_dynamic_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<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="M5.28,4C4.294,4 3.679,5.064 4.176,5.911L10.896,17.369C11.389,18.21 12.611,18.21 13.104,17.369L19.824,5.911C20.321,5.064 19.706,4 18.72,4H5.28Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
|
|
@ -4,7 +4,9 @@
|
|||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M19,8H5V10H19V8ZM19,14H5V16H19V14Z"
|
||||
android:fillColor="#B0B0B0"
|
||||
android:fillType="evenOdd"/>
|
||||
android:pathData="M5,8H19M5,16H19"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
|
|
|
|||
9
core/ui/src/main/res/drawable/ic_static_dynamic_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_static_dynamic_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<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,12m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
9
core/ui/src/main/res/drawable/ic_up_dynamic_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_up_dynamic_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<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="M5.28,20C4.294,20 3.679,18.936 4.176,18.089L10.896,6.631C11.389,5.79 12.611,5.79 13.104,6.631L19.824,18.089C20.321,18.936 19.706,20 18.72,20H5.28Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
Loading…
Add table
Add a link
Reference in a new issue