Updated on 2026-08-14
This commit is contained in:
parent
5e3da8cc47
commit
bc3443c479
11 changed files with 492 additions and 133 deletions
|
|
@ -6,24 +6,33 @@ import androidx.compose.foundation.border
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.BlurredEdgeTreatment
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.innerShadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.shadow.Shadow
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.earn.EarnBlockUM.Type
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
|
|
@ -47,9 +56,12 @@ import com.tangem.core.res.R as CoreResR
|
|||
private const val TINTED_BACKGROUND_ALPHA = 0.1f
|
||||
private const val TINTED_BORDER_ALPHA = 0.1f
|
||||
private const val TINTED_INNER_SHADOW_ALPHA = 0.3f
|
||||
private const val GLOW_ALPHA = 0.7f
|
||||
private val BorderWidth = 1.dp
|
||||
private val InnerShadowBlur = 20.dp
|
||||
private val ShimmerSubtitleWidth = 78.dp
|
||||
private val LoaderSize = 12.dp
|
||||
private val LoaderStrokeWidth = 1.5.dp
|
||||
|
||||
@Composable
|
||||
fun EarnBlock(state: EarnBlockUM, modifier: Modifier = Modifier) {
|
||||
|
|
@ -95,92 +107,87 @@ private fun EarnBlockLoading(modifier: Modifier = Modifier) {
|
|||
private fun EarnBlockContent(state: EarnBlockUM.Content, modifier: Modifier = Modifier) {
|
||||
val shape = RoundedCornerShape(TangemTheme.dimens2.x4)
|
||||
|
||||
val backgroundModifier = resolveBackgroundModifier(state.backgroundUM, shape)
|
||||
|
||||
val clickModifier = when (val trailing = state.trailingUM) {
|
||||
is EarnBlockUM.TrailingUM.Balance -> Modifier.clickable(onClick = trailing.onClick)
|
||||
else -> Modifier
|
||||
}
|
||||
val clickModifier = state.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier
|
||||
|
||||
TangemRowContainer(
|
||||
modifier = modifier
|
||||
.clip(shape)
|
||||
.then(backgroundModifier)
|
||||
.then(clickModifier),
|
||||
.then(clickModifier.backgroundModifier(state.type, state.backgroundUM, shape)),
|
||||
contentPadding = PaddingValues(all = TangemTheme.dimens2.x3),
|
||||
content = {
|
||||
// Icon (HEAD)
|
||||
EarnBlockIcon(
|
||||
type = state.type,
|
||||
iconUM = state.iconUM,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.HEAD)
|
||||
.padding(end = TangemTheme.dimens2.x3),
|
||||
)
|
||||
|
||||
// Title (START_TOP)
|
||||
Text(
|
||||
text = state.titleUM.text.resolveReference(),
|
||||
style = when (state.titleUM.style) {
|
||||
EarnBlockUM.TitleUM.Style.Large -> TangemTheme.typography2.bodySemibold16
|
||||
EarnBlockUM.TitleUM.Style.Small -> TangemTheme.typography2.captionMedium12
|
||||
},
|
||||
color = state.titleUM.color(),
|
||||
style = state.titleUM.style.textStyle,
|
||||
color = state.titleUM.tone.color(state.type),
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.START_TOP)
|
||||
.padding(end = TangemTheme.dimens2.x2),
|
||||
)
|
||||
|
||||
// Subtitle (START_BOTTOM)
|
||||
val subtitle = state.subtitleUM
|
||||
if (subtitle is EarnBlockUM.SubtitleUM.Text) {
|
||||
Text(
|
||||
text = subtitle.text.resolveReference(),
|
||||
style = when (subtitle.style) {
|
||||
EarnBlockUM.SubtitleUM.Style.Large -> TangemTheme.typography2.bodySemibold16
|
||||
EarnBlockUM.SubtitleUM.Style.Small -> TangemTheme.typography2.captionMedium12
|
||||
},
|
||||
color = subtitle.color(),
|
||||
EarnBlockSubtitle(
|
||||
subtitle = subtitle,
|
||||
type = state.type,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.START_BOTTOM)
|
||||
.padding(end = TangemTheme.dimens2.x2),
|
||||
)
|
||||
}
|
||||
|
||||
// Trailing
|
||||
EarnBlockTrailing(trailingUM = state.trailingUM)
|
||||
EarnBlockTrailing(type = state.type, trailingUM = state.trailingUM, onClick = state.onClick)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun resolveBackgroundModifier(backgroundUM: EarnBlockUM.BackgroundUM, shape: RoundedCornerShape): Modifier {
|
||||
private fun Modifier.backgroundModifier(
|
||||
type: Type,
|
||||
backgroundUM: EarnBlockUM.BackgroundUM,
|
||||
shape: RoundedCornerShape,
|
||||
): Modifier {
|
||||
return when (backgroundUM) {
|
||||
is EarnBlockUM.BackgroundUM.Surface -> Modifier
|
||||
is EarnBlockUM.BackgroundUM.Surface -> this
|
||||
.background(TangemTheme.colors2.surface.level3)
|
||||
.border(width = BorderWidth, color = TangemTheme.colors2.border.neutral.primary, shape = shape)
|
||||
is EarnBlockUM.BackgroundUM.Tinted -> {
|
||||
val tintColor = backgroundUM.color()
|
||||
Modifier
|
||||
.background(tintColor.copy(alpha = TINTED_BACKGROUND_ALPHA))
|
||||
.border(width = BorderWidth, color = tintColor.copy(alpha = TINTED_BORDER_ALPHA), shape = shape)
|
||||
.innerShadow(
|
||||
shape = shape,
|
||||
shadow = Shadow(
|
||||
radius = InnerShadowBlur,
|
||||
color = tintColor.copy(alpha = TINTED_INNER_SHADOW_ALPHA),
|
||||
offset = DpOffset.Zero,
|
||||
),
|
||||
)
|
||||
}
|
||||
is EarnBlockUM.BackgroundUM.AccentSoft -> tintedBackground(type.accentSoftTint(), shape)
|
||||
is EarnBlockUM.BackgroundUM.AccentStrong -> tintedBackground(type.accentStrongTint(), shape)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.tintedBackground(tintColor: Color, shape: RoundedCornerShape): Modifier = this
|
||||
.background(tintColor.copy(alpha = TINTED_BACKGROUND_ALPHA))
|
||||
.border(width = BorderWidth, color = tintColor.copy(alpha = TINTED_BORDER_ALPHA), shape = shape)
|
||||
.innerShadow(
|
||||
shape = shape,
|
||||
shadow = Shadow(
|
||||
radius = InnerShadowBlur,
|
||||
color = tintColor.copy(alpha = TINTED_INNER_SHADOW_ALPHA),
|
||||
offset = DpOffset.Zero,
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun EarnBlockTrailing(trailingUM: EarnBlockUM.TrailingUM?) {
|
||||
private fun EarnBlockTrailing(type: Type, trailingUM: EarnBlockUM.TrailingUM?, onClick: (() -> Unit)?) {
|
||||
when (trailingUM) {
|
||||
is EarnBlockUM.TrailingUM.Button -> {
|
||||
TangemButton(
|
||||
buttonUM = trailingUM.buttonUM,
|
||||
buttonUM = TangemButtonUM(
|
||||
text = trailingUM.text,
|
||||
type = type.buttonType(),
|
||||
size = TangemButtonSize.X9,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
isEnabled = trailingUM.isEnabled,
|
||||
onClick = onClick ?: {},
|
||||
),
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.TAIL),
|
||||
)
|
||||
}
|
||||
|
|
@ -200,12 +207,43 @@ private fun EarnBlockTrailing(trailingUM: EarnBlockUM.TrailingUM?) {
|
|||
)
|
||||
}
|
||||
}
|
||||
is EarnBlockUM.TrailingUM.Icon -> {
|
||||
TangemIcon(
|
||||
tangemIconUM = TangemIconUM.Icon(
|
||||
iconRes = trailingUM.tone.iconRes(),
|
||||
tintReference = { trailingUM.tone.tint() },
|
||||
),
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.TAIL)
|
||||
.size(TangemTheme.dimens2.x6),
|
||||
)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EarnBlockIcon(iconUM: EarnBlockUM.IconUM, modifier: Modifier = Modifier) {
|
||||
private fun EarnBlockSubtitle(subtitle: EarnBlockUM.SubtitleUM.Text, type: Type, modifier: Modifier = Modifier) {
|
||||
val textStyle = subtitle.style.textStyle
|
||||
val textColor = subtitle.tone.color(type)
|
||||
if (subtitle.loader == null) {
|
||||
Text(text = subtitle.text.resolveReference(), style = textStyle, color = textColor, modifier = modifier)
|
||||
return
|
||||
}
|
||||
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text = subtitle.text.resolveReference(), style = textStyle, color = textColor)
|
||||
Spacer(modifier = Modifier.width(3.dp))
|
||||
CircularProgressIndicator(
|
||||
color = subtitle.loader.tone.color(),
|
||||
strokeWidth = LoaderStrokeWidth,
|
||||
strokeCap = StrokeCap.Round,
|
||||
modifier = Modifier.size(LoaderSize),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EarnBlockIcon(type: Type, iconUM: EarnBlockUM.IconUM, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier.size(TangemTheme.dimens2.x10),
|
||||
|
|
@ -216,7 +254,7 @@ private fun EarnBlockIcon(iconUM: EarnBlockUM.IconUM, modifier: Modifier = Modif
|
|||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x6)
|
||||
.blur(radius = TangemTheme.dimens2.x4, edgeTreatment = BlurredEdgeTreatment.Unbounded)
|
||||
.background(color = iconUM.glowColor().copy(alpha = 0.7f), shape = glowShape),
|
||||
.background(color = type.accentGlow().copy(alpha = GLOW_ALPHA), shape = glowShape),
|
||||
)
|
||||
}
|
||||
val iconRes = when (iconUM) {
|
||||
|
|
@ -230,6 +268,93 @@ private fun EarnBlockIcon(iconUM: EarnBlockUM.IconUM, modifier: Modifier = Modif
|
|||
}
|
||||
}
|
||||
|
||||
// region Type → theme mapping
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun Type.accentText(): Color = when (this) {
|
||||
Type.Staking -> TangemTheme.colors2.text.status.accent
|
||||
Type.YieldSupply -> TangemTheme.colors2.text.status.positive
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun Type.accentGlow(): Color = when (this) {
|
||||
Type.Staking -> TangemTheme.colors2.border.status.accent
|
||||
Type.YieldSupply -> TangemTheme.colors2.text.status.positive
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun Type.accentSoftTint(): Color = when (this) {
|
||||
Type.Staking -> TangemTheme.colors2.markers.backgroundTintedBlue
|
||||
Type.YieldSupply -> TangemTheme.colors2.markers.backgroundTintedGreen
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun Type.accentStrongTint(): Color = when (this) {
|
||||
Type.Staking -> TangemTheme.colors2.text.status.accent
|
||||
Type.YieldSupply -> TangemTheme.colors2.text.status.positive
|
||||
}
|
||||
|
||||
private fun Type.buttonType(): TangemButtonType = when (this) {
|
||||
Type.Staking -> TangemButtonType.Accent
|
||||
Type.YieldSupply -> TangemButtonType.Positive
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun EarnBlockUM.TitleUM.Tone.color(type: Type): Color = when (this) {
|
||||
EarnBlockUM.TitleUM.Tone.Primary -> TangemTheme.colors2.text.neutral.primary
|
||||
EarnBlockUM.TitleUM.Tone.Secondary -> TangemTheme.colors2.text.neutral.secondary
|
||||
EarnBlockUM.TitleUM.Tone.Disabled -> TangemTheme.colors2.text.neutral.tertiary
|
||||
EarnBlockUM.TitleUM.Tone.Accent -> type.accentText()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun EarnBlockUM.SubtitleUM.Tone.color(type: Type): Color = when (this) {
|
||||
EarnBlockUM.SubtitleUM.Tone.Primary -> TangemTheme.colors2.text.neutral.primary
|
||||
EarnBlockUM.SubtitleUM.Tone.Disabled -> TangemTheme.colors2.text.neutral.tertiary
|
||||
EarnBlockUM.SubtitleUM.Tone.Accent -> type.accentText()
|
||||
}
|
||||
|
||||
private fun EarnBlockUM.TrailingUM.IconTone.iconRes(): Int = when (this) {
|
||||
EarnBlockUM.TrailingUM.IconTone.Warning -> R.drawable.ic_alert_triangle_20
|
||||
EarnBlockUM.TrailingUM.IconTone.Info -> R.drawable.ic_alert_circle_red_20
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun EarnBlockUM.TrailingUM.IconTone.tint(): Color = when (this) {
|
||||
EarnBlockUM.TrailingUM.IconTone.Warning -> TangemTheme.colors2.graphic.status.attention
|
||||
EarnBlockUM.TrailingUM.IconTone.Info -> TangemTheme.colors2.fill.neutral.secondary
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun EarnBlockUM.SubtitleUM.LoaderTone.color(): Color = when (this) {
|
||||
EarnBlockUM.SubtitleUM.LoaderTone.Positive -> TangemTheme.colors2.text.status.positive
|
||||
EarnBlockUM.SubtitleUM.LoaderTone.Muted -> TangemTheme.colors2.graphic.neutral.tertiaryConstant
|
||||
}
|
||||
|
||||
private val EarnBlockUM.TitleUM.Style.textStyle: TextStyle
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = when (this) {
|
||||
EarnBlockUM.TitleUM.Style.Large -> TangemTheme.typography2.bodySemibold16
|
||||
EarnBlockUM.TitleUM.Style.Small -> TangemTheme.typography2.captionMedium12
|
||||
}
|
||||
|
||||
private val EarnBlockUM.SubtitleUM.Style.textStyle: TextStyle
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = when (this) {
|
||||
EarnBlockUM.SubtitleUM.Style.Large -> TangemTheme.typography2.bodySemibold16
|
||||
EarnBlockUM.SubtitleUM.Style.Small -> TangemTheme.typography2.captionMedium12
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
|
|
@ -247,68 +372,79 @@ private class EarnBlockPreviewProvider : CollectionPreviewParameterProvider<Earn
|
|||
collection = listOf(
|
||||
EarnBlockUM.Loading,
|
||||
EarnBlockUM.Content(
|
||||
type = Type.Staking,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.Surface,
|
||||
iconUM = EarnBlockUM.IconUM.Plain(iconRes = R.drawable.ic_staking_disable_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.staking_native),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
color = { TangemTheme.colors2.text.neutral.tertiary },
|
||||
tone = EarnBlockUM.TitleUM.Tone.Disabled,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(CoreResR.string.staking_notification_network_error_text),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
color = { TangemTheme.colors2.text.neutral.tertiary },
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
),
|
||||
trailingUM = null,
|
||||
),
|
||||
EarnBlockUM.Content(
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.Tinted { TangemTheme.colors2.border.status.accent },
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(
|
||||
iconRes = R.drawable.ic_staking_40,
|
||||
glowColor = { TangemTheme.colors2.border.status.accent },
|
||||
),
|
||||
type = Type.Staking,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.AccentSoft,
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_staking_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.token_details_staking_block_title),
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
color = { TangemTheme.colors2.text.status.accent },
|
||||
tone = EarnBlockUM.TitleUM.Tone.Accent,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = stringReference("Average APR 5.24%"),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
color = { TangemTheme.colors2.text.neutral.primary },
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Primary,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
buttonUM = TangemButtonUM(
|
||||
text = stringReference("Stake"),
|
||||
type = TangemButtonType.Accent,
|
||||
size = TangemButtonSize.X9,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
onClick = {},
|
||||
),
|
||||
text = stringReference("Stake"),
|
||||
),
|
||||
onClick = {},
|
||||
),
|
||||
EarnBlockUM.Content(
|
||||
type = Type.Staking,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.Surface,
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(
|
||||
iconRes = R.drawable.ic_staking_40,
|
||||
glowColor = { TangemTheme.colors2.text.status.accent },
|
||||
),
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_staking_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = stringReference("Native staking"),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
color = { TangemTheme.colors2.text.neutral.primary },
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = stringReference("$ 12.34 rewards"),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
color = { TangemTheme.colors2.text.status.accent },
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Balance(
|
||||
fiatValue = stringReference("$ 500.17"),
|
||||
cryptoValue = stringReference("500.00 SOL"),
|
||||
isBalanceHidden = false,
|
||||
onClick = {},
|
||||
),
|
||||
onClick = {},
|
||||
),
|
||||
EarnBlockUM.Content(
|
||||
type = Type.YieldSupply,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.AccentSoft,
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_yield_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = stringReference("Earn yield"),
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Accent,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = stringReference("Start earning · 5.24%"),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Primary,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
text = stringReference("More"),
|
||||
),
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package com.tangem.common.ui.earn
|
|||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.extensions.ColorReference2
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
|
|
@ -12,38 +10,38 @@ sealed interface EarnBlockUM {
|
|||
data object Loading : EarnBlockUM
|
||||
|
||||
data class Content(
|
||||
val type: Type,
|
||||
val backgroundUM: BackgroundUM,
|
||||
val iconUM: IconUM,
|
||||
val titleUM: TitleUM,
|
||||
val subtitleUM: SubtitleUM?,
|
||||
val trailingUM: TrailingUM?,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
) : EarnBlockUM
|
||||
|
||||
enum class Type { Staking, YieldSupply }
|
||||
|
||||
@Immutable
|
||||
sealed interface BackgroundUM {
|
||||
data object Surface : BackgroundUM
|
||||
data class Tinted(val color: ColorReference2) : BackgroundUM
|
||||
data object AccentSoft : BackgroundUM
|
||||
data object AccentStrong : BackgroundUM
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface IconUM {
|
||||
data class Glowing(
|
||||
@DrawableRes val iconRes: Int,
|
||||
val glowColor: ColorReference2,
|
||||
) : IconUM
|
||||
|
||||
data class Plain(
|
||||
@DrawableRes val iconRes: Int,
|
||||
) : IconUM
|
||||
data class Glowing(@DrawableRes val iconRes: Int) : IconUM
|
||||
data class Plain(@DrawableRes val iconRes: Int) : IconUM
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class TitleUM(
|
||||
val text: TextReference,
|
||||
val style: Style,
|
||||
val color: ColorReference2,
|
||||
val tone: Tone,
|
||||
) {
|
||||
enum class Style { Large, Small }
|
||||
enum class Tone { Primary, Secondary, Disabled, Accent }
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
@ -51,21 +49,34 @@ sealed interface EarnBlockUM {
|
|||
data class Text(
|
||||
val text: TextReference,
|
||||
val style: Style,
|
||||
val color: ColorReference2,
|
||||
val tone: Tone,
|
||||
val loader: Loader? = null,
|
||||
) : SubtitleUM
|
||||
|
||||
data class Loader(val tone: LoaderTone)
|
||||
|
||||
enum class Style { Large, Small }
|
||||
enum class Tone { Primary, Disabled, Accent }
|
||||
enum class LoaderTone { Positive, Muted }
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface TrailingUM {
|
||||
data class Button(val buttonUM: TangemButtonUM) : TrailingUM
|
||||
data class Button(
|
||||
val text: TextReference,
|
||||
val isEnabled: Boolean = true,
|
||||
) : TrailingUM
|
||||
|
||||
data class Balance(
|
||||
val fiatValue: TextReference,
|
||||
val cryptoValue: TextReference,
|
||||
val isBalanceHidden: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
) : TrailingUM
|
||||
|
||||
data class Icon(
|
||||
val tone: IconTone,
|
||||
) : TrailingUM
|
||||
|
||||
enum class IconTone { Warning, Info }
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +98,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
|
|||
TokenDetailsScreen(
|
||||
tokenDetailsUM = tokenDetailsUM,
|
||||
tokenMarketBlockComponent = tokenMarketBlockComponent,
|
||||
yieldSupplyComponent = yieldSupplyComponent,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@ import androidx.compose.ui.text.SpanStyle
|
|||
import com.tangem.common.getRewardStakingBalance
|
||||
import com.tangem.common.getTotalStakingBalance
|
||||
import com.tangem.common.ui.earn.EarnBlockUM
|
||||
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.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -58,17 +54,18 @@ internal class UpdateStakingNotificationTransformer(
|
|||
|
||||
private fun buildTemporaryUnavailable(): EarnBlockUM.Content {
|
||||
return EarnBlockUM.Content(
|
||||
type = EarnBlockUM.Type.Staking,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.Surface,
|
||||
iconUM = EarnBlockUM.IconUM.Plain(iconRes = CoreUiR.drawable.ic_staking_disable_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.staking_native),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
color = { TangemTheme.colors2.text.neutral.tertiary },
|
||||
tone = EarnBlockUM.TitleUM.Tone.Disabled,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(CoreResR.string.staking_notification_network_error_text),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
color = { TangemTheme.colors2.text.neutral.tertiary },
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
),
|
||||
trailingUM = null,
|
||||
)
|
||||
|
|
@ -118,31 +115,24 @@ internal class UpdateStakingNotificationTransformer(
|
|||
isEnabled: Boolean,
|
||||
): EarnBlockUM.Content {
|
||||
return EarnBlockUM.Content(
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.Tinted { TangemTheme.colors2.markers.backgroundTintedBlue },
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(
|
||||
iconRes = CoreUiR.drawable.ic_staking_40,
|
||||
glowColor = { TangemTheme.colors2.border.status.accent },
|
||||
),
|
||||
type = EarnBlockUM.Type.Staking,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.AccentSoft,
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = CoreUiR.drawable.ic_staking_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(id = R.string.token_details_staking_block_title),
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
color = { TangemTheme.colors2.text.status.accent },
|
||||
tone = EarnBlockUM.TitleUM.Tone.Accent,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = stakeAvailableSubtitle(availability.option.displayApy),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
color = { TangemTheme.colors2.text.neutral.primary },
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Primary,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
buttonUM = TangemButtonUM(
|
||||
text = resourceReference(R.string.common_stake),
|
||||
type = TangemButtonType.Accent,
|
||||
size = TangemButtonSize.X9,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
isEnabled = isEnabled,
|
||||
onClick = clickIntents::onStakeBannerClick,
|
||||
),
|
||||
text = resourceReference(R.string.common_stake),
|
||||
isEnabled = isEnabled,
|
||||
),
|
||||
onClick = clickIntents::onStakeBannerClick,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -167,15 +157,13 @@ internal class UpdateStakingNotificationTransformer(
|
|||
val fiatAmount = stakingAmount?.let { fiatRate?.multiply(it) }
|
||||
val rewardFiatAmount = rewardAmount?.let { fiatRate?.multiply(it) }
|
||||
return EarnBlockUM.Content(
|
||||
type = EarnBlockUM.Type.Staking,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.Surface,
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(
|
||||
iconRes = CoreUiR.drawable.ic_staking_40,
|
||||
glowColor = { TangemTheme.colors2.border.status.accent },
|
||||
),
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = CoreUiR.drawable.ic_staking_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.staking_native),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
color = { TangemTheme.colors2.text.neutral.primary },
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = getRewardSubtitle(status, rewardFiatAmount),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Balance(
|
||||
|
|
@ -197,8 +185,8 @@ internal class UpdateStakingNotificationTransformer(
|
|||
},
|
||||
),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
onClick = clickIntents::onStakeBannerClick,
|
||||
),
|
||||
onClick = clickIntents::onStakeBannerClick,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -262,11 +250,7 @@ internal class UpdateStakingNotificationTransformer(
|
|||
return EarnBlockUM.SubtitleUM.Text(
|
||||
text = text,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
color = if (isAccent) {
|
||||
{ TangemTheme.colors2.text.status.accent }
|
||||
} else {
|
||||
{ TangemTheme.colors2.text.neutral.tertiary }
|
||||
},
|
||||
tone = if (isAccent) EarnBlockUM.SubtitleUM.Tone.Accent else EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlock
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlockHeight
|
||||
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyComponent
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import dev.chrisbanes.haze.HazeTint
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -67,6 +68,7 @@ private val MarketBlockHorizontalPadding: Dp = 14.dp
|
|||
internal fun TokenDetailsScreen(
|
||||
tokenDetailsUM: TokenDetailsUM,
|
||||
tokenMarketBlockComponent: TokenMarketBlockComponent?,
|
||||
yieldSupplyComponent: YieldSupplyComponent,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() }
|
||||
|
|
@ -107,6 +109,7 @@ internal fun TokenDetailsScreen(
|
|||
body = {
|
||||
TokenDetailsBody(
|
||||
tokenDetailsUM = tokenDetailsUM,
|
||||
yieldSupplyComponent = yieldSupplyComponent,
|
||||
rootBackground = rootBackground,
|
||||
bottomContentPadding = marketBlockHeight,
|
||||
modifier = Modifier
|
||||
|
|
@ -193,6 +196,7 @@ private fun BoxScope.TokenDetailsMarketBlockOverlay(
|
|||
@Composable
|
||||
private fun TokenDetailsBody(
|
||||
tokenDetailsUM: TokenDetailsUM,
|
||||
yieldSupplyComponent: YieldSupplyComponent,
|
||||
rootBackground: Color,
|
||||
bottomContentPadding: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -215,6 +219,9 @@ private fun TokenDetailsBody(
|
|||
)
|
||||
}
|
||||
}
|
||||
item(key = "yield_supply_block") {
|
||||
yieldSupplyComponent.Content(modifier = itemModifier.padding(vertical = TangemTheme.dimens2.x2))
|
||||
}
|
||||
// TODO [REDACTED_TASK_KEY] Token Details Make Transaction History
|
||||
}
|
||||
}
|
||||
|
|
@ -262,6 +269,10 @@ private fun TokenDetailsScreen_Preview() {
|
|||
isBalanceHidden = false,
|
||||
isMarketPriceAvailable = true,
|
||||
),
|
||||
yieldSupplyComponent = object : YieldSupplyComponent {
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) = Unit
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ android {
|
|||
namespace = "com.tangem.features.yield.supply.impl"
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Feature */
|
||||
|
|
@ -20,6 +24,7 @@ dependencies {
|
|||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.analytics)
|
||||
|
|
@ -75,4 +80,11 @@ dependencies {
|
|||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Tests */
|
||||
testImplementation(deps.test.junit5)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.coroutine)
|
||||
}
|
||||
|
|
@ -4,11 +4,13 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.common.ui.earn.EarnBlock
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyComponent
|
||||
import com.tangem.features.yield.supply.impl.main.model.YieldSupplyModel
|
||||
import com.tangem.features.yield.supply.impl.main.ui.YieldSupplyBlockContent
|
||||
import com.tangem.features.yield.supply.impl.main.ui.YieldSupplyBlockContentLegacy
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -22,9 +24,13 @@ internal class DefaultYieldSupplyComponent @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val yieldSupplyUM by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
YieldSupplyBlockContent(yieldSupplyUM = yieldSupplyUM, modifier = modifier)
|
||||
if (LocalRedesignEnabled.current) {
|
||||
val earnBlockUM by model.uiState.collectAsStateWithLifecycle()
|
||||
earnBlockUM?.let { EarnBlock(state = it, modifier = modifier) }
|
||||
} else {
|
||||
val yieldSupplyUM by model.uiStateLegacy.collectAsStateWithLifecycle()
|
||||
YieldSupplyBlockContentLegacy(yieldSupplyUM = yieldSupplyUM, modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ import com.tangem.domain.yield.supply.usecase.*
|
|||
import com.tangem.features.yield.supply.api.YieldSupplyComponent
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.common.ui.earn.EarnBlockUM
|
||||
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
|
||||
import com.tangem.features.yield.supply.impl.main.model.converter.YieldSupplyToEarnBlockConverter
|
||||
import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.transformer.update
|
||||
|
|
@ -61,11 +63,16 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
|
||||
) : Model(), YieldSupplyClickIntents {
|
||||
|
||||
private val earnBlockConverter = YieldSupplyToEarnBlockConverter()
|
||||
private val params = paramsContainer.require<YieldSupplyComponent.Params>()
|
||||
|
||||
val uiState: StateFlow<YieldSupplyUM>
|
||||
val uiStateLegacy: StateFlow<YieldSupplyUM>
|
||||
field = MutableStateFlow<YieldSupplyUM>(YieldSupplyUM.Initial)
|
||||
|
||||
val uiState: StateFlow<EarnBlockUM?> = uiStateLegacy
|
||||
.map(earnBlockConverter::convert)
|
||||
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = null)
|
||||
|
||||
private val cryptoCurrency = params.cryptoCurrency
|
||||
private var appCurrency: AppCurrency = AppCurrency.Default
|
||||
var userWallet: UserWallet by Delegates.notNull()
|
||||
|
|
@ -143,7 +150,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
|
||||
yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken)
|
||||
.onRight { tokenStatus ->
|
||||
uiState.update(
|
||||
uiStateLegacy.update(
|
||||
YieldSupplyTokenStatusSuccessTransformer(
|
||||
tokenStatus = tokenStatus,
|
||||
onStartEarningClick = ::onStartEarningClick,
|
||||
|
|
@ -151,7 +158,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
)
|
||||
}.onLeft { error ->
|
||||
TangemLogger.e("Error", error)
|
||||
uiState.update { YieldSupplyUM.Initial }
|
||||
uiStateLegacy.update { YieldSupplyUM.Initial }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +172,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
|
||||
private fun navigateToYieldSupplyEntry() {
|
||||
val cryptoCurrencyStatus = latestCryptoCurrencyStatus ?: return
|
||||
val apy = when (val yieldSupplyUM = uiState.value) {
|
||||
val apy = when (val yieldSupplyUM = uiStateLegacy.value) {
|
||||
is YieldSupplyUM.Available -> yieldSupplyUM.apy
|
||||
is YieldSupplyUM.Content -> yieldSupplyUM.apy
|
||||
else -> ""
|
||||
|
|
@ -181,8 +188,8 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
|
||||
private suspend fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val isCryptoCurrencyStatusFromCache = cryptoCurrencyStatus.value.sources.networkSource != StatusSource.ACTUAL
|
||||
val processing = uiState.value is YieldSupplyUM.Processing
|
||||
if (isCryptoCurrencyStatusFromCache && processing) {
|
||||
val isProcessing = uiStateLegacy.value is YieldSupplyUM.Processing
|
||||
if (isCryptoCurrencyStatusFromCache && isProcessing) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +206,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun showProcessing(status: YieldSupplyPendingStatus) {
|
||||
uiState.update {
|
||||
uiStateLegacy.update {
|
||||
when (status) {
|
||||
is YieldSupplyPendingStatus.Enter -> YieldSupplyUM.Processing.Enter
|
||||
is YieldSupplyPendingStatus.Exit -> YieldSupplyUM.Processing.Exit
|
||||
|
|
@ -225,7 +232,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
) {
|
||||
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
|
||||
val showWarningIcon = !yieldSupplyStatus.isAllowedToSpend
|
||||
val isShowInfoIconPrevState = when (val state = uiState.value) {
|
||||
val isShowInfoIconPrevState = when (val state = uiStateLegacy.value) {
|
||||
is YieldSupplyUM.Content -> state.showInfoIcon
|
||||
else -> false
|
||||
}
|
||||
|
|
@ -239,7 +246,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
}
|
||||
yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken)
|
||||
.onRight { tokenStatus ->
|
||||
uiState.update {
|
||||
uiStateLegacy.update {
|
||||
YieldSupplyUM.Content(
|
||||
title = resourceReference(
|
||||
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
|
||||
|
|
@ -262,7 +269,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
computeAndApplyShowInfoIcon(cryptoCurrencyStatus)
|
||||
}.onLeft { t ->
|
||||
TangemLogger.e("Error", t)
|
||||
uiState.update {
|
||||
uiStateLegacy.update {
|
||||
YieldSupplyUM.Content(
|
||||
title = resourceReference(
|
||||
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
|
||||
|
|
@ -299,7 +306,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
} else {
|
||||
false
|
||||
}
|
||||
uiState.update { state ->
|
||||
uiStateLegacy.update { state ->
|
||||
when (state) {
|
||||
is YieldSupplyUM.Content -> state.copy(showInfoIcon = isShowInfoIcon)
|
||||
else -> state
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
package com.tangem.features.yield.supply.impl.main.model.converter
|
||||
|
||||
import com.tangem.common.ui.earn.EarnBlockUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
|
||||
internal class YieldSupplyToEarnBlockConverter : Converter<YieldSupplyUM, EarnBlockUM?> {
|
||||
|
||||
override fun convert(value: YieldSupplyUM): EarnBlockUM? = when (value) {
|
||||
is YieldSupplyUM.Initial,
|
||||
is YieldSupplyUM.Unavailable,
|
||||
-> null
|
||||
is YieldSupplyUM.Available -> buildAvailable(value)
|
||||
is YieldSupplyUM.Content -> buildContent(value)
|
||||
is YieldSupplyUM.Processing.Enter -> buildProcessingEnter()
|
||||
is YieldSupplyUM.Processing.Exit -> buildProcessingExit()
|
||||
is YieldSupplyUM.Loading -> EarnBlockUM.Loading
|
||||
}
|
||||
|
||||
private fun buildAvailable(value: YieldSupplyUM.Available): EarnBlockUM.Content {
|
||||
return EarnBlockUM.Content(
|
||||
type = EarnBlockUM.Type.YieldSupply,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.AccentSoft,
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = CoreUiR.drawable.ic_yield_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(
|
||||
id = CoreResR.string.yield_module_token_details_earn_notification_subtitle,
|
||||
formatArgs = wrappedList(value.apy),
|
||||
),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(
|
||||
CoreResR.string.yield_module_token_details_earn_notification_description,
|
||||
),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
text = resourceReference(CoreResR.string.common_more),
|
||||
),
|
||||
onClick = value.onClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildContent(value: YieldSupplyUM.Content): EarnBlockUM.Content {
|
||||
return EarnBlockUM.Content(
|
||||
type = EarnBlockUM.Type.YieldSupply,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.Surface,
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = CoreUiR.drawable.ic_yield_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = if (value.showWarningIcon) {
|
||||
resourceReference(CoreResR.string.common_yield_mode)
|
||||
} else {
|
||||
resourceReference(CoreResR.string.yield_module_transaction_enter)
|
||||
},
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(
|
||||
id = CoreResR.string.yield_module_average_apy,
|
||||
formatArgs = wrappedList(value.apy),
|
||||
),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
),
|
||||
trailingUM = buildContentTrailing(value),
|
||||
onClick = value.onClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildContentTrailing(value: YieldSupplyUM.Content): EarnBlockUM.TrailingUM? = when {
|
||||
value.showWarningIcon -> EarnBlockUM.TrailingUM.Icon(
|
||||
tone = EarnBlockUM.TrailingUM.IconTone.Warning,
|
||||
)
|
||||
value.showInfoIcon -> EarnBlockUM.TrailingUM.Icon(
|
||||
tone = EarnBlockUM.TrailingUM.IconTone.Info,
|
||||
)
|
||||
else -> EarnBlockUM.TrailingUM.Button(
|
||||
text = resourceReference(CoreResR.string.details_title),
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildProcessingEnter(): EarnBlockUM.Content {
|
||||
return EarnBlockUM.Content(
|
||||
type = EarnBlockUM.Type.YieldSupply,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.Surface,
|
||||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = CoreUiR.drawable.ic_yield_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.common_yield_mode),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(CoreResR.string.common_enabling),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
loader = EarnBlockUM.SubtitleUM.Loader(tone = EarnBlockUM.SubtitleUM.LoaderTone.Positive),
|
||||
),
|
||||
trailingUM = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildProcessingExit(): EarnBlockUM.Content {
|
||||
return EarnBlockUM.Content(
|
||||
type = EarnBlockUM.Type.YieldSupply,
|
||||
backgroundUM = EarnBlockUM.BackgroundUM.Surface,
|
||||
iconUM = EarnBlockUM.IconUM.Plain(iconRes = CoreUiR.drawable.ic_yield_disabling_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.common_yield_mode),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(CoreResR.string.common_disabling),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
loader = EarnBlockUM.SubtitleUM.Loader(tone = EarnBlockUM.SubtitleUM.LoaderTone.Muted),
|
||||
),
|
||||
trailingUM = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
|
|||
import com.tangem.utils.StringsSigns
|
||||
|
||||
@Composable
|
||||
internal fun YieldSupplyBlockContent(yieldSupplyUM: YieldSupplyUM, modifier: Modifier = Modifier) {
|
||||
internal fun YieldSupplyBlockContentLegacy(yieldSupplyUM: YieldSupplyUM, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(
|
||||
targetState = yieldSupplyUM,
|
||||
contentKey = { it::class },
|
||||
|
|
@ -330,7 +330,7 @@ private fun SupplyInfo(
|
|||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun YieldSupplyBlockContent_Preview(@PreviewParameter(PreviewProvider::class) params: YieldSupplyUM) {
|
||||
TangemThemePreview {
|
||||
YieldSupplyBlockContent(yieldSupplyUM = params, modifier = Modifier)
|
||||
YieldSupplyBlockContentLegacy(yieldSupplyUM = params, modifier = Modifier)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +86,68 @@ internal class YieldSupplyToEarnBlockConverterTest {
|
|||
assertThat(earnBlock.trailingUM).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN Content with showWarningIcon WHEN convert THEN trailing Warning Icon`() {
|
||||
val content = YieldSupplyUM.Content(
|
||||
apy = "5.1",
|
||||
title = stringReference("Yield Mode"),
|
||||
subtitle = stringReference("Interest accrues automatically"),
|
||||
rewardsApy = stringReference("APY 5.1%"),
|
||||
onClick = {},
|
||||
showWarningIcon = true,
|
||||
showInfoIcon = false,
|
||||
)
|
||||
|
||||
val result = converter.convert(content)
|
||||
|
||||
assertThat(result).isInstanceOf(EarnBlockUM.Content::class.java)
|
||||
val earnBlock = result as EarnBlockUM.Content
|
||||
assertThat(earnBlock.trailingUM).isInstanceOf(EarnBlockUM.TrailingUM.Icon::class.java)
|
||||
val icon = earnBlock.trailingUM as EarnBlockUM.TrailingUM.Icon
|
||||
assertThat(icon.tone).isEqualTo(EarnBlockUM.TrailingUM.IconTone.Warning)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN Content with showInfoIcon WHEN convert THEN trailing Info Icon`() {
|
||||
val content = YieldSupplyUM.Content(
|
||||
apy = "5.1",
|
||||
title = stringReference("Yield Mode"),
|
||||
subtitle = stringReference("Interest accrues automatically"),
|
||||
rewardsApy = stringReference("APY 5.1%"),
|
||||
onClick = {},
|
||||
showWarningIcon = false,
|
||||
showInfoIcon = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(content)
|
||||
|
||||
assertThat(result).isInstanceOf(EarnBlockUM.Content::class.java)
|
||||
val earnBlock = result as EarnBlockUM.Content
|
||||
assertThat(earnBlock.trailingUM).isInstanceOf(EarnBlockUM.TrailingUM.Icon::class.java)
|
||||
val icon = earnBlock.trailingUM as EarnBlockUM.TrailingUM.Icon
|
||||
assertThat(icon.tone).isEqualTo(EarnBlockUM.TrailingUM.IconTone.Info)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN Content with both icons WHEN convert THEN Warning takes precedence`() {
|
||||
val content = YieldSupplyUM.Content(
|
||||
apy = "5.1",
|
||||
title = stringReference("Yield Mode"),
|
||||
subtitle = stringReference("Interest accrues automatically"),
|
||||
rewardsApy = stringReference("APY 5.1%"),
|
||||
onClick = {},
|
||||
showWarningIcon = true,
|
||||
showInfoIcon = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(content)
|
||||
|
||||
val earnBlock = result as EarnBlockUM.Content
|
||||
assertThat(earnBlock.trailingUM).isInstanceOf(EarnBlockUM.TrailingUM.Icon::class.java)
|
||||
val icon = earnBlock.trailingUM as EarnBlockUM.TrailingUM.Icon
|
||||
assertThat(icon.tone).isEqualTo(EarnBlockUM.TrailingUM.IconTone.Warning)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN Available WHEN convert THEN Content with expected structure`() {
|
||||
var clicked = false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue