Updated on 2026-08-14
This commit is contained in:
parent
0bfeea6bc6
commit
3a84102e1d
13 changed files with 996 additions and 2 deletions
|
|
@ -7,6 +7,7 @@ import com.tangem.core.decompose.context.AppComponentContext
|
|||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.features.feed.components.earn.DefaultEarnComponent
|
||||
import com.tangem.features.feed.components.feed.DefaultFeedComponent
|
||||
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
|
||||
|
|
@ -45,6 +46,10 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
@Serializable
|
||||
@Immutable
|
||||
data class NewsDetails(val params: DefaultNewsDetailsComponent.Params) : Child
|
||||
|
||||
@Serializable
|
||||
@Immutable
|
||||
data class Earn(val params: DefaultEarnComponent.Params) : Child
|
||||
}
|
||||
|
||||
fun createChild(
|
||||
|
|
@ -97,6 +102,12 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
params = DefaultFeedComponent.FeedParams(feedClickIntents = feedEntryClickIntents),
|
||||
)
|
||||
}
|
||||
is Child.Earn -> {
|
||||
DefaultEarnComponent(
|
||||
appComponentContext = appComponentContext,
|
||||
params = child.params,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.features.feed.components.earn
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.features.feed.model.earn.EarnModel
|
||||
import com.tangem.features.feed.ui.earn.EarnContent
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal class DefaultEarnComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
private val params: Params,
|
||||
) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val earnModel = getOrCreateModel<EarnModel, Params>(params = params)
|
||||
|
||||
@Composable
|
||||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val state by earnModel.state.collectAsStateWithLifecycle()
|
||||
TangemTopAppBar(
|
||||
containerColor = background,
|
||||
title = stringResourceSafe(R.string.earn_title),
|
||||
startButton = TopAppBarButtonUM.Icon(
|
||||
iconRes = R.drawable.ic_back_24,
|
||||
onClicked = state.onBackClick,
|
||||
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(bottomSheetState: State<BottomSheetState>, modifier: Modifier) {
|
||||
val state by earnModel.state.collectAsStateWithLifecycle()
|
||||
EarnContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Params(
|
||||
val onBackClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.feed.di
|
|||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.feed.model.FeedEntryModel
|
||||
import com.tangem.features.feed.model.earn.EarnModel
|
||||
import com.tangem.features.feed.model.feed.FeedComponentModel
|
||||
import com.tangem.features.feed.model.market.details.MarketsTokenDetailsModel
|
||||
import com.tangem.features.feed.model.market.list.MarketsListModel
|
||||
|
|
@ -18,6 +19,11 @@ import dagger.multibindings.IntoMap
|
|||
@InstallIn(ModelComponent::class)
|
||||
internal interface ModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(EarnModel::class)
|
||||
fun bindsEarnModel(model: EarnModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(FeedComponentModel::class)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.feed.model.earn
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.feed.components.earn.DefaultEarnComponent
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class EarnModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<DefaultEarnComponent.Params>()
|
||||
|
||||
val state: StateFlow<EarnUM>
|
||||
field = MutableStateFlow(createInitialState())
|
||||
|
||||
private fun createInitialState(): EarnUM = EarnUM(
|
||||
mostlyUsed = EarnListUM.Loading,
|
||||
bestOpportunities = EarnListUM.Loading,
|
||||
selectedNetworkFilter = null,
|
||||
selectedTypeFilter = null,
|
||||
networkFilters = persistentListOf(),
|
||||
typeFilters = persistentListOf(),
|
||||
onBackClick = params.onBackClick,
|
||||
onNetworkFilterClick = { },
|
||||
onTypeFilterClick = { },
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,472 @@
|
|||
package com.tangem.features.feed.ui.earn
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
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.SpacerH
|
||||
import com.tangem.core.ui.components.SmallButtonShimmer
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.UnableToLoadData
|
||||
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.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
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.MostlyUsedPlaceholder
|
||||
import com.tangem.features.feed.ui.earn.state.EarnFilterUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun EarnContent(state: EarnUM, modifier: Modifier = Modifier) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val density = LocalDensity.current
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(background),
|
||||
contentPadding = PaddingValues(bottom = bottomBarHeight),
|
||||
) {
|
||||
item(key = "mostly_used_header") {
|
||||
SectionHeader(
|
||||
title = stringResourceSafe(R.string.earn_mostly_used),
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
item(key = "mostly_used_content") {
|
||||
MostlyUsedContent(state = state.mostlyUsed)
|
||||
}
|
||||
|
||||
item(key = "best_opportunities_header") {
|
||||
SectionHeader(
|
||||
title = stringResourceSafe(R.string.earn_best_opportunities),
|
||||
modifier = Modifier.padding(top = 20.dp),
|
||||
)
|
||||
}
|
||||
|
||||
item(key = "best_opportunities_filters") {
|
||||
SpacerH(12.dp)
|
||||
BestOpportunitiesFilters(
|
||||
state = state.bestOpportunities,
|
||||
selectedNetworkFilter = state.selectedNetworkFilter,
|
||||
selectedTypeFilter = state.selectedTypeFilter,
|
||||
onNetworkFilterClick = state.onNetworkFilterClick,
|
||||
onTypeFilterClick = state.onTypeFilterClick,
|
||||
)
|
||||
}
|
||||
|
||||
bestOpportunitiesItems(
|
||||
state = state.bestOpportunities,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MostlyUsedContent(state: EarnListUM) {
|
||||
when (state) {
|
||||
is EarnListUM.Loading -> {
|
||||
MostlyUsedPlaceholder()
|
||||
}
|
||||
is EarnListUM.Content -> {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = 16.dp,
|
||||
vertical = 12.dp,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
items(
|
||||
items = state.items,
|
||||
key = { "${it.tokenName}-${it.network}" },
|
||||
) { item ->
|
||||
MostlyUsedCard(
|
||||
item = item,
|
||||
onClick = item.onItemClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is EarnListUM.Error -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 12.dp,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
UnableToLoadData(onRetryClick = state.onRetryClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MostlyUsedCard(item: EarnListItemUM, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.width(148.dp)
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
CurrencyIcon(
|
||||
modifier = Modifier.size(32.dp),
|
||||
state = item.currencyIconState,
|
||||
shouldDisplayNetwork = true,
|
||||
networkBadgeSize = 12.dp,
|
||||
networkBadgeBackground = TangemTheme.colors.background.action,
|
||||
)
|
||||
|
||||
SpacerH(8.dp)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = item.tokenName.resolveReference(),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
maxLines = 1,
|
||||
)
|
||||
SpacerW(4.dp)
|
||||
Text(
|
||||
text = item.symbol.resolveReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
|
||||
SpacerH(2.dp)
|
||||
|
||||
Text(
|
||||
text = item.earnValue.resolveReference(),
|
||||
color = TangemTheme.colors.text.accent,
|
||||
style = TangemTheme.typography.caption1,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BestOpportunitiesFilters(
|
||||
state: EarnListUM,
|
||||
selectedNetworkFilter: EarnFilterUM?,
|
||||
selectedTypeFilter: EarnFilterUM?,
|
||||
onNetworkFilterClick: () -> Unit,
|
||||
onTypeFilterClick: () -> Unit,
|
||||
) {
|
||||
when (state) {
|
||||
is EarnListUM.Loading -> {
|
||||
FilterButtonsShimmer()
|
||||
}
|
||||
is EarnListUM.Content -> {
|
||||
FilterButtons(
|
||||
selectedNetworkFilter = selectedNetworkFilter,
|
||||
selectedTypeFilter = selectedTypeFilter,
|
||||
isEnabled = true,
|
||||
onNetworkFilterClick = onNetworkFilterClick,
|
||||
onTypeFilterClick = onTypeFilterClick,
|
||||
)
|
||||
}
|
||||
is EarnListUM.Error -> {
|
||||
FilterButtons(
|
||||
selectedNetworkFilter = selectedNetworkFilter,
|
||||
selectedTypeFilter = selectedTypeFilter,
|
||||
isEnabled = false,
|
||||
onNetworkFilterClick = onNetworkFilterClick,
|
||||
onTypeFilterClick = onTypeFilterClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.bestOpportunitiesItems(state: EarnListUM) {
|
||||
when (state) {
|
||||
is EarnListUM.Loading -> {
|
||||
val lastIndex = PLACEHOLDER_ITEMS_COUNT - 1
|
||||
items(
|
||||
count = PLACEHOLDER_ITEMS_COUNT,
|
||||
key = { "placeholder_$it" },
|
||||
) { index ->
|
||||
EarnItemPlaceholder(
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = lastIndex,
|
||||
backgroundColor = TangemTheme.colors.background.action,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
is EarnListUM.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.colors.background.action,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is EarnListUM.Error -> {
|
||||
item(key = "best_opportunities_error") {
|
||||
SpacerH(12.dp)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(vertical = 32.dp, horizontal = 12.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
UnableToLoadData(onRetryClick = state.onRetryClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FilterButtons(
|
||||
selectedNetworkFilter: EarnFilterUM?,
|
||||
selectedTypeFilter: EarnFilterUM?,
|
||||
isEnabled: Boolean,
|
||||
onNetworkFilterClick: () -> Unit,
|
||||
onTypeFilterClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
SecondarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
text = selectedNetworkFilter?.name
|
||||
?: resourceReference(R.string.earn_filter_all_networks),
|
||||
onClick = onNetworkFilterClick,
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
|
||||
isEnabled = isEnabled,
|
||||
),
|
||||
)
|
||||
|
||||
SpacerWMax()
|
||||
|
||||
SecondarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
text = selectedTypeFilter?.name
|
||||
?: resourceReference(R.string.earn_filter_all_types),
|
||||
onClick = onTypeFilterClick,
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
|
||||
isEnabled = isEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SectionHeader(title: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = title,
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun EarnContentPreview() {
|
||||
TangemThemePreview {
|
||||
val background = TangemTheme.colors.background.tertiary
|
||||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
|
||||
) {
|
||||
EarnContent(
|
||||
state = EarnUM(
|
||||
mostlyUsed = EarnListUM.Content(
|
||||
items = persistentListOf(
|
||||
previewEarnListItemUM(),
|
||||
previewEarnListItemUM(
|
||||
tokenName = "Cosmos",
|
||||
symbol = "ATOM",
|
||||
network = "Cosmos",
|
||||
),
|
||||
),
|
||||
),
|
||||
bestOpportunities = EarnListUM.Content(
|
||||
items = persistentListOf(
|
||||
previewEarnListItemUM(
|
||||
tokenName = "Cosmos Hub",
|
||||
symbol = "ATOM",
|
||||
network = "Cosmos network",
|
||||
),
|
||||
previewEarnListItemUM(
|
||||
tokenName = "Tether",
|
||||
symbol = "USDT",
|
||||
network = "Ethereum Network",
|
||||
),
|
||||
),
|
||||
),
|
||||
selectedNetworkFilter = null,
|
||||
selectedTypeFilter = null,
|
||||
networkFilters = persistentListOf(),
|
||||
typeFilters = persistentListOf(),
|
||||
onBackClick = {},
|
||||
onNetworkFilterClick = {},
|
||||
onTypeFilterClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun EarnContentLoadingPreview() {
|
||||
TangemThemePreview {
|
||||
val background = TangemTheme.colors.background.tertiary
|
||||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
|
||||
) {
|
||||
EarnContent(
|
||||
state = EarnUM(
|
||||
mostlyUsed = EarnListUM.Loading,
|
||||
bestOpportunities = EarnListUM.Loading,
|
||||
selectedNetworkFilter = null,
|
||||
selectedTypeFilter = null,
|
||||
networkFilters = persistentListOf(),
|
||||
typeFilters = persistentListOf(),
|
||||
onBackClick = {},
|
||||
onNetworkFilterClick = {},
|
||||
onTypeFilterClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun EarnContentErrorPreview() {
|
||||
TangemThemePreview {
|
||||
val background = TangemTheme.colors.background.tertiary
|
||||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
|
||||
) {
|
||||
EarnContent(
|
||||
state = EarnUM(
|
||||
mostlyUsed = EarnListUM.Content(
|
||||
items = persistentListOf(
|
||||
previewEarnListItemUM(),
|
||||
previewEarnListItemUM(
|
||||
tokenName = "Cosmos",
|
||||
symbol = "ATOM",
|
||||
network = "Cosmos",
|
||||
),
|
||||
),
|
||||
),
|
||||
bestOpportunities = EarnListUM.Error(onRetryClicked = {}),
|
||||
selectedNetworkFilter = null,
|
||||
selectedTypeFilter = null,
|
||||
networkFilters = persistentListOf(),
|
||||
typeFilters = persistentListOf(),
|
||||
onBackClick = {},
|
||||
onNetworkFilterClick = {},
|
||||
onTypeFilterClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun previewEarnListItemUM(
|
||||
tokenName: String = "USDC",
|
||||
symbol: String = "USDC",
|
||||
network: String = "Ethereum",
|
||||
): EarnListItemUM = EarnListItemUM(
|
||||
network = stringReference(network),
|
||||
symbol = stringReference(symbol),
|
||||
tokenName = stringReference(tokenName),
|
||||
currencyIconState = CurrencyIconState.TokenIcon(
|
||||
url = null,
|
||||
topBadgeIconResId = R.drawable.img_eth_22,
|
||||
fallbackTint = TangemColorPalette.Black,
|
||||
fallbackBackground = TangemColorPalette.Meadow,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 6.54%"),
|
||||
earnType = stringReference("Yield"),
|
||||
onItemClick = {},
|
||||
)
|
||||
|
||||
private const val PLACEHOLDER_ITEMS_COUNT = 8
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package com.tangem.features.feed.ui.earn.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
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.text.style.TextOverflow
|
||||
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.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.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.features.feed.ui.earn.state.EarnListItemUM
|
||||
|
||||
@Composable
|
||||
internal fun EarnListItem(item: EarnListItemUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RectangleShape)
|
||||
.clickable { item.onItemClick() }
|
||||
.padding(
|
||||
horizontal = 12.dp,
|
||||
vertical = 15.dp,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CurrencyIcon(
|
||||
modifier = Modifier.size(36.dp),
|
||||
state = item.currencyIconState,
|
||||
shouldDisplayNetwork = true,
|
||||
networkBadgeBackground = TangemTheme.colors.background.action,
|
||||
)
|
||||
|
||||
SpacerW(12.dp)
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
TokenTitle(
|
||||
name = item.tokenName.resolveReference(),
|
||||
symbol = item.symbol.resolveReference(),
|
||||
)
|
||||
SpacerH(2.dp)
|
||||
Text(
|
||||
text = item.network.resolveReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
SpacerW(8.dp)
|
||||
|
||||
Column(horizontalAlignment = Alignment.End) {
|
||||
Text(
|
||||
text = item.earnValue.resolveReference(),
|
||||
color = TangemTheme.colors.text.accent,
|
||||
style = TangemTheme.typography.body2,
|
||||
maxLines = 1,
|
||||
)
|
||||
SpacerH(2.dp)
|
||||
Text(
|
||||
text = item.earnType.resolveReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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,
|
||||
)
|
||||
SpacerW(4.dp)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
text = symbol,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Visible,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun EarnListItemPreview() {
|
||||
TangemThemePreview {
|
||||
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
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 = {},
|
||||
),
|
||||
)
|
||||
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 = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package com.tangem.features.feed.ui.earn.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
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.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.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
internal fun EarnListPlaceholder(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
repeat(PLACEHOLDER_ITEMS_COUNT) {
|
||||
EarnItemPlaceholder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun EarnItemPlaceholder(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 12.dp,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CircleShimmer(
|
||||
modifier = Modifier.size(36.dp),
|
||||
)
|
||||
|
||||
SpacerW(12.dp)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(top = 4.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(70.dp)
|
||||
.height(12.dp),
|
||||
radius = 4.dp,
|
||||
)
|
||||
SpacerWMax()
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(40.dp)
|
||||
.height(12.dp),
|
||||
radius = 4.dp,
|
||||
)
|
||||
}
|
||||
|
||||
SpacerH(8.dp)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(52.dp)
|
||||
.height(12.dp),
|
||||
radius = 4.dp,
|
||||
)
|
||||
SpacerWMax()
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(40.dp)
|
||||
.height(12.dp),
|
||||
radius = 4.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
TangemThemePreview {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.tertiary),
|
||||
) {
|
||||
EarnListPlaceholder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
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.lazy.LazyRow
|
||||
import androidx.compose.runtime.Composable
|
||||
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.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
internal fun MostlyUsedPlaceholder(modifier: Modifier = Modifier) {
|
||||
LazyRow(
|
||||
modifier = modifier,
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = 16.dp,
|
||||
vertical = 12.dp,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
items(PLACEHOLDER_ITEMS_COUNT) {
|
||||
MostlyUsedItemPlaceholder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MostlyUsedItemPlaceholder(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.width(148.dp)
|
||||
.height(102.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
CircleShimmer(
|
||||
modifier = Modifier
|
||||
.size(32.dp),
|
||||
)
|
||||
SpacerH(8.dp)
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(74.dp)
|
||||
.height(20.dp),
|
||||
radius = 4.dp,
|
||||
)
|
||||
SpacerH(2.dp)
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(84.dp)
|
||||
.height(16.dp),
|
||||
radius = 4.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
TangemThemePreview {
|
||||
MostlyUsedPlaceholder()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.feed.ui.earn.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed interface EarnListUM {
|
||||
data object Loading : EarnListUM
|
||||
data class Content(val items: ImmutableList<EarnListItemUM>) : EarnListUM
|
||||
data class Error(val onRetryClicked: () -> Unit) : EarnListUM
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal data class EarnListItemUM(
|
||||
val network: TextReference,
|
||||
val symbol: TextReference,
|
||||
val tokenName: TextReference,
|
||||
val currencyIconState: CurrencyIconState,
|
||||
val earnValue: TextReference,
|
||||
val earnType: TextReference,
|
||||
val onItemClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.feed.ui.earn.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal data class EarnUM(
|
||||
val mostlyUsed: EarnListUM,
|
||||
val bestOpportunities: EarnListUM,
|
||||
val selectedNetworkFilter: EarnFilterUM?,
|
||||
val selectedTypeFilter: EarnFilterUM?,
|
||||
val networkFilters: ImmutableList<EarnFilterUM>,
|
||||
val typeFilters: ImmutableList<EarnFilterUM>,
|
||||
val onBackClick: () -> Unit,
|
||||
val onNetworkFilterClick: () -> Unit,
|
||||
val onTypeFilterClick: () -> Unit,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal data class EarnFilterUM(
|
||||
val id: String,
|
||||
val name: TextReference,
|
||||
val isSelected: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue