Updated on 2026-08-14
This commit is contained in:
parent
4c8267738b
commit
5d13d36cb3
11 changed files with 582 additions and 540 deletions
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
|
||||
|
|
@ -14,13 +15,13 @@ import com.valentinilk.shimmer.shimmer
|
|||
* Rectangle shimmer item with rounded shape from DS
|
||||
*/
|
||||
@Composable
|
||||
fun RectangleShimmer(modifier: Modifier = Modifier) {
|
||||
fun RectangleShimmer(modifier: Modifier = Modifier, radius: Dp = TangemTheme.dimens.radius6) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.shimmer()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius6),
|
||||
shape = RoundedCornerShape(size = radius),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -31,16 +32,14 @@ fun RectangleShimmer(modifier: Modifier = Modifier) {
|
|||
*/
|
||||
@Composable
|
||||
fun CircleShimmer(modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier.shimmer()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = modifier
|
||||
.shimmer()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// region preview
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ data class TangemDimens internal constructor(
|
|||
val size10: Dp = 10.dp,
|
||||
val size11: Dp = 11.dp,
|
||||
val size12: Dp = 12.dp,
|
||||
val size14: Dp = 14.dp,
|
||||
val size16: Dp = 16.dp,
|
||||
val size18: Dp = 18.dp,
|
||||
val size20: Dp = 20.dp,
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ internal object WalletPreviewData {
|
|||
hasPending = true,
|
||||
tokenOptions = TokenOptionsState.Visible(
|
||||
fiatAmount = "321 $",
|
||||
priceChange = PriceChangeConfig(
|
||||
config = PriceChangeConfig(
|
||||
valueInPercent = "2%",
|
||||
type = PriceChangeConfig.Type.UP,
|
||||
),
|
||||
|
|
@ -126,7 +126,7 @@ internal object WalletPreviewData {
|
|||
amount = "5,412 MATIC",
|
||||
hasPending = true,
|
||||
tokenOptions = TokenOptionsState.Hidden(
|
||||
priceChange = PriceChangeConfig(
|
||||
config = PriceChangeConfig(
|
||||
valueInPercent = "2%",
|
||||
type = PriceChangeConfig.Type.UP,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,320 +1,72 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.Dp
|
||||
import androidx.constraintlayout.compose.ConstrainedLayoutReference
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.ConstraintLayoutScope
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.common.component.token.TokenCryptoInfoBlock
|
||||
import com.tangem.feature.wallet.presentation.common.component.token.TokenFiatInfoBlock
|
||||
import com.tangem.feature.wallet.presentation.common.component.token.TokenIcon
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
|
||||
private const val DOTS = "•••"
|
||||
private const val GRAY_SCALE_SATURATION = 0f
|
||||
|
||||
val TOKEN_ITEM_HEIGHT: Dp
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemTheme.dimens.size68
|
||||
|
||||
// TODO: Add custom token state: [REDACTED_JIRA]
|
||||
@Composable
|
||||
internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||
// TODO: Add custom token state: [REDACTED_JIRA]
|
||||
when (state) {
|
||||
is TokenItemState.Content -> ContentTokenItem(state, modifier)
|
||||
is TokenItemState.Loading -> LoadingTokenItem(modifier)
|
||||
is TokenItemState.Draggable -> DraggableTokenItem(state, modifier, reorderableTokenListState = null)
|
||||
is TokenItemState.Unreachable -> UnreachableTokenItem(state, modifier)
|
||||
is TokenItemState.Locked -> LockedTokenItem(modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun ContentTokenItem(content: TokenItemState.Content, modifier: Modifier = Modifier) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
InternalTokenItem(
|
||||
modifier = modifier.combinedClickable(
|
||||
onClick = content.onItemClick,
|
||||
onLongClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
content.onItemLongClick()
|
||||
},
|
||||
),
|
||||
name = content.name,
|
||||
tokenIconUrl = content.tokenIconUrl,
|
||||
tokenIconResId = content.tokenIconResId,
|
||||
networkBadgeIconResId = content.networkBadgeIconResId,
|
||||
amount = if (content.tokenOptions is TokenOptionsState.Hidden) DOTS else content.amount,
|
||||
hasPending = content.hasPending,
|
||||
options = { ref ->
|
||||
TokenOptionsBlock(
|
||||
modifier = Modifier.constrainAsOptionsItem(scope = this, ref),
|
||||
state = content.tokenOptions,
|
||||
)
|
||||
},
|
||||
isTestnet = content.isTestnet,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DraggableTokenItem(
|
||||
state: TokenItemState.Draggable,
|
||||
internal fun TokenItem(
|
||||
state: TokenItemState,
|
||||
modifier: Modifier = Modifier,
|
||||
reorderableTokenListState: ReorderableLazyListState? = null,
|
||||
) {
|
||||
InternalTokenItem(
|
||||
modifier = modifier,
|
||||
name = state.name,
|
||||
tokenIconUrl = state.tokenIconUrl,
|
||||
tokenIconResId = state.tokenIconResId,
|
||||
networkBadgeIconResId = state.networkBadgeIconResId,
|
||||
amount = state.fiatAmount,
|
||||
hasPending = false,
|
||||
isTestnet = state.isTestnet,
|
||||
options = { ref ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size32)
|
||||
.constrainAsOptionsItem(scope = this, ref)
|
||||
.let {
|
||||
if (reorderableTokenListState != null) {
|
||||
it.detectReorder(reorderableTokenListState)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_drag_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
BaseContainer(modifier = modifier) {
|
||||
val (iconRef, cryptoInfoRef, fiatInfoRef) = createRefs()
|
||||
|
||||
@Composable
|
||||
internal fun UnreachableTokenItem(state: TokenItemState.Unreachable, modifier: Modifier = Modifier) {
|
||||
InternalTokenItem(
|
||||
modifier = modifier,
|
||||
name = state.name,
|
||||
tokenIconUrl = state.tokenIconUrl,
|
||||
tokenIconResId = state.tokenIconResId,
|
||||
networkBadgeIconResId = state.networkBadgeIconResId,
|
||||
amount = null,
|
||||
hasPending = false,
|
||||
options = { ref ->
|
||||
Text(
|
||||
modifier = Modifier.constrainAsOptionsItem(scope = this, ref),
|
||||
text = stringResource(id = R.string.common_unreachable),
|
||||
style = TangemTypography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
TokenIcon(
|
||||
state = state,
|
||||
modifier = Modifier.constrainAs(iconRef) {
|
||||
centerVerticallyTo(parent)
|
||||
start.linkTo(parent.start)
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun LoadingTokenItem(modifier: Modifier = Modifier) {
|
||||
BaseSurface(modifier) {
|
||||
Row(
|
||||
TokenCryptoInfoBlock(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
vertical = TangemTheme.dimens.spacing4,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
|
||||
) {
|
||||
CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size72,
|
||||
height = TangemTheme.dimens.size12,
|
||||
),
|
||||
)
|
||||
RectangleShimmer(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size50,
|
||||
height = TangemTheme.dimens.size12,
|
||||
),
|
||||
)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size40,
|
||||
height = TangemTheme.dimens.size12,
|
||||
),
|
||||
)
|
||||
RectangleShimmer(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size40,
|
||||
height = TangemTheme.dimens.size12,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(horizontal = TangemTheme.dimens.spacing8)
|
||||
.constrainAs(cryptoInfoRef) {
|
||||
centerVerticallyTo(parent)
|
||||
start.linkTo(iconRef.end)
|
||||
end.linkTo(fiatInfoRef.start)
|
||||
width = Dimension.fillToConstraints
|
||||
},
|
||||
)
|
||||
|
||||
TokenFiatInfoBlock(
|
||||
state = state,
|
||||
modifier = Modifier.constrainAsOptionsItem(scope = this, ref = fiatInfoRef),
|
||||
reorderableTokenListState = reorderableTokenListState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LockedTokenItem(modifier: Modifier = Modifier) {
|
||||
BaseSurface(modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
vertical = TangemTheme.dimens.spacing4,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
|
||||
) {
|
||||
Box(modifier = Modifier.size(size = TangemTheme.dimens.size42)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
LockedContent(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size72,
|
||||
height = TangemTheme.dimens.size12,
|
||||
),
|
||||
)
|
||||
LockedContent(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size50,
|
||||
height = TangemTheme.dimens.size12,
|
||||
),
|
||||
)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
LockedContent(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size40,
|
||||
height = TangemTheme.dimens.size12,
|
||||
),
|
||||
)
|
||||
LockedContent(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size40,
|
||||
height = TangemTheme.dimens.size12,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LockedContent(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.background(
|
||||
color = TangemTheme.colors.field.primary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius6),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Block for end part of token item
|
||||
* shows status is reachable, is drag, hidden or show balance
|
||||
*/
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun TokenOptionsBlock(state: TokenOptionsState, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(targetState = state, label = "Update the options", modifier = modifier) { options ->
|
||||
when (options) {
|
||||
is TokenOptionsState.Visible -> {
|
||||
TokenFiatPercentageBlock(fiatAmount = options.fiatAmount, priceChange = options.priceChange)
|
||||
}
|
||||
is TokenOptionsState.Hidden -> {
|
||||
TokenFiatPercentageBlock(fiatAmount = DOTS, priceChange = options.priceChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun InternalTokenItem(
|
||||
name: String,
|
||||
tokenIconUrl: String?,
|
||||
@DrawableRes tokenIconResId: Int,
|
||||
@DrawableRes networkBadgeIconResId: Int?,
|
||||
amount: String?,
|
||||
hasPending: Boolean,
|
||||
options: @Composable ConstraintLayoutScope.(ref: ConstrainedLayoutReference) -> Unit,
|
||||
inline fun BaseContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
isTestnet: Boolean = false,
|
||||
crossinline content: @Composable ConstraintLayoutScope.() -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.defaultMinSize(minHeight = TOKEN_ITEM_HEIGHT)
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size68)
|
||||
.background(color = TangemTheme.colors.background.primary),
|
||||
) {
|
||||
ConstraintLayout(
|
||||
|
|
@ -322,38 +74,10 @@ private fun InternalTokenItem(
|
|||
.fillMaxSize()
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing14,
|
||||
vertical = TangemTheme.dimens.spacing4,
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
),
|
||||
) {
|
||||
val (iconItem, tokenNameItem, optionsItem) = createRefs()
|
||||
|
||||
TokenIcon(
|
||||
modifier = Modifier.constrainAs(iconItem) {
|
||||
centerVerticallyTo(parent)
|
||||
start.linkTo(parent.start)
|
||||
},
|
||||
tokenIconUrl = tokenIconUrl,
|
||||
tokenIconResId = tokenIconResId,
|
||||
networkBadgeIconRes = networkBadgeIconResId,
|
||||
isTestnet = isTestnet,
|
||||
)
|
||||
|
||||
TokenTitleAmountBlock(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing8)
|
||||
.constrainAs(tokenNameItem) {
|
||||
centerVerticallyTo(parent)
|
||||
start.linkTo(iconItem.end)
|
||||
end.linkTo(optionsItem.start)
|
||||
width = Dimension.fillToConstraints
|
||||
},
|
||||
title = name,
|
||||
amount = amount,
|
||||
hasPending = hasPending,
|
||||
)
|
||||
|
||||
options(optionsItem)
|
||||
}
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,173 +91,6 @@ private fun Modifier.constrainAsOptionsItem(scope: ConstraintLayoutScope, ref: C
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BaseSurface(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier.defaultMinSize(minHeight = TOKEN_ITEM_HEIGHT),
|
||||
color = TangemTheme.colors.background.primary,
|
||||
onClick = onClick ?: {},
|
||||
enabled = onClick != null,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenTitleAmountBlock(title: String, amount: String?, hasPending: Boolean, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = TangemTypography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
AnimatedVisibility(visible = hasPending, modifier = Modifier.align(Alignment.CenterVertically)) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.img_loader_15),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = !amount.isNullOrBlank()) {
|
||||
Text(
|
||||
text = requireNotNull(amount),
|
||||
style = TangemTypography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun TokenFiatPercentageBlock(
|
||||
fiatAmount: String,
|
||||
priceChange: PriceChangeConfig,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier.requiredWidth(IntrinsicSize.Max)) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
text = fiatAmount,
|
||||
style = TangemTypography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerH2()
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = priceChange.type,
|
||||
label = "Update the price change's arrow",
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = when (priceChange.type) {
|
||||
PriceChangeConfig.Type.UP -> R.drawable.img_arrow_up_8
|
||||
PriceChangeConfig.Type.DOWN -> R.drawable.img_arrow_down_8
|
||||
},
|
||||
),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
SpacerW4()
|
||||
|
||||
AnimatedContent(
|
||||
targetState = priceChange.type,
|
||||
label = "Update the price change's arrow",
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
text = priceChange.valueInPercent,
|
||||
style = TangemTypography.body2,
|
||||
color = when (priceChange.type) {
|
||||
PriceChangeConfig.Type.UP -> TangemTheme.colors.text.accent
|
||||
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.text.warning
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenIcon(
|
||||
tokenIconUrl: String?,
|
||||
@DrawableRes tokenIconResId: Int,
|
||||
isTestnet: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
@DrawableRes networkBadgeIconRes: Int? = null,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.padding(end = TangemTheme.dimens.spacing16)
|
||||
.size(TangemTheme.dimens.size42),
|
||||
) {
|
||||
val tokenImageModifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.size(TangemTheme.dimens.size36)
|
||||
|
||||
val iconData: Any = remember(tokenIconUrl) {
|
||||
if (tokenIconUrl.isNullOrEmpty()) tokenIconResId else tokenIconUrl
|
||||
}
|
||||
val colorFilter = remember(isTestnet) {
|
||||
if (isTestnet) {
|
||||
val colorMatrix = ColorMatrix().also {
|
||||
it.setToSaturation(GRAY_SCALE_SATURATION)
|
||||
}
|
||||
|
||||
ColorFilter.colorMatrix(colorMatrix)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = tokenImageModifier,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(iconData)
|
||||
.placeholder(tokenIconResId)
|
||||
.error(tokenIconResId)
|
||||
.fallback(tokenIconResId)
|
||||
.crossfade(enable = true)
|
||||
.build(),
|
||||
colorFilter = colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = networkBadgeIconRes != null,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.size(TangemTheme.dimens.size18)
|
||||
.background(color = Color.White, shape = CircleShape),
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.padding(all = TangemTheme.dimens.spacing0_5)
|
||||
.align(Alignment.Center),
|
||||
painter = painterResource(id = requireNotNull(networkBadgeIconRes)),
|
||||
colorFilter = colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region preview
|
||||
|
||||
@Preview
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun LockedRectangle(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.background(
|
||||
color = TangemTheme.colors.background.secondary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius4),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.Companion.DOTS
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
||||
|
||||
@Composable
|
||||
internal fun TokenCryptoInfoBlock(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TokenItemState.ContentState -> ContentBlock(state = state, modifier = modifier)
|
||||
is TokenItemState.Loading -> LoadingBlock(modifier = modifier)
|
||||
is TokenItemState.Locked -> LockedBlock(modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentBlock(state: TokenItemState.ContentState, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
|
||||
) {
|
||||
CurrencyNameText(
|
||||
name = state.name,
|
||||
hasPending = (state as? TokenItemState.Content)?.hasPending == true,
|
||||
)
|
||||
|
||||
AmountText(
|
||||
amount = when (state) {
|
||||
is TokenItemState.Content -> if (state.tokenOptions is TokenOptionsState.Hidden) DOTS else state.amount
|
||||
is TokenItemState.Draggable -> state.fiatAmount
|
||||
is TokenItemState.Unreachable -> null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CurrencyNameText(name: String, hasPending: Boolean) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
Text(
|
||||
text = name,
|
||||
style = TangemTypography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
PendingTransactionImage(hasPending = hasPending, modifier = Modifier.align(Alignment.CenterVertically))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PendingTransactionImage(hasPending: Boolean, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(visible = hasPending, modifier = modifier) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.img_loader_15),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountText(amount: String?) {
|
||||
AnimatedVisibility(visible = !amount.isNullOrBlank()) {
|
||||
if (amount == null) return@AnimatedVisibility
|
||||
Text(
|
||||
text = amount,
|
||||
style = TangemTypography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingBlock(modifier: Modifier = Modifier) {
|
||||
NonContentContainer(modifier = modifier) {
|
||||
RectangleShimmer(modifier = Modifier.nameSize(), radius = TangemTheme.dimens.radius4)
|
||||
RectangleShimmer(modifier = Modifier.amountSize(), radius = TangemTheme.dimens.radius4)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LockedBlock(modifier: Modifier = Modifier) {
|
||||
NonContentContainer(modifier = modifier) {
|
||||
LockedRectangle(modifier = Modifier.nameSize())
|
||||
LockedRectangle(modifier = Modifier.amountSize())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NonContentContainer(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing10),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Modifier.nameSize(): Modifier = composed {
|
||||
return@composed size(width = TangemTheme.dimens.size72, height = TangemTheme.dimens.size12)
|
||||
}
|
||||
|
||||
private fun Modifier.amountSize(): Modifier = composed {
|
||||
return@composed size(width = TangemTheme.dimens.size50, height = TangemTheme.dimens.size12)
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SpacerW4
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.Companion.DOTS
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
|
||||
@Composable
|
||||
internal fun TokenFiatInfoBlock(
|
||||
state: TokenItemState,
|
||||
modifier: Modifier = Modifier,
|
||||
reorderableTokenListState: ReorderableLazyListState? = null,
|
||||
) {
|
||||
when (state) {
|
||||
is TokenItemState.Content -> ContentBlock(state = state.tokenOptions, modifier = modifier)
|
||||
is TokenItemState.Draggable -> {
|
||||
DraggableBlock(
|
||||
modifier = modifier,
|
||||
reorderableTokenListState = reorderableTokenListState,
|
||||
)
|
||||
}
|
||||
is TokenItemState.Unreachable -> UnreachableBlock(modifier = modifier)
|
||||
is TokenItemState.Loading -> LoadingBlock(modifier = modifier)
|
||||
is TokenItemState.Locked -> LockedBlock(modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun ContentBlock(state: TokenOptionsState, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.requiredWidth(IntrinsicSize.Max),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = state,
|
||||
label = "Update the fiat percentage block",
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
) {
|
||||
Text(
|
||||
text = when (it) {
|
||||
is TokenOptionsState.Visible -> it.fiatAmount
|
||||
is TokenOptionsState.Hidden -> DOTS
|
||||
},
|
||||
style = TangemTypography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
|
||||
PriceChangeBlock(config = state.config)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PriceChangeBlock(config: PriceChangeConfig) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
PriceChangeIcon(type = config.type, modifier = Modifier.align(Alignment.CenterVertically))
|
||||
SpacerW4()
|
||||
PriceChangeText(config = config, modifier = Modifier.align(Alignment.CenterVertically))
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun PriceChangeIcon(type: PriceChangeConfig.Type, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(
|
||||
targetState = type,
|
||||
label = "Update the price change's arrow",
|
||||
modifier = modifier,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = when (it) {
|
||||
PriceChangeConfig.Type.UP -> R.drawable.img_arrow_up_8
|
||||
PriceChangeConfig.Type.DOWN -> R.drawable.img_arrow_down_8
|
||||
},
|
||||
),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun PriceChangeText(config: PriceChangeConfig, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(
|
||||
targetState = config.type,
|
||||
label = "Update the price change's arrow",
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = config.valueInPercent,
|
||||
style = TangemTypography.body2,
|
||||
color = when (it) {
|
||||
PriceChangeConfig.Type.UP -> TangemTheme.colors.text.accent
|
||||
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.text.warning
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DraggableBlock(reorderableTokenListState: ReorderableLazyListState?, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size32)
|
||||
.then(
|
||||
other = if (reorderableTokenListState != null) {
|
||||
Modifier.detectReorder(reorderableTokenListState)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_drag_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnreachableBlock(modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = stringResource(id = R.string.common_unreachable),
|
||||
style = TangemTypography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingBlock(modifier: Modifier = Modifier) {
|
||||
NonContentContainer(modifier = modifier) {
|
||||
RectangleShimmer(modifier = Modifier.viewSize(), radius = TangemTheme.dimens.radius4)
|
||||
RectangleShimmer(modifier = Modifier.viewSize(), radius = TangemTheme.dimens.radius4)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LockedBlock(modifier: Modifier = Modifier) {
|
||||
NonContentContainer(modifier = modifier) {
|
||||
LockedRectangle(modifier = Modifier.viewSize())
|
||||
LockedRectangle(modifier = Modifier.viewSize())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NonContentContainer(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing10),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Modifier.viewSize(): Modifier = composed {
|
||||
return@composed size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12)
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
|
||||
private const val GRAY_SCALE_SATURATION = 0f
|
||||
|
||||
@Composable
|
||||
internal fun TokenIcon(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TokenItemState.ContentState -> ContentIcon(content = state, modifier = modifier)
|
||||
is TokenItemState.Loading -> LoadingIcon(modifier = modifier)
|
||||
is TokenItemState.Locked -> LockedIcon(modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentIcon(content: TokenItemState.ContentState, modifier: Modifier = Modifier) {
|
||||
BaseContainer(modifier = modifier) {
|
||||
val isTestnet = when (content) {
|
||||
is TokenItemState.Content -> content.isTestnet
|
||||
is TokenItemState.Draggable -> content.isTestnet
|
||||
is TokenItemState.Unreachable -> false
|
||||
}
|
||||
|
||||
val colorFilter = remember(isTestnet) {
|
||||
if (isTestnet) {
|
||||
ColorFilter.colorMatrix(
|
||||
colorMatrix = ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) },
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
Icon(
|
||||
content = content,
|
||||
colorFilter = colorFilter,
|
||||
modifier = Modifier.align(Alignment.BottomStart),
|
||||
)
|
||||
|
||||
NetworkBadge(
|
||||
iconResId = content.networkBadgeIconResId,
|
||||
colorFilter = colorFilter,
|
||||
modifier = Modifier.align(Alignment.TopEnd),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Icon(content: TokenItemState.ContentState, colorFilter: ColorFilter?, modifier: Modifier = Modifier) {
|
||||
val iconUrl = content.tokenIconUrl
|
||||
val iconData: Any = remember(iconUrl) {
|
||||
if (iconUrl.isNullOrEmpty()) content.tokenIconResId else iconUrl
|
||||
}
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = modifier.iconSize(),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(data = iconData)
|
||||
.placeholder(drawableResId = content.tokenIconResId)
|
||||
.error(drawableResId = content.tokenIconResId)
|
||||
.fallback(drawableResId = content.tokenIconResId)
|
||||
.crossfade(enable = true)
|
||||
.build(),
|
||||
colorFilter = colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.NetworkBadge(
|
||||
@DrawableRes iconResId: Int?,
|
||||
colorFilter: ColorFilter?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = iconResId != null,
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size18)
|
||||
.background(color = TangemTheme.colors.background.primary, shape = CircleShape),
|
||||
) {
|
||||
if (iconResId == null) return@AnimatedVisibility
|
||||
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.padding(all = TangemTheme.dimens.spacing2)
|
||||
.align(Alignment.Center),
|
||||
painter = painterResource(id = iconResId),
|
||||
colorFilter = colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingIcon(modifier: Modifier = Modifier) {
|
||||
BaseContainer(modifier) {
|
||||
CircleShimmer(
|
||||
modifier = Modifier
|
||||
.iconSize()
|
||||
.align(alignment = Alignment.BottomStart),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LockedIcon(modifier: Modifier = Modifier) {
|
||||
BaseContainer(modifier) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.iconSize()
|
||||
.align(Alignment.BottomStart),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(color = TangemTheme.colors.background.secondary, shape = CircleShape),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private inline fun BaseContainer(modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit) {
|
||||
Box(modifier = modifier.size(size = TangemTheme.dimens.size40), content = content)
|
||||
}
|
||||
|
||||
private fun Modifier.iconSize(): Modifier = composed {
|
||||
return@composed this.size(size = TangemTheme.dimens.size36)
|
||||
}
|
||||
|
|
@ -14,92 +14,111 @@ internal sealed interface TokenItemState {
|
|||
/** Loading token state */
|
||||
data class Loading(override val id: String) : TokenItemState
|
||||
|
||||
/** Locked token state */
|
||||
data class Locked(override val id: String) : TokenItemState
|
||||
|
||||
/** Content state */
|
||||
sealed class ContentState(
|
||||
override val id: String,
|
||||
open val tokenIconUrl: String?,
|
||||
@DrawableRes open val tokenIconResId: Int,
|
||||
@DrawableRes open val networkBadgeIconResId: Int?,
|
||||
open val name: String,
|
||||
) : TokenItemState
|
||||
|
||||
/**
|
||||
* Content token state
|
||||
*
|
||||
* @property id unique id
|
||||
* @property tokenIconUrl token icon url
|
||||
* @property tokenIconResId token icon resource id
|
||||
* @property networkBadgeIconResId network badge icon resource id, may be null if it is a coin
|
||||
* @property name token name
|
||||
* @property amount amount of token
|
||||
* @property hasPending pending tx in blockchain
|
||||
* @property tokenOptions state for token options
|
||||
* @property isTestnet indicates whether the token is from test network or not
|
||||
* @property onItemClick callback which will be called when an item is clicked
|
||||
* @property onItemLongClick callback which will be called when an item is long clicked
|
||||
* @property id unique id
|
||||
* @property tokenIconUrl token icon url
|
||||
* @property tokenIconResId token icon resource id
|
||||
* @property networkBadgeIconResId network badge icon resource id, may be null if it is a coin
|
||||
* @property name token name
|
||||
* @property amount amount of token
|
||||
* @property hasPending pending tx in blockchain
|
||||
* @property tokenOptions state for token options
|
||||
* @property isTestnet indicates whether the token is from test network or not
|
||||
* @property onItemClick callback which will be called when an item is clicked
|
||||
* @property onItemLongClick callback which will be called when an item is long clicked
|
||||
*/
|
||||
data class Content(
|
||||
override val id: String,
|
||||
val tokenIconUrl: String?,
|
||||
@DrawableRes val tokenIconResId: Int,
|
||||
@DrawableRes val networkBadgeIconResId: Int?,
|
||||
val name: String,
|
||||
override val tokenIconUrl: String?,
|
||||
@DrawableRes override val tokenIconResId: Int,
|
||||
@DrawableRes override val networkBadgeIconResId: Int?,
|
||||
override val name: String,
|
||||
val amount: String,
|
||||
val hasPending: Boolean,
|
||||
val tokenOptions: TokenOptionsState,
|
||||
val isTestnet: Boolean,
|
||||
val onItemClick: () -> Unit,
|
||||
val onItemLongClick: () -> Unit,
|
||||
) : TokenItemState
|
||||
) : ContentState(id, tokenIconUrl, tokenIconResId, networkBadgeIconResId, name)
|
||||
|
||||
/**
|
||||
* Draggable token state
|
||||
*
|
||||
* @property id unique id
|
||||
* @property tokenIconUrl token icon url
|
||||
* @property tokenIconResId token icon resource id
|
||||
* @property networkBadgeIconResId network badge icon resource id, may be null if it is a coin
|
||||
* @property name token name
|
||||
* @property fiatAmount fiat amount of token
|
||||
* @property isTestnet indicates whether the token is from test network or not
|
||||
* @property id unique id
|
||||
* @property tokenIconUrl token icon url
|
||||
* @property tokenIconResId token icon resource id
|
||||
* @property networkBadgeIconResId network badge icon resource id, may be null if it is a coin
|
||||
* @property name token name
|
||||
* @property fiatAmount fiat amount of token
|
||||
* @property isTestnet indicates whether the token is from test network or not
|
||||
*/
|
||||
data class Draggable(
|
||||
override val id: String,
|
||||
val tokenIconUrl: String?,
|
||||
@DrawableRes val tokenIconResId: Int,
|
||||
@DrawableRes val networkBadgeIconResId: Int?,
|
||||
val name: String,
|
||||
override val tokenIconUrl: String?,
|
||||
@DrawableRes override val tokenIconResId: Int,
|
||||
@DrawableRes override val networkBadgeIconResId: Int?,
|
||||
override val name: String,
|
||||
val fiatAmount: String,
|
||||
val isTestnet: Boolean,
|
||||
) : TokenItemState
|
||||
) : ContentState(id, tokenIconUrl, tokenIconResId, networkBadgeIconResId, name)
|
||||
|
||||
/**
|
||||
* Unreachable token state
|
||||
*
|
||||
* @property id token id
|
||||
* @property tokenIconUrl token icon url
|
||||
* @property tokenIconResId token icon resource id
|
||||
* @property networkBadgeIconResId network badge icon resource id, may be null if it is a coin
|
||||
* @property name token name
|
||||
* @property id token id
|
||||
* @property tokenIconUrl token icon url
|
||||
* @property tokenIconResId token icon resource id
|
||||
* @property networkBadgeIconResId network badge icon resource id, may be null if it is a coin
|
||||
* @property name token name
|
||||
*/
|
||||
data class Unreachable(
|
||||
override val id: String,
|
||||
val tokenIconUrl: String?,
|
||||
@DrawableRes val tokenIconResId: Int,
|
||||
@DrawableRes val networkBadgeIconResId: Int?,
|
||||
val name: String,
|
||||
) : TokenItemState
|
||||
override val tokenIconUrl: String?,
|
||||
@DrawableRes override val tokenIconResId: Int,
|
||||
@DrawableRes override val networkBadgeIconResId: Int?,
|
||||
override val name: String,
|
||||
) : ContentState(id, tokenIconUrl, tokenIconResId, networkBadgeIconResId, name)
|
||||
|
||||
/** Token options state */
|
||||
@Immutable
|
||||
sealed interface TokenOptionsState {
|
||||
|
||||
val config: PriceChangeConfig
|
||||
|
||||
/**
|
||||
* Visible token options state
|
||||
*
|
||||
* @property fiatAmount fiat amount of token
|
||||
* @property priceChange value of price changing
|
||||
* @property fiatAmount fiat amount of token
|
||||
* @property config value of price changing
|
||||
*/
|
||||
data class Visible(val fiatAmount: String, val priceChange: PriceChangeConfig) : TokenOptionsState
|
||||
data class Visible(
|
||||
override val config: PriceChangeConfig,
|
||||
val fiatAmount: String,
|
||||
) : TokenOptionsState
|
||||
|
||||
/**
|
||||
* Hidden token options state
|
||||
*
|
||||
* @property priceChange value of price changing
|
||||
* @property config value of price changing
|
||||
*/
|
||||
data class Hidden(val priceChange: PriceChangeConfig) : TokenOptionsState
|
||||
data class Hidden(override val config: PriceChangeConfig) : TokenOptionsState
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DOTS = "•••"
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkGroupItem
|
||||
import com.tangem.feature.wallet.presentation.common.component.DraggableTokenItem
|
||||
import com.tangem.feature.wallet.presentation.common.component.TokenItem
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
|
||||
|
|
@ -151,7 +151,7 @@ private fun LazyItemScope.DraggableItem(
|
|||
networkName = item.networkName,
|
||||
reorderableTokenListState = reorderableState,
|
||||
)
|
||||
is DraggableItem.Token -> DraggableTokenItem(
|
||||
is DraggableItem.Token -> TokenItem(
|
||||
modifier = itemModifier,
|
||||
state = item.tokenItemState,
|
||||
reorderableTokenListState = reorderableState,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
|
|||
} else {
|
||||
TokenItemState.TokenOptionsState.Visible(
|
||||
fiatAmount = getFormattedFiatAmount(),
|
||||
priceChange = getPriceChangeConfig(),
|
||||
config = getPriceChangeConfig(),
|
||||
)
|
||||
},
|
||||
isTestnet = currency.network.isTestnet,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue