Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-07 14:09:21 +03:00
parent 1a4e8d8f20
commit 17f89eebd5
23 changed files with 1001 additions and 501 deletions

View file

@ -471,6 +471,7 @@
<string name="wallet_error_no_account">Аккаунт не создан</string>
<string name="wallet_error_unsupported_blockchain">Эта карта не поддерживается</string>
<string name="wallet_error_unsupported_blockchain_subtitle">Ваша карта Tangem предназначена для работы с другим приложением. Пожалуйста, ознакомьтесь с названием и инструкциями на вашей карте и установите правильное приложение.</string>
<string name="wallet_network_group_title">Сеть %s</string>
<string name="wallet_notification_address_copied">Адрес скопирован в буфер обмена</string>
<string name="wallet_notification_no_internet">Нет соединения с интернетом</string>
<string name="wallet_pending_tx_receiving">Получение</string>

View file

@ -463,6 +463,7 @@
<string name="wallet_error_no_account">Account is not created</string>
<string name="wallet_error_unsupported_blockchain">This card is not supported</string>
<string name="wallet_error_unsupported_blockchain_subtitle">Your Tangem card was made to work with a different application. Please see the name and instructions on your card, and install the correct app</string>
<string name="wallet_network_group_title">%s network</string>
<string name="wallet_notification_address_copied">Address was copied to clipboard</string>
<string name="wallet_notification_no_internet">No internet connection</string>
<string name="wallet_pending_tx_receiving">Receiving</string>

View file

@ -1,29 +1,9 @@
package com.tangem.core.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Button
import androidx.compose.material.ButtonColors
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ButtonElevation
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -244,6 +224,78 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif
}
// endregion Other
// region Action
/**
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-305&t=3z98eFnTeyIx5TH5-4)
* */
@Composable
fun RoundedActionButton(
text: String,
icon: Painter,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Left(icon),
onClick = onClick,
enabled = enabled,
showProgress = false,
colors = TangemButtonsDefaults.secondaryButtonColors,
size = TangemButtonSize.RoundedAction,
)
}
/**
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1208-1395&t=3z98eFnTeyIx5TH5-4)
* */
@Composable
fun ActionButton(
text: String,
icon: Painter,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Left(icon),
onClick = onClick,
enabled = enabled,
showProgress = false,
colors = TangemButtonsDefaults.secondaryButtonColors,
size = TangemButtonSize.Action,
)
}
/**
* Same as [RoundedActionButton] but colored in primary background color
* */
@Composable
fun BackgroundActionButton(
text: String,
icon: Painter,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Left(icon),
onClick = onClick,
enabled = enabled,
showProgress = false,
colors = TangemButtonsDefaults.backgroundButtonColors,
size = TangemButtonSize.RoundedAction,
)
}
// endregion Action
// region Defaults
@Suppress("LongParameterList")
@Composable
@ -335,13 +387,12 @@ private fun ButtonContent(
}
}
sealed interface TangemButtonIcon {
@Immutable
private sealed interface TangemButtonIcon {
val painter: Painter?
@Immutable
data class Left(override val painter: Painter) : TangemButtonIcon
@Immutable
data class Right(override val painter: Painter) : TangemButtonIcon
object None : TangemButtonIcon {
@ -349,10 +400,12 @@ sealed interface TangemButtonIcon {
}
}
enum class TangemButtonSize {
private enum class TangemButtonSize {
Default,
Text,
Selector,
Action,
RoundedAction,
}
@Composable
@ -360,6 +413,9 @@ private fun TangemButtonSize.toHeightDp(): Dp = when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.size48
TangemButtonSize.Text -> TangemTheme.dimens.size40
TangemButtonSize.Selector -> TangemTheme.dimens.size24
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> TangemTheme.dimens.size36
}
@Composable
@ -367,6 +423,8 @@ private fun TangemButtonSize.toShape(): Shape = when (this) {
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge
}
@Composable
@ -374,6 +432,9 @@ private fun TangemButtonSize.toIconPadding(): Dp = when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
TangemButtonSize.Selector -> 0.dp
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> TangemTheme.dimens.spacing8
}
object TangemButtonsDefaults {
@ -423,6 +484,14 @@ object TangemButtonsDefaults {
disabledBackgroundColor = Color.Transparent,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val backgroundButtonColors: ButtonColors
@Composable get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.background.primary,
contentColor = TangemTheme.colors.text.primary1,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
)
}
@Immutable
@ -650,4 +719,67 @@ private fun TextButtonPreview_Dark() {
TextButtonSample()
}
}
@Composable
private fun ActionButtonSample(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.background(TangemTheme.colors.background.primary),
) {
RoundedActionButton(
text = "Send",
icon = painterResource(id = R.drawable.ic_arrow_up_24),
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
ActionButton(
text = "Send",
icon = painterResource(id = R.drawable.ic_arrow_up_24),
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
BackgroundActionButton(
text = "Send",
icon = painterResource(id = R.drawable.ic_arrow_up_24),
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
RoundedActionButton(
text = "Send",
icon = painterResource(id = R.drawable.ic_arrow_up_24),
enabled = false,
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
ActionButton(
text = "Send",
icon = painterResource(id = R.drawable.ic_arrow_up_24),
enabled = false,
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
BackgroundActionButton(
text = "Send",
icon = painterResource(id = R.drawable.ic_arrow_up_24),
enabled = false,
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun ActionButtonPreview_Light() {
TangemTheme {
ActionButtonSample()
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun ActionButtonPreview_Dark() {
TangemTheme(isDark = true) {
ActionButtonSample()
}
}
// endregion Preview

View file

@ -14,7 +14,7 @@ import com.valentinilk.shimmer.shimmer
* Rectangle shimmer item with rounded shape from DS
*/
@Composable
fun ShimmerRectangle(modifier: Modifier = Modifier) {
fun RectangleShimmer(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.shimmer()
@ -51,7 +51,7 @@ private fun ShimmersPreview() {
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
) {
ShimmerRectangle(
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size72,
height = TangemTheme.dimens.size12,

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:fillType="evenOdd"
android:pathData="M7,8H5V10H7V8ZM13,8H11V10H13V8ZM17,8H19V10H17V8ZM7,14H5V16H7V14ZM11,14H13V16H11V14ZM19,14H17V16H19V14Z" />
</vector>

View file

@ -22,6 +22,7 @@ dependencies {
implementation(deps.compose.ui.tooling)
implementation(deps.compose.shimmer)
implementation(deps.compose.accompanist.systemUiController)
implementation(deps.kotlin.immutable.collections)
/** DI */
implementation(deps.hilt.android)

View file

@ -0,0 +1,161 @@
package com.tangem.feature.wallet.presentation.common
import com.tangem.core.ui.R
import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import kotlinx.collections.immutable.persistentListOf
import java.util.UUID
internal object WalletPreviewData {
val walletCardContent = WalletCardState.Content(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
balance = "8923,05 $",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardLoading = WalletCardState.Loading(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardHiddenContent = WalletCardState.HiddenContent(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardError = WalletCardState.Error(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletScreenState = WalletStateHolder(
onBackClick = {},
headerConfig = WalletStateHolder.HeaderConfig(
wallets = persistentListOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError),
onScanCardClick = {},
onMoreClick = {},
),
)
val tokenItemVisibleState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
tokenIconUrl = null,
tokenIconResId = R.drawable.img_polygon_22,
networkIconResId = R.drawable.img_polygon_22,
name = "Polygon",
amount = "5,412 MATIC",
hasPending = true,
tokenOptions = TokenOptionsState.Visible(
fiatAmount = "321 $",
priceChange = TokenOptionsState.PriceChange(
valuePercent = "2%",
type = TokenOptionsState.PriceChange.Type.UP,
),
),
)
val tokenItemHiddenState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
tokenIconUrl = null,
tokenIconResId = R.drawable.img_polygon_22,
networkIconResId = R.drawable.img_polygon_22,
name = "Polygon",
amount = "5,412 MATIC",
hasPending = true,
tokenOptions = TokenOptionsState.Hidden(
priceChange = TokenOptionsState.PriceChange(
valuePercent = "2%",
type = TokenOptionsState.PriceChange.Type.UP,
),
),
)
val tokenItemDragState = TokenItemState.Draggable(
id = UUID.randomUUID().toString(),
tokenIconUrl = null,
tokenIconResId = R.drawable.img_polygon_22,
networkIconResId = R.drawable.img_polygon_22,
name = "Polygon",
fiatAmount = "3 172,14 $",
)
val tokenItemUnreachableState = TokenItemState.Unreachable(
id = UUID.randomUUID().toString(),
tokenIconUrl = null,
tokenIconResId = R.drawable.img_polygon_22,
networkIconResId = R.drawable.img_polygon_22,
name = "Polygon",
)
val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString())
private val draggableTokenList = persistentListOf(
tokenItemDragState.copy(
id = "token_1",
name = "Ethereum",
tokenIconResId = R.drawable.img_eth_22,
networkIconResId = null,
fiatAmount = "3 172,14 $",
),
tokenItemDragState.copy(
id = "token_2",
networkIconResId = R.drawable.img_eth_22,
fiatAmount = "803,65 $",
),
tokenItemDragState.copy(
id = "token_3",
name = "USDT",
tokenIconResId = R.drawable.img_arbitrum_22,
networkIconResId = R.drawable.img_eth_22,
fiatAmount = "88,01 $",
),
)
val networkGroup = NetworkGroupState.Content(
id = UUID.randomUUID().toString(),
networkName = "Ethereum",
tokens = persistentListOf(
tokenItemVisibleState.copy(
id = "token_1",
name = "Ethereum",
tokenIconResId = R.drawable.img_eth_22,
networkIconResId = null,
amount = "1,89340821 ETH",
),
tokenItemVisibleState.copy(
id = "token_2",
networkIconResId = R.drawable.img_eth_22,
amount = "733,71097 MATIC",
),
tokenItemVisibleState.copy(
id = "token_3",
name = "USDT",
tokenIconResId = R.drawable.img_arbitrum_22,
networkIconResId = R.drawable.img_eth_22,
amount = "0,25404523 ARB",
),
),
)
val draggableNetworkGroup = NetworkGroupState.Draggable(
id = UUID.randomUUID().toString(),
networkName = "Ethereum",
tokens = draggableTokenList,
)
}

View file

@ -0,0 +1,79 @@
package com.tangem.feature.wallet.presentation.common.component
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
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.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
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.state.NetworkGroupState
@Composable
internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Row(
modifier = Modifier
.background(TangemTheme.colors.background.primary)
.padding(horizontal = TangemTheme.dimens.spacing12)
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size48),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(id = R.string.wallet_network_group_title, state.networkName),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
if (state is NetworkGroupState.Draggable) {
Icon(
painter = painterResource(id = R.drawable.ic_group_drop_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
Column {
state.tokens.forEach { token ->
key(token.id) {
TokenItem(state = token)
}
}
}
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) {
TangemTheme {
NetworkGroupItem(group)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) {
TangemTheme(isDark = true) {
NetworkGroupItem(group)
}
}
private class NetworkGroupProvider : CollectionPreviewParameterProvider<NetworkGroupState>(
collection = listOf(
WalletPreviewData.networkGroup,
WalletPreviewData.draggableNetworkGroup,
),
)
// endregion Preview

View file

@ -0,0 +1,412 @@
package com.tangem.feature.wallet.presentation.common.component
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
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.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.state.TokenItemState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
private const val DOTS = "•••"
@Composable
internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) {
when (state) {
is TokenItemState.Content -> ContentTokenItem(state, modifier)
is TokenItemState.Loading -> LoadingTokenItem(modifier)
is TokenItemState.Draggable -> DraggableTokenItem(state, modifier)
is TokenItemState.Unreachable -> UnreachableTokenItem(state, modifier)
}
}
@Composable
private fun ContentTokenItem(content: TokenItemState.Content, modifier: Modifier = Modifier) {
InternalTokenItem(
modifier = modifier,
name = content.name,
tokenIconUrl = content.tokenIconUrl,
tokenIconResId = content.tokenIconResId,
networkIconResId = content.networkIconResId,
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,
)
},
)
}
@Composable
internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modifier = Modifier) {
InternalTokenItem(
modifier = modifier,
name = state.name,
tokenIconUrl = state.tokenIconUrl,
tokenIconResId = state.tokenIconResId,
networkIconResId = state.networkIconResId,
amount = state.fiatAmount,
hasPending = false,
options = { ref ->
Icon(
modifier = Modifier.constrainAsOptionsItem(scope = this, ref),
painter = painterResource(id = R.drawable.ic_drag_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
},
)
}
@Composable
internal fun UnreachableTokenItem(state: TokenItemState.Unreachable, modifier: Modifier = Modifier) {
InternalTokenItem(
modifier = modifier,
name = state.name,
tokenIconUrl = state.tokenIconUrl,
tokenIconResId = state.tokenIconResId,
networkIconResId = state.networkIconResId,
amount = null,
hasPending = false,
options = { ref ->
Text(
modifier = Modifier.constrainAsOptionsItem(scope = this, ref),
text = "Unreachable", // TODO (conform this text)
style = TangemTypography.body2,
color = TangemTheme.colors.text.tertiary,
)
},
)
}
@Composable
private fun LoadingTokenItem(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.spacing12),
) {
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,
),
)
}
}
}
}
}
/**
* Block for end part of token item
* shows status is reachable, is drag, hidden or show balance
*/
@Composable
private fun TokenOptionsBlock(state: TokenOptionsState, modifier: Modifier = Modifier) {
when (state) {
is TokenOptionsState.Visible -> {
TokenFiatPercentageBlock(
modifier = modifier,
fiatAmount = state.fiatAmount,
priceChange = state.priceChange,
)
}
is TokenOptionsState.Hidden -> {
TokenFiatPercentageBlock(
modifier = modifier,
fiatAmount = DOTS,
priceChange = state.priceChange,
)
}
}
}
@Suppress("LongParameterList")
@Composable
private fun InternalTokenItem(
name: String,
tokenIconUrl: String?,
@DrawableRes tokenIconResId: Int,
@DrawableRes networkIconResId: Int?,
amount: String?,
hasPending: Boolean,
options: @Composable ConstraintLayoutScope.(ref: ConstrainedLayoutReference) -> Unit,
modifier: Modifier = Modifier,
) {
BaseSurface(modifier) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing4,
),
) {
val (iconItem, tokenNameItem, optionsItem) = createRefs()
TokenIcon(
modifier = Modifier.constrainAs(iconItem) {
centerVerticallyTo(parent)
start.linkTo(parent.start)
},
tokenIconUrl = tokenIconUrl,
tokenIconResId = tokenIconResId,
networkIconRes = networkIconResId,
)
TokenTitleAmountBlock(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing12)
.constrainAs(tokenNameItem) {
centerVerticallyTo(parent)
start.linkTo(iconItem.end)
end.linkTo(optionsItem.start)
width = Dimension.fillToConstraints
},
title = name,
amount = amount,
hasPending = hasPending,
)
options(optionsItem)
}
}
}
@Stable
private fun Modifier.constrainAsOptionsItem(scope: ConstraintLayoutScope, ref: ConstrainedLayoutReference): Modifier {
return with(scope) {
this@constrainAsOptionsItem.constrainAs(ref) {
centerVerticallyTo(parent)
end.linkTo(parent.end)
}
}
}
@Composable
private fun BaseSurface(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
content: @Composable () -> Unit,
) {
Surface(
modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68),
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,
)
if (hasPending) {
Image(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = R.drawable.img_loader_15),
contentDescription = null,
)
}
}
if (!amount.isNullOrBlank()) {
Text(
text = amount,
style = TangemTypography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Composable
private fun TokenFiatPercentageBlock(
fiatAmount: String,
priceChange: TokenOptionsState.PriceChange,
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,
) {
val iconChangeArrow: Int
val changeTextColor: Color
when (priceChange.type) {
TokenOptionsState.PriceChange.Type.UP -> {
iconChangeArrow = R.drawable.img_arrow_up_8
changeTextColor = TangemTheme.colors.text.accent
}
TokenOptionsState.PriceChange.Type.DOWN -> {
iconChangeArrow = R.drawable.img_arrow_down_8
changeTextColor = TangemTheme.colors.text.warning
}
}
Image(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = iconChangeArrow),
contentDescription = null,
)
SpacerW4()
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = priceChange.valuePercent,
style = TangemTypography.body2,
color = changeTextColor,
)
}
}
}
@Composable
private fun TokenIcon(
tokenIconUrl: String?,
modifier: Modifier = Modifier,
@DrawableRes tokenIconResId: Int? = null,
@DrawableRes networkIconRes: 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 data = if (tokenIconUrl.isNullOrEmpty()) tokenIconResId else tokenIconUrl
SubcomposeAsyncImage(
modifier = tokenImageModifier,
model = ImageRequest.Builder(LocalContext.current)
.data(data)
.crossfade(true)
.build(),
loading = { CircleShimmer(modifier = tokenImageModifier) },
contentDescription = null,
)
if (networkIconRes != null) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(TangemTheme.dimens.size18)
.background(color = Color.White, shape = CircleShape),
contentAlignment = Alignment.Center,
) {
Image(
modifier = Modifier.padding(all = 0.5.dp),
painter = painterResource(id = networkIconRes),
contentDescription = null,
)
}
}
}
}
// region preview
@Preview
@Composable
private fun Preview_Tokens_LightTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
TangemTheme(isDark = false) {
TokenItem(state)
}
}
@Preview
@Composable
private fun Preview_Tokens_DarkTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
TangemTheme(isDark = true) {
TokenItem(state)
}
}
private class TokenConfigProvider : CollectionPreviewParameterProvider<TokenItemState>(
collection = listOf(
WalletPreviewData.tokenItemVisibleState,
WalletPreviewData.tokenItemUnreachableState,
WalletPreviewData.tokenItemDragState,
WalletPreviewData.tokenItemHiddenState,
WalletPreviewData.loadingTokenItemState,
),
)
// endregion preview

View file

@ -0,0 +1,111 @@
package com.tangem.feature.wallet.presentation.common.state
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState.PriceChange.Type
/** Token item state */
@Immutable
internal sealed interface TokenItemState {
/** Token id */
val id: String
/**
* Loading token state
*
* @property id token id
*/
data class Loading(override val id: String) : TokenItemState
/**
* Content token state
*
* @property id token id
* @property tokenIconUrl token icon url
* @property tokenIconResId token icon resource id
* @property networkIconResId network 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
*/
data class Content(
override val id: String,
val tokenIconUrl: String?,
@DrawableRes val tokenIconResId: Int,
@DrawableRes val networkIconResId: Int?,
val name: String,
val amount: String,
val hasPending: Boolean,
val tokenOptions: TokenOptionsState,
) : TokenItemState
/**
* Draggable token state
*
* @property id token id
* @property tokenIconUrl token icon url
* @property tokenIconResId token icon resource id
* @property networkIconResId network icon resource id, may be null if it is a coin
* @property name token name
*/
data class Draggable(
override val id: String,
val tokenIconUrl: String?,
@DrawableRes val tokenIconResId: Int,
@DrawableRes val networkIconResId: Int?,
val name: String,
val fiatAmount: String,
) : TokenItemState
/**
* Unreachable token state
*
* @property id token id
* @property tokenIconUrl token icon url
* @property tokenIconResId token icon resource id
* @property networkIconResId network 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 networkIconResId: Int?,
val name: String,
) : TokenItemState
/** Token options state */
sealed interface TokenOptionsState {
/**
* Visible token options state
*
* @property fiatAmount fiat amount of token
* @property priceChange value of price changing
*/
data class Visible(val fiatAmount: String, val priceChange: PriceChange) : TokenOptionsState
/**
* Hidden token options state
*
* @property priceChange value of price changing
*/
data class Hidden(val priceChange: PriceChange) : TokenOptionsState
/**
* Price changing option state
*
* @property valuePercent value in percent
* @property type type [Type]
*/
data class PriceChange(val valuePercent: String, val type: Type) {
/** Price changing type */
enum class Type {
UP, DOWN
}
}
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.feature.wallet.presentation.common.state
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal sealed interface TokenListState {
data class GroupedByNetwork(
val groups: ImmutableList<NetworkGroupState>,
) : TokenListState
data class Ungrouped(
val tokens: ImmutableList<TokenItemState>,
) : TokenListState
}
@Immutable
internal sealed interface NetworkGroupState {
val id: String
val networkName: String
val tokens: ImmutableList<TokenItemState>
data class Draggable(
override val id: String,
override val networkName: String,
override val tokens: ImmutableList<TokenItemState.Draggable>,
) : NetworkGroupState
data class Content(
override val id: String,
override val networkName: String,
override val tokens: ImmutableList<TokenItemState>,
) : NetworkGroupState
}

View file

@ -10,8 +10,8 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.WalletFragment
import com.tangem.feature.wallet.presentation.ui.WalletScreen
import com.tangem.feature.wallet.presentation.viewmodels.WalletViewModel
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */

View file

@ -1,13 +1,18 @@
package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import com.tangem.features.wallet.navigation.WalletRouter
/**
* Interface of inner wallet feature router
*
* Annotated as [Stable] because implementation of this interface passed as argument to [Initialize] method by compose
* compiler
*
[REDACTED_AUTHOR]
*/
@Stable
internal interface InnerWalletRouter : WalletRouter {
/** Initialize router */

View file

@ -1,9 +0,0 @@
package com.tangem.feature.wallet.presentation.state
internal enum class TokenUIState {
LOADED, LOADING
}
internal enum class TokenOptionsUIState {
VISIBLE, HIDDEN, DRAG, UNREACHABLE
}

View file

@ -1,39 +0,0 @@
package com.tangem.feature.wallet.presentation.state
import androidx.annotation.DrawableRes
/**
* Token config
*
* @property name of token/coin
* @property amount of token
* @property fiatAmount amount in fiat
* @property priceChange value of price changing
* @property iconUrl
* @property icon token/coin icon
* @property networkIcon
* @property tokenUIState token state [TokenUIState] loading etc.
* @property tokenOptionsUIState state for token options like 'unreachable', 'hidden' etc
* @property hasPending pending tx in blockchain
*/
internal data class TokenV2Config(
val name: String,
val amount: String,
val fiatAmount: String,
val priceChange: PriceChange,
val iconUrl: String?,
@DrawableRes val icon: Int?,
@DrawableRes val networkIcon: Int,
val tokenUIState: TokenUIState,
val tokenOptionsUIState: TokenOptionsUIState,
val hasPending: Boolean,
)
data class PriceChange(
val valuePercent: String,
val type: PriceChangeType,
)
enum class PriceChangeType {
UP, DOWN
}

View file

@ -1,352 +0,0 @@
package com.tangem.feature.wallet.presentation.ui
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.*
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.state.*
private const val DOTS = "•••"
@Composable
internal fun TokenItemV2(config: TokenV2Config) {
when (config.tokenUIState) {
TokenUIState.LOADED -> LoadedTokenState(config)
TokenUIState.LOADING -> LoadingTokenState()
}
}
@Composable
private fun LoadedTokenState(config: TokenV2Config) {
BaseSurface {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing4,
),
) {
val (iconItem, tokenNameItem, fiatItem) = createRefs()
TokenIcon(
modifier = Modifier.constrainAs(iconItem) {
centerVerticallyTo(parent)
start.linkTo(parent.start)
},
tokenIconUrl = config.iconUrl,
icon = config.icon,
networkIconRes = config.networkIcon,
)
TokenTitleAmountBlock(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing12)
.constrainAs(tokenNameItem) {
centerVerticallyTo(parent)
start.linkTo(iconItem.end)
end.linkTo(fiatItem.start)
width = Dimension.fillToConstraints
},
title = config.name,
amount = config.amount,
hasPending = config.hasPending,
)
TokenOptionsBlock(
config = config,
modifier = Modifier.constrainAs(fiatItem) {
centerVerticallyTo(parent)
end.linkTo(parent.end)
},
)
}
}
}
/**
* Block for end part of token item
* shows status is reachable, is drag, hidden or show balance
*/
@Composable
private fun TokenOptionsBlock(config: TokenV2Config, modifier: Modifier = Modifier) {
when (config.tokenOptionsUIState) {
TokenOptionsUIState.VISIBLE -> TokenFiatPercentageBlock(
modifier = modifier,
fiatAmount = config.fiatAmount,
priceChange = config.priceChange,
)
TokenOptionsUIState.UNREACHABLE -> Text(
modifier = modifier,
text = "Unreachable", // TODO (conform this text)
style = TangemTypography.body2,
color = TangemTheme.colors.text.tertiary,
)
TokenOptionsUIState.HIDDEN -> TokenFiatPercentageBlock(
modifier = modifier,
fiatAmount = DOTS,
priceChange = config.priceChange,
)
TokenOptionsUIState.DRAG -> Icon(
modifier = modifier,
painter = painterResource(id = R.drawable.ic_drag_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
@Composable
private fun LoadingTokenState() {
BaseSurface {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing4,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42))
SpacerW12()
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size72,
height = TangemTheme.dimens.size12,
),
)
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size50,
height = TangemTheme.dimens.size12,
),
)
}
}
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size40,
height = TangemTheme.dimens.size12,
),
)
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size40,
height = TangemTheme.dimens.size12,
),
)
}
}
}
@Composable
private fun BaseSurface(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) {
Surface(
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68),
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,
)
if (hasPending) {
Image(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = R.drawable.img_loader_15),
contentDescription = null,
)
}
}
Text(
text = amount,
style = TangemTypography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
}
@Composable
private fun TokenFiatPercentageBlock(fiatAmount: String, priceChange: PriceChange, 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,
) {
val iconChangeArrow: Int
val changeTextColor: Color
when (priceChange.type) {
PriceChangeType.UP -> {
iconChangeArrow = R.drawable.img_arrow_up_8
changeTextColor = TangemTheme.colors.text.accent
}
PriceChangeType.DOWN -> {
iconChangeArrow = R.drawable.img_arrow_down_8
changeTextColor = TangemTheme.colors.text.warning
}
}
Image(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = iconChangeArrow),
contentDescription = null,
)
SpacerW4()
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = priceChange.valuePercent,
style = TangemTypography.body2,
color = changeTextColor,
)
}
}
}
@Composable
private fun TokenIcon(
tokenIconUrl: String?,
modifier: Modifier = Modifier,
@DrawableRes icon: Int? = null,
@DrawableRes networkIconRes: 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 data = if (tokenIconUrl.isNullOrEmpty()) {
icon
} else {
tokenIconUrl
}
SubcomposeAsyncImage(
modifier = tokenImageModifier,
model = ImageRequest.Builder(LocalContext.current)
.data(data)
.crossfade(true)
.build(),
loading = { CircleShimmer(modifier = tokenImageModifier) },
contentDescription = null,
)
if (networkIconRes != null) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(TangemTheme.dimens.size18)
.background(color = Color.White, shape = CircleShape),
contentAlignment = Alignment.Center,
) {
Image(
modifier = Modifier.padding(all = 0.5.dp),
painter = painterResource(id = networkIconRes),
contentDescription = null,
)
}
}
}
}
// region preview
@Composable
private fun TokensPreview() {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
) {
val config = TokenV2Config(
name = "Polygon",
amount = "5,412 MATIC",
fiatAmount = "321 $",
priceChange = PriceChange(
valuePercent = "2%",
type = PriceChangeType.UP,
),
iconUrl = null,
icon = R.drawable.img_polygon_22,
networkIcon = R.drawable.img_polygon_22,
tokenUIState = TokenUIState.LOADED,
tokenOptionsUIState = TokenOptionsUIState.VISIBLE,
hasPending = true,
)
// Loaded item
TokenItemV2(config)
// Unreachable item
TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE))
// Drag item
TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.DRAG))
// Hidden item
TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE))
// Loading item
TokenItemV2(
config.copy(
tokenUIState = TokenUIState.LOADING,
),
)
}
}
@Preview(showBackground = true)
@Composable
private fun Preview_Tokens_InLightTheme() {
TangemTheme(isDark = false) {
TokensPreview()
}
}
@Preview(showBackground = true)
@Composable
private fun Preview_Tokens_InDarkTheme() {
TangemTheme(isDark = true) {
TokensPreview()
}
}
// endregion preview

View file

@ -1,51 +0,0 @@
package com.tangem.feature.wallet.presentation.ui
import com.tangem.core.ui.R
import com.tangem.feature.wallet.presentation.state.WalletCardState
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import java.util.UUID
internal object WalletPreviewData {
val walletCardContent = WalletCardState.Content(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
balance = "8923,05 $",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardLoading = WalletCardState.Loading(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardHiddenContent = WalletCardState.HiddenContent(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardError = WalletCardState.Error(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletScreenState = WalletStateHolder(
onBackClick = {},
headerConfig = WalletStateHolder.HeaderConfig(
wallets = listOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError),
onScanCardClick = {},
onMoreClick = {},
),
)
}

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.state
package com.tangem.feature.wallet.presentation.wallet.state
import androidx.annotation.DrawableRes

View file

@ -1,4 +1,6 @@
package com.tangem.feature.wallet.presentation.state
package com.tangem.feature.wallet.presentation.wallet.state
import kotlinx.collections.immutable.ImmutableList
/**
* Wallet state holder
@ -21,7 +23,7 @@ internal data class WalletStateHolder(
* @property onMoreClick lambda be invoked when more button is clicked
*/
data class HeaderConfig(
val wallets: List<WalletCardState>,
val wallets: ImmutableList<WalletCardState>,
val onScanCardClick: () -> Unit,
val onMoreClick: () -> Unit,
)

View file

@ -1,10 +1,10 @@
package com.tangem.feature.wallet.presentation.ui
package com.tangem.feature.wallet.presentation.wallet.ui
import androidx.activity.compose.BackHandler
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.ui.components.WalletHeader
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletHeader
/**
* Wallet screen

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.ui.components
package com.tangem.feature.wallet.presentation.wallet.ui.components
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
@ -17,12 +17,12 @@ import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import com.tangem.core.ui.components.FontSizeRange
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.ShimmerRectangle
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.state.WalletCardState
import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
private const val DOTS = "•••"
@ -119,7 +119,7 @@ private fun Balance(state: WalletCardState) {
)
}
is WalletCardState.Loading -> {
ShimmerRectangle(
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size102,
height = TangemTheme.dimens.size24,

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.ui.components
package com.tangem.feature.wallet.presentation.wallet.ui.components
import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi
@ -21,9 +21,10 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.state.WalletCardState
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import kotlinx.collections.immutable.persistentListOf
/**
* Wallet screen header
@ -109,7 +110,7 @@ private fun Preview_WalletHeader_DarkTheme(
private class WalletHeaderProvider : CollectionPreviewParameterProvider<WalletStateHolder.HeaderConfig>(
collection = listOf(
WalletStateHolder.HeaderConfig(
wallets = listOf(
wallets = persistentListOf(
WalletPreviewData.walletCardContent,
WalletPreviewData.walletCardLoading,
WalletPreviewData.walletCardHiddenContent,

View file

@ -1,12 +1,12 @@
package com.tangem.feature.wallet.presentation.viewmodels
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlin.properties.Delegates