Updated on 2026-08-14
This commit is contained in:
commit
3edb222a84
22 changed files with 1381 additions and 222 deletions
|
|
@ -72,14 +72,14 @@ fun TangemBadge(badgeUM: TangemBadgeUM, modifier: Modifier = Modifier) {
|
|||
*/
|
||||
@Composable
|
||||
fun TangemBadge(
|
||||
text: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
text: TextReference? = null,
|
||||
@DrawableRes iconRes: Int? = null,
|
||||
size: TangemBadgeSize = X9,
|
||||
shape: TangemBadgeShape = TangemBadgeShape.Default,
|
||||
color: TangemBadgeColor = TangemBadgeColor.Gray,
|
||||
type: TangemBadgeType = TangemBadgeType.Solid,
|
||||
iconPosition: TangemBadgeIconPosition = TangemBadgeIconPosition.Start,
|
||||
iconPosition: TangemBadgeIconPosition = TangemBadgeIconPosition.None,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val iconColor = getIconColor(type = type, color = color)
|
||||
|
|
@ -94,7 +94,7 @@ fun TangemBadge(
|
|||
.clickableSingle(enabled = onClick != null, onClick = { onClick?.invoke() }),
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = iconRes != null && iconPosition == TangemBadgeIconPosition.Start,
|
||||
visible = iconRes != null && iconPosition != TangemBadgeIconPosition.End,
|
||||
modifier = Modifier.size(size = size.toContentSize()),
|
||||
label = "Start Icon Visibility",
|
||||
) {
|
||||
|
|
@ -105,13 +105,18 @@ fun TangemBadge(
|
|||
tint = iconColor,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = size.toTextStyle(),
|
||||
maxLines = 1,
|
||||
color = getTextColor(type = type, color = color),
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = text != null,
|
||||
label = "Text Visibility",
|
||||
) {
|
||||
val wrappedText = remember(this) { requireNotNull(text) }
|
||||
Text(
|
||||
text = wrappedText.resolveReference(),
|
||||
style = size.toTextStyle(),
|
||||
maxLines = 1,
|
||||
color = getTextColor(type = type, color = color),
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = iconRes != null && iconPosition == TangemBadgeIconPosition.End,
|
||||
modifier = Modifier.size(size = size.toContentSize()),
|
||||
|
|
@ -178,14 +183,17 @@ enum class TangemBadgeSize {
|
|||
X4 -> when (position) {
|
||||
TangemBadgeIconPosition.Start -> PaddingValues(start = 4.dp, end = 6.dp)
|
||||
TangemBadgeIconPosition.End -> PaddingValues(start = 6.dp, end = 4.dp)
|
||||
TangemBadgeIconPosition.None -> PaddingValues(start = 6.dp, end = 6.dp)
|
||||
}
|
||||
X6 -> when (position) {
|
||||
TangemBadgeIconPosition.Start -> PaddingValues(start = 8.dp, end = 12.dp)
|
||||
TangemBadgeIconPosition.End -> PaddingValues(start = 12.dp, end = 8.dp)
|
||||
TangemBadgeIconPosition.None -> PaddingValues(start = 12.dp, end = 12.dp)
|
||||
}
|
||||
X9 -> when (position) {
|
||||
TangemBadgeIconPosition.Start -> PaddingValues(start = 12.dp, end = 16.dp)
|
||||
TangemBadgeIconPosition.End -> PaddingValues(start = 16.dp, end = 12.dp)
|
||||
TangemBadgeIconPosition.None -> PaddingValues(start = 16.dp, end = 16.dp)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,6 +230,7 @@ enum class TangemBadgeSize {
|
|||
enum class TangemBadgeIconPosition {
|
||||
Start,
|
||||
End,
|
||||
None,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -240,6 +249,7 @@ enum class TangemBadgeColor {
|
|||
Blue,
|
||||
Red,
|
||||
Gray,
|
||||
Green,
|
||||
}
|
||||
|
||||
@ReadOnlyComposable
|
||||
|
|
@ -258,6 +268,12 @@ private fun getIconColor(type: TangemBadgeType, color: TangemBadgeColor) = when
|
|||
-> TangemTheme.colors2.markers.iconRed
|
||||
TangemBadgeType.Solid -> TangemTheme.colors2.graphic.neutral.primaryInvertedConstant
|
||||
}
|
||||
TangemBadgeColor.Green -> when (type) {
|
||||
TangemBadgeType.Outline,
|
||||
TangemBadgeType.Tinted,
|
||||
-> TangemTheme.colors2.markers.iconGreen
|
||||
TangemBadgeType.Solid -> TangemTheme.colors2.graphic.neutral.primaryInvertedConstant
|
||||
}
|
||||
}
|
||||
|
||||
@ReadOnlyComposable
|
||||
|
|
@ -276,8 +292,15 @@ private fun getTextColor(type: TangemBadgeType, color: TangemBadgeColor) = when
|
|||
-> TangemTheme.colors2.markers.textRed
|
||||
TangemBadgeType.Solid -> TangemTheme.colors2.text.neutral.primaryInvertedConstant
|
||||
}
|
||||
TangemBadgeColor.Green -> when (type) {
|
||||
TangemBadgeType.Outline,
|
||||
TangemBadgeType.Tinted,
|
||||
-> TangemTheme.colors2.markers.textGreen
|
||||
TangemBadgeType.Solid -> TangemTheme.colors2.text.neutral.primaryInvertedConstant
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
private fun Modifier.getBackgroundColor(type: TangemBadgeType, color: TangemBadgeColor, shape: Shape) = when (type) {
|
||||
|
|
@ -286,6 +309,7 @@ private fun Modifier.getBackgroundColor(type: TangemBadgeType, color: TangemBadg
|
|||
TangemBadgeColor.Gray -> TangemTheme.colors2.markers.backgroundSolidGray
|
||||
TangemBadgeColor.Blue -> TangemTheme.colors2.markers.backgroundSolidBlue
|
||||
TangemBadgeColor.Red -> TangemTheme.colors2.markers.backgroundSolidRed
|
||||
TangemBadgeColor.Green -> TangemTheme.colors2.markers.backgroundSolidGreen
|
||||
},
|
||||
)
|
||||
TangemBadgeType.Tinted -> background(
|
||||
|
|
@ -293,6 +317,7 @@ private fun Modifier.getBackgroundColor(type: TangemBadgeType, color: TangemBadg
|
|||
TangemBadgeColor.Gray -> TangemTheme.colors2.markers.backgroundTintedGray
|
||||
TangemBadgeColor.Blue -> TangemTheme.colors2.markers.backgroundTintedBlue
|
||||
TangemBadgeColor.Red -> TangemTheme.colors2.markers.backgroundTintedRed
|
||||
TangemBadgeColor.Green -> TangemTheme.colors2.markers.backgroundTintedGreen
|
||||
},
|
||||
)
|
||||
TangemBadgeType.Outline -> {
|
||||
|
|
@ -301,6 +326,7 @@ private fun Modifier.getBackgroundColor(type: TangemBadgeType, color: TangemBadg
|
|||
TangemBadgeColor.Gray -> TangemTheme.colors2.markers.borderGray
|
||||
TangemBadgeColor.Blue -> TangemTheme.colors2.markers.borderTintedBlue
|
||||
TangemBadgeColor.Red -> TangemTheme.colors2.markers.borderTintedRed
|
||||
TangemBadgeColor.Green -> TangemTheme.colors2.markers.borderTintedGreen
|
||||
},
|
||||
shape = shape,
|
||||
width = 1.dp,
|
||||
|
|
@ -320,16 +346,16 @@ private fun TangemBadge_Preview(@PreviewParameter(TangemBadgePreviewProvider::cl
|
|||
.background(TangemTheme.colors2.surface.level1)
|
||||
.padding(8.dp),
|
||||
) {
|
||||
repeat(2) { yIndex ->
|
||||
repeat(3) { yIndex ->
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
repeat(TangemBadgeType.entries.size) { index ->
|
||||
TangemBadge(
|
||||
text = stringReference("Title"),
|
||||
text = stringReference("Title").takeIf { yIndex < 2 },
|
||||
iconRes = R.drawable.ic_information_24,
|
||||
type = TangemBadgeType.entries[index],
|
||||
color = params,
|
||||
shape = TangemBadgeShape.entries[yIndex % 2],
|
||||
iconPosition = TangemBadgeIconPosition.entries[yIndex % 2],
|
||||
iconPosition = TangemBadgeIconPosition.entries[yIndex],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -344,6 +370,7 @@ private class TangemBadgePreviewProvider : PreviewParameterProvider<TangemBadgeC
|
|||
TangemBadgeColor.Gray,
|
||||
TangemBadgeColor.Blue,
|
||||
TangemBadgeColor.Red,
|
||||
TangemBadgeColor.Green,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -17,7 +17,7 @@ import kotlin.math.max
|
|||
* A custom layout composable that arranges its children in a row with specific layout IDs.
|
||||
*/
|
||||
enum class TangemRowLayoutId {
|
||||
HEAD, START_TOP, END_TOP, START_BOTTOM, END_BOTTOM, TAIL, EXTRA_TOP
|
||||
HEAD, START_TOP, END_TOP, START_BOTTOM, END_BOTTOM, TAIL, EXTRA_TOP, EXTRA_BOTTOM
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -37,6 +37,7 @@ fun TangemRowContainer(
|
|||
val density = LocalDensity.current
|
||||
val localDirection = LocalLayoutDirection.current
|
||||
val verticalPadding = with(density) { TangemTheme.dimens2.x1.roundToPx() }
|
||||
val extraContentPadding = with(density) { TangemTheme.dimens2.x2.roundToPx() }
|
||||
val contentTopPadding = with(density) { contentPadding.calculateTopPadding().roundToPx() }
|
||||
val contentBottomPadding = with(density) { contentPadding.calculateBottomPadding().roundToPx() }
|
||||
val contentStartPadding = with(density) { contentPadding.calculateLeftPadding(localDirection).roundToPx() }
|
||||
|
|
@ -110,6 +111,10 @@ fun TangemRowContainer(
|
|||
layoutId = TangemRowLayoutId.EXTRA_TOP,
|
||||
constraints = constraints,
|
||||
)
|
||||
val extraBottomPlaceable = measurables.measure(
|
||||
layoutId = TangemRowLayoutId.EXTRA_BOTTOM,
|
||||
constraints = constraints,
|
||||
)
|
||||
|
||||
val mainLayoutHeight = maxOf(
|
||||
headPlaceable.heightOrZero(),
|
||||
|
|
@ -124,7 +129,13 @@ fun TangemRowContainer(
|
|||
contentTopPadding
|
||||
}
|
||||
|
||||
val layoutHeight = mainLayoutHeight + mainContentTopPadding + contentBottomPadding
|
||||
val mainContentBottomPadding = if (extraBottomPlaceable != null) {
|
||||
extraBottomPlaceable.heightOrZero() + contentBottomPadding
|
||||
} else {
|
||||
contentBottomPadding
|
||||
}
|
||||
|
||||
val layoutHeight = mainLayoutHeight + mainContentTopPadding + mainContentBottomPadding
|
||||
|
||||
layout(width = constraints.maxWidth, height = layoutHeight) {
|
||||
extraTopPlaceable?.placeRelative(x = 0, y = 0)
|
||||
|
|
@ -174,6 +185,11 @@ fun TangemRowContainer(
|
|||
x = layoutWidth - tailPlaceable.width + contentEndPadding,
|
||||
y = mainContentTopPadding + (mainLayoutHeight - tailPlaceable.height).div(other = 2),
|
||||
)
|
||||
|
||||
extraBottomPlaceable?.placeRelative(
|
||||
x = 0,
|
||||
y = mainContentTopPadding + mainLayoutHeight + extraContentPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,15 +52,6 @@ fun TangemTokenRow(
|
|||
.testTag(tag = TokenElementsTestTags.TOKEN_ICON),
|
||||
)
|
||||
|
||||
TokenRowPromoBanner(
|
||||
promoBannerUM = tokenRowUM.promoBannerUM,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.EXTRA_TOP)
|
||||
.testTag(tag = TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER)
|
||||
.padding(horizontal = TangemTheme.dimens2.x3)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
TokenRowTitle(
|
||||
titleUM = tokenRowUM.titleUM,
|
||||
modifier = Modifier
|
||||
|
|
@ -77,17 +68,21 @@ fun TangemTokenRow(
|
|||
.testTag(tag = TokenElementsTestTags.TOKEN_PRICE),
|
||||
)
|
||||
|
||||
TokenRowEndTopContent(
|
||||
TokenRowEndContent(
|
||||
endContentUM = tokenRowUM.topEndContentUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
textStyle = TangemTheme.typography2.bodySemibold16,
|
||||
textColor = TangemTheme.colors2.text.neutral.primary,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.END_TOP)
|
||||
.testTag(tag = TokenElementsTestTags.TOKEN_FIAT_AMOUNT),
|
||||
)
|
||||
|
||||
TokenRowEndBottomContent(
|
||||
TokenRowEndContent(
|
||||
endContentUM = tokenRowUM.bottomEndContentUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
textStyle = TangemTheme.typography2.captionSemibold12,
|
||||
textColor = TangemTheme.colors2.text.neutral.secondary,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM)
|
||||
.testTag(tag = TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT),
|
||||
|
|
@ -100,6 +95,15 @@ fun TangemTokenRow(
|
|||
.layoutId(layoutId = TangemRowLayoutId.TAIL)
|
||||
.testTag(tag = TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK),
|
||||
)
|
||||
|
||||
TokenRowPromoBanner(
|
||||
promoBannerUM = tokenRowUM.promoBannerUM,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.EXTRA_BOTTOM)
|
||||
.testTag(tag = TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER)
|
||||
.padding(start = TangemTheme.dimens2.x10, bottom = TangemTheme.dimens2.x2)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
},
|
||||
modifier = modifier.tokenClickable(tokenRowUM = tokenRowUM),
|
||||
)
|
||||
|
|
@ -159,17 +163,21 @@ fun TangemTokenRow(
|
|||
.testTag(tag = TokenElementsTestTags.TOKEN_PRICE),
|
||||
)
|
||||
|
||||
TokenRowEndTopContent(
|
||||
TokenRowEndContent(
|
||||
endContentUM = tokenRowUM.topEndContentUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
textStyle = TangemTheme.typography2.bodySemibold16,
|
||||
textColor = TangemTheme.colors2.text.neutral.primary,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.END_TOP)
|
||||
.testTag(tag = TokenElementsTestTags.TOKEN_FIAT_AMOUNT),
|
||||
)
|
||||
|
||||
TokenRowEndBottomContent(
|
||||
TokenRowEndContent(
|
||||
endContentUM = tokenRowUM.bottomEndContentUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
textStyle = TangemTheme.typography2.captionSemibold12,
|
||||
textColor = TangemTheme.colors2.text.neutral.secondary,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM)
|
||||
.testTag(tag = TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT),
|
||||
|
|
|
|||
|
|
@ -133,7 +133,8 @@ sealed class TangemTokenRowUM : TangemRowUM {
|
|||
val text: TextReference,
|
||||
val isAvailable: Boolean = true,
|
||||
val isFlickering: Boolean = false,
|
||||
val icons: ImmutableList<TangemIconUM.Icon> = persistentListOf(),
|
||||
val startIcons: ImmutableList<TangemIconUM.Icon> = persistentListOf(),
|
||||
val endIcons: ImmutableList<TangemIconUM.Icon> = persistentListOf(),
|
||||
val priceChangeUM: PriceChangeState = PriceChangeState.Unknown,
|
||||
) : EndContentUM()
|
||||
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ internal object TangemTokenRowPreviewData {
|
|||
)
|
||||
}),
|
||||
),
|
||||
icons = persistentListOf(
|
||||
startIcons = persistentListOf(
|
||||
TangemIconUM.Icon(R.drawable.ic_staking_mini_10),
|
||||
TangemIconUM.Icon(R.drawable.ic_attention_12),
|
||||
TangemIconUM.Icon(R.drawable.ic_error_sync_24),
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
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 (val priceChangeUM = endContentUM.priceChangeUM) {
|
||||
is PriceChangeState.Content -> TokenRowPriceChangeContent(
|
||||
priceChangeState = 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
|
||||
|
|
@ -8,15 +8,18 @@ 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.Color
|
||||
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.TextStyle
|
||||
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.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
|
||||
|
|
@ -25,9 +28,11 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
@Composable
|
||||
internal fun TokenRowEndTopContent(
|
||||
internal fun TokenRowEndContent(
|
||||
endContentUM: TangemTokenRowUM.EndContentUM,
|
||||
isBalanceHidden: Boolean,
|
||||
textStyle: TextStyle,
|
||||
textColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (endContentUM) {
|
||||
|
|
@ -35,11 +40,13 @@ internal fun TokenRowEndTopContent(
|
|||
modifier = modifier,
|
||||
endContentUM = endContentUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
textStyle = textStyle,
|
||||
textColor = textColor,
|
||||
)
|
||||
TangemTokenRowUM.EndContentUM.Empty -> Unit
|
||||
TangemTokenRowUM.EndContentUM.Loading -> TextShimmer(
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
modifier = modifier.width(TangemTheme.dimens2.x18),
|
||||
style = textStyle,
|
||||
modifier = modifier.width(TangemTheme.dimens2.x10),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
}
|
||||
|
|
@ -48,6 +55,8 @@ internal fun TokenRowEndTopContent(
|
|||
@Composable
|
||||
private fun Content(
|
||||
endContentUM: TangemTokenRowUM.EndContentUM.Content,
|
||||
textStyle: TextStyle,
|
||||
textColor: Color,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -56,14 +65,14 @@ private fun Content(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = endContentUM.icons.isNotEmpty(),
|
||||
visible = endContentUM.startIcons.isNotEmpty(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x1),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
) {
|
||||
endContentUM.icons.fastForEach { icon ->
|
||||
endContentUM.startIcons.fastForEach { icon ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x3),
|
||||
painter = rememberVectorPainter(image = ImageVector.vectorResource(icon.iconRes)),
|
||||
|
|
@ -75,11 +84,11 @@ private fun Content(
|
|||
}
|
||||
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
text = endContentUM.text.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography2.bodySemibold16.applyBladeBrush(
|
||||
color = textColor,
|
||||
style = textStyle.applyBladeBrush(
|
||||
isEnabled = endContentUM.isFlickering,
|
||||
textColor = if (endContentUM.isAvailable) {
|
||||
TangemTheme.colors2.text.neutral.primary
|
||||
|
|
@ -88,6 +97,34 @@ private fun Content(
|
|||
},
|
||||
),
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = endContentUM.endIcons.isNotEmpty(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens2.x0_5),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
) {
|
||||
endContentUM.endIcons.fastForEach { icon ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x3),
|
||||
painter = rememberVectorPainter(image = ImageVector.vectorResource(icon.iconRes)),
|
||||
tint = icon.tintReference(),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (val priceChangeUM = endContentUM.priceChangeUM) {
|
||||
is PriceChangeState.Content -> TokenRowPriceChangeContent(
|
||||
priceChangeState = priceChangeUM,
|
||||
isFlickering = endContentUM.isFlickering,
|
||||
isAvailable = endContentUM.isAvailable,
|
||||
)
|
||||
PriceChangeState.Unknown -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,13 +132,15 @@ private fun Content(
|
|||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TokenRowEndTopContent_Preview(
|
||||
private fun TokenRowEndContent_Preview(
|
||||
@PreviewParameter(TokenRowEndContentPreviewProvider::class) params: TangemTokenRowUM.EndContentUM,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TokenRowEndTopContent(
|
||||
TokenRowEndContent(
|
||||
endContentUM = params,
|
||||
isBalanceHidden = false,
|
||||
textColor = TangemTheme.colors2.text.neutral.primary,
|
||||
textStyle = TangemTheme.typography2.captionSemibold12,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +148,7 @@ private fun TokenRowEndTopContent_Preview(
|
|||
private class TokenRowEndContentPreviewProvider : PreviewParameterProvider<TangemTokenRowUM.EndContentUM> {
|
||||
override val values: Sequence<TangemTokenRowUM.EndContentUM>
|
||||
get() = sequenceOf(
|
||||
TangemTokenRowPreviewData.topEndContentUM,
|
||||
TangemTokenRowPreviewData.bottomEndContentUM,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -3,15 +3,12 @@ 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
|
||||
|
|
@ -19,6 +16,7 @@ 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.badge.*
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -40,55 +38,52 @@ internal fun TokenRowPromoBanner(promoBannerUM: TangemTokenRowUM.PromoBannerUM.C
|
|||
LaunchedEffect(promoBannerUM) {
|
||||
promoBannerUM.onPromoShown()
|
||||
}
|
||||
val bgColor = TangemTheme.colors.control.default
|
||||
Column(modifier = modifier) {
|
||||
val bgColor = TangemTheme.colors2.markers.backgroundTintedGreen
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.shape_triangular),
|
||||
contentDescription = null,
|
||||
tint = bgColor,
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens2.x5),
|
||||
)
|
||||
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(),
|
||||
.padding(
|
||||
start = TangemTheme.dimens2.x2_5,
|
||||
end = TangemTheme.dimens2.x0_5,
|
||||
top = TangemTheme.dimens2.x0_5,
|
||||
bottom = TangemTheme.dimens2.x0_5,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_analytics_up_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
tint = TangemTheme.colors2.markers.textGreen,
|
||||
modifier = Modifier
|
||||
.padding(end = TangemTheme.dimens2.x2)
|
||||
.size(TangemTheme.dimens2.x4),
|
||||
.padding(vertical = TangemTheme.dimens2.x0_5)
|
||||
.size(TangemTheme.dimens2.x3),
|
||||
)
|
||||
Text(
|
||||
text = promoBannerUM.title.resolveReference(),
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
style = TangemTheme.typography2.captionSemibold11,
|
||||
color = TangemTheme.colors2.markers.textGreen,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = TangemTheme.dimens2.x2),
|
||||
.padding(vertical = TangemTheme.dimens2.x0_5),
|
||||
)
|
||||
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),
|
||||
TangemBadge(
|
||||
size = TangemBadgeSize.X4,
|
||||
shape = TangemBadgeShape.Rounded,
|
||||
color = TangemBadgeColor.Green,
|
||||
type = TangemBadgeType.Tinted,
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
iconPosition = TangemBadgeIconPosition.None,
|
||||
onClick = promoBannerUM.onCloseClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,20 +59,45 @@ object TangemColorPalette {
|
|||
val DarkGreen = Color(0xFF06311F)
|
||||
// endregion Green
|
||||
|
||||
// region Blue
|
||||
// region Azure
|
||||
val Azure = Color(0xFF0099FF)
|
||||
// endregion Blue
|
||||
val Azure_50 = Color(0x800099FF)
|
||||
val Azure_10 = Color(0x1A0099FF)
|
||||
// endregion Azure
|
||||
|
||||
// region Red
|
||||
// region Amaranth
|
||||
val Amaranth = Color(0xFFFF3333)
|
||||
val Amaranth_50 = Color(0x80FF3333)
|
||||
val Amaranth_20 = Color(0x33FF3333)
|
||||
val Amaranth_10 = Color(0x1AFF3333)
|
||||
// endregion Amaranth
|
||||
|
||||
// region Flamingo
|
||||
val Flamingo = Color(0xFFFF5B5B)
|
||||
// endregion Red
|
||||
val Flamingo_50 = Color(0x80FF5B5B)
|
||||
val Flamingo_20 = Color(0x33FF5B5B)
|
||||
val Flamingo_10 = Color(0x1AFF5B5B)
|
||||
// endregion Flamingo
|
||||
|
||||
// region Yellow
|
||||
val Tangerine = Color(0xFFFFB71B)
|
||||
val Mustard = Color(0xFFFDDE55)
|
||||
// endregion Yellow
|
||||
|
||||
// region Emerald
|
||||
val Emerald = Color(0xFF34DF12)
|
||||
val Emerald_50 = Color(0x8034DF12)
|
||||
val Emerald_20 = Color(0x3334DF12)
|
||||
val Emerald_10 = Color(0x1A34DF12)
|
||||
// endregion Emerald
|
||||
|
||||
// region Eucalyptus
|
||||
val Eucalyptus = Color(0xFF0C9F3D)
|
||||
val Eucalyptus_50 = Color(0x800C9F3D)
|
||||
val Eucalyptus_20 = Color(0x330C9F3D)
|
||||
val Eucalyptus_10 = Color(0x1A0C9F3D)
|
||||
// endregion Eucalyptus
|
||||
|
||||
// region Overlay
|
||||
val Overlay1 = Color(0x66000000)
|
||||
val Overlay2 = Color(0xB2000000)
|
||||
|
|
|
|||
|
|
@ -448,24 +448,36 @@ class TangemColors2 internal constructor(
|
|||
|
||||
@Stable
|
||||
class Markers internal constructor(
|
||||
backgroundSolidGray: Color,
|
||||
backgroundDisabled: Color,
|
||||
backgroundSolidBlue: Color,
|
||||
textGray: Color,
|
||||
textDisabled: Color,
|
||||
iconGray: Color,
|
||||
iconDisabled: Color,
|
||||
backgroundDisabled: Color,
|
||||
textGray: Color,
|
||||
iconGray: Color,
|
||||
borderGray: Color,
|
||||
backgroundTintedBlue: Color,
|
||||
backgroundSolidGray: Color,
|
||||
backgroundTintedGray: Color,
|
||||
textBlue: Color,
|
||||
iconBlue: Color,
|
||||
borderTintedBlue: Color,
|
||||
backgroundSolidBlue: Color,
|
||||
backgroundTintedBlue: Color,
|
||||
textRed: Color,
|
||||
iconRed: Color,
|
||||
borderTintedRed: Color,
|
||||
backgroundSolidRed: Color,
|
||||
backgroundTintedRed: Color,
|
||||
iconBlue: Color,
|
||||
iconRed: Color,
|
||||
textRed: Color,
|
||||
backgroundTintedGray: Color,
|
||||
borderTintedBlue: Color,
|
||||
borderTintedRed: Color,
|
||||
textGreen: Color,
|
||||
iconGreen: Color,
|
||||
borderTintedGreen: Color,
|
||||
borderSolidColor: Color,
|
||||
backgroundTintedGreen: Color,
|
||||
backgroundSolidGreen: Color,
|
||||
textGreenAlt: Color,
|
||||
iconGreenAlt: Color,
|
||||
borderTintedGreenAlt: Color,
|
||||
borderSolidColorAlt: Color,
|
||||
backgroundTintedGreenAlt: Color,
|
||||
backgroundSolidGreenAlt: Color,
|
||||
) {
|
||||
var backgroundSolidGray by mutableStateOf(backgroundSolidGray)
|
||||
private set
|
||||
|
|
@ -504,6 +516,32 @@ class TangemColors2 internal constructor(
|
|||
var borderTintedRed by mutableStateOf(borderTintedRed)
|
||||
private set
|
||||
|
||||
var textGreen by mutableStateOf(textGreen)
|
||||
private set
|
||||
var iconGreen by mutableStateOf(iconGreen)
|
||||
private set
|
||||
var borderTintedGreen by mutableStateOf(borderTintedGreen)
|
||||
private set
|
||||
var borderSolidColor by mutableStateOf(borderSolidColor)
|
||||
private set
|
||||
var backgroundTintedGreen by mutableStateOf(backgroundTintedGreen)
|
||||
private set
|
||||
var backgroundSolidGreen by mutableStateOf(backgroundSolidGreen)
|
||||
private set
|
||||
var textGreenAlt by mutableStateOf(textGreenAlt)
|
||||
private set
|
||||
var iconGreenAlt by mutableStateOf(iconGreenAlt)
|
||||
private set
|
||||
var borderTintedGreenAlt by mutableStateOf(borderTintedGreenAlt)
|
||||
private set
|
||||
var borderSolidColorAlt by mutableStateOf(borderSolidColorAlt)
|
||||
private set
|
||||
|
||||
var backgroundTintedGreenAlt by mutableStateOf(backgroundTintedGreenAlt)
|
||||
private set
|
||||
var backgroundSolidGreenAlt by mutableStateOf(backgroundSolidGreenAlt)
|
||||
private set
|
||||
|
||||
fun update(other: Markers) {
|
||||
backgroundSolidGray = other.backgroundSolidGray
|
||||
backgroundDisabled = other.backgroundDisabled
|
||||
|
|
@ -523,6 +561,18 @@ class TangemColors2 internal constructor(
|
|||
backgroundTintedGray = other.backgroundTintedGray
|
||||
borderTintedBlue = other.borderTintedBlue
|
||||
borderTintedRed = other.borderTintedRed
|
||||
textGreen = other.textGreen
|
||||
iconGreen = other.iconGreen
|
||||
borderTintedGreen = other.borderTintedGreen
|
||||
borderSolidColor = other.borderSolidColor
|
||||
backgroundTintedGreen = other.backgroundTintedGreen
|
||||
backgroundSolidGreen = other.backgroundSolidGreen
|
||||
textGreenAlt = other.textGreenAlt
|
||||
iconGreenAlt = other.iconGreenAlt
|
||||
borderTintedGreenAlt = other.borderTintedGreenAlt
|
||||
borderSolidColorAlt = other.borderSolidColorAlt
|
||||
backgroundTintedGreenAlt = other.backgroundTintedGreenAlt
|
||||
backgroundSolidGreenAlt = other.backgroundSolidGreenAlt
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -154,16 +154,28 @@ private fun lightThemeColors2(): TangemColors2 {
|
|||
iconGray = TangemColorPalette.Dark1,
|
||||
iconDisabled = TangemColorPalette.Light2,
|
||||
borderGray = TangemColorPalette.Light3,
|
||||
backgroundTintedBlue = TangemColorPalette.Azure.copy(alpha = 0.1f),
|
||||
backgroundTintedBlue = TangemColorPalette.Azure_10,
|
||||
textBlue = text.status.accent,
|
||||
backgroundSolidRed = TangemColorPalette.Amaranth,
|
||||
backgroundTintedRed = TangemColorPalette.Amaranth.copy(alpha = 0.1f),
|
||||
backgroundTintedRed = TangemColorPalette.Amaranth_10,
|
||||
iconBlue = TangemColorPalette.Azure,
|
||||
iconRed = TangemColorPalette.Amaranth,
|
||||
textRed = TangemColorPalette.Amaranth,
|
||||
backgroundTintedGray = TangemColorPalette.Dark6.copy(alpha = 0.1f),
|
||||
borderTintedBlue = TangemColorPalette.Azure.copy(alpha = 0.1f),
|
||||
borderTintedRed = TangemColorPalette.Amaranth.copy(alpha = 0.1f),
|
||||
borderTintedBlue = TangemColorPalette.Azure_10,
|
||||
borderTintedRed = TangemColorPalette.Amaranth_10,
|
||||
textGreen = TangemColorPalette.Emerald,
|
||||
iconGreen = TangemColorPalette.Emerald,
|
||||
borderTintedGreen = TangemColorPalette.Emerald_10,
|
||||
borderSolidColor = TangemColorPalette.Emerald_50,
|
||||
backgroundTintedGreen = TangemColorPalette.Emerald_10,
|
||||
backgroundSolidGreen = TangemColorPalette.Emerald,
|
||||
textGreenAlt = TangemColorPalette.Eucalyptus,
|
||||
iconGreenAlt = TangemColorPalette.Eucalyptus,
|
||||
borderTintedGreenAlt = TangemColorPalette.Eucalyptus_10,
|
||||
borderSolidColorAlt = TangemColorPalette.Eucalyptus_50,
|
||||
backgroundTintedGreenAlt = TangemColorPalette.Eucalyptus_10,
|
||||
backgroundSolidGreenAlt = TangemColorPalette.Eucalyptus,
|
||||
)
|
||||
val tabs = TangemColors2.Tabs(
|
||||
textPrimary = TangemColorPalette.Light2,
|
||||
|
|
@ -304,7 +316,7 @@ private fun darkThemeColors2(): TangemColors2 {
|
|||
iconGray = TangemColorPalette.Dark2,
|
||||
iconDisabled = TangemColorPalette.Dark5,
|
||||
borderGray = TangemColorPalette.White.copy(alpha = 0.2f),
|
||||
backgroundTintedBlue = TangemColorPalette.Azure.copy(alpha = 0.1f),
|
||||
backgroundTintedBlue = TangemColorPalette.Azure_10,
|
||||
textBlue = text.status.accent,
|
||||
backgroundSolidRed = TangemColorPalette.Amaranth,
|
||||
backgroundTintedRed = TangemColorPalette.Amaranth.copy(alpha = 0.1f),
|
||||
|
|
@ -312,8 +324,20 @@ private fun darkThemeColors2(): TangemColors2 {
|
|||
iconRed = TangemColorPalette.Flamingo,
|
||||
textRed = TangemColorPalette.Flamingo,
|
||||
backgroundTintedGray = TangemColorPalette.White.copy(alpha = 0.1f),
|
||||
borderTintedBlue = TangemColorPalette.Azure.copy(alpha = 0.1f),
|
||||
borderTintedRed = TangemColorPalette.Amaranth.copy(alpha = 0.1f),
|
||||
borderTintedBlue = TangemColorPalette.Azure_10,
|
||||
borderTintedRed = TangemColorPalette.Amaranth_10,
|
||||
textGreen = TangemColorPalette.Emerald,
|
||||
iconGreen = TangemColorPalette.Emerald,
|
||||
borderTintedGreen = TangemColorPalette.Emerald_10,
|
||||
borderSolidColor = TangemColorPalette.Emerald_50,
|
||||
backgroundTintedGreen = TangemColorPalette.Emerald_10,
|
||||
backgroundSolidGreen = TangemColorPalette.Emerald,
|
||||
textGreenAlt = TangemColorPalette.Emerald,
|
||||
iconGreenAlt = TangemColorPalette.Emerald,
|
||||
borderTintedGreenAlt = TangemColorPalette.Emerald_10,
|
||||
borderSolidColorAlt = TangemColorPalette.Emerald_50,
|
||||
backgroundTintedGreenAlt = TangemColorPalette.Emerald_10,
|
||||
backgroundSolidGreenAlt = TangemColorPalette.Emerald,
|
||||
)
|
||||
val tabs = TangemColors2.Tabs(
|
||||
textPrimary = TangemColorPalette.Dark4,
|
||||
|
|
|
|||
9
core/ui/src/main/res/drawable/shape_triangular.xml
Normal file
9
core/ui/src/main/res/drawable/shape_triangular.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="8dp"
|
||||
android:height="8dp"
|
||||
android:viewportWidth="8"
|
||||
android:viewportHeight="8">
|
||||
<path
|
||||
android:fillColor="#34DF12"
|
||||
android:pathData="M8,8L0,8L0,0C0,0 2,6 8,8Z" />
|
||||
</vector>
|
||||
|
|
@ -7,10 +7,7 @@ import com.tangem.domain.card.common.util.getCardsCount
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.disableButtons
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -53,7 +50,17 @@ internal class SetTokenListErrorTransformer(
|
|||
}
|
||||
|
||||
override fun transform(walletUM: WalletUM): WalletUM {
|
||||
return walletUM // todo redesign main
|
||||
return when (walletUM) {
|
||||
is WalletUM.Content -> {
|
||||
walletUM.copy(
|
||||
tokensListUM = WalletTokensListUM.Empty,
|
||||
)
|
||||
}
|
||||
is WalletUM.Locked -> {
|
||||
Timber.w("Impossible to load tokens list for locked wallet")
|
||||
walletUM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletCardState.toLoadedState(): WalletCardState {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,10 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCardStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.WalletTokensListUMTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -23,6 +21,7 @@ internal class SetTokenListTransformer(
|
|||
private val yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
private val stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
|
||||
private val shouldShowMainPromo: Boolean,
|
||||
private val isAccountsModeEnabled: Boolean,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
|
|
@ -47,7 +46,17 @@ internal class SetTokenListTransformer(
|
|||
}
|
||||
|
||||
override fun transform(walletUM: WalletUM): WalletUM {
|
||||
return walletUM // todo redesign main
|
||||
return when (walletUM) {
|
||||
is WalletUM.Content -> {
|
||||
walletUM.copy(
|
||||
tokensListUM = toLoadedState(),
|
||||
)
|
||||
}
|
||||
is WalletUM.Locked -> {
|
||||
Timber.w("Impossible to load tokens list for locked wallet")
|
||||
walletUM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletCardState.toLoadedState(): WalletCardState {
|
||||
|
|
@ -73,4 +82,19 @@ internal class SetTokenListTransformer(
|
|||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
).convert(value = this)
|
||||
}
|
||||
|
||||
private fun toLoadedState(): WalletTokensListUM {
|
||||
if (params !is TokenConverterParams.Account) return WalletTokensListUM.Empty
|
||||
|
||||
return WalletTokensListUMTransformer(
|
||||
selectedWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
yieldModuleApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
isAccountsModeEnabled = isAccountsModeEnabled,
|
||||
expandedAccounts = params.expandedAccounts,
|
||||
).convert(value = params.accountList)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingOption
|
||||
import com.tangem.domain.staking.model.common.RewardInfo
|
||||
import com.tangem.domain.staking.model.common.RewardType
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.EarnApyConverter.EarnApyInfo
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class EarnApyConverter(
|
||||
val yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
val stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
|
||||
) : Converter<CryptoCurrencyStatus, EarnApyInfo?> {
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): EarnApyInfo? {
|
||||
val token = value.currency as? CryptoCurrency.Token
|
||||
if (token != null && yieldModuleApyMap.isNotEmpty()) {
|
||||
val yieldSupplyApy = yieldModuleApyMap.entries.firstOrNull { apy ->
|
||||
apy.key.equals(
|
||||
other = token.yieldSupplyKey(),
|
||||
ignoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId),
|
||||
)
|
||||
}?.value
|
||||
if (yieldSupplyApy != null) {
|
||||
val isActive = value.value.yieldSupplyStatus?.isActive == false
|
||||
return EarnApyInfo(
|
||||
text = resourceReference(
|
||||
R.string.yield_module_earn_badge,
|
||||
wrappedList(yieldSupplyApy),
|
||||
),
|
||||
isActive = isActive,
|
||||
apy = yieldSupplyApy.toString(),
|
||||
source = TokenItemStateConverter.ApySource.YIELD_SUPPLY,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (stakingApyMap.isNotEmpty()) {
|
||||
val stakingInfo = findStakingRate(
|
||||
currencyStatus = value,
|
||||
stakingApyMap = stakingApyMap,
|
||||
)
|
||||
val rewardTypeRes = when (stakingInfo.rewardType) {
|
||||
RewardType.APR -> R.string.staking_apr_earn_badge
|
||||
RewardType.UNKNOWN,
|
||||
RewardType.APY,
|
||||
null,
|
||||
-> R.string.yield_module_earn_badge
|
||||
}
|
||||
if (stakingInfo.rate != null) {
|
||||
val apyString = stakingInfo.rate.format { percent(withPercentSign = false) }
|
||||
return EarnApyInfo(
|
||||
text = resourceReference(
|
||||
rewardTypeRes,
|
||||
wrappedList(apyString),
|
||||
),
|
||||
isActive = stakingInfo.isActive,
|
||||
apy = apyString,
|
||||
source = TokenItemStateConverter.ApySource.STAKING,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findStakingRate(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
|
||||
): StakingLocalInfo {
|
||||
val stakingAvailability = stakingApyMap[currencyStatus.currency] as? StakingAvailability.Available
|
||||
?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
|
||||
|
||||
val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data
|
||||
val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit
|
||||
val p2pEthPoolBalance = stakingBalance as? StakingBalance.Data.P2PEthPool
|
||||
|
||||
val rateInfo = when (val stakingOptions = stakingAvailability.option) {
|
||||
is StakingOption.P2PEthPool -> {
|
||||
RewardInfo(
|
||||
rate = stakingOptions.apy,
|
||||
type = RewardType.APY,
|
||||
)
|
||||
}
|
||||
is StakingOption.StakeKit -> if (stakeKitBalance != null) {
|
||||
val validatorsByAddress = stakingOptions.yield.validators.associateBy { it.address }
|
||||
stakeKitBalance.balance.items
|
||||
.mapNotNull { it.validatorAddress }
|
||||
.firstNotNullOfOrNull { address ->
|
||||
validatorsByAddress[address]?.rewardInfo
|
||||
} ?: stakingOptions.yield.validators
|
||||
.filter { it.preferred }
|
||||
.mapNotNull { validator ->
|
||||
validator.rewardInfo
|
||||
}
|
||||
.maxByOrNull { it.rate }
|
||||
} else {
|
||||
stakingOptions.yield.validators
|
||||
.filter { it.preferred }
|
||||
.mapNotNull { validator ->
|
||||
validator.rewardInfo
|
||||
}
|
||||
.maxByOrNull { it.rate }
|
||||
}
|
||||
}
|
||||
|
||||
return StakingLocalInfo(
|
||||
rate = rateInfo?.rate,
|
||||
isActive = stakeKitBalance != null || p2pEthPoolBalance != null,
|
||||
rewardType = rateInfo?.type,
|
||||
)
|
||||
}
|
||||
|
||||
data class StakingLocalInfo(
|
||||
val rate: BigDecimal?,
|
||||
val isActive: Boolean,
|
||||
val rewardType: RewardType?,
|
||||
)
|
||||
|
||||
data class EarnApyInfo(
|
||||
val text: TextReference?,
|
||||
val isActive: Boolean,
|
||||
val apy: String?,
|
||||
val source: TokenItemStateConverter.ApySource,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,521 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import com.tangem.common.getTotalCryptoAmount
|
||||
import com.tangem.common.getTotalFiatAmount
|
||||
import com.tangem.common.ui.account.AccountIconItemStateConverter
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.ds.badge.*
|
||||
import com.tangem.core.ui.ds.button.TangemButtonShape
|
||||
import com.tangem.core.ui.ds.button.TangemButtonSize
|
||||
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.header.TangemHeaderRowUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM.EndContentUM
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TokensListItemUM2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListUM
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.addIf
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
internal class WalletTokensListUMTransformer(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
private val isAccountsModeEnabled: Boolean,
|
||||
private val expandedAccounts: Set<AccountId>,
|
||||
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability>,
|
||||
shouldShowMainPromo: Boolean,
|
||||
) : Converter<AccountStatusList, WalletTokensListUM> {
|
||||
|
||||
private val yieldSupplyPromoBannerConverter = YieldSupplyPromoBannerConverter(
|
||||
yieldModuleApyMap,
|
||||
shouldShowMainPromo,
|
||||
)
|
||||
private val currencyToIconStateConverter = CryptoCurrencyToIconStateConverter()
|
||||
private val earnApyConverter = EarnApyConverter(
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
stakingApyMap = stakingAvailabilityMap,
|
||||
)
|
||||
|
||||
override fun convert(value: AccountStatusList): WalletTokensListUM {
|
||||
val promoCryptoCurrency = yieldSupplyPromoBannerConverter.convert2(value = value)
|
||||
return if (value.accountStatuses.isEmpty()) {
|
||||
WalletTokensListUM.Empty
|
||||
} else {
|
||||
val isCollapsable = value.accountStatuses.count {
|
||||
it is AccountStatus.CryptoPortfolio && it.account.tokensCount > 0
|
||||
} > 1
|
||||
|
||||
val tokenListUM = value.accountStatuses
|
||||
.filterIsInstance<AccountStatus.CryptoPortfolio>()
|
||||
.asSequence()
|
||||
.flatMap { accountStatus ->
|
||||
if (isAccountsModeEnabled) {
|
||||
val isExpanded = expandedAccounts.contains(accountStatus.account.accountId)
|
||||
sequenceOf(
|
||||
TokensListItemUM2.Portfolio(
|
||||
tokenRowUM = toAccountRow(accountStatus, isExpanded),
|
||||
isExpanded = isExpanded || !isCollapsable,
|
||||
isCollapsable = isCollapsable,
|
||||
tokenList = getTokenListItems(
|
||||
accountStatus.tokenList,
|
||||
promoCryptoCurrency,
|
||||
).toPersistentList(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
getTokenListItems(accountStatus.tokenList, promoCryptoCurrency)
|
||||
}
|
||||
}.toPersistentList()
|
||||
|
||||
WalletTokensListUM.Content(
|
||||
tokenList = tokenListUM,
|
||||
organizeButtonUM = getOrganizeButtonUM(value),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTokenListItems(
|
||||
tokenList: TokenList,
|
||||
promoCryptoCurrency: CryptoCurrencyStatus?,
|
||||
): Sequence<TokensListItemUM2> {
|
||||
return when (tokenList) {
|
||||
TokenList.Empty -> emptySequence()
|
||||
is TokenList.GroupedByNetwork -> {
|
||||
tokenList.groups.asSequence().flatMap { (network, currencies) ->
|
||||
buildList {
|
||||
add(
|
||||
TokensListItemUM2.GroupTitle(
|
||||
tokenRowUM = toGroupRow(network),
|
||||
),
|
||||
)
|
||||
addAll(
|
||||
currencies.asSequence().map { currencyStatus ->
|
||||
val shouldShowPromo = promoCryptoCurrency?.currency?.id == currencyStatus.currency.id
|
||||
TokensListItemUM2.Token(
|
||||
tokenRowUM = toCurrencyRow(
|
||||
currencyStatus = currencyStatus,
|
||||
shouldShowPromo = shouldShowPromo,
|
||||
),
|
||||
)
|
||||
}.toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is TokenList.Ungrouped -> {
|
||||
tokenList.currencies.asSequence().map { currencyStatus ->
|
||||
TokensListItemUM2.Token(
|
||||
toCurrencyRow(
|
||||
currencyStatus = currencyStatus,
|
||||
shouldShowPromo = promoCryptoCurrency?.currency?.id == currencyStatus.currency.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toAccountRow(accountStatus: AccountStatus.CryptoPortfolio, isExpanded: Boolean): TangemTokenRowUM {
|
||||
val account = accountStatus.account
|
||||
|
||||
val (topEndContent, bottomEndContent) = when (val accountBalance = accountStatus.tokenList.totalFiatBalance) {
|
||||
TotalFiatBalance.Failed -> toFailedAccountRow()
|
||||
is TotalFiatBalance.Loaded -> toLoadedAccountRow(accountStatus, accountBalance)
|
||||
TotalFiatBalance.Loading -> EndContentUM.Loading to EndContentUM.Loading
|
||||
}
|
||||
|
||||
return TangemTokenRowUM.Content(
|
||||
id = accountStatus.account.accountId.value,
|
||||
headIconUM = TangemIconUM.Currency(
|
||||
currencyIconState = AccountIconItemStateConverter(size = AccountIconSize.ExtraSmall).convert(account),
|
||||
),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = account.accountName.toUM().value,
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = pluralReference(
|
||||
R.plurals.common_tokens_count,
|
||||
count = account.tokensCount,
|
||||
formatArgs = wrappedList(account.tokensCount),
|
||||
),
|
||||
),
|
||||
topEndContentUM = topEndContent,
|
||||
bottomEndContentUM = bottomEndContent,
|
||||
onItemClick = {
|
||||
if (isExpanded) {
|
||||
clickIntents.onAccountCollapseClick(account)
|
||||
} else {
|
||||
clickIntents.onAccountExpandClick(account)
|
||||
}
|
||||
},
|
||||
onItemLongClick = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun toFailedAccountRow(): Pair<EndContentUM.Content, EndContentUM.Content> {
|
||||
return EndContentUM.Content(
|
||||
text = stringReference(StringsSigns.DASH_SIGN),
|
||||
) to EndContentUM.Content(
|
||||
text = styledResourceReference(
|
||||
id = R.string.common_unreachable,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) },
|
||||
),
|
||||
endIcons = persistentListOf(
|
||||
TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_attention_default_24,
|
||||
tintReference = { TangemTheme.colors2.graphic.status.attention },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun toLoadedAccountRow(
|
||||
accountStatus: AccountStatus.CryptoPortfolio,
|
||||
accountBalance: TotalFiatBalance.Loaded,
|
||||
): Pair<EndContentUM.Content, EndContentUM> {
|
||||
val priceChange = accountStatus.priceChangeLce.getOrNull()
|
||||
|
||||
return EndContentUM.Content(
|
||||
text = accountBalance.amount.formatStyled {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
|
||||
)
|
||||
},
|
||||
) to if (priceChange != null) {
|
||||
val priceChangeType = PriceChangeType.fromBigDecimal(priceChange.value)
|
||||
|
||||
EndContentUM.Content(
|
||||
text = stringReference(
|
||||
priceChange.value.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
),
|
||||
priceChangeUM = PriceChangeState.Content(
|
||||
type = priceChangeType,
|
||||
valueInPercent = priceChange.value.format { percent() },
|
||||
),
|
||||
)
|
||||
} else {
|
||||
EndContentUM.Empty
|
||||
}
|
||||
}
|
||||
|
||||
private fun toGroupRow(network: Network): TangemHeaderRowUM {
|
||||
return TangemHeaderRowUM(
|
||||
id = network.hashCode().toString(),
|
||||
title = resourceReference(
|
||||
id = R.string.wallet_network_group_title,
|
||||
formatArgs = wrappedList(network.name),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun toCurrencyRow(currencyStatus: CryptoCurrencyStatus, shouldShowPromo: Boolean): TangemTokenRowUM {
|
||||
val earnApyInfo = earnApyConverter.convert(currencyStatus)
|
||||
|
||||
return TangemTokenRowUM.Content(
|
||||
id = currencyStatus.currency.id.value,
|
||||
headIconUM = TangemIconUM.Currency(
|
||||
currencyIconState = currencyToIconStateConverter.convert(currencyStatus),
|
||||
),
|
||||
titleUM = toCurrencyRowTitle(currencyStatus, earnApyInfo),
|
||||
subtitleUM = toCurrencyRowSubtitle(currencyStatus),
|
||||
topEndContentUM = toCurrencyRowTopEnd(currencyStatus),
|
||||
bottomEndContentUM = toCurrencyRowBottomEnd(currencyStatus),
|
||||
promoBannerUM = toPromoBannerUM(
|
||||
currencyStatus,
|
||||
earnApyInfo.takeIf { shouldShowPromo },
|
||||
),
|
||||
onItemClick = when (currencyStatus.value) {
|
||||
CryptoCurrencyStatus.Loading,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
-> null
|
||||
else -> {
|
||||
{
|
||||
clickIntents.onTokenItemClick(selectedWallet.walletId, currencyStatus)
|
||||
}
|
||||
}
|
||||
},
|
||||
onItemLongClick = when (currencyStatus.value) {
|
||||
CryptoCurrencyStatus.Loading -> null
|
||||
else -> {
|
||||
{
|
||||
clickIntents.onTokenItemLongClick(selectedWallet.walletId, currencyStatus)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun toCurrencyRowTitle(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
earnApyInfo: EarnApyConverter.EarnApyInfo?,
|
||||
): TangemTokenRowUM.TitleUM = when (val value = currencyStatus.value) {
|
||||
is CryptoCurrencyStatus.Loading,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> {
|
||||
TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference(currencyStatus.currency.name),
|
||||
)
|
||||
}
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> {
|
||||
TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference(currencyStatus.currency.name),
|
||||
hasPending = value.hasCurrentNetworkTransactions,
|
||||
badge = if (earnApyInfo != null && earnApyInfo.text != null) {
|
||||
TangemBadgeUM(
|
||||
type = TangemBadgeType.Solid,
|
||||
color = when {
|
||||
earnApyInfo.isActive -> TangemBadgeColor.Blue
|
||||
else -> TangemBadgeColor.Gray
|
||||
},
|
||||
shape = TangemBadgeShape.Rounded,
|
||||
size = TangemBadgeSize.X4,
|
||||
text = earnApyInfo.text,
|
||||
onClick = if (earnApyInfo.apy != null) {
|
||||
{
|
||||
clickIntents.onApyLabelClick(
|
||||
userWalletId = selectedWallet.walletId,
|
||||
currencyStatus = currencyStatus,
|
||||
apySource = earnApyInfo.source,
|
||||
apy = earnApyInfo.apy,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toCurrencyRowSubtitle(currencyStatus: CryptoCurrencyStatus): TangemTokenRowUM.SubtitleUM {
|
||||
return when (currencyStatus.value) {
|
||||
is CryptoCurrencyStatus.Loading -> TangemTokenRowUM.SubtitleUM.Loading
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference(
|
||||
currencyStatus.value.fiatRate.format {
|
||||
fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol)
|
||||
},
|
||||
),
|
||||
priceChangeUM = PriceChangeState.Content(
|
||||
type = PriceChangeType.fromBigDecimal(currencyStatus.value.priceChange.orZero()),
|
||||
valueInPercent = currencyStatus.value.priceChange.format { percent() },
|
||||
),
|
||||
isFlickering = currencyStatus.value.isFlickering(),
|
||||
)
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> TangemTokenRowUM.SubtitleUM.Empty
|
||||
}
|
||||
}
|
||||
|
||||
private fun toCurrencyRowTopEnd(currencyStatus: CryptoCurrencyStatus): EndContentUM {
|
||||
val yieldSupply = currencyStatus.value.yieldSupplyStatus
|
||||
return when (currencyStatus.value) {
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> {
|
||||
EndContentUM.Content(
|
||||
text = currencyStatus.getTotalFiatAmount().formatStyled {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
|
||||
)
|
||||
},
|
||||
isFlickering = currencyStatus.value.isFlickering(),
|
||||
startIcons = buildList {
|
||||
addIf(
|
||||
element = TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_attention_default_24,
|
||||
tintReference = { TangemTheme.colors2.graphic.status.attention },
|
||||
),
|
||||
condition = yieldSupply?.isActive == true && !yieldSupply.isAllowedToSpend,
|
||||
)
|
||||
addIf(
|
||||
element = TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_error_sync_default_24,
|
||||
tintReference = { TangemTheme.colors2.graphic.neutral.tertiary },
|
||||
),
|
||||
condition = currencyStatus.value.sources.total == StatusSource.ONLY_CACHE,
|
||||
)
|
||||
}.toImmutableList(),
|
||||
)
|
||||
}
|
||||
is CryptoCurrencyStatus.Loading -> EndContentUM.Loading
|
||||
is CryptoCurrencyStatus.MissedDerivation -> EndContentUM.Content(
|
||||
text = stringReference(StringsSigns.DASH_SIGN),
|
||||
)
|
||||
is CryptoCurrencyStatus.Unreachable -> EndContentUM.Content(
|
||||
text = styledResourceReference(
|
||||
id = R.string.common_unreachable,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) },
|
||||
),
|
||||
endIcons = persistentListOf(
|
||||
TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_attention_default_24,
|
||||
tintReference = { TangemTheme.colors2.graphic.status.attention },
|
||||
),
|
||||
),
|
||||
)
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> EndContentUM.Empty
|
||||
}
|
||||
}
|
||||
|
||||
private fun toCurrencyRowBottomEnd(currencyStatus: CryptoCurrencyStatus): EndContentUM {
|
||||
return when (currencyStatus.value) {
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> EndContentUM.Content(
|
||||
text = stringReference(
|
||||
currencyStatus.getTotalCryptoAmount().format {
|
||||
crypto(cryptoCurrency = currencyStatus.currency)
|
||||
},
|
||||
),
|
||||
isFlickering = currencyStatus.value.isFlickering(),
|
||||
)
|
||||
is CryptoCurrencyStatus.Loading -> EndContentUM.Loading
|
||||
is CryptoCurrencyStatus.MissedDerivation -> EndContentUM.Content(
|
||||
text = styledResourceReference(
|
||||
id = R.string.common_no_address,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) },
|
||||
),
|
||||
endIcons = persistentListOf(
|
||||
TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_attention_default_24,
|
||||
tintReference = { TangemTheme.colors2.graphic.status.attention },
|
||||
),
|
||||
),
|
||||
)
|
||||
is CryptoCurrencyStatus.Unreachable -> EndContentUM.Content(
|
||||
text = styledResourceReference(
|
||||
id = R.string.common_unreachable,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) },
|
||||
),
|
||||
endIcons = persistentListOf(
|
||||
TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_attention_default_24,
|
||||
tintReference = { TangemTheme.colors2.graphic.status.attention },
|
||||
),
|
||||
),
|
||||
)
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> EndContentUM.Empty
|
||||
}
|
||||
}
|
||||
|
||||
private fun toPromoBannerUM(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
earnApyInfo: EarnApyConverter.EarnApyInfo?,
|
||||
): TangemTokenRowUM.PromoBannerUM {
|
||||
val currency = currencyStatus.currency
|
||||
val isTokenCurrency = currency is CryptoCurrency.Token
|
||||
val isCurrencyStatusLoaded = currencyStatus.value is CryptoCurrencyStatus.Loaded
|
||||
val isApyInfoNotNull = earnApyInfo != null && earnApyInfo.apy != null
|
||||
|
||||
if (!isTokenCurrency || !isCurrencyStatusLoaded || !isApyInfoNotNull) {
|
||||
return TangemTokenRowUM.PromoBannerUM.Empty
|
||||
}
|
||||
|
||||
return TangemTokenRowUM.PromoBannerUM.Content(
|
||||
title = resourceReference(
|
||||
R.string.yield_module_main_screen_promo_banner_message,
|
||||
wrappedList(earnApyInfo.apy),
|
||||
),
|
||||
onPromoBannerClick = {
|
||||
clickIntents.onYieldPromoClicked(currency)
|
||||
clickIntents.onApyLabelClick(
|
||||
userWalletId = selectedWallet.walletId,
|
||||
currencyStatus = currencyStatus,
|
||||
apySource = earnApyInfo.source,
|
||||
apy = earnApyInfo.apy,
|
||||
)
|
||||
},
|
||||
onCloseClick = clickIntents::onYieldPromoCloseClick,
|
||||
onPromoShown = {
|
||||
clickIntents.onYieldPromoShown(currency)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun getOrganizeButtonUM(accountList: AccountStatusList): TangemButtonUM? {
|
||||
return if (accountList.flattenCurrencies().size > 1 && !isSingleCurrencyWalletWithToken()) {
|
||||
TangemButtonUM(
|
||||
text = resourceReference(R.string.organize_tokens_title),
|
||||
isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading,
|
||||
size = TangemButtonSize.X9,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
type = TangemButtonType.PrimaryInverse,
|
||||
iconRes = R.drawable.ic_filter_default_24,
|
||||
onClick = clickIntents::onOrganizeTokensClick,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.Value.isFlickering(): Boolean = sources.total == StatusSource.CACHE
|
||||
|
||||
private fun isSingleCurrencyWalletWithToken(): Boolean {
|
||||
return selectedWallet is UserWallet.Cold &&
|
||||
selectedWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
|
|
@ -28,6 +29,35 @@ internal class YieldSupplyPromoBannerConverter(
|
|||
if (cryptoCurrencyStatuses.any { it.value.yieldSupplyStatus?.isActive == true }) return null
|
||||
if (yieldModuleApyMap.isEmpty()) return null
|
||||
|
||||
val max = cryptoCurrencyStatuses.asSequence()
|
||||
.mapNotNull { status ->
|
||||
val token = status.currency as? CryptoCurrency.Token ?: return@mapNotNull null
|
||||
val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId)
|
||||
val tokenKey = "${token.network.rawId}_${token.contractAddress}"
|
||||
|
||||
val matchedKey = yieldModuleApyMap.keys.firstOrNull { mapKey ->
|
||||
mapKey.equals(tokenKey, shouldIgnoreCase)
|
||||
} ?: return@mapNotNull null
|
||||
|
||||
status to matchedKey
|
||||
}
|
||||
.maxByOrNull { (status, _) -> status.value.amount ?: BigDecimal.ZERO }
|
||||
|
||||
return max?.first
|
||||
}
|
||||
|
||||
fun convert2(value: AccountStatusList): CryptoCurrencyStatus? {
|
||||
if (!shouldShowMainPromo) return null
|
||||
|
||||
val currencies = value.flattenCurrencies().filter { status ->
|
||||
status.value is CryptoCurrencyStatus.Loaded
|
||||
}
|
||||
|
||||
val cryptoCurrencyStatuses = currencies.filter { it.currency is CryptoCurrency.Token }
|
||||
|
||||
if (cryptoCurrencyStatuses.any { it.value.yieldSupplyStatus?.isActive == true }) return null
|
||||
if (yieldModuleApyMap.isEmpty()) return null
|
||||
|
||||
val max = cryptoCurrencyStatuses.asSequence()
|
||||
.mapNotNull { status ->
|
||||
val token = status.currency as? CryptoCurrency.Token ?: return@mapNotNull null
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -37,6 +38,7 @@ internal class AccountListSubscriber @AssistedInject constructor(
|
|||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
) : BasicAccountListSubscriber() {
|
||||
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine7(
|
||||
|
|
@ -51,15 +53,27 @@ internal class AccountListSubscriber @AssistedInject constructor(
|
|||
accountList, appCurrency, expandedAccounts, isAccountMode,
|
||||
yieldSupplyApyMap, shouldShowMainPromo, stakingAvailabilityMap,
|
||||
->
|
||||
updateState(
|
||||
accountList = accountList,
|
||||
appCurrency = appCurrency,
|
||||
expandedAccounts = expandedAccounts,
|
||||
isAccountMode = isAccountMode,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
updateState2(
|
||||
accountList = accountList,
|
||||
appCurrency = appCurrency,
|
||||
expandedAccounts = expandedAccounts,
|
||||
isAccountMode = isAccountMode,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
} else {
|
||||
updateState(
|
||||
accountList = accountList,
|
||||
appCurrency = appCurrency,
|
||||
expandedAccounts = expandedAccounts,
|
||||
isAccountMode = isAccountMode,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stakingAvailabilityFlow(): Flow<Map<CryptoCurrency, StakingAvailability>> = getAccountStatusListFlow()
|
||||
|
|
|
|||
|
|
@ -85,6 +85,29 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
}
|
||||
}
|
||||
|
||||
protected fun updateState2(
|
||||
accountList: AccountStatusList,
|
||||
appCurrency: AppCurrency,
|
||||
expandedAccounts: Set<AccountId>,
|
||||
isAccountMode: Boolean,
|
||||
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
|
||||
shouldShowMainPromo: Boolean = false,
|
||||
) {
|
||||
stateController.update(
|
||||
SetTokenListTransformer(
|
||||
params = TokenConverterParams.Account(accountList, expandedAccounts),
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
isAccountsModeEnabled = isAccountMode,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun singleAccountTransform(
|
||||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
appCurrency: AppCurrency,
|
||||
|
|
@ -141,6 +164,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
isAccountsModeEnabled = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ internal abstract class BasicTokenListSubscriber(
|
|||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
isAccountsModeEnabled = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ private fun LazyListScope.portfolioItem(
|
|||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun SlideInItemVisibility(
|
||||
internal fun SlideInItemVisibility(
|
||||
visible: Boolean,
|
||||
currentIndex: Int,
|
||||
lastIndex: Int,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.SharedTransitionScope.ResizeMode.Companion.scaleToBounds
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -8,22 +12,37 @@ import androidx.compose.foundation.lazy.LazyListScope
|
|||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.lerp
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.tokenlist.TokenListItem
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
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.token.TangemTokenRow
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.ds.row.token.internal.TokenRowTitle
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.core.ui.utils.ProvideSharedTransitionScope
|
||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TokensListItemUM2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST"
|
||||
|
|
@ -59,6 +78,180 @@ internal fun LazyListScope.tokensListItems(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LazyList extension for [WalletTokensListState]
|
||||
*
|
||||
* @param walletTokensListUM state
|
||||
* @param modifier modifier
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal fun LazyListScope.tokensListItems2(
|
||||
walletTokensListUM: WalletTokensListUM,
|
||||
modifier: Modifier = Modifier,
|
||||
isBalanceHidden: Boolean,
|
||||
) {
|
||||
when (walletTokensListUM) {
|
||||
is WalletTokensListUM.Loading,
|
||||
is WalletTokensListUM.Content,
|
||||
-> {
|
||||
walletTokensListUM.tokenList.fastForEachIndexed { index, listItem ->
|
||||
when (listItem) {
|
||||
is TokensListItemUM2.GroupTitle,
|
||||
is TokensListItemUM2.Token,
|
||||
-> tokenItem(
|
||||
listItem = listItem,
|
||||
index = index,
|
||||
lastIndex = walletTokensListUM.tokenList.lastIndex,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier,
|
||||
)
|
||||
is TokensListItemUM2.Portfolio -> portfolioItem(
|
||||
listItem = listItem,
|
||||
index = index,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
WalletTokensListUM.Empty -> nonContentItem(modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.tokenItem(
|
||||
listItem: TokensListItemUM2,
|
||||
index: Int,
|
||||
lastIndex: Int,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
item(
|
||||
key = listItem.tokenRowUM.id,
|
||||
contentType = listItem.tokenRowUM::class.java,
|
||||
) {
|
||||
val itemModifier = modifier
|
||||
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
.semantics { lazyListItemPosition = index }
|
||||
.padding(top = if (index == 0) TangemTheme.dimens2.x3 else 0.dp)
|
||||
.roundedShapeItemDecoration(
|
||||
radius = 18.dp,
|
||||
currentIndex = index,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = lastIndex,
|
||||
backgroundColor = TangemTheme.colors2.surface.level3,
|
||||
)
|
||||
|
||||
when (val tokenRowUM = listItem.tokenRowUM) {
|
||||
is TangemTokenRowUM -> TangemTokenRow(
|
||||
tokenRowUM = tokenRowUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
reorderableTokenListState = null,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
is TangemHeaderRowUM -> TangemHeaderRow(
|
||||
headerRowUM = tokenRowUM,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.portfolioItem(
|
||||
listItem: TokensListItemUM2.Portfolio,
|
||||
index: Int,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val lastIndex = listItem.tokenList.lastIndex + 1
|
||||
|
||||
accountItem(
|
||||
listItem = listItem,
|
||||
modifier = modifier,
|
||||
index = index,
|
||||
lastIndex = lastIndex,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
itemsIndexed(
|
||||
items = listItem.tokenList,
|
||||
key = { _, item -> item.tokenRowUM.id },
|
||||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { tokenIndex, item ->
|
||||
SlideInItemVisibility(
|
||||
currentIndex = tokenIndex + 1,
|
||||
lastIndex = lastIndex,
|
||||
modifier = modifier
|
||||
.animateItem(fadeInSpec = null, placementSpec = null, fadeOutSpec = null)
|
||||
.roundedShapeItemDecoration(
|
||||
radius = 18.dp,
|
||||
currentIndex = tokenIndex + 1,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = lastIndex,
|
||||
backgroundColor = TangemTheme.colors2.surface.level3,
|
||||
),
|
||||
visible = listItem.isExpanded,
|
||||
) {
|
||||
val itemModifier = Modifier
|
||||
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
.semantics { lazyListItemPosition = tokenIndex + 1 }
|
||||
|
||||
when (val tokenRowUM = item.tokenRowUM) {
|
||||
is TangemTokenRowUM -> TangemTokenRow(
|
||||
tokenRowUM = tokenRowUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
reorderableTokenListState = null,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
is TangemHeaderRowUM -> TangemHeaderRow(
|
||||
headerRowUM = tokenRowUM,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun LazyListScope.accountItem(
|
||||
listItem: TokensListItemUM2.Portfolio,
|
||||
modifier: Modifier,
|
||||
index: Int,
|
||||
lastIndex: Int,
|
||||
isBalanceHidden: Boolean,
|
||||
) {
|
||||
item(
|
||||
key = listItem.tokenRowUM.id,
|
||||
contentType = listItem.tokenRowUM::class.java,
|
||||
) {
|
||||
val portfolioModifier = modifier
|
||||
.padding(top = if (index != 0) TangemTheme.dimens2.x2 else TangemTheme.dimens2.x3)
|
||||
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
.semantics { lazyListItemPosition = index }
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = 0,
|
||||
radius = 18.dp,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = if (listItem.isExpanded) lastIndex else 0,
|
||||
backgroundColor = TangemTheme.colors2.surface.level3,
|
||||
)
|
||||
if (listItem.isCollapsable) {
|
||||
PortfolioRowItem(
|
||||
item = listItem,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = portfolioModifier,
|
||||
)
|
||||
} else {
|
||||
TangemHeaderRow(
|
||||
title = (listItem.tokenRowUM.titleUM as? TangemTokenRowUM.TitleUM.Content)?.text.orEmpty(),
|
||||
subtitle = (listItem.tokenRowUM.topEndContentUM as? TangemTokenRowUM.EndContentUM.Content)
|
||||
?.text?.orMaskWithStars(isBalanceHidden),
|
||||
headTangemIconUM = listItem.tokenRowUM.headIconUM,
|
||||
modifier = portfolioModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.contentItems(
|
||||
items: ImmutableList<TokensListItemUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -77,6 +270,7 @@ private fun LazyListScope.contentItems(
|
|||
currentIndex = index,
|
||||
lastIndex = items.lastIndex,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
radius = 18.dp,
|
||||
)
|
||||
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
.semantics { lazyListItemPosition = index },
|
||||
|
|
@ -85,6 +279,117 @@ private fun LazyListScope.contentItems(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber", "ReusedModifierInstance", "LongMethod")
|
||||
@Composable
|
||||
internal fun PortfolioRowItem(
|
||||
item: TokensListItemUM2.Portfolio,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// TangemSharedTransitionLayout {
|
||||
ProvideSharedTransitionScope(modifier) {
|
||||
val iconSharedContentState = rememberSharedContentState(key = "icon")
|
||||
val titleSharedContentState = rememberSharedContentState(key = "title")
|
||||
val boundsTransform = BoundsTransform { _, _ -> tween(250) }
|
||||
|
||||
AnimatedContent(
|
||||
item.isExpanded,
|
||||
transitionSpec = {
|
||||
fadeIn(animationSpec = tween(350, delayMillis = 90))
|
||||
.togetherWith(fadeOut(animationSpec = tween(350)))
|
||||
},
|
||||
) { isExpandedWrapped ->
|
||||
val animatedContentScope = this
|
||||
|
||||
val composables = remember {
|
||||
SharedTokenRowComposables(
|
||||
icon = { modifier ->
|
||||
val size = if (isExpandedWrapped) AccountIconSize.ExtraSmall else AccountIconSize.Default
|
||||
val currencyIconState =
|
||||
when (val currencyIconState = item.tokenRowUM.headIconUM.currencyIconState) {
|
||||
is CurrencyIconState.CryptoPortfolio.Icon ->
|
||||
currencyIconState.copy(size = size)
|
||||
is CurrencyIconState.CryptoPortfolio.Letter ->
|
||||
currencyIconState.copy(size = size)
|
||||
else -> currencyIconState
|
||||
}
|
||||
|
||||
TangemIcon(
|
||||
tangemIconUM = item.tokenRowUM.headIconUM.copy(currencyIconState = currencyIconState),
|
||||
modifier = modifier.sharedBounds(
|
||||
sharedContentState = iconSharedContentState,
|
||||
animatedVisibilityScope = animatedContentScope,
|
||||
boundsTransform = boundsTransform,
|
||||
),
|
||||
)
|
||||
},
|
||||
title = { modifier ->
|
||||
val targetAnimationFraction = if (isExpandedWrapped) 0f else 1f
|
||||
|
||||
val animationFraction = animateFloatAsState(
|
||||
targetValue = targetAnimationFraction,
|
||||
animationSpec = tween(durationMillis = 350),
|
||||
)
|
||||
|
||||
val startStyle = TangemTheme.typography2.captionSemibold12
|
||||
val stopStyle = TangemTheme.typography2.bodySemibold16
|
||||
|
||||
val textStyle by remember(animationFraction.value) {
|
||||
derivedStateOf { lerp(startStyle, stopStyle, animationFraction.value) }
|
||||
}
|
||||
|
||||
val resizedTitle = when (val titleUM = item.tokenRowUM.titleUM) {
|
||||
is TangemTokenRowUM.TitleUM.Content -> titleUM.copy(
|
||||
text = styledStringReference(
|
||||
titleUM.text.resolveReference(),
|
||||
{ textStyle.toSpanStyle() },
|
||||
),
|
||||
)
|
||||
else -> titleUM
|
||||
}
|
||||
|
||||
TokenRowTitle(
|
||||
titleUM = resizedTitle,
|
||||
modifier = modifier.sharedBounds(
|
||||
sharedContentState = titleSharedContentState,
|
||||
animatedVisibilityScope = animatedContentScope,
|
||||
boundsTransform = boundsTransform,
|
||||
resizeMode = scaleToBounds(ContentScale.Fit, Alignment.CenterStart),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (isExpandedWrapped) {
|
||||
TangemHeaderRow(
|
||||
subtitle = (item.tokenRowUM.topEndContentUM as? TangemTokenRowUM.EndContentUM.Content)
|
||||
?.text?.orMaskWithStars(isBalanceHidden),
|
||||
titleContent = composables.title,
|
||||
headContent = composables.icon,
|
||||
footerTangemIconRes = R.drawable.ic_minimize_24,
|
||||
onItemClick = item.tokenRowUM.onItemClick,
|
||||
)
|
||||
} else {
|
||||
TangemTokenRow(
|
||||
tokenRowUM = item.tokenRowUM,
|
||||
headComponent = composables.icon,
|
||||
titleComponent = composables.title,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
reorderableTokenListState = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
class SharedTokenRowComposables(
|
||||
val title: @Composable (Modifier) -> Unit,
|
||||
val icon: @Composable (Modifier) -> Unit,
|
||||
)
|
||||
|
||||
private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) {
|
||||
item(
|
||||
key = NON_CONTENT_TOKENS_LIST_KEY,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue