Updated on 2026-08-14
This commit is contained in:
parent
640e6433ff
commit
c1561a366c
9 changed files with 498 additions and 377 deletions
|
|
@ -153,7 +153,7 @@ internal object WalletPreviewData {
|
|||
icon = tokenIconState,
|
||||
name = "Polygon",
|
||||
amount = "5,412 MATIC",
|
||||
hasPending = true,
|
||||
hasPending = false,
|
||||
tokenOptions = TokenOptionsState(
|
||||
config = PriceChangeConfig(
|
||||
valueInPercent = "2%",
|
||||
|
|
@ -161,7 +161,6 @@ internal object WalletPreviewData {
|
|||
),
|
||||
fiatAmount = "321 $",
|
||||
isBalanceHidden = false,
|
||||
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
|
|
|
|||
|
|
@ -1,40 +1,40 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.constraintlayout.compose.ConstrainedLayoutReference
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.ConstraintLayoutScope
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import androidx.constraintlayout.compose.*
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
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.*
|
||||
import com.tangem.feature.wallet.presentation.common.component.token.icon.TokenIcon
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun TokenItem(
|
||||
state: TokenItemState,
|
||||
modifier: Modifier = Modifier,
|
||||
reorderableTokenListState: ReorderableLazyListState? = null,
|
||||
) {
|
||||
BaseContainer(modifier = modifier.tokenClickable(state)) {
|
||||
val (iconRef, cryptoInfoRef, fiatInfoRef) = createRefs()
|
||||
var rootWidth by remember { mutableStateOf(Int.MIN_VALUE) }
|
||||
|
||||
@Suppress("DestructuringDeclarationWithTooManyEntries")
|
||||
BaseContainer(
|
||||
modifier = modifier
|
||||
.tokenClickable(state)
|
||||
.onSizeChanged { rootWidth = it.width },
|
||||
) {
|
||||
val (iconRef, titleRef, cryptoAmountRef, fiatAmountRef, priceChangeRef, nonFiatContentRef) = createRefs()
|
||||
|
||||
TokenIcon(
|
||||
state = state,
|
||||
|
|
@ -44,22 +44,98 @@ internal fun TokenItem(
|
|||
},
|
||||
)
|
||||
|
||||
TokenCryptoInfoBlock(
|
||||
val density = LocalDensity.current
|
||||
val titleRequiredMinWidth by remember(rootWidth) {
|
||||
derivedStateOf { with(density) { rootWidth.toDp().times(other = 0.22f) } }
|
||||
}
|
||||
|
||||
TokenTitle(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing8)
|
||||
.constrainAs(cryptoInfoRef) {
|
||||
centerVerticallyTo(parent)
|
||||
.constrainAs(titleRef) {
|
||||
start.linkTo(iconRef.end)
|
||||
end.linkTo(fiatInfoRef.start)
|
||||
width = Dimension.fillToConstraints
|
||||
top.linkTo(parent.top)
|
||||
|
||||
width = Dimension.fillToConstraints.atLeast(dp = titleRequiredMinWidth)
|
||||
|
||||
when (state) {
|
||||
is TokenItemState.Content -> end.linkTo(fiatAmountRef.start)
|
||||
is TokenItemState.Draggable -> end.linkTo(nonFiatContentRef.start)
|
||||
is TokenItemState.Unreachable,
|
||||
is TokenItemState.NoAddress,
|
||||
-> {
|
||||
end.linkTo(nonFiatContentRef.start)
|
||||
bottom.linkTo(parent.bottom)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
TokenFiatInfoBlock(
|
||||
TokenFiatAmount(
|
||||
state = state,
|
||||
modifier = Modifier.constrainAs(fiatAmountRef) {
|
||||
top.linkTo(parent.top)
|
||||
end.linkTo(parent.end)
|
||||
|
||||
width = Dimension.fillToConstraints.atMostWrapContent
|
||||
|
||||
if (state is TokenItemState.Content) {
|
||||
start.linkTo(titleRef.end)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val marginBetweenRows = TangemTheme.dimens.spacing2
|
||||
TokenCryptoAmount(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing8)
|
||||
.constrainAs(cryptoAmountRef) {
|
||||
start.linkTo(iconRef.end)
|
||||
top.linkTo(titleRef.bottom, marginBetweenRows)
|
||||
bottom.linkTo(parent.bottom)
|
||||
|
||||
when (state) {
|
||||
is TokenItemState.Content -> {
|
||||
end.linkTo(priceChangeRef.start)
|
||||
width = Dimension.fillToConstraints.atMostWrapContent
|
||||
}
|
||||
is TokenItemState.Draggable -> {
|
||||
end.linkTo(nonFiatContentRef.start)
|
||||
width = Dimension.fillToConstraints
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val priceChangeRequiredMinWidth by remember(rootWidth) {
|
||||
derivedStateOf { with(density) { rootWidth.toDp().times(other = 0.16f) } }
|
||||
}
|
||||
TokenPriceChange(
|
||||
state = state,
|
||||
modifier = Modifier.constrainAs(priceChangeRef) {
|
||||
top.linkTo(fiatAmountRef.bottom, marginBetweenRows)
|
||||
end.linkTo(anchor = parent.end)
|
||||
bottom.linkTo(parent.bottom)
|
||||
|
||||
if (state is TokenItemState.ContentState) {
|
||||
start.linkTo(cryptoAmountRef.end)
|
||||
width = Dimension.fillToConstraints
|
||||
.atLeast(priceChangeRequiredMinWidth)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
NonFiatContentBlock(
|
||||
state = state,
|
||||
modifier = Modifier.constrainAsOptionsItem(scope = this, ref = fiatInfoRef),
|
||||
reorderableTokenListState = reorderableTokenListState,
|
||||
modifier = Modifier.constrainAs(nonFiatContentRef) {
|
||||
centerVerticallyTo(parent)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -77,57 +153,26 @@ private inline fun BaseContainer(
|
|||
ConstraintLayout(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing14,
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
),
|
||||
.padding(all = TangemTheme.dimens.spacing14),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
private fun Modifier.constrainAsOptionsItem(scope: ConstraintLayoutScope, ref: ConstrainedLayoutReference): Modifier {
|
||||
return with(scope) {
|
||||
this@constrainAsOptionsItem.constrainAs(ref) {
|
||||
centerVerticallyTo(parent)
|
||||
end.linkTo(parent.end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun Modifier.tokenClickable(state: TokenItemState): Modifier = composed {
|
||||
when (state) {
|
||||
is TokenItemState.Content -> {
|
||||
val onLongClick = rememberHapticFeedback(
|
||||
state = state,
|
||||
onAction = state.onItemLongClick,
|
||||
)
|
||||
this.combinedClickable(
|
||||
onClick = state.onItemClick,
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
val onLongClick = rememberHapticFeedback(state = state, onAction = state.onItemLongClick)
|
||||
combinedClickable(onClick = state.onItemClick, onLongClick = onLongClick)
|
||||
}
|
||||
is TokenItemState.Unreachable -> {
|
||||
val onLongClick = rememberHapticFeedback(
|
||||
state = state,
|
||||
onAction = state.onItemLongClick,
|
||||
)
|
||||
this.combinedClickable(
|
||||
onClick = state.onItemClick,
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
val onLongClick = rememberHapticFeedback(state = state, onAction = state.onItemLongClick)
|
||||
combinedClickable(onClick = state.onItemClick, onLongClick = onLongClick)
|
||||
}
|
||||
is TokenItemState.NoAddress -> {
|
||||
val onLongClick = rememberHapticFeedback(
|
||||
state = state,
|
||||
onAction = state.onItemLongClick,
|
||||
)
|
||||
this.combinedClickable(
|
||||
onClick = {},
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
val onLongClick = rememberHapticFeedback(state = state, onAction = state.onItemLongClick)
|
||||
combinedClickable(onClick = {}, onLongClick = onLongClick)
|
||||
}
|
||||
is TokenItemState.Draggable,
|
||||
is TokenItemState.Loading,
|
||||
|
|
@ -155,6 +200,22 @@ private fun Preview_Tokens_DarkTheme(@PreviewParameter(TokenConfigProvider::clas
|
|||
|
||||
private class TokenConfigProvider : CollectionPreviewParameterProvider<TokenItemState>(
|
||||
collection = listOf(
|
||||
WalletPreviewData.tokenItemVisibleState.copy(amount = "5,41221467146712416241274127841274174213421 MATIC"),
|
||||
WalletPreviewData.tokenItemVisibleState.copy(
|
||||
tokenOptions = WalletPreviewData.tokenItemVisibleState.tokenOptions.copy(
|
||||
config = WalletPreviewData.tokenItemVisibleState.tokenOptions.config.copy(
|
||||
valueInPercent = "31231231231231231231223123123123212312312312.00%",
|
||||
),
|
||||
),
|
||||
),
|
||||
WalletPreviewData.tokenItemVisibleState.copy(
|
||||
amount = "5,41221467146712416241274127841274174213421 MATIC",
|
||||
tokenOptions = WalletPreviewData.tokenItemVisibleState.tokenOptions.copy(
|
||||
config = WalletPreviewData.tokenItemVisibleState.tokenOptions.config.copy(
|
||||
valueInPercent = "31231231231231231231223123123123212312312312.00%",
|
||||
),
|
||||
),
|
||||
),
|
||||
WalletPreviewData.tokenItemVisibleState,
|
||||
WalletPreviewData.tokenItemUnreachableState,
|
||||
WalletPreviewData.tokenItemNoAddressState,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
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 org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
internal fun NonFiatContentBlock(
|
||||
state: TokenItemState,
|
||||
reorderableTokenListState: ReorderableLazyListState?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = state,
|
||||
label = "Update non content fiat block",
|
||||
modifier = modifier,
|
||||
) { animatedState ->
|
||||
when (animatedState) {
|
||||
is TokenItemState.Draggable -> DraggableImage(reorderableTokenListState = reorderableTokenListState)
|
||||
is TokenItemState.Unreachable -> NonFiatContentText(text = R.string.common_unreachable)
|
||||
is TokenItemState.NoAddress -> NonFiatContentText(text = R.string.common_no_address)
|
||||
is TokenItemState.Content,
|
||||
is TokenItemState.Loading,
|
||||
is TokenItemState.Locked,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DraggableImage(reorderableTokenListState: ReorderableLazyListState?) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(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 NonFiatContentText(@StringRes text: Int) {
|
||||
Text(
|
||||
text = stringResource(id = text),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTypography.body2,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.common.Strings
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
internal fun TokenCryptoAmount(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(targetState = state, label = "Update crypto amount", modifier = modifier) { animatedState ->
|
||||
when (animatedState) {
|
||||
is TokenItemState.Content -> {
|
||||
CryptoAmountText(
|
||||
amount = if (animatedState.tokenOptions.isBalanceHidden) Strings.STARS else animatedState.amount,
|
||||
)
|
||||
}
|
||||
is TokenItemState.Draggable -> {
|
||||
CryptoAmountText(amount = animatedState.info.resolveReference())
|
||||
}
|
||||
is TokenItemState.Loading -> {
|
||||
RectangleShimmer(modifier = Modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
}
|
||||
is TokenItemState.Locked -> {
|
||||
LockedRectangle(modifier = Modifier.placeholderSize())
|
||||
}
|
||||
is TokenItemState.Unreachable,
|
||||
is TokenItemState.NoAddress,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CryptoAmountText(amount: String) {
|
||||
Text(
|
||||
text = amount,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTypography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Modifier.placeholderSize(): Modifier = composed {
|
||||
return@composed this
|
||||
.padding(vertical = TangemTheme.dimens.spacing4)
|
||||
.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12)
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
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.common.Strings.STARS
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
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
|
||||
|
||||
@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.isBalanceHidden) STARS else state.amount
|
||||
is TokenItemState.Draggable -> state.info.resolveReference()
|
||||
is TokenItemState.Unreachable,
|
||||
is TokenItemState.NoAddress,
|
||||
-> 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,54 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.common.Strings
|
||||
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.presentation.common.state.TokenItemState
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
internal fun TokenFiatAmount(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(targetState = state, label = "Update fiat amount", modifier = modifier) { animatedState ->
|
||||
when (animatedState) {
|
||||
is TokenItemState.Content -> {
|
||||
Text(
|
||||
text = if (animatedState.tokenOptions.isBalanceHidden) {
|
||||
Strings.STARS
|
||||
} else {
|
||||
animatedState.tokenOptions.fiatAmount
|
||||
},
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTypography.body2,
|
||||
)
|
||||
}
|
||||
is TokenItemState.Loading -> {
|
||||
RectangleShimmer(modifier = Modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
}
|
||||
is TokenItemState.Locked -> {
|
||||
LockedRectangle(modifier = Modifier.placeholderSize())
|
||||
}
|
||||
is TokenItemState.Unreachable,
|
||||
is TokenItemState.Draggable,
|
||||
is TokenItemState.NoAddress,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.placeholderSize(): Modifier = composed {
|
||||
return@composed this
|
||||
.padding(vertical = TangemTheme.dimens.spacing4)
|
||||
.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12)
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
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.common.Strings.STARS
|
||||
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.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.NoAddress -> NoAddressBlock(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 = if (it.isBalanceHidden) {
|
||||
STARS
|
||||
} else {
|
||||
it.fiatAmount
|
||||
},
|
||||
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,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = when (it) {
|
||||
PriceChangeConfig.Type.UP -> R.drawable.ic_arrow_up_8
|
||||
PriceChangeConfig.Type.DOWN -> R.drawable.ic_arrow_down_8
|
||||
},
|
||||
),
|
||||
tint = when (it) {
|
||||
PriceChangeConfig.Type.UP -> TangemTheme.colors.icon.accent
|
||||
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.icon.warning
|
||||
},
|
||||
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 NoAddressBlock(modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = stringResource(id = R.string.common_no_address),
|
||||
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,102 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
internal fun TokenPriceChange(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(targetState = state, label = "Update the price change", modifier = modifier) { animatedState ->
|
||||
when (animatedState) {
|
||||
is TokenItemState.Content -> {
|
||||
PriceChangeBlock(config = animatedState.tokenOptions.config)
|
||||
}
|
||||
is TokenItemState.Loading -> {
|
||||
RectangleShimmer(modifier = Modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
}
|
||||
is TokenItemState.Locked -> {
|
||||
LockedRectangle(modifier = Modifier.placeholderSize())
|
||||
}
|
||||
is TokenItemState.Unreachable,
|
||||
is TokenItemState.Draggable,
|
||||
is TokenItemState.NoAddress,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PriceChangeBlock(config: PriceChangeConfig) {
|
||||
Row(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,
|
||||
) { animatedType ->
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = when (animatedType) {
|
||||
PriceChangeConfig.Type.UP -> R.drawable.ic_arrow_up_8
|
||||
PriceChangeConfig.Type.DOWN -> R.drawable.ic_arrow_down_8
|
||||
},
|
||||
),
|
||||
tint = when (animatedType) {
|
||||
PriceChangeConfig.Type.UP -> TangemTheme.colors.icon.accent
|
||||
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.icon.warning
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PriceChangeText(config: PriceChangeConfig, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = config.valueInPercent,
|
||||
modifier = modifier,
|
||||
color = when (config.type) {
|
||||
PriceChangeConfig.Type.UP -> TangemTheme.colors.text.accent
|
||||
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.text.warning
|
||||
},
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTypography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Modifier.placeholderSize(): Modifier = composed {
|
||||
return@composed this
|
||||
.padding(vertical = TangemTheme.dimens.spacing4)
|
||||
.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12)
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
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.text.style.TextOverflow
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
internal fun TokenTitle(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(targetState = state, label = "Update title", modifier = modifier) { animatedState ->
|
||||
when (animatedState) {
|
||||
is TokenItemState.ContentState -> {
|
||||
ContentTitle(
|
||||
name = animatedState.name,
|
||||
hasPending = (animatedState as? TokenItemState.Content)?.hasPending == true,
|
||||
)
|
||||
}
|
||||
is TokenItemState.Loading -> {
|
||||
RectangleShimmer(modifier = Modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
}
|
||||
is TokenItemState.Locked -> {
|
||||
LockedRectangle(modifier = Modifier.placeholderSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentTitle(name: String, hasPending: Boolean) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) {
|
||||
CurrencyNameText(name = name, modifier = Modifier.weight(weight = 1f, fill = false))
|
||||
|
||||
PendingTransactionImage(
|
||||
hasPending = hasPending,
|
||||
modifier = Modifier.align(alignment = Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CurrencyNameText(name: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = name,
|
||||
modifier = modifier,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTypography.subtitle2,
|
||||
)
|
||||
}
|
||||
|
||||
@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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.placeholderSize(): Modifier = composed {
|
||||
return@composed this
|
||||
.padding(vertical = TangemTheme.dimens.spacing4)
|
||||
.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue