Updated on 2026-08-14
This commit is contained in:
parent
b42fa5283c
commit
18474637b9
8 changed files with 249 additions and 105 deletions
|
|
@ -1,43 +1,31 @@
|
|||
package com.tangem.common.ui.account
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.account.AccountIconPreviewData.randomAccountIcon
|
||||
import com.tangem.core.ui.components.account.AccountCharIcon
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.account.AccountResIcon
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.Color
|
||||
|
||||
enum class AccountIconSize {
|
||||
Default, Large, Medium, Small, ExtraSmall
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an account icon that can either show a letter (derived from [name])
|
||||
* or a predefined vector resource (from [icon]).
|
||||
* Displays a portfolio account icon that can either show:
|
||||
* - a single character (derived from the [name]) using [AccountCharIcon], or
|
||||
* - a predefined vector resource (from [icon]) using [AccountResIcon].
|
||||
*
|
||||
* The background color is determined by the icon's [CryptoPortfolioIconUM.color],
|
||||
* and the icon size, text style, and box modifier are adapted based on the given [size].
|
||||
* The background color is taken from [CryptoPortfolioIconUM.color],
|
||||
* and the icon size, text style, and container shape are adapted based on the given [size].
|
||||
*
|
||||
* @param name The text reference used to resolve and display the first letter
|
||||
* when [icon] is set to [CryptoPortfolioIcon.Icon.Letter].
|
||||
* @param name A [TextReference] used to resolve and display the first letter
|
||||
* when [icon.value] is set to [CryptoPortfolioIcon.Icon.Letter].
|
||||
* @param icon The account icon definition, which can be a letter or a drawable resource.
|
||||
* @param size The size of the icon, defined by [AccountIconSize].
|
||||
* @param size The icon size, defined by [AccountIconSize].
|
||||
*
|
||||
* @see AccountCharIcon
|
||||
* @see AccountResIcon
|
||||
* @see AccountIconSize
|
||||
*/
|
||||
@Composable
|
||||
fun AccountIcon(
|
||||
|
|
@ -46,82 +34,20 @@ fun AccountIcon(
|
|||
size: AccountIconSize,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val boxModifier = modifier.selectBoxModifier(size)
|
||||
val iconSize = Modifier.selectIconSize(size)
|
||||
val textStyle = when (size) {
|
||||
AccountIconSize.Default -> TangemTheme.typography.h3
|
||||
AccountIconSize.Large -> TangemTheme.typography.h1
|
||||
AccountIconSize.Medium -> TangemTheme.typography.subtitle1
|
||||
AccountIconSize.Small -> TangemTheme.typography.subtitle2
|
||||
AccountIconSize.ExtraSmall -> TangemTheme.typography.caption1
|
||||
}
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = boxModifier.background(icon.color.getUiColor()),
|
||||
) {
|
||||
val icon = icon.value
|
||||
val letter = name.resolveReference().firstOrNull()
|
||||
when {
|
||||
icon == CryptoPortfolioIcon.Icon.Letter -> Text(
|
||||
text = letter?.uppercase() ?: "",
|
||||
style = textStyle,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
else -> Icon(
|
||||
modifier = iconSize,
|
||||
tint = TangemTheme.colors.text.constantWhite,
|
||||
imageVector = ImageVector.vectorResource(id = icon.getResId()),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.selectIconSize(size: AccountIconSize): Modifier = when (size) {
|
||||
AccountIconSize.Default -> this.size(20.dp)
|
||||
AccountIconSize.Large -> this.size(40.dp)
|
||||
AccountIconSize.Medium -> this.size(16.dp)
|
||||
AccountIconSize.Small -> this.size(12.dp)
|
||||
AccountIconSize.ExtraSmall -> this.size(8.dp)
|
||||
}
|
||||
|
||||
private fun Modifier.selectBoxModifier(size: AccountIconSize): Modifier = when (size) {
|
||||
AccountIconSize.Default -> size(36.dp).clip(RoundedCornerShape(10.dp))
|
||||
AccountIconSize.Large -> size(88.dp).clip(RoundedCornerShape(24.dp))
|
||||
AccountIconSize.Medium -> size(28.dp).clip(RoundedCornerShape(8.dp))
|
||||
AccountIconSize.Small -> size(20.dp).clip(RoundedCornerShape(6.dp))
|
||||
AccountIconSize.ExtraSmall -> size(14.dp).clip(RoundedCornerShape(4.dp))
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_AccountIcon() {
|
||||
TangemThemePreview {
|
||||
Sample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Sample() {
|
||||
val name = stringReference("Account Name")
|
||||
Row(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.Default)
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.Large)
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.Medium)
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.Small)
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.ExtraSmall)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.Default)
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.Large)
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.Medium)
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.Small)
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.ExtraSmall)
|
||||
}
|
||||
val letter = name.resolveReference().firstOrNull()
|
||||
when {
|
||||
icon.value == CryptoPortfolioIcon.Icon.Letter -> AccountCharIcon(
|
||||
char = letter ?: 'N',
|
||||
color = icon.color.getUiColor(),
|
||||
size = size,
|
||||
modifier = modifier,
|
||||
)
|
||||
else -> AccountResIcon(
|
||||
resId = icon.value.getResId(),
|
||||
color = icon.color.getUiColor(),
|
||||
size = size,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
package com.tangem.core.ui.components.account
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
enum class AccountIconSize {
|
||||
Default, Large, Medium, Small, ExtraSmall
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a portfolio icon using a predefined vector resource.
|
||||
*
|
||||
* The background color is defined by [color], and the icon tint is constant white.
|
||||
* The icon size and container shape are adapted based on the provided [size].
|
||||
*
|
||||
* @param resId The vector drawable resource ID to display.
|
||||
* @param color The background color of the icon container.
|
||||
* @param size The size of the icon, defined by [AccountIconSize].
|
||||
*/
|
||||
@Composable
|
||||
fun AccountResIcon(@DrawableRes resId: Int, color: Color, size: AccountIconSize, modifier: Modifier = Modifier) {
|
||||
val boxModifier = modifier.selectBoxModifier(size)
|
||||
val iconSize = Modifier.selectIconSize(size)
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = boxModifier.background(color),
|
||||
) {
|
||||
Icon(
|
||||
modifier = iconSize,
|
||||
tint = TangemTheme.colors.text.constantWhite,
|
||||
imageVector = ImageVector.vectorResource(id = resId),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a portfolio icon using a single character.
|
||||
*
|
||||
* The background color is defined by [color], the text is uppercased,
|
||||
* and its style is chosen based on the provided [size].
|
||||
* The container shape and size also adapt to [size].
|
||||
*
|
||||
* @param char The character to display inside the icon.
|
||||
* @param color The background color of the icon container.
|
||||
* @param size The size of the icon, defined by [AccountIconSize].
|
||||
*/
|
||||
@Composable
|
||||
fun AccountCharIcon(char: Char, color: Color, size: AccountIconSize, modifier: Modifier = Modifier) {
|
||||
val boxModifier = modifier.selectBoxModifier(size)
|
||||
val textStyle = when (size) {
|
||||
AccountIconSize.Default -> TangemTheme.typography.h3
|
||||
AccountIconSize.Large -> TangemTheme.typography.h1
|
||||
AccountIconSize.Medium -> TangemTheme.typography.subtitle1
|
||||
AccountIconSize.Small -> TangemTheme.typography.subtitle2
|
||||
AccountIconSize.ExtraSmall -> TangemTheme.typography.caption1
|
||||
}
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = boxModifier.background(color),
|
||||
) {
|
||||
Text(
|
||||
text = char.uppercase(),
|
||||
style = textStyle,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.selectIconSize(size: AccountIconSize): Modifier = when (size) {
|
||||
AccountIconSize.Default -> this.size(20.dp)
|
||||
AccountIconSize.Large -> this.size(40.dp)
|
||||
AccountIconSize.Medium -> this.size(16.dp)
|
||||
AccountIconSize.Small -> this.size(12.dp)
|
||||
AccountIconSize.ExtraSmall -> this.size(8.dp)
|
||||
}
|
||||
|
||||
private fun Modifier.selectBoxModifier(size: AccountIconSize): Modifier = when (size) {
|
||||
AccountIconSize.Default -> size(36.dp).clip(RoundedCornerShape(10.dp))
|
||||
AccountIconSize.Large -> size(88.dp).clip(RoundedCornerShape(24.dp))
|
||||
AccountIconSize.Medium -> size(28.dp).clip(RoundedCornerShape(8.dp))
|
||||
AccountIconSize.Small -> size(20.dp).clip(RoundedCornerShape(6.dp))
|
||||
AccountIconSize.ExtraSmall -> size(14.dp).clip(RoundedCornerShape(4.dp))
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
Row(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
Sample()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Sample() {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
AccountResIcon(resId = R.drawable.ic_shirt_24, color = Color.Red, size = AccountIconSize.Default)
|
||||
AccountResIcon(resId = R.drawable.ic_rounded_star_24, color = Color.Blue, size = AccountIconSize.Large)
|
||||
AccountResIcon(resId = R.drawable.ic_user_24, color = Color.Magenta, size = AccountIconSize.Medium)
|
||||
AccountResIcon(resId = R.drawable.ic_family_24, color = Color.DarkGray, size = AccountIconSize.Small)
|
||||
AccountResIcon(resId = R.drawable.ic_wallet_24, color = Color.Green, size = AccountIconSize.ExtraSmall)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
AccountCharIcon(char = 'D', color = Color.Red, size = AccountIconSize.Default)
|
||||
AccountCharIcon(char = 'L', color = Color.Blue, size = AccountIconSize.Large)
|
||||
AccountCharIcon(char = 'M', color = Color.Magenta, size = AccountIconSize.Medium)
|
||||
AccountCharIcon(char = 'S', color = Color.DarkGray, size = AccountIconSize.Small)
|
||||
AccountCharIcon(char = 'E', color = Color.Green, size = AccountIconSize.ExtraSmall)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,9 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.account.AccountCharIcon
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.account.AccountResIcon
|
||||
import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
|
|
@ -58,6 +61,18 @@ internal fun ContentIcon(
|
|||
background = icon.background,
|
||||
alpha = alpha,
|
||||
)
|
||||
is CurrencyIconState.CryptoPortfolio.Icon -> AccountResIcon(
|
||||
modifier = modifier,
|
||||
resId = icon.resId,
|
||||
color = icon.color,
|
||||
size = AccountIconSize.Default,
|
||||
)
|
||||
is CurrencyIconState.CryptoPortfolio.Letter -> AccountCharIcon(
|
||||
modifier = modifier,
|
||||
char = icon.char,
|
||||
color = icon.color,
|
||||
size = AccountIconSize.Default,
|
||||
)
|
||||
CurrencyIconState.Loading,
|
||||
CurrencyIconState.Locked,
|
||||
is CurrencyIconState.Empty,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ fun CurrencyIcon(
|
|||
is CurrencyIconState.FiatIcon,
|
||||
is CurrencyIconState.CustomTokenIcon,
|
||||
is CurrencyIconState.TokenIcon,
|
||||
is CurrencyIconState.CryptoPortfolio.Icon,
|
||||
is CurrencyIconState.CryptoPortfolio.Letter,
|
||||
-> {
|
||||
ContentIconContainer(
|
||||
icon = state,
|
||||
|
|
|
|||
|
|
@ -86,6 +86,24 @@ sealed class CurrencyIconState {
|
|||
override val topBadgeIconResId: Int? = null
|
||||
}
|
||||
|
||||
sealed class CryptoPortfolio : CurrencyIconState() {
|
||||
override val showCustomBadge: Boolean = false
|
||||
override val topBadgeIconResId: Int? = null
|
||||
abstract val color: Color
|
||||
|
||||
data class Icon(
|
||||
@DrawableRes val resId: Int,
|
||||
override val color: Color,
|
||||
override val isGrayscale: Boolean,
|
||||
) : CryptoPortfolio()
|
||||
|
||||
data class Letter(
|
||||
val char: Char,
|
||||
override val color: Color,
|
||||
override val isGrayscale: Boolean,
|
||||
) : CryptoPortfolio()
|
||||
}
|
||||
|
||||
data object Loading : CurrencyIconState() {
|
||||
override val isGrayscale: Boolean = false
|
||||
override val showCustomBadge: Boolean = false
|
||||
|
|
@ -125,6 +143,12 @@ sealed class CurrencyIconState {
|
|||
showCustomBadge = showCustomBadge,
|
||||
topBadgeIconResId = topBadgeIconResId,
|
||||
)
|
||||
is CryptoPortfolio.Icon -> copy(
|
||||
isGrayscale = isGrayscale,
|
||||
)
|
||||
is CryptoPortfolio.Letter -> copy(
|
||||
isGrayscale = isGrayscale,
|
||||
)
|
||||
is Loading,
|
||||
is Locked,
|
||||
is Empty,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.Measurable
|
||||
import androidx.compose.ui.layout.Placeable
|
||||
|
|
@ -309,7 +310,13 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
},
|
||||
)
|
||||
|
||||
fiatAmount?.placeRelative(x = layoutWidth - fiatAmount.width, y = verticalPadding)
|
||||
fiatAmount?.placeRelative(
|
||||
x = layoutWidth - fiatAmount.width,
|
||||
y = when (state.subtitle2State) {
|
||||
null -> (layoutHeight - fiatAmount.height).div(other = 2)
|
||||
else -> verticalPadding
|
||||
},
|
||||
)
|
||||
|
||||
priceChange?.placeRelative(
|
||||
x = icon.width,
|
||||
|
|
@ -462,7 +469,7 @@ private fun calculateLayoutHeight(
|
|||
return max(firstColumnHeight, secondColumnHeight).coerceAtLeast(minLayoutHeight)
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360)
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_TokenItem_InLight(@PreviewParameter(TokenItemStateProvider::class) state: TokenItemState) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
|
|
@ -589,6 +596,8 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
iconState = customTokenIconState.copy(isGrayscale = true),
|
||||
),
|
||||
AccountItemPreviewData.accountItem,
|
||||
AccountItemPreviewData.accountItem.copy(iconState = AccountItemPreviewData.accountLetterIcon),
|
||||
),
|
||||
) {
|
||||
|
||||
|
|
@ -641,4 +650,38 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object AccountItemPreviewData {
|
||||
val accountResIcon: CurrencyIconState.CryptoPortfolio.Icon
|
||||
get() = CurrencyIconState.CryptoPortfolio.Icon(
|
||||
resId = R.drawable.ic_rounded_star_24,
|
||||
color = Color.Blue,
|
||||
isGrayscale = false,
|
||||
)
|
||||
val accountLetterIcon: CurrencyIconState.CryptoPortfolio.Letter
|
||||
get() = CurrencyIconState.CryptoPortfolio.Letter(
|
||||
char = 'A',
|
||||
color = Color.Blue,
|
||||
isGrayscale = false,
|
||||
)
|
||||
|
||||
val accountItem: TokenItemState.Content
|
||||
get() = TokenItemState.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = accountResIcon,
|
||||
titleState = TokenItemState.TitleState.Content(
|
||||
text = stringReference(value = "Portfolio"),
|
||||
),
|
||||
fiatAmountState = FiatAmountState.Content(
|
||||
text = "$22,129.65",
|
||||
),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(
|
||||
value = stringReference("24 tokens"),
|
||||
isAvailable = false,
|
||||
),
|
||||
subtitle2State = null,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ import com.tangem.core.ui.components.SpacerH
|
|||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.fields.AutoSizeTextField
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue