Updated on 2026-08-14
This commit is contained in:
commit
f5a9381eb2
41 changed files with 1393 additions and 268 deletions
|
|
@ -7,11 +7,10 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.*
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
|
|
@ -172,6 +171,8 @@ fun SecondaryButton(
|
|||
modifier: Modifier = Modifier,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
shape: Shape = size.toShape(),
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
|
|
@ -181,6 +182,8 @@ fun SecondaryButton(
|
|||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
size = size,
|
||||
shape = shape,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
|
|
@ -30,13 +31,14 @@ fun TangemButton(
|
|||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
elevation: ButtonElevation = TangemButtonsDefaults.elevation,
|
||||
textStyle: TextStyle = TangemTheme.typography.button,
|
||||
shape: Shape = size.toShape(),
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier.heightIn(min = size.toHeightDp()),
|
||||
onClick = { if (!showProgress) onClick() },
|
||||
enabled = enabled,
|
||||
elevation = elevation,
|
||||
shape = size.toShape(),
|
||||
shape = shape,
|
||||
colors = colors,
|
||||
contentPadding = size.toContentPadding(icon = icon),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,161 @@
|
|||
package com.tangem.core.ui.components.buttons.segmentedbutton
|
||||
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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 kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Segmented buttons
|
||||
*
|
||||
* [Figma component](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1961-2004&mode=design&t=OFPQ18YhLHVAANab-4)
|
||||
*
|
||||
* @param config list of buttons in SegmentedButtons
|
||||
* @param onClick button click
|
||||
* @param modifier component modifier
|
||||
* @param color default button color
|
||||
* @param selectedColor selected button color
|
||||
* @param dividerColor border and divider color
|
||||
* @param showIndication show ripple indication
|
||||
* @param buttonContent content as separate button
|
||||
*/
|
||||
@Composable
|
||||
inline fun <reified T> SegmentedButtons(
|
||||
config: PersistentList<T>,
|
||||
crossinline onClick: (T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.background.tertiary,
|
||||
selectedColor: Color = TangemTheme.colors.background.action,
|
||||
dividerColor: Color = TangemTheme.colors.stroke.primary,
|
||||
showIndication: Boolean = true,
|
||||
crossinline buttonContent: @Composable (T) -> Unit,
|
||||
) {
|
||||
if (config.isEmpty() || config.size == 1) return
|
||||
|
||||
var selected by remember { mutableIntStateOf(0) }
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius26))
|
||||
.background(dividerColor)
|
||||
.padding(TangemTheme.dimens.spacing1),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing1),
|
||||
) {
|
||||
repeat(config.size) { index ->
|
||||
val leftRadius = if (index == 0) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0
|
||||
val rightRadius = if (index == config.lastIndex) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.background(
|
||||
color = if (index == selected) selectedColor else color,
|
||||
shape = RoundedCornerShape(
|
||||
topStart = leftRadius,
|
||||
topEnd = rightRadius,
|
||||
bottomEnd = rightRadius,
|
||||
bottomStart = leftRadius,
|
||||
),
|
||||
)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = if (showIndication) LocalIndication.current else null,
|
||||
) {
|
||||
onClick(config[index])
|
||||
selected = index
|
||||
},
|
||||
) {
|
||||
buttonContent.invoke(config[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SegmentedButtonsPreview_Light(
|
||||
@PreviewParameter(SegmentedButtonsPreviewProvider::class) config: PersistentList<SegmentedButtonsConfigPreview>,
|
||||
) {
|
||||
TangemTheme {
|
||||
SegmentedButtons(
|
||||
config = config,
|
||||
onClick = {},
|
||||
) {
|
||||
Text(
|
||||
text = it.text,
|
||||
modifier = Modifier.padding(TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SegmentedButtonsPreview_Dark(
|
||||
@PreviewParameter(SegmentedButtonsPreviewProvider::class) config: PersistentList<SegmentedButtonsConfigPreview>,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
SegmentedButtons(
|
||||
config = config,
|
||||
onClick = {},
|
||||
) {
|
||||
Text(
|
||||
text = it.text,
|
||||
modifier = Modifier.padding(TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region Preview config
|
||||
/**
|
||||
* Segmented buttons preview model
|
||||
*
|
||||
* @param text button title
|
||||
*/
|
||||
internal data class SegmentedButtonsConfigPreview(
|
||||
val text: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Segmented button preview provider
|
||||
*/
|
||||
internal class SegmentedButtonsPreviewProvider :
|
||||
CollectionPreviewParameterProvider<PersistentList<SegmentedButtonsConfigPreview>>(
|
||||
collection = listOf(
|
||||
persistentListOf(
|
||||
SegmentedButtonsConfigPreview(
|
||||
text = "Title 1",
|
||||
),
|
||||
SegmentedButtonsConfigPreview(
|
||||
text = "Title 2",
|
||||
),
|
||||
SegmentedButtonsConfigPreview(
|
||||
text = "Title 3",
|
||||
),
|
||||
),
|
||||
persistentListOf(
|
||||
SegmentedButtonsConfigPreview(
|
||||
text = "Title 1",
|
||||
),
|
||||
SegmentedButtonsConfigPreview(
|
||||
text = "Title 2",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.core.ui.components.currency
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
internal inline fun DefaultCurrencyIcon(
|
||||
iconData: Any,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
crossinline errorIcon: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) }
|
||||
val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb()
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = iconBackgroundColor,
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(iconData)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.listener(
|
||||
onSuccess = { _, result ->
|
||||
if (isDarkTheme) {
|
||||
coroutineScope.launch {
|
||||
val color = ImageBackgroundContrastChecker(
|
||||
drawable = result.drawable,
|
||||
backgroundColor = itemBackgroundColor,
|
||||
).getContrastColorIfNeeded(isDarkTheme)
|
||||
iconBackgroundColor = color
|
||||
}
|
||||
}
|
||||
},
|
||||
).build(),
|
||||
loading = { LoadingIcon() },
|
||||
error = { errorIcon() },
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.core.ui.components.currency.fiaticon
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
|
||||
|
||||
/**
|
||||
* Simple icon from network
|
||||
*
|
||||
* @param url link to icon
|
||||
* @param fallbackResId fallback icon
|
||||
* @param modifier component modifier
|
||||
*/
|
||||
@Composable
|
||||
fun FiatIcon(
|
||||
url: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
@DrawableRes fallbackResId: Int = R.drawable.ic_shape_circle,
|
||||
) {
|
||||
val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url
|
||||
|
||||
DefaultCurrencyIcon(
|
||||
modifier = modifier,
|
||||
iconData = iconData,
|
||||
errorIcon = {
|
||||
Image(
|
||||
painter = painterResource(id = fallbackResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
alpha = 1f,
|
||||
colorFilter = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
package com.tangem.core.ui.components.currency.tokenicon
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
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.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
|
||||
|
||||
@Composable
|
||||
internal fun ContentIcon(
|
||||
icon: TokenIconState,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (icon) {
|
||||
is TokenIconState.CoinIcon -> CoinIcon(
|
||||
modifier = modifier,
|
||||
url = icon.url,
|
||||
fallbackResId = icon.fallbackResId,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
is TokenIconState.TokenIcon -> TokenIcon(
|
||||
modifier = modifier,
|
||||
url = icon.url,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
errorIcon = {
|
||||
CustomTokenIcon(
|
||||
modifier = modifier,
|
||||
tint = icon.fallbackTint,
|
||||
background = icon.fallbackBackground,
|
||||
alpha = alpha,
|
||||
)
|
||||
},
|
||||
)
|
||||
is TokenIconState.CustomTokenIcon -> CustomTokenIcon(
|
||||
modifier = modifier,
|
||||
tint = icon.tint,
|
||||
background = icon.background,
|
||||
alpha = alpha,
|
||||
)
|
||||
TokenIconState.Loading,
|
||||
TokenIconState.Locked,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CoinIcon(
|
||||
url: String?,
|
||||
@DrawableRes fallbackResId: Int,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url
|
||||
|
||||
DefaultCurrencyIcon(
|
||||
modifier = modifier,
|
||||
iconData = iconData,
|
||||
errorIcon = {
|
||||
Image(
|
||||
painter = painterResource(id = fallbackResId),
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenIcon(
|
||||
url: String?,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
errorIcon: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (url == null) {
|
||||
errorIcon()
|
||||
} else {
|
||||
DefaultCurrencyIcon(
|
||||
modifier = modifier,
|
||||
iconData = url,
|
||||
errorIcon = errorIcon,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CustomTokenIcon(tint: Color, background: Color, alpha: Float, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = background.copy(alpha = alpha),
|
||||
shape = CircleShape,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
painter = painterResource(id = R.drawable.ic_custom_token_44),
|
||||
tint = tint.copy(alpha = alpha),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.core.ui.components.currency.tokenicon
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun NetworkBadge(
|
||||
@DrawableRes iconResId: Int,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size18)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.primary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.padding(all = TangemTheme.dimens.spacing2)
|
||||
.matchParentSize(),
|
||||
painter = painterResource(id = iconResId),
|
||||
colorFilter = colorFilter,
|
||||
alpha = alpha,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun CustomBadge(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size12)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.primary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(all = TangemTheme.dimens.spacing2)
|
||||
.matchParentSize()
|
||||
.background(
|
||||
color = TangemTheme.colors.icon.informative,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.core.ui.components.currency.tokenicon
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
private const val GRAY_SCALE_SATURATION = 0f
|
||||
private const val GRAY_SCALE_ALPHA = 0.4f
|
||||
private const val NORMAL_ALPHA = 1f
|
||||
|
||||
/**
|
||||
* Cryptocurrency icon with network badge
|
||||
*
|
||||
* TODO [separate domain from ui]([REDACTED_JIRA])
|
||||
*
|
||||
* @param state cryptocurrency icon config
|
||||
* @param modifier component modifier
|
||||
* @param shouldDisplayNetwork specifies whether to display network badge
|
||||
*/
|
||||
@Composable
|
||||
fun TokenIcon(state: TokenIconState, modifier: Modifier = Modifier, shouldDisplayNetwork: Boolean = true) {
|
||||
BaseContainer(modifier = modifier) {
|
||||
val iconModifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(TangemTheme.dimens.size36)
|
||||
|
||||
when (state) {
|
||||
is TokenIconState.Loading -> LoadingIcon(modifier = iconModifier)
|
||||
is TokenIconState.Locked -> LockedIcon(modifier = iconModifier)
|
||||
is TokenIconState.CoinIcon,
|
||||
is TokenIconState.CustomTokenIcon,
|
||||
is TokenIconState.TokenIcon,
|
||||
-> {
|
||||
ContentIconContainer(
|
||||
icon = state,
|
||||
modifier = iconModifier,
|
||||
shouldDisplayNetwork = shouldDisplayNetwork,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun LoadingIcon(modifier: Modifier = Modifier) {
|
||||
CircleShimmer(modifier = modifier)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LockedIcon(modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
color = TangemTheme.colors.field.primary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.ContentIconContainer(
|
||||
icon: TokenIconState,
|
||||
modifier: Modifier = Modifier,
|
||||
shouldDisplayNetwork: Boolean = true,
|
||||
) {
|
||||
val networkBadgeOffset = TangemTheme.dimens.spacing4
|
||||
val (alpha, colorFilter) = remember(icon.isGrayscale) {
|
||||
if (icon.isGrayscale) {
|
||||
GRAY_SCALE_ALPHA to GrayscaleColorFilter
|
||||
} else {
|
||||
NORMAL_ALPHA to null
|
||||
}
|
||||
}
|
||||
|
||||
ContentIcon(
|
||||
modifier = modifier,
|
||||
icon = icon,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
|
||||
if (icon.networkBadgeIconResId != null && shouldDisplayNetwork) {
|
||||
NetworkBadge(
|
||||
modifier = Modifier
|
||||
.offset(x = networkBadgeOffset, y = -networkBadgeOffset)
|
||||
.align(Alignment.TopEnd),
|
||||
iconResId = requireNotNull(icon.networkBadgeIconResId),
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
}
|
||||
|
||||
if (icon.showCustomBadge) {
|
||||
CustomBadge(modifier = Modifier.align(Alignment.BottomEnd))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private inline fun BaseContainer(modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit) {
|
||||
Box(modifier = modifier.size(size = TangemTheme.dimens.size40), content = content)
|
||||
}
|
||||
|
||||
private val GrayscaleColorFilter: ColorFilter
|
||||
get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) })
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package com.tangem.core.ui.components.currency.tokenicon
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
* Represents the various states an icon can be in.
|
||||
*
|
||||
* [REDACTED_TODO_COMMENT]
|
||||
*/
|
||||
@Immutable
|
||||
sealed class TokenIconState {
|
||||
|
||||
abstract val isGrayscale: Boolean
|
||||
abstract val showCustomBadge: Boolean
|
||||
abstract val networkBadgeIconResId: Int?
|
||||
|
||||
/**
|
||||
* Represents a coin icon.
|
||||
*
|
||||
* @property url The URL where the coin icon can be fetched from. May be `null` if not found.
|
||||
* @property fallbackResId The drawable resource ID to be used as a fallback if the URL is not available.
|
||||
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
||||
* @property showCustomBadge Specifies whether to show the custom token badge.
|
||||
*/
|
||||
data class CoinIcon(
|
||||
val url: String?,
|
||||
@DrawableRes val fallbackResId: Int,
|
||||
override val isGrayscale: Boolean,
|
||||
override val showCustomBadge: Boolean,
|
||||
) : TokenIconState() {
|
||||
|
||||
override val networkBadgeIconResId: Int? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a token icon.
|
||||
*
|
||||
* @property url The URL where the token icon can be fetched from. May be `null` if not found.
|
||||
* @property networkBadgeIconResId The drawable resource ID for the network badge.
|
||||
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
||||
* @property showCustomBadge Specifies whether to show the custom token badge.
|
||||
* @property fallbackTint The color to be used for tinting the fallback icon.
|
||||
* @property fallbackBackground The background color to be used for the fallback icon.
|
||||
*/
|
||||
data class TokenIcon(
|
||||
val url: String?,
|
||||
@DrawableRes override val networkBadgeIconResId: Int,
|
||||
override val isGrayscale: Boolean,
|
||||
override val showCustomBadge: Boolean,
|
||||
val fallbackTint: Color,
|
||||
val fallbackBackground: Color,
|
||||
) : TokenIconState()
|
||||
|
||||
/**
|
||||
* Represents a custom token icon.
|
||||
*
|
||||
* @property tint The color to be used for tinting the icon.
|
||||
* @property background The background color to be used for the icon.
|
||||
* @property networkBadgeIconResId The drawable resource ID for the network badge.
|
||||
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
||||
*/
|
||||
data class CustomTokenIcon(
|
||||
val tint: Color,
|
||||
val background: Color,
|
||||
@DrawableRes override val networkBadgeIconResId: Int,
|
||||
override val isGrayscale: Boolean,
|
||||
) : TokenIconState() {
|
||||
|
||||
override val showCustomBadge: Boolean = true
|
||||
}
|
||||
|
||||
object Loading : TokenIconState() {
|
||||
override val isGrayscale: Boolean = false
|
||||
override val showCustomBadge: Boolean = false
|
||||
override val networkBadgeIconResId: Int? = null
|
||||
}
|
||||
|
||||
object Locked : TokenIconState() {
|
||||
override val isGrayscale: Boolean = false
|
||||
override val showCustomBadge: Boolean = false
|
||||
override val networkBadgeIconResId: Int? = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.core.ui.components.currency.tokenicon.converter
|
||||
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
|
||||
import com.tangem.core.ui.extensions.getTintForTokenIcon
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.tryGetBackgroundForTokenIcon
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
||||
/**
|
||||
* Converts [CryptoCurrencyStatus] to [TokenIconState]
|
||||
*/
|
||||
class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrencyStatus, TokenIconState> {
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): TokenIconState {
|
||||
return when (val currency = value.currency) {
|
||||
is CryptoCurrency.Coin -> getIconStateForCoin(currency, value.value.isError)
|
||||
is CryptoCurrency.Token -> getIconStateForToken(currency, value.value.isError)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconStateForCoin(coin: CryptoCurrency.Coin, isUnreachable: Boolean): TokenIconState.CoinIcon {
|
||||
return TokenIconState.CoinIcon(
|
||||
url = coin.iconUrl,
|
||||
fallbackResId = coin.networkIconResId,
|
||||
isGrayscale = coin.network.isTestnet || isUnreachable,
|
||||
showCustomBadge = coin.isCustom,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getIconStateForToken(token: CryptoCurrency.Token, isErrorStatus: Boolean): TokenIconState {
|
||||
val isGrayscale = token.network.isTestnet || isErrorStatus
|
||||
val background = token.tryGetBackgroundForTokenIcon(isGrayscale)
|
||||
val tint = getTintForTokenIcon(background)
|
||||
|
||||
return if (token.isCustom && token.iconUrl == null) {
|
||||
TokenIconState.CustomTokenIcon(
|
||||
tint = tint,
|
||||
background = background,
|
||||
networkBadgeIconResId = token.networkIconResId,
|
||||
isGrayscale = isGrayscale,
|
||||
)
|
||||
} else {
|
||||
TokenIconState.TokenIcon(
|
||||
url = token.iconUrl,
|
||||
networkBadgeIconResId = token.networkIconResId,
|
||||
isGrayscale = isGrayscale,
|
||||
fallbackTint = tint,
|
||||
fallbackBackground = background,
|
||||
showCustomBadge = token.isCustom, // `true` for tokens with custom derivation
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -170,7 +170,6 @@ class TangemColors internal constructor(
|
|||
plain = other.plain
|
||||
action = other.action
|
||||
fade = other.fade
|
||||
tertiary = other.tertiary
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ object BigDecimalFormatter {
|
|||
|
||||
private const val TEMP_CURRENCY_CODE = "USD"
|
||||
|
||||
fun formatCryptoAmount(cryptoAmount: BigDecimal, cryptoCurrency: String, decimals: Int): String {
|
||||
fun formatCryptoAmount(cryptoAmount: BigDecimal?, cryptoCurrency: String, decimals: Int): String {
|
||||
if (cryptoAmount == null) return EMPTY_BALANCE_SIGN
|
||||
|
||||
val formatter = NumberFormat.getNumberInstance().apply {
|
||||
maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8)
|
||||
minimumFractionDigits = 2
|
||||
|
|
@ -23,11 +25,13 @@ object BigDecimalFormatter {
|
|||
}
|
||||
|
||||
fun formatFiatAmount(
|
||||
fiatAmount: BigDecimal,
|
||||
fiatAmount: BigDecimal?,
|
||||
fiatCurrencyCode: String,
|
||||
fiatCurrencySymbol: String,
|
||||
locale: Locale = Locale.getDefault(),
|
||||
): String {
|
||||
if (fiatAmount == null) return EMPTY_BALANCE_SIGN
|
||||
|
||||
val formatterCurrency = getCurrency(fiatCurrencyCode)
|
||||
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
|
||||
currency = formatterCurrency
|
||||
|
|
|
|||
9
core/ui/src/main/res/drawable/ic_shape_circle.xml
Normal file
9
core/ui/src/main/res/drawable/ic_shape_circle.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp" />
|
||||
|
||||
</shape>
|
||||
Loading…
Add table
Add a link
Reference in a new issue