Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-12 07:42:59 +01:00
parent d31d3f8a9b
commit d4b7aa85e1
18 changed files with 1225 additions and 183 deletions

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="M13.452,7.634C13.838,7.239 14.471,7.233 14.866,7.618C15.261,8.004 15.267,8.638 14.882,9.033L10.879,13.129C10.396,13.623 9.604,13.623 9.121,13.129L5.118,9.033C4.732,8.638 4.74,8.004 5.135,7.618C5.53,7.233 6.163,7.239 6.549,7.634L10.001,11.166L13.452,7.634Z"
android:fillColor="#656565"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M14.106,9.477C14.395,9.315 14.76,9.418 14.923,9.707C15.085,9.995 14.983,10.361 14.694,10.523L9.471,13.461C8.558,13.975 7.442,13.975 6.529,13.461L1.306,10.523C1.017,10.361 0.915,9.995 1.077,9.707C1.24,9.418 1.606,9.315 1.895,9.477L7.118,12.415C7.666,12.723 8.335,12.724 8.883,12.415L14.106,9.477ZM6.529,2.54C7.443,2.026 8.557,2.026 9.471,2.54L14.694,5.477C14.883,5.583 15,5.784 15,6.001C15,6.217 14.883,6.417 14.694,6.523L9.471,9.461C8.558,9.975 7.442,9.975 6.529,9.461L1.306,6.523C1.117,6.417 1,6.217 1,6.001C1,5.784 1.117,5.583 1.306,5.477L6.529,2.54ZM8.883,3.585C8.335,3.277 7.666,3.277 7.118,3.585L2.824,6.001L7.118,8.416C7.666,8.723 8.335,8.724 8.883,8.416L13.177,6.001L8.883,3.585Z"
android:fillColor="#656565"/>
</vector>

View file

