Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-02 07:32:06 +01:00
parent 96478230a0
commit 4acd7ce3b2
25 changed files with 1010 additions and 338 deletions

View file

@ -1,60 +0,0 @@
package com.tangem.core.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
/**
* A composable that draws a fade effect at the bottom of the screen. Used on screens with a list of repeating
* elements and floating button at the bottom of the screen.
*/
@Composable
fun BottomFade(modifier: Modifier = Modifier, backgroundColor: Color = TangemTheme.colors.background.secondary) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
Box(
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size100 + bottomBarHeight)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
backgroundColor,
),
),
),
)
}
/**
* A composable that draws a fade effect. Used on screens with a list of repeating
* elements and floating button at the bottom of the screen.
*/
@Composable
fun Fade(
modifier: Modifier = Modifier,
backgroundColor: Color = TangemTheme.colors.background.secondary,
height: Dp = 32.dp,
) {
Box(
modifier = modifier
.fillMaxWidth()
.height(height)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
backgroundColor,
),
),
),
)
}

View file

@ -0,0 +1,144 @@
package com.tangem.core.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.res.TangemTheme
import dev.chrisbanes.haze.HazeProgressive
import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.HazeTint
/**
* A composable that draws a fade effect at the bottom of the screen. Used on screens with a list of repeating
* elements and floating button at the bottom of the screen.
*/
@Composable
fun BottomFade(modifier: Modifier = Modifier, backgroundColor: Color = TangemTheme.colors.background.secondary) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
Box(
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size100 + bottomBarHeight)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
backgroundColor,
),
),
),
)
}
/**
* A composable that draws a fade effect at the right end of the screen. Same as [BottomFade]
* but with a horizontal gradient.
*/
@Composable
fun HorizontalFade(modifier: Modifier = Modifier, backgroundColor: Color = TangemTheme.colors.background.secondary) {
Box(
modifier = modifier
.fillMaxHeight()
.background(
brush = Brush.horizontalGradient(
colors = listOf(
Color.Transparent,
backgroundColor,
),
),
),
)
}
/**
* A composable that draws a fade effect at the bottom of the screen. Same as [BottomFade]
* but with a vertical blur.
*/
@Composable
fun BottomFadeWithBlur(backgroundColor: Color, modifier: Modifier = Modifier) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
Box(
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size100 + bottomBarHeight)
.hazeEffectTangem(
style = HazeStyle(
blurRadius = 20.dp,
tint = HazeTint(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
backgroundColor,
),
),
),
),
) {
progressive =
HazeProgressive.verticalGradient(startIntensity = 0f, endIntensity = 1f)
},
)
}
/**
* A composable that draws a fade effect at the right end of the screen. Same as [HorizontalFade]
* but with blur.
*/
@Composable
fun HorizontalFadeWithBlur(backgroundColor: Color, modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxHeight()
.hazeEffectTangem(
style = HazeStyle(
blurRadius = 20.dp,
tint = HazeTint(
brush = Brush.horizontalGradient(
colors = listOf(
Color.Transparent,
backgroundColor,
),
),
),
backgroundColor = Color.Transparent,
),
) {
progressive =
HazeProgressive.horizontalGradient(startIntensity = 0f, endIntensity = 1f)
},
)
}
/**
* A composable that draws a fade effect. Used on screens with a list of repeating
* elements and floating button at the bottom of the screen.
*/
@Composable
fun Fade(
modifier: Modifier = Modifier,
backgroundColor: Color = TangemTheme.colors.background.secondary,
height: Dp = 32.dp,
) {
Box(
modifier = modifier
.fillMaxWidth()
.height(height)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
backgroundColor,
),
),
),
)
}

View file

@ -8,16 +8,29 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
fun UnableToLoadData(onRetryClick: () -> Unit, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
UnableToLoadDataV2(onRetryClick, modifier)
} else {
UnableToLoadDataV1(onRetryClick, modifier)
}
}
@Composable
private fun UnableToLoadDataV1(onRetryClick: () -> Unit, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
@ -37,11 +50,43 @@ fun UnableToLoadData(onRetryClick: () -> Unit, modifier: Modifier = Modifier) {
}
}
@Composable
private fun UnableToLoadDataV2(onRetryClick: () -> Unit, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(R.string.markets_loading_error_title),
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.secondary,
)
TangemButton(
buttonUM = TangemButtonUM(
text = resourceReference(R.string.try_to_load_data_again_button_title),
onClick = onRetryClick,
type = TangemButtonType.Secondary,
size = TangemButtonSize.X8,
shape = TangemButtonShape.Rounded,
),
)
}
}
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, showBackground = true)
@Composable
private fun PreviewV2() {
TangemThemePreviewRedesign {
UnableToLoadDataV2(onRetryClick = {})
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview() {
private fun PreviewV1() {
TangemThemePreview {
UnableToLoadData(onRetryClick = {})
UnableToLoadDataV1(onRetryClick = {})
}
}

View file

@ -5,13 +5,7 @@ import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
@ -72,7 +66,7 @@ fun Chip(state: ChipUM, modifier: Modifier = Modifier) {
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ChipPreview() {
private fun ChipPreviewV() {
TangemThemePreview {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),

View file

@ -1,13 +1,11 @@
package com.tangem.core.ui.ds.badge
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.runtime.ReadOnlyComposable
@ -15,18 +13,17 @@ import androidx.compose.runtime.remember
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.Shape
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.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.ds.badge.TangemBadgeSize.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -43,7 +40,7 @@ import com.tangem.core.ui.res.TangemThemePreviewRedesign
fun TangemBadge(badgeUM: TangemBadgeUM, modifier: Modifier = Modifier) {
TangemBadge(
text = badgeUM.text,
iconRes = badgeUM.iconRes,
tangemIconUM = badgeUM.tangemIconUM,
size = badgeUM.size,
shape = badgeUM.shape,
color = badgeUM.color,
@ -60,7 +57,7 @@ fun TangemBadge(badgeUM: TangemBadgeUM, modifier: Modifier = Modifier) {
*
* @param text TextReference for the badge label.
* @param modifier Modifier to be applied to the badge.
* @param iconRes Drawable resource ID for the icon to be displayed in the badge.
* @param tangemIconUM Model of representation for the icon to be displayed in the badge.
* @param size [TangemBadgeSize] defining the size of the badge.
* @param shape [TangemBadgeShape] defining the shape of the badge.
* @param color [TangemBadgeColor] defining the color scheme of the badge.
@ -74,7 +71,7 @@ fun TangemBadge(badgeUM: TangemBadgeUM, modifier: Modifier = Modifier) {
fun TangemBadge(
modifier: Modifier = Modifier,
text: TextReference? = null,
@DrawableRes iconRes: Int? = null,
tangemIconUM: TangemIconUM? = null,
size: TangemBadgeSize = X9,
shape: TangemBadgeShape = TangemBadgeShape.Default,
color: TangemBadgeColor = TangemBadgeColor.Gray,
@ -93,18 +90,12 @@ fun TangemBadge(
.padding(size.toPaddingDp(position = iconPosition))
.clickableSingle(enabled = onClick != null, onClick = { onClick?.invoke() }),
) {
AnimatedVisibility(
visible = iconRes != null && iconPosition != TangemBadgeIconPosition.End,
modifier = Modifier.size(size = size.toContentSize()),
label = "Start Icon Visibility",
) {
val wrappedIconRes = remember(this) { requireNotNull(iconRes) }
Icon(
painter = painterResource(id = wrappedIconRes),
contentDescription = null,
tint = iconColor,
)
}
StartIcon(
tangemIconUM = tangemIconUM,
iconPosition = iconPosition,
size = size,
iconColor = iconColor,
)
AnimatedVisibility(
visible = text != null,
label = "Text Visibility",
@ -117,18 +108,66 @@ fun TangemBadge(
color = getTextColor(type = type, color = color),
)
}
AnimatedVisibility(
visible = iconRes != null && iconPosition == TangemBadgeIconPosition.End,
modifier = Modifier.size(size = size.toContentSize()),
label = "End Icon Visibility",
) {
val wrappedIconRes = remember(this) { requireNotNull(iconRes) }
Icon(
painter = painterResource(id = wrappedIconRes),
contentDescription = null,
tint = iconColor,
)
}
EndIcon(
tangemIconUM = tangemIconUM,
iconPosition = iconPosition,
size = size,
iconColor = iconColor,
)
}
}
@Composable
private fun StartIcon(
iconPosition: TangemBadgeIconPosition,
size: TangemBadgeSize,
iconColor: Color,
tangemIconUM: TangemIconUM? = null,
) {
AnimatedVisibility(
visible = tangemIconUM != null && iconPosition != TangemBadgeIconPosition.End,
modifier = Modifier.size(size = size.toContentSize()),
label = "Start Icon Visibility",
) {
val wrappedIconRes = remember(this) { requireNotNull(tangemIconUM) }
TangemIcon(
modifier = Modifier.fillMaxSize(),
tangemIconUM = when (wrappedIconRes) {
is TangemIconUM.Currency,
is TangemIconUM.Ident,
is TangemIconUM.Image,
is TangemIconUM.Url,
-> wrappedIconRes
is TangemIconUM.Icon -> wrappedIconRes.copy(tintReference = ColorReference2 { iconColor })
},
)
}
}
@Composable
private fun EndIcon(
iconPosition: TangemBadgeIconPosition,
size: TangemBadgeSize,
iconColor: Color,
tangemIconUM: TangemIconUM? = null,
) {
AnimatedVisibility(
visible = tangemIconUM != null && iconPosition == TangemBadgeIconPosition.End,
modifier = Modifier.size(size = size.toContentSize()),
label = "End Icon Visibility",
) {
val wrappedIconRes = remember(this) { requireNotNull(tangemIconUM) }
TangemIcon(
modifier = Modifier.fillMaxSize(),
tangemIconUM = when (wrappedIconRes) {
is TangemIconUM.Currency,
is TangemIconUM.Ident,
is TangemIconUM.Image,
is TangemIconUM.Url,
-> wrappedIconRes
is TangemIconUM.Icon -> wrappedIconRes.copy(tintReference = ColorReference2 { iconColor })
},
)
}
}
@ -351,7 +390,7 @@ private fun TangemBadge_Preview(@PreviewParameter(TangemBadgePreviewProvider::cl
repeat(TangemBadgeType.entries.size) { index ->
TangemBadge(
text = stringReference("Title").takeIf { yIndex < 2 },
iconRes = R.drawable.ic_information_24,
tangemIconUM = TangemIconUM.Icon(R.drawable.ic_information_24),
type = TangemBadgeType.entries[index],
color = params,
shape = TangemBadgeShape.entries[yIndex % 2],

View file

@ -1,14 +1,14 @@
package com.tangem.core.ui.ds.badge
import androidx.annotation.DrawableRes
import com.tangem.core.ui.ds.badge.TangemBadgeSize.X9
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
/**
* UI model for [TangemBadge] component
*
* @param text TextReference for the badge label.
* @param iconRes Drawable resource ID for the icon to be displayed in the badge.
* @param tangemIconUM Model of representation for the icon to be displayed in the badge.
* @param size [TangemBadgeSize] defining the size of the badge.
* @param shape [TangemBadgeShape] defining the shape of the badge.
* @param color [TangemBadgeColor] defining the color scheme of the badge.
@ -18,7 +18,7 @@ import com.tangem.core.ui.extensions.TextReference
*/
class TangemBadgeUM(
val text: TextReference,
@DrawableRes val iconRes: Int? = null,
val tangemIconUM: TangemIconUM? = null,
val size: TangemBadgeSize = X9,
val shape: TangemBadgeShape = TangemBadgeShape.Default,
val color: TangemBadgeColor = TangemBadgeColor.Gray,

View file

@ -2,12 +2,19 @@ package com.tangem.core.ui.ds.image
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.runtime.Immutable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.icons.identicon.IdentIcon
@ -40,6 +47,11 @@ sealed interface TangemIconUM {
data class Ident(
val text: String,
) : TangemIconUM
/** Image represented from network by url */
data class Url(
val url: String,
) : TangemIconUM
}
/**
@ -72,5 +84,24 @@ fun TangemIcon(tangemIconUM: TangemIconUM, modifier: Modifier = Modifier) {
address = tangemIconUM.text,
modifier = modifier,
)
is TangemIconUM.Url -> SubcomposeAsyncImage(
modifier = modifier,
model = ImageRequest.Builder(context = LocalContext.current)
.data(tangemIconUM.url)
.crossfade(enable = true)
.allowHardware(enable = false)
.build(),
loading = { CircleShimmer() },
error = {
Box(
modifier = Modifier
.background(
color = TangemTheme.colors2.surface.level3,
shape = CircleShape,
),
)
},
contentDescription = null,
)
}
}

View file

@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@ -27,12 +28,15 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.R
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.haze.hazeForegroundEffectTangem
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import dev.chrisbanes.haze.HazeStyle
@ -117,6 +121,7 @@ private fun BoxScope.BackgroundLayer(icon: TangemIconUM, blurRadius: Dp = 26.dp)
is TangemIconUM.Icon -> SolidColorBackground(icon.tintReference(), blurRadius)
is TangemIconUM.Ident -> Unit
is TangemIconUM.Image -> ResBackground(icon.imageRes, blurRadius)
is TangemIconUM.Url -> UrlColorBackground(icon.url, blurRadius)
}
}
@ -213,6 +218,31 @@ private fun BoxScope.SolidColorBackground(color: Color, blurRadius: Dp) {
)
}
@Composable
private fun BoxScope.UrlColorBackground(url: String, blurRadius: Dp) {
SubcomposeAsyncImage(
modifier = Modifier
.matchParentSize()
.hazeForegroundEffectTangem(style = HazeStyle(blurRadius = blurRadius, tint = null)),
model = ImageRequest.Builder(context = LocalContext.current)
.data(url)
.crossfade(enable = true)
.allowHardware(enable = false)
.build(),
loading = { CircleShimmer() },
error = {
Box(
modifier = Modifier
.background(
color = TangemTheme.colors2.surface.level3,
shape = CircleShape,
),
)
},
contentDescription = null,
)
}
private const val SCALE_FACTOR = 1.5f
private const val INNER_SHADOW_COLOR_START = 0x00000000
private const val INNER_SHADOW_COLOR_END = 0xFFFFFFFF

View file

@ -17,6 +17,7 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.ds.badge.*
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@ -81,7 +82,7 @@ internal fun TokenRowPromoBanner(promoBannerUM: TangemTokenRowUM.PromoBannerUM.C
shape = TangemBadgeShape.Rounded,
color = TangemBadgeColor.Green,
type = TangemBadgeType.Tinted,
iconRes = R.drawable.ic_close_24,
tangemIconUM = TangemIconUM.Icon(R.drawable.ic_close_24),
iconPosition = TangemBadgeIconPosition.None,
onClick = promoBannerUM.onCloseClick,
)

View file

@ -45,7 +45,7 @@ fun TangemTab(
val backgroundColor = if (isChecked) {
TangemTheme.colors2.tabs.backgroundPrimary
} else {
TangemTheme.colors2.tabs.textPrimary
TangemTheme.colors2.tabs.backgroundSecondary
}
val textColor = if (isChecked) {
TangemTheme.colors2.tabs.textPrimary

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:pathData="M15.631,7.455C16.024,7.067 16.657,7.071 17.045,7.464C17.433,7.857 17.429,8.491 17.036,8.879L11.851,14L17.036,19.122C17.429,19.51 17.433,20.144 17.045,20.537C16.657,20.93 16.024,20.933 15.631,20.545L9.848,14.833C9.384,14.375 9.384,13.626 9.848,13.168L15.631,7.455Z"
android:fillColor="#000000"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M15.417,3C17.118,3 18,3.892 18,5.611V15.389C18,17.1 17.118,18 15.417,18H4.583C2.882,18 2,17.108 2,15.389V5.611C2,3.892 2.882,3 4.583,3H15.417ZM4.559,6.896C3.905,6.897 3.543,7.242 3.543,7.949V15.373C3.543,16.072 3.905,16.425 4.559,16.425H15.425C16.086,16.425 16.457,16.072 16.457,15.373V7.949C16.457,7.242 16.086,6.896 15.425,6.896H4.559ZM6.37,13.638C6.653,13.638 6.748,13.726 6.748,14.015V14.481C6.748,14.77 6.653,14.858 6.37,14.858H5.905C5.63,14.858 5.535,14.77 5.535,14.481V14.015C5.535,13.726 5.63,13.638 5.905,13.638H6.37ZM8.945,13.638C9.228,13.638 9.323,13.726 9.323,14.015V14.481C9.323,14.77 9.228,14.858 8.945,14.858H8.48C8.205,14.858 8.11,14.77 8.11,14.481V14.015C8.11,13.726 8.205,13.638 8.48,13.638H8.945ZM11.519,13.638C11.803,13.638 11.897,13.726 11.898,14.015V14.481C11.897,14.77 11.803,14.858 11.519,14.858H11.064C10.78,14.858 10.685,14.77 10.685,14.481V14.015C10.685,13.726 10.78,13.638 11.064,13.638H11.519ZM6.37,11.051C6.653,11.051 6.748,11.139 6.748,11.428V11.894C6.748,12.183 6.654,12.271 6.37,12.271H5.905C5.63,12.271 5.535,12.183 5.535,11.894V11.428C5.535,11.139 5.63,11.051 5.905,11.051H6.37ZM8.945,11.051C9.228,11.051 9.323,11.139 9.323,11.428V11.894C9.323,12.183 9.228,12.271 8.945,12.271H8.48C8.205,12.271 8.11,12.183 8.11,11.894V11.428C8.11,11.139 8.205,11.051 8.48,11.051H8.945ZM11.519,11.051C11.803,11.051 11.897,11.139 11.898,11.428V11.894C11.898,12.183 11.803,12.271 11.519,12.271H11.064C10.78,12.271 10.685,12.183 10.685,11.894V11.428C10.685,11.139 10.78,11.051 11.064,11.051H11.519ZM14.095,11.051C14.378,11.051 14.473,11.139 14.473,11.428V11.894C14.473,12.183 14.378,12.271 14.095,12.271H13.638C13.354,12.271 13.26,12.183 13.26,11.894V11.428C13.26,11.139 13.354,11.051 13.638,11.051H14.095ZM8.945,8.463C9.228,8.463 9.323,8.552 9.323,8.841V9.307C9.323,9.596 9.228,9.684 8.945,9.685H8.48C8.205,9.685 8.11,9.596 8.11,9.307V8.841C8.11,8.552 8.205,8.463 8.48,8.463H8.945ZM11.519,8.463C11.803,8.463 11.897,8.552 11.898,8.841V9.307C11.898,9.596 11.803,9.685 11.519,9.685H11.064C10.78,9.685 10.685,9.596 10.685,9.307V8.841C10.685,8.552 10.78,8.463 11.064,8.463H11.519ZM14.095,8.463C14.378,8.463 14.473,8.552 14.473,8.841V9.307C14.473,9.596 14.378,9.685 14.095,9.685H13.638C13.354,9.685 13.26,9.596 13.26,9.307V8.841C13.26,8.552 13.354,8.463 13.638,8.463H14.095Z"
android:fillColor="#ffffff"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M9.5,10H7.5C6.672,10 6,10.672 6,11.5V18.5C6,19.328 6.672,20 7.5,20H16.5C17.328,20 18,19.328 18,18.5V11.5C18,10.672 17.328,10 16.5,10H14.5V8H16.5C18.433,8 20,9.567 20,11.5V18.5C20,20.433 18.433,22 16.5,22H7.5C5.567,22 4,20.433 4,18.5V11.5C4,9.567 5.567,8 7.5,8H9.5V10ZM11.374,2.225C11.75,1.965 12.249,1.965 12.625,2.225L12.704,2.285L15.641,4.731C16.065,5.085 16.122,5.716 15.769,6.141C15.415,6.565 14.784,6.622 14.359,6.268L13,5.135V14C13,14.552 12.552,15 12,15C11.448,15 11,14.552 11,14V5.135L9.641,6.268C9.216,6.622 8.585,6.565 8.231,6.141C7.878,5.716 7.935,5.085 8.359,4.731L11.296,2.285L11.374,2.225Z"
android:fillColor="#ffffff"/>
</vector>

View file

@ -25,9 +25,7 @@ import com.tangem.features.feed.ui.earn.state.EarnListItemUM
@Composable
internal fun MostlyUsedCard(item: EarnListItemUM, onClick: () -> Unit, modifier: Modifier = Modifier) {
val isRedesignEnabled = LocalRedesignEnabled.current
if (isRedesignEnabled) {
if (LocalRedesignEnabled.current) {
MostlyUsedCardV2(
modifier = modifier,
item = item,

View file

@ -1,15 +1,96 @@
package com.tangem.features.feed.ui.feed.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.HorizontalFadeWithBlur
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ShowMoreArticlesCard
import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
@Suppress("LongMethod")
@Composable
internal fun NewsSlider(newsSliderConfig: NewsSliderConfig) {
val background = LocalMainBottomSheetColor.current.value
val isRedesignEnabled = LocalRedesignEnabled.current
if (isRedesignEnabled) {
NewsSliderV2(newsSliderConfig)
} else {
NewsSliderV1(newsSliderConfig)
Box(
modifier = Modifier.fillMaxWidth(),
) {
LazyRow(
modifier = Modifier
.conditionalCompose(
condition = isRedesignEnabled,
modifier = {
hazeSourceTangem(-1f)
},
)
.background(color = background),
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = rememberLazyListState(),
) {
itemsIndexed(
items = newsSliderConfig.content,
key = { index, _ -> index },
contentType = { _, _ -> "article" },
) { index, article ->
val articleModifier = if (index == FOURTH_ITEM_INDEX) {
Modifier.onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderScroll,
)
} else {
Modifier
}
ArticleCard(
articleConfigUM = article,
onArticleClick = { newsSliderConfig.callbacks.onArticleClick(article.id) },
modifier = articleModifier
.width(228.dp)
.heightIn(min = 172.dp)
.fillMaxHeight(),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
)
}
if (newsSliderConfig.shouldShowSeeAllNewsItem) {
item(contentType = "show_more") {
ShowMoreArticlesCard(
modifier = Modifier
.width(228.dp)
.heightIn(min = 172.dp)
.onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderEndReached,
),
onClick = newsSliderConfig.callbacks.onOpenAllNews,
)
}
}
}
if (isRedesignEnabled) {
HorizontalFadeWithBlur(
modifier = Modifier
.align(Alignment.TopEnd)
.heightIn(min = 172.dp)
.fillMaxHeight()
.width(100.dp),
backgroundColor = background,
)
}
}
}

View file

@ -1,67 +0,0 @@
package com.tangem.features.feed.ui.feed.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.unit.dp
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ShowMoreArticlesCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
@Composable
internal fun NewsSliderV1(newsSliderConfig: NewsSliderConfig) {
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = rememberLazyListState(),
) {
itemsIndexed(
items = newsSliderConfig.content,
key = { index, _ -> index },
contentType = { _, _ -> "article" },
) { index, article ->
val articleModifier = if (index == FOURTH_ITEM_INDEX) {
Modifier.onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderScroll,
)
} else {
Modifier
}
ArticleCard(
articleConfigUM = article,
onArticleClick = { newsSliderConfig.callbacks.onArticleClick(article.id) },
modifier = articleModifier
.heightIn(min = 164.dp)
.width(216.dp),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
)
}
if (newsSliderConfig.shouldShowSeeAllNewsItem) {
item(contentType = "show_more") {
ShowMoreArticlesCard(
modifier = Modifier
.width(216.dp)
.heightIn(min = 164.dp)
.onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderEndReached,
),
onClick = newsSliderConfig.callbacks.onOpenAllNews,
)
}
}
}
}

View file

@ -1,112 +0,0 @@
package com.tangem.features.feed.ui.feed.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ShowMoreArticlesCard
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
private val dividerSpacerWidth = 20.dp
@Suppress("MagicNumber", "LongMethod")
@Composable
internal fun NewsSliderV2(newsSliderConfig: NewsSliderConfig) {
val density = LocalDensity.current
val dividerWidthPx = with(density) { 1.dp.roundToPx() }
val spacerWidthPx = with(density) { dividerSpacerWidth.roundToPx() }
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
state = rememberLazyListState(),
) {
itemsIndexed(
items = newsSliderConfig.content,
key = { index, _ -> index },
contentType = { _, _ -> "article" },
) { index, article ->
val articleModifier = Modifier.conditional(
condition = index == FOURTH_ITEM_INDEX,
modifier = {
onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderScroll,
)
},
)
val shouldShowDivider = newsSliderConfig.shouldShowSeeAllNewsItem ||
index < newsSliderConfig.content.size - 1
// have to use layout cause LazyRow has not fixed height and divider can not be measured
Layout(
modifier = Modifier,
content = {
ArticleCard(
articleConfigUM = article,
onArticleClick = { newsSliderConfig.callbacks.onArticleClick(article.id) },
modifier = articleModifier
.fillMaxHeight()
.width(220.dp),
)
Spacer(modifier = Modifier.width(dividerSpacerWidth))
Box(
modifier = Modifier
.width(1.dp)
.background(TangemTheme.colors2.border.neutral.secondary),
)
Spacer(modifier = Modifier.width(dividerSpacerWidth))
},
) { measurables, constraints ->
val cardPlaceable = measurables[0].measure(constraints)
val height = cardPlaceable.height
if (shouldShowDivider) {
val leftSpacer = measurables[1].measure(Constraints.fixed(spacerWidthPx, height))
val divider = measurables[2].measure(Constraints.fixed(dividerWidthPx, height))
val rightSpacer = measurables[3].measure(Constraints.fixed(spacerWidthPx, height))
val totalWidth = cardPlaceable.width + leftSpacer.width + divider.width + rightSpacer.width
layout(totalWidth, height) {
cardPlaceable.place(0, 0)
leftSpacer.place(cardPlaceable.width, 0)
divider.place(cardPlaceable.width + leftSpacer.width, 0)
rightSpacer.place(cardPlaceable.width + leftSpacer.width + divider.width, 0)
}
} else {
layout(cardPlaceable.width, height) {
cardPlaceable.place(0, 0)
}
}
}
}
if (newsSliderConfig.shouldShowSeeAllNewsItem) {
item(contentType = "show_more") {
ShowMoreArticlesCard(
modifier = Modifier
.fillMaxHeight()
.width(216.dp)
.onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderEndReached,
),
onClick = newsSliderConfig.callbacks.onOpenAllNews,
)
}
}
}
}

View file

@ -13,9 +13,7 @@ fun ArticleCard(
modifier: Modifier = Modifier,
colors: CardColors = TangemBlockCardColors,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
if (isRedesignEnabled) {
if (LocalRedesignEnabled.current) {
ArticleCardV2(
articleConfigUM = articleConfigUM,
onArticleClick = onArticleClick,

View file

@ -122,6 +122,7 @@ internal fun ShowMoreArticlesCardV2(modifier: Modifier = Modifier, onClick: () -
.padding(vertical = 41.dp, horizontal = 16.dp),
) {
Image(
modifier = Modifier.size(40.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_show_more_news_48),
contentDescription = stringResourceSafe(R.string.common_show_more),
)
@ -134,6 +135,8 @@ internal fun ShowMoreArticlesCardV2(modifier: Modifier = Modifier, onClick: () -
color = TangemTheme.colors2.text.neutral.primary,
)
SpacerH(4.dp)
Text(
text = stringResourceSafe(R.string.news_stay_in_the_loop),
style = TangemTheme.typography2.captionSemibold12,
@ -150,17 +153,27 @@ private fun DefaultArticle(
) {
Column(
modifier = modifier
.background(TangemTheme.colors2.surface.level2)
.clickable { onArticleClick() }
.padding(vertical = 16.dp, horizontal = 10.dp),
.clip(RoundedCornerShape(20.dp))
.background(color = TangemTheme.colors2.surface.level3)
.clickable(onClick = onArticleClick)
.border(
width = 1.dp,
color = TangemTheme.colors2.border.neutral.primary,
shape = RoundedCornerShape(20.dp),
)
.padding(16.dp),
) {
Row(verticalAlignment = Alignment.CenterVertically) {
RatingInfo(rating = stringReference("${articleConfigUM.score}"))
RatingInfo(
rating = stringReference("${articleConfigUM.score}"),
isTrending = false,
)
}
SpacerH(8.dp)
Text(
modifier = Modifier.weight(1f),
text = articleConfigUM.title,
color = if (articleConfigUM.isViewed) {
TangemTheme.colors2.text.neutral.tertiary
@ -168,13 +181,13 @@ private fun DefaultArticle(
TangemTheme.colors2.text.neutral.primary
},
style = TangemTheme.typography2.bodyRegular16,
minLines = 3,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
SpacerH(8.dp)
Text(
modifier = Modifier.padding(vertical = 20.dp),
text = articleConfigUM.createdAt.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
@ -182,6 +195,8 @@ private fun DefaultArticle(
maxLines = 1,
)
SpacerH(8.dp)
Tags(tags = articleConfigUM.tags.toImmutableList())
}
}
@ -269,7 +284,7 @@ private fun DayAndRatingInfo(rating: TextReference, modifier: Modifier = Modifie
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
RatingInfo(rating)
RatingInfo(rating = rating, isTrending = true)
SpacerW(8.dp)
@ -282,10 +297,14 @@ private fun DayAndRatingInfo(rating: TextReference, modifier: Modifier = Modifie
}
@Composable
private fun RatingInfo(rating: TextReference) {
private fun RatingInfo(rating: TextReference, isTrending: Boolean) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_wrapped_circle_star_16),
tint = TangemTheme.colors2.fill.status.attention,
tint = if (isTrending) {
TangemTheme.colors2.fill.status.attention
} else {
TangemTheme.colors2.markers.iconGray
},
contentDescription = null,
)
@ -293,7 +312,11 @@ private fun RatingInfo(rating: TextReference) {
Text(
text = rating.resolveReference(),
color = TangemTheme.colors2.text.status.attention,
color = if (isTrending) {
TangemTheme.colors2.text.status.attention
} else {
TangemTheme.colors2.text.neutral.secondary
},
style = TangemTheme.typography2.captionSemibold12,
)
}

View file

@ -1,18 +1,32 @@
package com.tangem.features.feed.ui.feed.components.articles
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
fun TrendingLoadingArticle(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
TrendingLoadingArticleV2(modifier)
} else {
TrendingLoadingArticleV1(modifier)
}
}
@Composable
private fun TrendingLoadingArticleV1(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
@ -40,25 +54,167 @@ fun TrendingLoadingArticle(modifier: Modifier = Modifier) {
}
}
@Composable
private fun TrendingLoadingArticleV2(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors2.surface.level3),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
RectangleShimmer(modifier = Modifier.size(width = 46.dp, height = 16.dp), radius = TangemTheme.dimens2.x25)
SpacerH(8.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(24.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(48.dp)
RectangleShimmer(modifier = Modifier.size(width = 46.dp, height = 16.dp), radius = TangemTheme.dimens2.x25)
SpacerH(12.dp)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
RectangleShimmer(
modifier = Modifier.size(width = 58.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier.size(width = 58.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier.size(width = 58.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
}
}
@Composable
fun DefaultLoadingArticle(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
DefaultLoadingArticleV2(modifier)
} else {
DefaultLoadingArticleV1(modifier)
}
}
@Composable
private fun DefaultLoadingArticleV1(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(modifier = Modifier.padding(12.dp)) {
RectangleShimmer(modifier = Modifier.size(width = 110.dp, height = 16.dp), radius = 4.dp)
SpacerH(12.dp)
RectangleShimmer(modifier = Modifier.size(width = 142.dp, height = 18.dp), radius = 4.dp)
SpacerH(6.dp)
RectangleShimmer(modifier = Modifier.size(width = 176.dp, height = 18.dp), radius = 4.dp)
SpacerH(6.dp)
RectangleShimmer(modifier = Modifier.size(width = 120.dp, height = 18.dp), radius = 4.dp)
SpacerH(16.dp)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
RectangleShimmer(modifier = Modifier.size(width = 46.dp, height = 16.dp), radius = TangemTheme.dimens2.x25)
SpacerH(8.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(28.dp)
RectangleShimmer(modifier = Modifier.size(width = 46.dp, height = 16.dp), radius = TangemTheme.dimens2.x25)
SpacerH(8.dp)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
RectangleShimmer(modifier = Modifier.size(width = 72.dp, height = 24.dp), radius = 8.dp)
RectangleShimmer(modifier = Modifier.size(width = 72.dp, height = 24.dp), radius = 8.dp)
RectangleShimmer(
modifier = Modifier.size(width = 72.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier.size(width = 62.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier.size(width = 32.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
}
}
@Composable
private fun DefaultLoadingArticleV2(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors2.surface.level3),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
RectangleShimmer(modifier = Modifier.size(width = 46.dp, height = 16.dp), radius = TangemTheme.dimens2.x25)
SpacerH(8.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(44.dp)
RectangleShimmer(modifier = Modifier.size(width = 46.dp, height = 16.dp), radius = TangemTheme.dimens2.x25)
SpacerH(12.dp)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
RectangleShimmer(
modifier = Modifier.size(width = 72.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier.size(width = 62.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier.size(width = 32.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TrendingLoadingArticlePreviewV1() {
TangemThemePreview {
TrendingLoadingArticle()
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TrendingLoadingArticlePreviewV2() {
TangemThemePreviewRedesign {
TrendingLoadingArticle()
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun DefaultLoadingArticlePreviewV1() {
TangemThemePreview {
DefaultLoadingArticle()
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun DefaultLoadingArticlePreviewV2() {
TangemThemePreviewRedesign {
DefaultLoadingArticle()
}
}

View file

@ -8,8 +8,12 @@ import androidx.compose.ui.layout.SubcomposeMeasureScope
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.label.Label
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.ds.badge.*
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.utils.StringsSigns
import kotlinx.collections.immutable.ImmutableList
@ -31,7 +35,25 @@ internal fun Tags(tags: ImmutableList<LabelUM>, modifier: Modifier = Modifier) {
val tagPlaceables = subcompose(ContentSlot.Tags) {
tags.forEach { tag ->
Label(state = tag)
if (LocalRedesignEnabled.current) {
TangemBadge(
text = tag.text,
tangemIconUM = when (val content = tag.leadingContent) {
LabelLeadingContentUM.None -> null
is LabelLeadingContentUM.Token -> TangemIconUM.Url(content.iconUrl)
},
shape = TangemBadgeShape.Rounded,
size = TangemBadgeSize.X6,
type = TangemBadgeType.Tinted,
color = TangemBadgeColor.Gray,
iconPosition = when (tag.leadingContent) {
LabelLeadingContentUM.None -> TangemBadgeIconPosition.None
is LabelLeadingContentUM.Token -> TangemBadgeIconPosition.Start
},
)
} else {
Label(state = tag)
}
}
}.map { it.measure(constraints) }
@ -108,12 +130,22 @@ private fun SubcomposeMeasureScope.calculateLayoutInfo(
@Composable
private fun OverflowLabel(count: Int) {
Label(
state = LabelUM(
if (LocalRedesignEnabled.current) {
TangemBadge(
text = TextReference.Str("${StringsSigns.PLUS}$count"),
maxLines = 1,
),
)
shape = TangemBadgeShape.Rounded,
size = TangemBadgeSize.X6,
type = TangemBadgeType.Tinted,
color = TangemBadgeColor.Gray,
)
} else {
Label(
state = LabelUM(
text = TextReference.Str("${StringsSigns.PLUS}$count"),
maxLines = 1,
),
)
}
}
private fun calculateRowWidth(placeables: List<Placeable>, spacingPx: Int): Int {

View file

@ -1,23 +1,45 @@
package com.tangem.features.feed.ui.news.details.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
fun NewsDetailsPlaceholder(background: Color, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
NewsDetailsPlaceholderV2(background, modifier)
} else {
NewsDetailsPlaceholderV1(background, modifier)
}
}
@Suppress("LongMethod")
@Composable
private fun NewsDetailsPlaceholderV1(background: Color, modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxSize().background(background).padding(16.dp),
modifier = modifier
.fillMaxSize()
.background(background)
.padding(16.dp),
) {
RectangleShimmer(modifier = Modifier.size(width = 112.dp, height = 20.dp))
SpacerH(8.dp)
RectangleShimmer(
modifier = Modifier.fillMaxWidth().height(28.dp),
modifier = Modifier
.fillMaxWidth()
.height(28.dp),
)
SpacerH(4.dp)
RectangleShimmer(modifier = Modifier.size(height = 28.dp, width = 208.dp))
@ -29,29 +51,178 @@ fun NewsDetailsPlaceholder(background: Color, modifier: Modifier = Modifier) {
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
RectangleShimmer(
modifier = Modifier.fillMaxWidth().height(20.dp),
modifier = Modifier
.fillMaxWidth()
.height(20.dp),
)
RectangleShimmer(
modifier = Modifier.fillMaxWidth().height(20.dp).padding(end = 30.dp),
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 30.dp),
)
RectangleShimmer(
modifier = Modifier.fillMaxWidth().height(20.dp).padding(end = 30.dp),
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 30.dp),
)
RectangleShimmer(
modifier = Modifier.fillMaxWidth().height(20.dp).padding(end = 24.dp),
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 24.dp),
)
RectangleShimmer(
modifier = Modifier.fillMaxWidth().height(20.dp).padding(end = 70.dp),
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 70.dp),
)
RectangleShimmer(
modifier = Modifier.fillMaxWidth().height(20.dp).padding(end = 30.dp),
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 30.dp),
)
RectangleShimmer(
modifier = Modifier.fillMaxWidth().height(20.dp).padding(end = 96.dp),
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 96.dp),
)
RectangleShimmer(
modifier = Modifier.fillMaxWidth().height(20.dp).padding(end = 100.dp),
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 100.dp),
)
}
}
}
@Suppress("LongMethod")
@Composable
private fun NewsDetailsPlaceholderV2(background: Color, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.background(background)
.padding(16.dp),
) {
Row(
modifier = Modifier.height(50.dp),
horizontalArrangement = Arrangement.spacedBy(30.dp),
) {
Column {
RectangleShimmer(
modifier = Modifier.size(width = 50.dp, height = 20.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(10.dp)
RectangleShimmer(
modifier = Modifier.size(width = 90.dp, height = 18.dp),
radius = TangemTheme.dimens2.x25,
)
}
VerticalDivider(color = TangemTheme.colors2.border.neutral.primary)
Column {
RectangleShimmer(
modifier = Modifier.size(width = 50.dp, height = 20.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(10.dp)
RectangleShimmer(
modifier = Modifier.size(width = 90.dp, height = 18.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
SpacerH(36.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(40.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(12.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
.padding(end = 106.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(36.dp)
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
RectangleShimmer(
modifier = Modifier.size(width = 98.dp, height = 36.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier.size(width = 98.dp, height = 36.dp),
radius = TangemTheme.dimens2.x25,
)
}
SpacerH(20.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 22.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(12.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 66.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(12.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 18.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(12.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 46.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun NewsDetailsPlaceholderPreviewV1() {
TangemThemePreview {
NewsDetailsPlaceholder(background = TangemTheme.colors.background.tertiary)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun NewsDetailsPlaceholderPreviewV2() {
TangemThemePreviewRedesign {
NewsDetailsPlaceholder(background = TangemTheme.colors2.surface.level3)
}
}

View file

@ -0,0 +1,126 @@
package com.tangem.features.feed.ui.news.details.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun QuickRecap(content: String, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
QuickRecapV2(content, modifier)
} else {
QuickRecapV1(content, modifier)
}
}
@Composable
private fun QuickRecapV1(content: String, modifier: Modifier = Modifier) {
Box(
modifier = modifier.height(IntrinsicSize.Min),
) {
VerticalDivider(
modifier = Modifier
.fillMaxHeight()
.padding(start = 8.dp),
thickness = 2.dp,
color = TangemTheme.colors.stroke.primary,
)
Column(modifier = Modifier.padding(start = 20.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.ic_quick_recap_16),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
modifier = Modifier.size(20.dp),
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResourceSafe(R.string.news_quick_recap),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.accent,
)
}
Spacer(modifier = Modifier.height(12.dp))
Text(
text = content,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
}
}
}
@Composable
private fun QuickRecapV2(content: String, modifier: Modifier = Modifier) {
Column(modifier = modifier.height(IntrinsicSize.Min)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_stars_20),
contentDescription = null,
)
SpacerW(2.dp)
Text(
text = buildAnnotatedString {
withStyle(
SpanStyle().copy(
brush = Brush.linearGradient(
GRADIENT_START to Color(LINEAR_GRADIENT_FIRST_PART),
GRADIENT_END to Color(LINEAR_GRADIENT_SECOND_PART),
),
),
) {
append(stringResourceSafe(R.string.news_quick_recap))
}
},
style = TangemTheme.typography2.bodyRegular14,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
SpacerH(10.dp)
Box {
VerticalDivider(
modifier = Modifier.fillMaxHeight(),
thickness = 2.dp,
color = Color(QUICK_RECAP_DIVIDER_COLOR),
)
Text(
modifier = Modifier.padding(start = 16.dp),
text = content,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
}
}
}
private const val QUICK_RECAP_DIVIDER_COLOR = 0xFFA99FFF
private const val LINEAR_GRADIENT_FIRST_PART = 0xFFA3A0FF
private const val LINEAR_GRADIENT_SECOND_PART = 0xFFF79DFF
private const val GRADIENT_START = 0f
private const val GRADIENT_END = 0.5f

View file

@ -17,8 +17,10 @@ import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.chip.Chip
import com.tangem.core.ui.components.chip.entity.ChipUM
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.ds.tabs.TangemTab
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.feed.ui.news.list.components.NewsListLazyColumn
import com.tangem.features.feed.ui.news.list.state.NewsListState
@ -29,6 +31,7 @@ import kotlinx.collections.immutable.toImmutableSet
@Composable
internal fun NewsListContent(state: NewsListUM, modifier: Modifier = Modifier) {
val background = LocalMainBottomSheetColor.current.value
val isRedesignEnabled = LocalRedesignEnabled.current
val lazyListState = rememberLazyListState()
Column(
@ -44,7 +47,17 @@ internal fun NewsListContent(state: NewsListUM, modifier: Modifier = Modifier) {
items = state.filters,
key = { it.id },
) { filter ->
Chip(state = filter)
if (isRedesignEnabled) {
TangemTab(
text = filter.text,
isChecked = filter.isSelected,
onCheckedChange = {
filter.onClick()
},
)
} else {
Chip(state = filter)
}
}
}

View file

@ -112,7 +112,9 @@ private fun Content(
key = ArticleConfigUM::id,
) { article ->
ArticleCard(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.heightIn(min = 152.dp)
.fillMaxWidth(),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
articleConfigUM = article,
onArticleClick = {