@ -8,7 +8,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.news.ShortArticle
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.features.feed.ui.utils.mapFormattedDate
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet
@ -16,7 +15,7 @@ import kotlinx.collections.immutable.toPersistentList
import kotlinx.collections.immutable.toPersistentSet
internal class ShortArticleToArticleConfigUMConverter(
private val isTrending: Provider<Boolean>?,
private val isTrending: Boolean?,
) : Converter<List<ShortArticle>, ImmutableList<ArticleConfigUM>> {
override fun convert(value: List<ShortArticle>): ImmutableList<ArticleConfigUM> {
@ -25,7 +24,7 @@ internal class ShortArticleToArticleConfigUMConverter(
id = shortArticle.id,
title = shortArticle.title,
score = shortArticle.score,
isTrending = isTrending?.invoke() ?: shortArticle.isTrending,
isTrending = isTrending ?: shortArticle.isTrending,
tags = buildArticleTags(shortArticle),
createdAt = mapFormattedDate(shortArticle.createdAt),
isViewed = shortArticle.viewed,

View file

@ -9,7 +9,6 @@ import com.tangem.features.feed.model.feed.analytics.FeedAnalyticsEvent
import com.tangem.features.feed.ui.feed.state.FeedListUM
import com.tangem.features.feed.ui.feed.state.NewsUM
import com.tangem.features.feed.ui.feed.state.NewsUMState
import com.tangem.utils.Provider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import org.joda.time.DateTime
@ -92,6 +91,6 @@ internal class UpdateTrendingNewsStateTransformer(
}
private fun getShortArticleConfigConverter(isTrending: Boolean): ShortArticleToArticleConfigUMConverter {
return ShortArticleToArticleConfigUMConverter(isTrending = Provider { isTrending })
return ShortArticleToArticleConfigUMConverter(isTrending = isTrending)
}
}

View file

@ -151,7 +151,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
)
private val shortArticleToArticleConfigUMConverter by lazy {
ShortArticleToArticleConfigUMConverter(isTrending = Provider { false })
ShortArticleToArticleConfigUMConverter(isTrending = false)
}
private val descriptionConverter = DescriptionConverter(

View file

@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -21,25 +22,15 @@ import androidx.compose.ui.text.style.TextAlign
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.SmallButtonShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.list.InfiniteListHandler
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.feed.ui.earn.components.EarnItemPlaceholder
import com.tangem.features.feed.ui.earn.components.EarnListItem
import com.tangem.features.feed.ui.earn.components.MostlyUsedCard
import com.tangem.features.feed.ui.earn.components.MostlyUsedPlaceholder
import com.tangem.core.ui.res.*
import com.tangem.features.feed.ui.earn.components.*
import com.tangem.features.feed.ui.earn.state.*
import kotlinx.collections.immutable.persistentListOf
@ -51,6 +42,7 @@ internal fun EarnContent(state: EarnUM, modifier: Modifier = Modifier) {
val density = LocalDensity.current
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
val listState = rememberLazyListState()
val isRedesignEnabled = LocalRedesignEnabled.current
if (state.bestOpportunities is EarnBestOpportunitiesUM.Content) {
PaginationHandler(
@ -69,7 +61,9 @@ internal fun EarnContent(state: EarnUM, modifier: Modifier = Modifier) {
item(key = "mostly_used_header") {
SectionHeader(
title = stringResourceSafe(R.string.earn_mostly_used),
modifier = Modifier.padding(top = 16.dp),
modifier = Modifier.padding(
top = if (isRedesignEnabled) 10.dp else 16.dp,
),
)
}
@ -83,7 +77,9 @@ internal fun EarnContent(state: EarnUM, modifier: Modifier = Modifier) {
item(key = "best_opportunities_header") {
SectionHeader(
title = stringResourceSafe(R.string.earn_best_opportunities),
modifier = Modifier.padding(top = 20.dp),
modifier = Modifier.padding(
top = if (isRedesignEnabled) 32.dp else 20.dp,
),
)
}
@ -97,14 +93,17 @@ internal fun EarnContent(state: EarnUM, modifier: Modifier = Modifier) {
)
}
bestOpportunitiesItems(
state = state.bestOpportunities,
)
if (isRedesignEnabled) {
bestOpportunitiesItemsV2(state = state.bestOpportunities)
} else {
bestOpportunitiesItemsV1(state = state.bestOpportunities)
}
}
}
@Composable
private fun MostlyUsedContent(state: EarnListUM, onScroll: () -> Unit) {
val isRedesignEnabled = LocalRedesignEnabled.current
AnimatedContent(
targetState = state,
contentKey = { it::class.java },
@ -115,10 +114,7 @@ private fun MostlyUsedContent(state: EarnListUM, onScroll: () -> Unit) {
}
is EarnListUM.Content -> {
LazyRow(
contentPadding = PaddingValues(
horizontal = 16.dp,
vertical = 12.dp,
),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
itemsIndexed(
@ -127,12 +123,7 @@ private fun MostlyUsedContent(state: EarnListUM, onScroll: () -> Unit) {
) { index, item ->
val cardModifier = Modifier.conditional(
condition = index == FOURTH_ITEM_INDEX,
modifier = {
onFirstVisible(
minFractionVisible = 0.5f,
callback = onScroll,
)
},
modifier = { onFirstVisible(minFractionVisible = 0.5f, callback = onScroll) },
)
MostlyUsedCard(
modifier = cardModifier,
@ -147,15 +138,24 @@ private fun MostlyUsedContent(state: EarnListUM, onScroll: () -> Unit) {
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp)
.background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
.conditionalCompose(
condition = isRedesignEnabled,
modifier = {
background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x4),
)
},
otherModifier = {
background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
},
)
.padding(vertical = 32.dp, horizontal = 12.dp),
contentAlignment = Alignment.Center,
) {
UnableToLoadData(onRetryClick = animatedState.onRetryClicked)
}
) { UnableToLoadData(onRetryClick = animatedState.onRetryClicked) }
}
EarnListUM.Empty -> Unit // no need to handle
}
@ -179,7 +179,7 @@ private fun BestOpportunitiesFilters(
}
}
private fun LazyListScope.bestOpportunitiesItems(state: EarnBestOpportunitiesUM) {
private fun LazyListScope.bestOpportunitiesItemsV1(state: EarnBestOpportunitiesUM) {
when (state) {
is EarnBestOpportunitiesUM.Loading -> {
val lastIndex = PLACEHOLDER_ITEMS_COUNT - 1
@ -187,7 +187,7 @@ private fun LazyListScope.bestOpportunitiesItems(state: EarnBestOpportunitiesUM)
count = PLACEHOLDER_ITEMS_COUNT,
key = { "placeholder_$it" },
) { index ->
EarnItemPlaceholder(
EarnItemPlaceholderV1(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
@ -249,56 +249,102 @@ private fun LazyListScope.bestOpportunitiesItems(state: EarnBestOpportunitiesUM)
}
}
@Composable
private fun FilterButtons(
earnFilterUM: EarnFilterUM,
onNetworkFilterClick: () -> Unit,
onTypeFilterClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier.padding(horizontal = 16.dp),
) {
SecondarySmallButton(
config = SmallButtonConfig(
text = when (earnFilterUM.selectedNetworkFilter) {
is EarnFilterNetworkUM.AllNetworks -> TextReference.Res(R.string.earn_filter_all_networks)
is EarnFilterNetworkUM.MyNetworks -> TextReference.Res(R.string.earn_filter_my_networks)
is EarnFilterNetworkUM.Network -> TextReference.Str(earnFilterUM.selectedNetworkFilter.text)
},
onClick = onNetworkFilterClick,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
isEnabled = earnFilterUM.isNetworkFilterEnabled,
),
)
SpacerWMax()
SecondarySmallButton(
config = SmallButtonConfig(
text = earnFilterUM.selectedTypeFilter.text,
onClick = onTypeFilterClick,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
isEnabled = earnFilterUM.isTypeFilterEnabled,
),
)
private fun LazyListScope.bestOpportunitiesItemsV2(state: EarnBestOpportunitiesUM) {
when (state) {
is EarnBestOpportunitiesUM.Loading -> {
val lastIndex = PLACEHOLDER_ITEMS_COUNT - 1
items(
count = PLACEHOLDER_ITEMS_COUNT,
key = { "placeholder_$it" },
) { index ->
EarnItemPlaceholderV2(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors2.surface.level3,
),
)
}
}
is EarnBestOpportunitiesUM.Empty -> {
item(key = "best_opportunities_empty") {
SpacerH(12.dp)
BestOpportunitiesEmpty() // TODO in [REDACTED_TASK_KEY]
}
}
is EarnBestOpportunitiesUM.EmptyFiltered -> {
item(key = "best_opportunities_empty_filtered") {
SpacerH(12.dp)
BestOpportunitiesEmptyFiltered(onClearFilterClick = state.onClearFilterClick) // TODO in [REDACTED_TASK_KEY]
}
}
is EarnBestOpportunitiesUM.Content -> {
if (state.items.isNotEmpty()) {
val lastIndex = state.items.lastIndex
itemsIndexed(
items = state.items,
key = { _, item -> "${item.tokenName}-${item.network}" },
) { index, item ->
EarnListItem(
item = item,
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors2.surface.level3,
),
)
}
}
}
is EarnBestOpportunitiesUM.Error -> {
item(key = "best_opportunities_error") {
SpacerH(12.dp)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
)
.padding(vertical = 142.dp, horizontal = 114.dp),
contentAlignment = Alignment.Center,
) {
UnableToLoadData(onRetryClick = state.onRetryClicked)
}
}
}
}
}
@Composable
private fun FilterButtonsShimmer(modifier: Modifier = Modifier) {
Row(
modifier = modifier.padding(horizontal = 16.dp),
) {
SmallButtonShimmer(
modifier = Modifier.width(110.dp),
)
SpacerWMax()
SmallButtonShimmer(
modifier = Modifier.width(90.dp),
)
Row(modifier = modifier.padding(horizontal = 16.dp, vertical = 4.dp)) {
if (LocalRedesignEnabled.current) {
RectangleShimmer(
modifier = Modifier
.width(130.dp)
.height(36.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerWMax()
RectangleShimmer(
modifier = Modifier
.width(106.dp)
.height(36.dp),
radius = TangemTheme.dimens2.x25,
)
} else {
SmallButtonShimmer(
modifier = Modifier.width(110.dp),
)
SpacerWMax()
SmallButtonShimmer(
modifier = Modifier.width(90.dp),
)
}
}
}
@ -364,14 +410,25 @@ private fun BestOpportunitiesEmptyFiltered(onClearFilterClick: () -> Unit, modif
@Composable
private fun SectionHeader(title: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 16.dp),
text = title,
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
if (LocalRedesignEnabled.current) {
Text(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 24.dp),
text = title,
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
)
} else {
Text(
modifier = modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 16.dp),
text = title,
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
}
}
@Composable
@ -392,7 +449,7 @@ private fun PaginationHandler(listState: LazyListState, state: EarnBestOpportuni
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnContentPreview() {
private fun EarnContentPreviewV1() {
TangemThemePreview {
val background = TangemTheme.colors.background.tertiary
CompositionLocalProvider(
@ -434,7 +491,49 @@ private fun EarnContentPreview() {
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnContentLoadingPreview() {
private fun EarnContentPreviewV2() {
TangemThemePreviewRedesign {
val background = TangemTheme.colors2.surface.level3
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
) {
EarnContent(
state = previewEarnUM(
mostlyUsed = EarnListUM.Content(
items = persistentListOf(
previewEarnListItemUM(),
previewEarnListItemUM(
tokenName = "Cosmos",
symbol = "ATOM",
network = "Cosmos",
),
),
),
bestOpportunities = EarnBestOpportunitiesUM.Content(
items = persistentListOf(
previewEarnListItemUM(
tokenName = "Cosmos Hub",
symbol = "ATOM",
network = "Cosmos network",
),
previewEarnListItemUM(
tokenName = "Tether",
symbol = "USDT",
network = "Ethereum Network",
),
),
onLoadMore = {},
),
),
)
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnContentLoadingPreviewV1() {
TangemThemePreview {
val background = TangemTheme.colors.background.tertiary
CompositionLocalProvider(
@ -453,7 +552,26 @@ private fun EarnContentLoadingPreview() {
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnContentErrorPreview() {
private fun EarnContentLoadingPreviewV2() {
TangemThemePreviewRedesign {
val background = TangemTheme.colors2.surface.level3
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
) {
EarnContent(
state = previewEarnUM(
mostlyUsed = EarnListUM.Error(onRetryClicked = {}),
bestOpportunities = EarnBestOpportunitiesUM.Loading,
),
)
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnContentErrorPreviewV1() {
TangemThemePreview {
val background = TangemTheme.colors.background.tertiary
CompositionLocalProvider(
@ -481,7 +599,35 @@ private fun EarnContentErrorPreview() {
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnContentEmptyPreview() {
private fun EarnContentErrorPreviewV2() {
TangemThemePreviewRedesign {
val background = TangemTheme.colors2.surface.level3
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
) {
EarnContent(
state = previewEarnUM(
mostlyUsed = EarnListUM.Content(
items = persistentListOf(
previewEarnListItemUM(),
previewEarnListItemUM(
tokenName = "Cosmos",
symbol = "ATOM",
network = "Cosmos",
),
),
),
bestOpportunities = EarnBestOpportunitiesUM.Error(onRetryClicked = {}),
),
)
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnContentEmptyPreviewV1() {
TangemThemePreview {
val background = TangemTheme.colors.background.tertiary
CompositionLocalProvider(
@ -506,6 +652,34 @@ private fun EarnContentEmptyPreview() {
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnContentEmptyPreviewV2() {
TangemThemePreviewRedesign {
val background = TangemTheme.colors2.surface.level3
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
) {
EarnContent(
state = previewEarnUM(
mostlyUsed = EarnListUM.Content(
items = persistentListOf(
previewEarnListItemUM(),
previewEarnListItemUM(
tokenName = "Cosmos",
symbol = "ATOM",
network = "Cosmos",
),
),
),
bestOpportunities = EarnBestOpportunitiesUM.Empty,
),
)
}
}
}
@Composable
private fun previewEarnListItemUM(
tokenName: String = "USDC",

View file

@ -0,0 +1,30 @@
package com.tangem.features.feed.ui.earn.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun CardFilterBlock(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
Column(
modifier = modifier
.fillMaxWidth()
.background(
color = TangemTheme.colors2.surface.level2,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
)
.border(
width = 1.dp,
color = TangemTheme.colors2.border.neutral.primary,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
),
content = content,
)
}

View file

@ -0,0 +1,55 @@
package com.tangem.features.feed.ui.earn.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.impl.R
@Composable
internal inline fun <reified T : TangemBottomSheetConfigContent> EarnFilterBottomSheet(
config: TangemBottomSheetConfig,
crossinline content: @Composable ColumnScope.(T) -> Unit,
) {
TangemBottomSheet<T>(
config = config,
containerColor = TangemTheme.colors2.surface.level3,
title = {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing24)
.padding(bottom = TangemTheme.dimens.spacing12),
contentAlignment = Alignment.Center,
) {
Text(
text = stringResourceSafe(R.string.earn_filter_by),
style = TangemTheme.typography2.headingSemibold17,
color = TangemTheme.colors2.text.neutral.primary,
textAlign = TextAlign.Center,
)
SecondaryTangemButton(
modifier = Modifier.align(Alignment.CenterEnd),
onClick = config.onDismissRequest,
iconRes = R.drawable.ic_close_24,
size = TangemButtonSize.X9,
shape = TangemButtonShape.Rounded,
)
}
},
content = content,
)
}

View file

@ -1,9 +1,7 @@
package com.tangem.features.feed.ui.earn.components
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
@ -15,11 +13,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
@ -28,28 +28,53 @@ import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.components.rows.RowContentContainer
import com.tangem.core.ui.components.rows.RowText
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.checkbox.TangemCheckbox
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
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
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.earn.state.EarnFilterByNetworkBottomSheetContentUM
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@Composable
internal fun EarnFilterByNetworkBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
EarnFilterByNetworkBottomSheetV2(config = config)
} else {
EarnFilterByNetworkBottomSheetV1(config = config)
}
}
@Composable
private fun EarnFilterByNetworkBottomSheetV1(config: TangemBottomSheetConfig) {
TangemBottomSheet<EarnFilterByNetworkBottomSheetContentUM>(
config = config,
titleText = resourceReference(R.string.earn_filter_by),
containerColor = TangemTheme.colors.background.tertiary,
content = { Content(it) },
content = { ContentV1(it) },
)
}
@Composable
private fun Content(content: EarnFilterByNetworkBottomSheetContentUM) {
private fun EarnFilterByNetworkBottomSheetV2(config: TangemBottomSheetConfig) {
EarnFilterBottomSheet<EarnFilterByNetworkBottomSheetContentUM>(
config = config,
content = { ContentV2(it) },
)
}
@Composable
private fun ContentV1(content: EarnFilterByNetworkBottomSheetContentUM) {
val allMyNetworks = remember(content) {
content.networks.filterIsInstance<EarnFilterNetworkUM.AllNetworks>() +
content.networks.filterIsInstance<EarnFilterNetworkUM.MyNetworks>()
@ -79,6 +104,132 @@ private fun Content(content: EarnFilterByNetworkBottomSheetContentUM) {
}
}
@Composable
private fun ContentV2(content: EarnFilterByNetworkBottomSheetContentUM) {
val allMyNetworks = remember(content) {
(content.networks.filterIsInstance<EarnFilterNetworkUM.AllNetworks>() +
content.networks.filterIsInstance<EarnFilterNetworkUM.MyNetworks>()).toImmutableList()
}
val specificNetworks = remember(content) {
content
.networks
.filterIsInstance<EarnFilterNetworkUM.Network>()
.toImmutableList()
}
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
NetworksTypesBlock(
allMyNetworks = allMyNetworks,
onOptionClick = content.onOptionClick,
)
SpecificNetworksBlock(
specificNetworks = specificNetworks,
onOptionClick = content.onOptionClick,
)
}
}
@Composable
private fun NetworksTypesBlock(
allMyNetworks: ImmutableList<EarnFilterNetworkUM>,
onOptionClick: (EarnFilterNetworkUM) -> Unit,
) {
CardFilterBlock {
allMyNetworks.fastForEachIndexed { index, item ->
TangemRowContainer(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = allMyNetworks.lastIndex,
addDefaultPadding = false,
)
.clickable { onOptionClick(item) },
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp),
) {
Text(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_TOP),
text = when (item) {
is EarnFilterNetworkUM.AllNetworks -> TextReference.Res(R.string.earn_filter_all_networks)
is EarnFilterNetworkUM.MyNetworks -> TextReference.Res(R.string.earn_filter_my_networks)
is EarnFilterNetworkUM.Network -> TextReference.Str(item.text)
}.resolveReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
)
TangemCheckbox(
modifier = Modifier
.padding(start = 8.dp)
.layoutId(layoutId = TangemRowLayoutId.TAIL),
isChecked = item.isSelected,
onCheckedChange = { onOptionClick(item) },
)
}
}
}
}
@Composable
private fun SpecificNetworksBlock(
specificNetworks: ImmutableList<EarnFilterNetworkUM.Network>,
onOptionClick: (EarnFilterNetworkUM) -> Unit,
) {
if (specificNetworks.isNotEmpty()) {
CardFilterBlock {
Text(
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(top = 16.dp, bottom = 8.dp),
text = stringResourceSafe(id = R.string.earn_filter_networks),
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
)
specificNetworks.fastForEachIndexed { index, item ->
TangemRowContainer(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = specificNetworks.lastIndex,
addDefaultPadding = false,
)
.clickable { onOptionClick(item) },
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp),
) {
Image(
modifier = Modifier
.size(40.dp)
.layoutId(TangemRowLayoutId.HEAD),
imageVector = ImageVector.vectorResource(item.iconRes),
contentDescription = item.symbol,
)
Text(
modifier = Modifier
.padding(start = 8.dp)
.layoutId(layoutId = TangemRowLayoutId.START_TOP),
text = item.text,
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
)
TangemCheckbox(
modifier = Modifier
.padding(start = 8.dp)
.layoutId(layoutId = TangemRowLayoutId.TAIL),
isChecked = item.isSelected,
onCheckedChange = { onOptionClick(item) },
)
}
}
}
}
}
private fun LazyListScope.allMyNetworksList(
allMyNetworks: List<EarnFilterNetworkUM>,
onOptionClicked: (EarnFilterNetworkUM) -> Unit,
@ -201,7 +352,7 @@ private fun LazyListScope.specificNetworksList(
@Preview(widthDp = 360, heightDp = 800)
@Preview(widthDp = 360, heightDp = 800, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview() {
private fun PreviewV1() {
TangemThemePreview(
alwaysShowBottomSheets = true,
) {
@ -235,4 +386,43 @@ private fun Preview() {
)
}
}
}
@Preview(widthDp = 360, heightDp = 800)
@Preview(widthDp = 360, heightDp = 800, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewV2() {
TangemThemePreviewRedesign(
alwaysShowBottomSheets = true,
) {
Box(Modifier.background(TangemTheme.colors.background.secondary)) {
EarnFilterByNetworkBottomSheet(
TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = EarnFilterByNetworkBottomSheetContentUM(
networks = persistentListOf(
EarnFilterNetworkUM.AllNetworks(isSelected = true),
EarnFilterNetworkUM.MyNetworks(isSelected = false),
EarnFilterNetworkUM.Network(
id = "ethereum",
text = "Ethereum",
symbol = "ETH",
iconRes = R.drawable.img_btc_22,
isSelected = false,
),
EarnFilterNetworkUM.Network(
id = "polygon",
text = "Polygon",
symbol = "MATIC",
iconRes = R.drawable.img_btc_22,
isSelected = false,
),
),
onOptionClick = {},
),
),
)
}
}
}

View file

@ -5,34 +5,61 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.inputrow.InputRowChecked
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.checkbox.TangemCheckbox
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
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
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.earn.state.EarnFilterByTypeBottomSheetContentUM
import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM
@Composable
internal fun EarnFilterByTypeBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
EarnFilterByTypeBottomSheetV2(config)
} else {
EarnFilterByTypeBottomSheetV1(config)
}
}
@Composable
private fun EarnFilterByTypeBottomSheetV1(config: TangemBottomSheetConfig) {
TangemBottomSheet<EarnFilterByTypeBottomSheetContentUM>(
config = config,
titleText = resourceReference(R.string.earn_filter_by),
containerColor = TangemTheme.colors.background.tertiary,
content = { Content(it) },
content = { ContentV1(it) },
)
}
@Composable
private fun Content(content: EarnFilterByTypeBottomSheetContentUM) {
private fun EarnFilterByTypeBottomSheetV2(config: TangemBottomSheetConfig) {
EarnFilterBottomSheet<EarnFilterByTypeBottomSheetContentUM>(
config = config,
content = { ContentV2(it) },
)
}
@Composable
private fun ContentV1(content: EarnFilterByTypeBottomSheetContentUM) {
Column(
modifier = Modifier
.padding(
@ -62,10 +89,45 @@ private fun Content(content: EarnFilterByTypeBottomSheetContentUM) {
}
}
@Composable
private fun ContentV2(content: EarnFilterByTypeBottomSheetContentUM) {
CardFilterBlock(
modifier = Modifier.padding(horizontal = 16.dp),
) {
EarnFilterTypeUM.entries.forEachIndexed { index, type ->
TangemRowContainer(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = EarnFilterTypeUM.entries.lastIndex,
addDefaultPadding = false,
)
.clickable { content.onOptionClick(type) },
contentPadding = PaddingValues(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x3),
) {
Text(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_TOP),
text = type.text.resolveReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
)
TangemCheckbox(
modifier = Modifier
.padding(start = 8.dp)
.layoutId(layoutId = TangemRowLayoutId.TAIL),
isChecked = type == content.selectedOption,
onCheckedChange = { content.onOptionClick(type) },
)
}
}
}
}
@Preview(widthDp = 360, heightDp = 640)
@Preview(widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview() {
private fun PreviewV1() {
TangemThemePreview(
alwaysShowBottomSheets = true,
) {
@ -82,4 +144,26 @@ private fun Preview() {
)
}
}
}
@Preview(widthDp = 360, heightDp = 640)
@Preview(widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewV2() {
TangemThemePreviewRedesign(
alwaysShowBottomSheets = true,
) {
Box(Modifier.background(TangemTheme.colors.background.secondary)) {
EarnFilterByTypeBottomSheet(
TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = EarnFilterByTypeBottomSheetContentUM(
selectedOption = EarnFilterTypeUM.All,
onOptionClick = {},
),
),
)
}
}
}

View file

@ -4,12 +4,16 @@ import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -18,15 +22,33 @@ import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
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.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.*
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
@Composable
internal fun EarnListItem(item: EarnListItemUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
EarnListItemV2(
item = item,
modifier = modifier,
)
} else {
EarnListItemV1(
item = item,
modifier = modifier,
)
}
}
@Composable
private fun EarnListItemV1(item: EarnListItemUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
@ -82,27 +104,117 @@ internal fun EarnListItem(item: EarnListItemUM, modifier: Modifier = Modifier) {
}
}
@Composable
private fun EarnListItemV2(item: EarnListItemUM, modifier: Modifier = Modifier) {
TangemRowContainer(
modifier = modifier.clickable { item.onItemClick() },
contentPadding = PaddingValues(12.dp),
content = {
TangemIcon(
tangemIconUM = TangemIconUM.Currency(item.currencyIconState),
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.HEAD)
.padding(end = TangemTheme.dimens2.x2),
)
TokenTitle(
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.START_TOP)
.padding(end = TangemTheme.dimens2.x2),
name = item.tokenName.resolveReference(),
symbol = item.symbol.resolveReference(),
)
Text(
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.START_BOTTOM)
.padding(end = TangemTheme.dimens2.x2),
text = item.network.resolveReference(),
color = TangemTheme.colors2.text.neutral.secondary,
style = TangemTheme.typography2.captionSemibold12,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_TOP),
text = item.earnValue.resolveReference(),
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodySemibold16,
maxLines = 1,
)
ModeBlock(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM),
earnType = item.earnType,
)
},
)
}
@Composable
private fun TokenTitle(name: String, symbol: String, modifier: Modifier = Modifier) {
Row(modifier = modifier) {
Text(
modifier = Modifier
.weight(1f, fill = false)
.alignByBaseline(),
text = name,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
if (LocalRedesignEnabled.current) {
Text(
modifier = Modifier
.weight(1f, fill = false)
.alignByBaseline(),
text = name,
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodySemibold16,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SpacerW(4.dp)
Text(
modifier = Modifier.alignByBaseline(),
text = symbol,
color = TangemTheme.colors2.text.neutral.secondary,
style = TangemTheme.typography2.captionSemibold12,
maxLines = 1,
overflow = TextOverflow.Visible,
)
} else {
Text(
modifier = Modifier
.weight(1f, fill = false)
.alignByBaseline(),
text = name,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SpacerW(4.dp)
Text(
modifier = Modifier.alignByBaseline(),
text = symbol,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
overflow = TextOverflow.Visible,
)
}
}
}
@Composable
private fun ModeBlock(earnType: TextReference, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_staking_new_16),
tint = TangemTheme.colors2.markers.iconGray,
contentDescription = null,
)
SpacerW(4.dp)
Text(
modifier = Modifier.alignByBaseline(),
text = symbol,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
overflow = TextOverflow.Visible,
text = earnType.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.tertiary,
)
}
}
@ -110,7 +222,7 @@ private fun TokenTitle(name: String, symbol: String, modifier: Modifier = Modifi
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnListItemPreview() {
private fun EarnListItemPreviewV1() {
TangemThemePreview {
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
EarnListItem(
@ -151,4 +263,53 @@ private fun EarnListItemPreview() {
)
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnListItemPreviewV2() {
TangemThemePreviewRedesign {
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
SpacerH(12.dp)
EarnListItem(
item = EarnListItemUM(
network = stringReference("Ethereum"),
symbol = stringReference("ETH"),
tokenName = stringReference("Ethereum"),
currencyIconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_eth_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"),
onItemClick = {},
),
)
SpacerH(12.dp)
EarnListItem(
item = EarnListItemUM(
network = stringReference("Ethereum"),
symbol = stringReference("USDT"),
tokenName = stringReference("Tether"),
currencyIconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_eth_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"),
onItemClick = {},
),
)
SpacerH(12.dp)
}
}
}

View file

@ -8,27 +8,13 @@ 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.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
internal fun EarnListPlaceholder(modifier: Modifier = Modifier, placeholderCount: Int = PLACEHOLDER_ITEMS_COUNT) {
Column(
modifier = modifier.fillMaxSize(),
) {
repeat(placeholderCount) {
EarnItemPlaceholder()
}
}
}
@Composable
internal fun EarnItemPlaceholder(modifier: Modifier = Modifier) {
internal fun EarnItemPlaceholderV1(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
@ -38,9 +24,7 @@ internal fun EarnItemPlaceholder(modifier: Modifier = Modifier) {
),
verticalAlignment = Alignment.CenterVertically,
) {
CircleShimmer(
modifier = Modifier.size(36.dp),
)
CircleShimmer(modifier = Modifier.size(36.dp))
SpacerW(12.dp)
@ -91,18 +75,86 @@ internal fun EarnItemPlaceholder(modifier: Modifier = Modifier) {
}
}
@Composable
internal fun EarnItemPlaceholderV2(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
CircleShimmer(modifier = Modifier.size(40.dp))
SpacerW(4.dp)
Column(modifier = Modifier.weight(1f)) {
Row(modifier = Modifier.fillMaxWidth()) {
RectangleShimmer(
modifier = Modifier
.width(96.dp)
.height(20.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerWMax()
RectangleShimmer(
modifier = Modifier
.width(56.dp)
.height(20.dp),
radius = TangemTheme.dimens2.x25,
)
}
SpacerH(4.dp)
Row(modifier = Modifier.fillMaxWidth()) {
RectangleShimmer(
modifier = Modifier
.width(46.dp)
.height(16.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerWMax()
RectangleShimmer(
modifier = Modifier
.width(46.dp)
.height(16.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
}
}
private const val PLACEHOLDER_ITEMS_COUNT = 8
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnListPlaceholderPreview() {
private fun EarnListPlaceholderPreviewV1() {
TangemThemePreview {
Box(
Column(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary),
) {
EarnListPlaceholder()
repeat(PLACEHOLDER_ITEMS_COUNT) {
EarnItemPlaceholderV1()
}
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnListPlaceholderPreviewV2() {
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.background(TangemTheme.colors2.surface.level3),
) {
repeat(PLACEHOLDER_ITEMS_COUNT) {
EarnItemPlaceholderV2()
}
}
}
}

View file

@ -0,0 +1,115 @@
package com.tangem.features.feed.ui.earn.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.ds.button.PrimaryInverseTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.features.feed.ui.earn.state.EarnFilterUM
@Composable
internal fun FilterButtons(
earnFilterUM: EarnFilterUM,
onNetworkFilterClick: () -> Unit,
onTypeFilterClick: () -> Unit,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
FilterButtonsV2(
earnFilterUM = earnFilterUM,
onNetworkFilterClick = onNetworkFilterClick,
onTypeFilterClick = onTypeFilterClick,
modifier = modifier,
)
} else {
FilterButtonsV1(
earnFilterUM = earnFilterUM,
onNetworkFilterClick = onNetworkFilterClick,
onTypeFilterClick = onTypeFilterClick,
modifier = modifier,
)
}
}
@Composable
private fun FilterButtonsV1(
earnFilterUM: EarnFilterUM,
onNetworkFilterClick: () -> Unit,
onTypeFilterClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier.padding(horizontal = 16.dp),
) {
SecondarySmallButton(
config = SmallButtonConfig(
text = when (earnFilterUM.selectedNetworkFilter) {
is EarnFilterNetworkUM.AllNetworks -> TextReference.Res(R.string.earn_filter_all_networks)
is EarnFilterNetworkUM.MyNetworks -> TextReference.Res(R.string.earn_filter_my_networks)
is EarnFilterNetworkUM.Network -> TextReference.Str(earnFilterUM.selectedNetworkFilter.text)
},
onClick = onNetworkFilterClick,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
isEnabled = earnFilterUM.isNetworkFilterEnabled,
),
)
SpacerWMax()
SecondarySmallButton(
config = SmallButtonConfig(
text = earnFilterUM.selectedTypeFilter.text,
onClick = onTypeFilterClick,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
isEnabled = earnFilterUM.isTypeFilterEnabled,
),
)
}
}
@Composable
private fun FilterButtonsV2(
earnFilterUM: EarnFilterUM,
onNetworkFilterClick: () -> Unit,
onTypeFilterClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(modifier = modifier.padding(horizontal = 16.dp, vertical = 4.dp)) {
PrimaryInverseTangemButton(
text = when (earnFilterUM.selectedNetworkFilter) {
is EarnFilterNetworkUM.AllNetworks -> TextReference.Res(R.string.earn_filter_all_networks)
is EarnFilterNetworkUM.MyNetworks -> TextReference.Res(R.string.earn_filter_my_networks)
is EarnFilterNetworkUM.Network -> TextReference.Str(earnFilterUM.selectedNetworkFilter.text)
},
onClick = onNetworkFilterClick,
iconRes = R.drawable.ic_chewron_down_20,
iconPosition = com.tangem.core.ui.ds.button.TangemButtonIconPosition.End,
size = TangemButtonSize.X9,
shape = TangemButtonShape.Rounded,
enabled = earnFilterUM.isNetworkFilterEnabled,
)
SpacerWMax()
PrimaryInverseTangemButton(
text = earnFilterUM.selectedTypeFilter.text,
onClick = onTypeFilterClick,
iconRes = R.drawable.ic_chewron_down_20,
iconPosition = com.tangem.core.ui.ds.button.TangemButtonIconPosition.End,
size = TangemButtonSize.X9,
shape = TangemButtonShape.Rounded,
enabled = earnFilterUM.isTypeFilterEnabled,
)
}
}

View file

@ -44,7 +44,7 @@ internal fun MostlyUsedCard(item: EarnListItemUM, onClick: () -> Unit, modifier:
private fun MostlyUsedCardV2(item: EarnListItemUM, onClick: () -> Unit, modifier: Modifier = Modifier) {
OpportunitiesBG(
modifier = modifier
.width(148.dp)
.width(178.dp)
.clip(TangemTheme.shapes.roundedCornersXMedium)
.clickable(onClick = onClick),
icon = TangemIconUM.Currency(item.currencyIconState),

View file

@ -2,14 +2,9 @@ package com.tangem.features.feed.ui.earn.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
@ -17,27 +12,36 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.CircleShimmer
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
internal fun MostlyUsedPlaceholder(modifier: Modifier = Modifier) {
internal fun MostlyUsedPlaceholder(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(
horizontal = 16.dp,
vertical = 12.dp,
),
) {
LazyRow(
modifier = modifier,
contentPadding = PaddingValues(
horizontal = 16.dp,
vertical = 12.dp,
),
contentPadding = contentPadding,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(PLACEHOLDER_ITEMS_COUNT) {
MostlyUsedItemPlaceholder()
if (LocalRedesignEnabled.current) {
MostlyUsedItemPlaceholderV2()
} else {
MostlyUsedItemPlaceholderV1()
}
}
}
}
@Composable
fun MostlyUsedItemPlaceholder(modifier: Modifier = Modifier) {
private fun MostlyUsedItemPlaceholderV1(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.width(148.dp)
@ -69,13 +73,52 @@ fun MostlyUsedItemPlaceholder(modifier: Modifier = Modifier) {
}
}
@Composable
private fun MostlyUsedItemPlaceholderV2(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.width(178.dp)
.height(130.dp)
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x4),
)
.padding(12.dp),
) {
CircleShimmer(modifier = Modifier.size(40.dp))
SpacerH(22.dp)
RectangleShimmer(
modifier = Modifier
.width(56.dp)
.height(20.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(4.dp)
RectangleShimmer(
modifier = Modifier
.width(46.dp)
.height(16.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
private const val PLACEHOLDER_ITEMS_COUNT = 3
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun MostlyUsedPlaceholderPreview() {
private fun MostlyUsedPlaceholderPreviewV1() {
TangemThemePreview {
MostlyUsedPlaceholder()
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun MostlyUsedPlaceholderPreviewV2() {
TangemThemePreviewRedesign {
MostlyUsedPlaceholder()
}
}

View file

@ -2,10 +2,15 @@ package com.tangem.features.feed.ui.feed.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
@ -13,6 +18,9 @@ import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun Header(
@ -21,6 +29,7 @@ internal fun Header(
shouldShowSeeAll: Boolean,
title: @Composable () -> Unit,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
AnimatedContent(isLoading) { animatedState ->
Row(
modifier = Modifier
@ -30,19 +39,47 @@ internal fun Header(
verticalAlignment = Alignment.CenterVertically,
) {
if (animatedState) {
RectangleShimmer(modifier = Modifier.size(width = 104.dp, height = 18.dp))
if (isRedesignEnabled) {
RectangleShimmer(
modifier = Modifier.size(width = 130.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
} else {
RectangleShimmer(modifier = Modifier.size(width = 104.dp, height = 18.dp))
}
} else {
Box(modifier = Modifier.weight(1f)) {
title()
}
SpacerW(8.dp)
AnimatedVisibility(shouldShowSeeAll) {
SecondarySmallButton(
config = SmallButtonConfig(
text = TextReference.Res(R.string.common_see_all),
onClick = onSeeAllClick,
),
)
if (isRedesignEnabled) {
Row(
modifier = Modifier
.padding(start = 8.dp)
.clickable(onClick = onSeeAllClick),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResourceSafe(R.string.common_see_all),
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodySemibold16,
)
Icon(
modifier = Modifier.size(24.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_small_right_24),
tint = TangemTheme.colors2.markers.iconGray,
contentDescription = null,
)
}
} else {
SecondarySmallButton(
config = SmallButtonConfig(
text = TextReference.Res(R.string.common_see_all),
onClick = onSeeAllClick,
),
)
}
}
}
}

View file

@ -1,9 +1,9 @@
package com.tangem.features.feed.ui.feed.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -16,9 +16,12 @@ import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.earn.components.EarnItemPlaceholderV1
import com.tangem.features.feed.ui.earn.components.EarnListItem
import com.tangem.features.feed.ui.earn.components.EarnListPlaceholder
import com.tangem.features.feed.ui.earn.components.MostlyUsedCard
import com.tangem.features.feed.ui.earn.components.MostlyUsedPlaceholder
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
import com.tangem.features.feed.ui.earn.state.EarnListUM
import kotlinx.collections.immutable.ImmutableList
@ -26,7 +29,15 @@ import kotlinx.collections.immutable.ImmutableList
@Composable
internal fun EarnBlock(onSeeAllClick: () -> Unit, earnListUM: EarnListUM, modifier: Modifier = Modifier) {
if (earnListUM is EarnListUM.Empty) return
if (LocalRedesignEnabled.current) {
EarnBlockV2(onSeeAllClick = onSeeAllClick, earnListUM = earnListUM, modifier = modifier)
} else {
EarnBlockV1(onSeeAllClick = onSeeAllClick, earnListUM = earnListUM, modifier = modifier)
}
}
@Composable
private fun EarnBlockV1(onSeeAllClick: () -> Unit, earnListUM: EarnListUM, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Header(
title = {
@ -56,7 +67,11 @@ internal fun EarnBlock(onSeeAllClick: () -> Unit, earnListUM: EarnListUM, modifi
when (earnListUM) {
is EarnListUM.Content -> EarnContentBlock(items = earnListUM.items)
is EarnListUM.Error -> EarnErrorBlock(onRetryClick = earnListUM.onRetryClicked)
EarnListUM.Loading -> EarnListPlaceholder(placeholderCount = PLACEHOLDER_ITEM_COUNT)
EarnListUM.Loading -> {
repeat(PLACEHOLDER_ITEM_COUNT) {
EarnItemPlaceholderV1()
}
}
EarnListUM.Empty -> Unit
}
}
@ -65,6 +80,55 @@ internal fun EarnBlock(onSeeAllClick: () -> Unit, earnListUM: EarnListUM, modifi
}
}
@Composable
private fun EarnBlockV2(onSeeAllClick: () -> Unit, earnListUM: EarnListUM, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Header(
title = {
Text(
text = stringResourceSafe(R.string.markets_earn_common_title),
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
},
onSeeAllClick = onSeeAllClick,
isLoading = earnListUM is EarnListUM.Loading,
shouldShowSeeAll = earnListUM is EarnListUM.Content,
)
SpacerH(12.dp)
AnimatedContent(
targetState = earnListUM,
contentKey = { it::class.java },
) { earnListUM ->
when (earnListUM) {
is EarnListUM.Content -> EarnContentBlock(items = earnListUM.items)
is EarnListUM.Error -> {
BlockCard(
modifier = Modifier.padding(horizontal = 16.dp),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors2.surface.level3),
) {
EarnErrorBlock(onRetryClick = earnListUM.onRetryClicked)
}
}
EarnListUM.Loading -> {
MostlyUsedPlaceholder(
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = 8.dp,
),
)
}
EarnListUM.Empty -> Unit
}
}
SpacerH(32.dp)
}
}
@Composable
private fun EarnErrorBlock(onRetryClick: () -> Unit) {
UnableToLoadData(
@ -77,9 +141,30 @@ private fun EarnErrorBlock(onRetryClick: () -> Unit) {
@Composable
private fun EarnContentBlock(items: ImmutableList<EarnListItemUM>) {
Column(modifier = Modifier.fillMaxWidth()) {
items.fastForEach { item ->
EarnListItem(item = item)
if (LocalRedesignEnabled.current) {
LazyRow(
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = 8.dp,
),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(
items = items,
key = { item -> "${item.tokenName}-${item.network}" },
) { item ->
MostlyUsedCard(
item = item,
onClick = item.onItemClick,
)
}
}
} else {
Column(modifier = Modifier.fillMaxWidth()) {
items.fastForEach { item ->
EarnListItem(item = item)
}
}
}
}

View file

@ -304,7 +304,7 @@ private inline fun BaseScaffoldWithMarkets(
val peekHeight = bottomSheetHeaderHeightProvider() + TangemTheme.dimens2.x3 + bottomBarHeight
val coroutineScope = rememberCoroutineScope()
val background = TangemTheme.colors2.surface.level3
val background = TangemTheme.colors2.surface.level2
val bottomSheetState = rememberTangemStandardBottomSheetState()
val scaffoldState = rememberTangemBottomSheetScaffoldState(bottomSheetState = bottomSheetState)