Updated on 2026-08-14
This commit is contained in:
parent
0bfc7eee60
commit
bac44ac402
15 changed files with 1503 additions and 6 deletions
|
|
@ -20,20 +20,35 @@ dependencies {
|
|||
|
||||
/** Features */
|
||||
implementation(projects.features.forYou.api)
|
||||
implementation(projects.features.promoBanners.api)
|
||||
implementation(projects.features.commonFeatures.api)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.common)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.appCurrency)
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.configToggles)
|
||||
|
||||
implementation(projects.common.ui)
|
||||
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.lifecycle.compose)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Test */
|
||||
testImplementation(projects.common.test)
|
||||
testImplementation(projects.test.core)
|
||||
}
|
||||
|
|
@ -5,15 +5,16 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
|
|
@ -22,7 +23,12 @@ import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
|||
import com.tangem.core.ui.extensions.clickableSingle
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_chevron_left_20
|
||||
import com.tangem.features.foryou.ForYouComponent
|
||||
import com.tangem.features.foryou.impl.model.ForYouModel
|
||||
import com.tangem.features.foryou.impl.ui.ForYouContent
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -30,8 +36,21 @@ import dagger.assisted.AssistedInject
|
|||
internal class DefaultForYouComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Suppress("UnusedPrivateMember") @Assisted params: Unit,
|
||||
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
|
||||
) : AppComponentContext by context, ForYouComponent {
|
||||
|
||||
private val model: ForYouModel = getOrCreateModel()
|
||||
|
||||
private val promoBannersBlockComponent: PromoBannersBlockComponent by lazy {
|
||||
promoBannersBlockComponentFactory.create(
|
||||
context = child("promoBannersBlockComponent"),
|
||||
params = PromoBannersBlockComponent.Params(
|
||||
placeholder = PromoBannersBlockComponent.Placeholder.FEED,
|
||||
isInitiallyVisibleOnScreen = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
||||
TangemTopBar(
|
||||
|
|
@ -39,7 +58,7 @@ internal class DefaultForYouComponent @AssistedInject constructor(
|
|||
type = TangemTopBarType.BottomSheet,
|
||||
startContent = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
|
||||
imageVector = Icons.ic_chevron_left_20,
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors3.icon.primary,
|
||||
modifier = Modifier
|
||||
|
|
@ -62,7 +81,15 @@ internal class DefaultForYouComponent @AssistedInject constructor(
|
|||
contentPadding: PaddingValues,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
Text("FOR YOU")
|
||||
val uiState by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
ForYouContent(
|
||||
forYouUM = uiState,
|
||||
bottomSheetState = bottomSheetState,
|
||||
promoBannersBlockComponent = promoBannersBlockComponent,
|
||||
contentPadding = contentPadding,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
package com.tangem.features.foryou.impl.di
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.foryou.ForYouComponent
|
||||
import com.tangem.features.foryou.ForYouFeatureToggles
|
||||
import com.tangem.features.foryou.impl.DefaultForYouComponent
|
||||
import com.tangem.features.foryou.impl.featuretoggles.DefaultForYouFeatureToggles
|
||||
import com.tangem.features.foryou.impl.model.ForYouModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -30,4 +34,9 @@ internal interface ForYouComponentModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindForYouComponent(factory: DefaultForYouComponent.Factory): ForYouComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(ForYouModel::class)
|
||||
fun bindForYouModel(impl: ForYouModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.foryou.impl.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class ForYouUM(
|
||||
val walletListUM: WalletListUM,
|
||||
val portfolioReviewUM: PortfolioReviewUM,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal sealed interface PortfolioReviewUM {
|
||||
val tokenList: ImmutableList<ForYouTokenListItemUM>
|
||||
|
||||
data class Loading(
|
||||
override val tokenList: ImmutableList<ForYouTokenListItemUM>,
|
||||
) : PortfolioReviewUM
|
||||
|
||||
data class Content(
|
||||
override val tokenList: ImmutableList<ForYouTokenListItemUM>,
|
||||
val periodPickerUM: TangemSegmentedPickerUM,
|
||||
val assetCount: TextReference,
|
||||
val topHoldingPercent: TextReference,
|
||||
val onPeriodClick: (TangemSegmentUM) -> Unit,
|
||||
) : PortfolioReviewUM
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal data class ForYouTokenListItemUM(
|
||||
val tokenRowUM: TangemTokenRowUM,
|
||||
val tokenList: ImmutableList<TangemTokenRowUM>,
|
||||
val isExpanded: Boolean,
|
||||
val isExpandable: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
package com.tangem.features.foryou.impl.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletTabUM
|
||||
import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM
|
||||
import com.tangem.features.foryou.impl.entity.ForYouUM
|
||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||
import com.tangem.features.foryou.impl.model.transformer.SetPortfolioReviewTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class ForYouModel @Inject constructor(
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val walletIconUMConverter: WalletIconUMConverter,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val locallySelectedWalletId = MutableStateFlow<UserWalletId?>(value = null)
|
||||
private val expandedAssetIds = MutableStateFlow<Set<String>>(value = emptySet())
|
||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
|
||||
val uiState: StateFlow<ForYouUM>
|
||||
field = MutableStateFlow<ForYouUM>(
|
||||
ForYouUM(
|
||||
walletListUM = WalletListUM(persistentListOf()),
|
||||
portfolioReviewUM = PortfolioReviewUM.Loading(
|
||||
tokenList = buildList<ForYouTokenListItemUM> {
|
||||
repeat(4) { index ->
|
||||
add(
|
||||
ForYouTokenListItemUM(
|
||||
tokenRowUM = TangemTokenRowUM.Loading(
|
||||
id = index.toString(),
|
||||
),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isExpandable = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
combine(
|
||||
flow = userWalletsListRepository.userWallets,
|
||||
flow2 = userWalletsListRepository.selectedUserWallet,
|
||||
flow3 = multiAccountStatusListSupplier.invokeAsMap(),
|
||||
flow4 = locallySelectedWalletId,
|
||||
flow5 = expandedAssetIds,
|
||||
) { wallets, globalSelectedWallet, accountStatusList, locallySelected, expanded ->
|
||||
val selectedId = locallySelected ?: globalSelectedWallet?.walletId
|
||||
val tabs = wallets.orEmpty().map { wallet ->
|
||||
WalletTabUM(
|
||||
text = stringReference(wallet.name),
|
||||
count = null,
|
||||
isSelected = wallet.walletId == selectedId,
|
||||
onClick = { onTabClick(wallet.walletId) },
|
||||
deviceIcon = walletIconUMConverter.convert(getWalletIconUseCase(wallet)),
|
||||
)
|
||||
}
|
||||
|
||||
val selectedAccountStatusList = accountStatusList[selectedId]
|
||||
val currencies = selectedAccountStatusList?.flattenCurrencies().orEmpty()
|
||||
val loadedBalance = selectedAccountStatusList?.totalFiatBalance as? TotalFiatBalance.Loaded
|
||||
val totalFiatBalance = loadedBalance?.amount.orZero()
|
||||
|
||||
uiState.update(
|
||||
SetPortfolioReviewTransformer(
|
||||
walletListUM = WalletListUM(
|
||||
items = if (tabs.size != 1) tabs.toPersistentList() else persistentListOf(),
|
||||
),
|
||||
currencies = currencies,
|
||||
totalFiatBalance = totalFiatBalance,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
expandedAssetIds = expanded,
|
||||
expandClick = ::onExpandClick,
|
||||
onPeriodClick = ::onPeriodClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
|
||||
return getSelectedAppCurrencyUseCase().map { maybeAppCurrency ->
|
||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
}.stateIn(
|
||||
scope = modelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = AppCurrency.Default,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onTabClick(walletId: UserWalletId) {
|
||||
locallySelectedWalletId.value = walletId
|
||||
}
|
||||
|
||||
private fun onExpandClick(assetId: String) {
|
||||
expandedAssetIds.update { ids ->
|
||||
if (assetId in ids) ids - assetId else ids + assetId
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPeriodClick(tangemSegmentUM: TangemSegmentUM) {
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
portfolioReviewUM = (state.portfolioReviewUM as? PortfolioReviewUM.Content)?.copy(
|
||||
periodPickerUM = state.portfolioReviewUM.periodPickerUM.copy(
|
||||
initialSelectedItem = tangemSegmentUM,
|
||||
),
|
||||
) ?: state.portfolioReviewUM,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.features.foryou.impl.model.converter
|
||||
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeColor
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeSize
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeType
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
* Formatting helpers shared by the For You portfolio-review converters.
|
||||
*
|
||||
* Kept null-safe so that non-[CryptoCurrencyStatus.Loaded] states (which carry no fiat amount) degrade
|
||||
* to a dash / empty instead of throwing.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cross-network grouping key for the portfolio review: the same asset on different networks (e.g. USDC
|
||||
* on Solana and Ethereum) shares its `rawCurrencyId`, so they group under a single item. Custom tokens
|
||||
* have no raw id and fall back to their unique currency id, staying in their own group.
|
||||
*/
|
||||
internal fun CryptoCurrencyStatus.forYouGroupKey(): String = currency.id.rawCurrencyId?.value ?: currency.id.value
|
||||
|
||||
/** Formats a (possibly null) fiat amount in [appCurrency]; a `null` amount renders as a dash. */
|
||||
internal fun BigDecimal?.toForYouFiatText(appCurrency: AppCurrency): TextReference = stringReference(
|
||||
format { fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol) },
|
||||
)
|
||||
|
||||
/**
|
||||
* Formats this fiat amount as a share of [totalFiatBalance]. Returns [TextReference.EMPTY] when the
|
||||
* share cannot be computed (no amount, or a zero total / amount).
|
||||
*/
|
||||
internal fun BigDecimal?.toForYouPercentText(totalFiatBalance: BigDecimal): TextReference {
|
||||
if (this == null || totalFiatBalance.isZero() || isZero()) return TextReference.EMPTY
|
||||
return stringReference(divide(totalFiatBalance, RoundingMode.HALF_UP).format { percent() })
|
||||
}
|
||||
|
||||
// TODO For You: replace this placeholder with the real price-change badge once the design is wired.
|
||||
internal fun forYouPlaceholderBadge(): TangemBadgeUM = TangemBadgeUM(
|
||||
text = stringReference("Positive"),
|
||||
size = TangemBadgeSize.X4,
|
||||
type = TangemBadgeType.Tinted,
|
||||
color = TangemBadgeColor.Green,
|
||||
)
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package com.tangem.features.foryou.impl.model.converter
|
||||
|
||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.foryou.impl.R
|
||||
import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Builds the For You portfolio-review list: groups the given currency statuses by asset across networks
|
||||
* (see [forYouGroupKey]) and maps each group to a [ForYouTokenListItemUM] — an aggregate asset row plus,
|
||||
* when the asset spans more than one network, its per-network child rows.
|
||||
*
|
||||
* The child rows are grouped by network (delegated to [ForYouTokenRowConverter]) so a network appears
|
||||
* once per asset even if the asset is held on it in several accounts;
|
||||
*
|
||||
* Modelled on `TokenListStateConverter` (a list converter delegating to a per-item converter).
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class ForYouTokenListConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val totalFiatBalance: BigDecimal,
|
||||
private val expandedAssetIds: Set<String>,
|
||||
private val expandClick: (assetId: String) -> Unit,
|
||||
private val otherAssetCount: Int,
|
||||
private val otherFiatBalance: BigDecimal,
|
||||
) : Converter<List<CryptoCurrencyStatus>, ImmutableList<ForYouTokenListItemUM>> {
|
||||
|
||||
private val iconConverter = CryptoCurrencyToIconStateConverter()
|
||||
private val rowConverter = ForYouTokenRowConverter(appCurrency = appCurrency, totalFiatBalance = totalFiatBalance)
|
||||
|
||||
override fun convert(value: List<CryptoCurrencyStatus>): ImmutableList<ForYouTokenListItemUM> {
|
||||
val assetItems = value
|
||||
.groupBy { it.forYouGroupKey() }
|
||||
.map { (assetId, currencies) -> createListItem(assetId, currencies) }
|
||||
|
||||
// Assets beyond the top ones are collapsed into a single non-expandable "Other" row at the bottom.
|
||||
return if (otherAssetCount > 0) {
|
||||
assetItems + createOtherItem()
|
||||
} else {
|
||||
assetItems
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
private fun createListItem(assetId: String, currencies: List<CryptoCurrencyStatus>): ForYouTokenListItemUM {
|
||||
// Group the asset's holdings by blockchain (network.id.rawId, derivation-independent) so each
|
||||
// network appears once even when the asset is held across several accounts/derivations on it,
|
||||
// summing those balances. Order by balance so the expanded breakdown reads top-down.
|
||||
val networkGroups = currencies
|
||||
.groupBy { it.currency.network.id.rawId }
|
||||
.values
|
||||
.sortedByDescending { group -> group.sumOf { it.value.fiatAmount.orZero() } }
|
||||
return ForYouTokenListItemUM(
|
||||
tokenRowUM = createAssetRow(
|
||||
assetId = assetId,
|
||||
currencies = currencies,
|
||||
networkCount = networkGroups.size,
|
||||
),
|
||||
tokenList = networkGroups.map(rowConverter::convertNetworkGroup).toPersistentList(),
|
||||
isExpanded = assetId in expandedAssetIds,
|
||||
isExpandable = true,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createAssetRow(
|
||||
assetId: String,
|
||||
currencies: List<CryptoCurrencyStatus>,
|
||||
networkCount: Int,
|
||||
): TangemTokenRowUM {
|
||||
if (currencies.all { it.value is CryptoCurrencyStatus.Loading }) {
|
||||
return TangemTokenRowUM.Loading(id = assetId)
|
||||
}
|
||||
|
||||
val asset = currencies.first()
|
||||
val assetFiatBalance = currencies.sumOf { it.value.fiatAmount.orZero() }
|
||||
|
||||
val subtitle = if (networkCount > 1) {
|
||||
stringReference("$networkCount networks")
|
||||
} else {
|
||||
val onlyCryptoCurrency = currencies.firstOrNull()?.currency
|
||||
val isMain = onlyCryptoCurrency is CryptoCurrency.Coin
|
||||
when {
|
||||
isMain -> resourceReference(R.string.common_main_network)
|
||||
onlyCryptoCurrency != null -> stringReference(onlyCryptoCurrency.network.standardType.name)
|
||||
else -> TextReference.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
return TangemTokenRowUM.Content(
|
||||
id = assetId,
|
||||
headIconUM = TangemIconUM.Currency(iconConverter.convert(asset)),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference(asset.currency.symbol),
|
||||
badge = forYouPlaceholderBadge(),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = subtitle,
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = assetFiatBalance.toForYouFiatText(appCurrency),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = assetFiatBalance.toForYouPercentText(totalFiatBalance),
|
||||
),
|
||||
onItemClick = { expandClick(assetId) },
|
||||
onItemLongClick = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createOtherItem(): ForYouTokenListItemUM = ForYouTokenListItemUM(
|
||||
tokenRowUM = TangemTokenRowUM.Content(
|
||||
id = OTHER_ROW_ID,
|
||||
headIconUM = TangemIconUM.Currency(CurrencyIconState.Empty()),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(text = stringReference("Other")),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference(if (otherAssetCount > 1) "$otherAssetCount assets" else "1 asset"),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = otherFiatBalance.toForYouFiatText(appCurrency),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = otherFiatBalance.toForYouPercentText(totalFiatBalance),
|
||||
),
|
||||
onItemClick = null,
|
||||
onItemLongClick = null,
|
||||
),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isExpandable = false,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val OTHER_ROW_ID = "for_you_other_assets"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.features.foryou.impl.model.converter
|
||||
|
||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Builds a single per-network child row of an asset for the For You portfolio review.
|
||||
*
|
||||
* The input is all [CryptoCurrencyStatus]es of one asset on the *same* network (the asset may be held in
|
||||
* several accounts on that network). They are aggregated into one row — the crypto amount and fiat balance
|
||||
* are the per-network totals — so a network never appears twice within an asset's expanded breakdown.
|
||||
*/
|
||||
internal class ForYouTokenRowConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val totalFiatBalance: BigDecimal,
|
||||
) {
|
||||
|
||||
private val iconConverter = CryptoCurrencyToIconStateConverter()
|
||||
|
||||
/** Builds one row for all [statuses] of a single asset on the same network. */
|
||||
fun convertNetworkGroup(statuses: List<CryptoCurrencyStatus>): TangemTokenRowUM {
|
||||
val representative = statuses.first()
|
||||
if (statuses.all { it.value is CryptoCurrencyStatus.Loading }) {
|
||||
return TangemTokenRowUM.Loading(id = representative.currency.id.value)
|
||||
}
|
||||
|
||||
val currency = representative.currency
|
||||
val cryptoAmount = statuses.sumOf { it.value.amount.orZero() }
|
||||
val fiatAmount = statuses.sumOf { it.value.fiatAmount.orZero() }
|
||||
return TangemTokenRowUM.Content(
|
||||
id = currency.id.value,
|
||||
headIconUM = TangemIconUM.Currency(iconConverter.convert(representative)),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference(currency.symbol),
|
||||
badge = forYouPlaceholderBadge(),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference(
|
||||
"${currency.network.name} ${StringsSigns.DOT} ${cryptoAmount.format { crypto(
|
||||
cryptoCurrency = currency,
|
||||
) }}",
|
||||
),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(text = fiatAmount.toForYouFiatText(appCurrency)),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = fiatAmount.toForYouPercentText(totalFiatBalance),
|
||||
),
|
||||
onItemClick = null,
|
||||
onItemLongClick = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package com.tangem.features.foryou.impl.model.transformer
|
||||
|
||||
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
||||
import com.tangem.features.foryou.impl.entity.ForYouUM
|
||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||
import com.tangem.features.foryou.impl.model.converter.ForYouTokenListConverter
|
||||
import com.tangem.features.foryou.impl.model.converter.forYouGroupKey
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
* Builds the [ForYouUM] state for the For You screen: the wallet tabs plus the portfolio
|
||||
* review (asset count, top-holding share, period picker and the grouped token list).
|
||||
*
|
||||
* The token list is delegated to [ForYouTokenListConverter]; the period picker selection is carried
|
||||
* over from the previous state so it is not reset on every balance refresh.
|
||||
*
|
||||
* Modelled on `SetTokenListTransformer` (a transformer that rebuilds the state while delegating the
|
||||
* token-list construction to a dedicated converter).
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class SetPortfolioReviewTransformer(
|
||||
private val walletListUM: WalletListUM,
|
||||
private val currencies: List<CryptoCurrencyStatus>,
|
||||
private val totalFiatBalance: BigDecimal,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val expandedAssetIds: Set<String>,
|
||||
private val expandClick: (assetId: String) -> Unit,
|
||||
private val onPeriodClick: (TangemSegmentUM) -> Unit,
|
||||
) : Transformer<ForYouUM> {
|
||||
|
||||
override fun transform(prevState: ForYouUM): ForYouUM {
|
||||
// Drop empty networks, then aggregate the rest into assets (the same token across networks shares
|
||||
// its forYouGroupKey) and rank assets by their *summed* fiat balance.
|
||||
val rankedAssets = currencies
|
||||
.filterNot { it.value.fiatAmount.orZero().isZero() }
|
||||
.groupBy { it.forYouGroupKey() }
|
||||
.map { (_, networks) -> networks to networks.sumOf { it.value.fiatAmount.orZero() } }
|
||||
.sortedByDescending { (_, assetBalance) -> assetBalance }
|
||||
|
||||
// The top assets are shown individually (each flattened back to its networks so the converter can
|
||||
// regroup them by network); the remaining assets are collapsed into a single "Other" row.
|
||||
val topAssets = rankedAssets.take(TOP_HOLDINGS_COUNT)
|
||||
val otherAssets = rankedAssets.drop(TOP_HOLDINGS_COUNT)
|
||||
val topCurrencies = topAssets.flatMap { (networks, _) -> networks }
|
||||
val topBalance = topAssets.sumOf { (_, assetBalance) -> assetBalance }
|
||||
|
||||
val tokenList = ForYouTokenListConverter(
|
||||
appCurrency = appCurrency,
|
||||
totalFiatBalance = totalFiatBalance,
|
||||
expandedAssetIds = expandedAssetIds,
|
||||
expandClick = expandClick,
|
||||
otherAssetCount = otherAssets.size,
|
||||
otherFiatBalance = otherAssets.sumOf { (_, assetBalance) -> assetBalance },
|
||||
).convert(topCurrencies)
|
||||
|
||||
return prevState.copy(
|
||||
walletListUM = walletListUM,
|
||||
portfolioReviewUM = PortfolioReviewUM.Content(
|
||||
assetCount = stringReference("${rankedAssets.size} assets"), // TODO For You lokalize
|
||||
topHoldingPercent = stringReference("Top holding ${topHoldingPercent(topBalance)}"),
|
||||
periodPickerUM = when (prevState.portfolioReviewUM) {
|
||||
is PortfolioReviewUM.Content -> prevState.portfolioReviewUM.periodPickerUM
|
||||
is PortfolioReviewUM.Loading -> createPeriodPicker()
|
||||
},
|
||||
tokenList = tokenList,
|
||||
onPeriodClick = onPeriodClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun topHoldingPercent(topBalance: BigDecimal): String {
|
||||
return if (!totalFiatBalance.isZero() && !topBalance.isZero()) {
|
||||
topBalance.divide(totalFiatBalance, RoundingMode.HALF_UP).format { percent() }
|
||||
} else {
|
||||
StringsSigns.DASH_SIGN
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPeriodPicker(): TangemSegmentedPickerUM {
|
||||
// TODO For you replace with data from backend
|
||||
val day = TangemSegmentUM(id = "0", title = stringReference("Day"))
|
||||
return TangemSegmentedPickerUM(
|
||||
items = persistentListOf(
|
||||
day,
|
||||
TangemSegmentUM(id = "1", title = stringReference("Week")),
|
||||
TangemSegmentUM(id = "2", title = stringReference("Month")),
|
||||
),
|
||||
initialSelectedItem = day,
|
||||
isFixed = true,
|
||||
isAltSurface = true,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TOP_HOLDINGS_COUNT = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package com.tangem.features.foryou.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletTabUM
|
||||
import com.tangem.features.foryou.impl.entity.ForYouUM
|
||||
import com.tangem.features.foryou.impl.ui.components.WalletTabsBlock
|
||||
import com.tangem.features.foryou.impl.ui.preview.ForYouPortfolioReviewPreviewData
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
||||
@Composable
|
||||
internal fun ForYouContent(
|
||||
forYouUM: ForYouUM,
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
promoBannersBlockComponent: PromoBannersBlockComponent,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LaunchedEffect(bottomSheetState, promoBannersBlockComponent) {
|
||||
snapshotFlow { bottomSheetState.value == BottomSheetState.EXPANDED }
|
||||
.distinctUntilChanged()
|
||||
.collect(promoBannersBlockComponent::setVisibleOnScreen)
|
||||
}
|
||||
val background = LocalMainBottomSheetColor.current
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(top = contentPadding.calculateTopPadding())
|
||||
.drawBehind { drawRect(background.value) },
|
||||
) {
|
||||
WalletTabsBlock(walletList = forYouUM.walletListUM)
|
||||
|
||||
SpacerH(12.dp)
|
||||
|
||||
promoBannersBlockComponent.ContentWithPadding(
|
||||
modifier = Modifier,
|
||||
horizontalItemPadding = 16.dp,
|
||||
)
|
||||
|
||||
ForYouPortfolioReview(
|
||||
portfolioReviewUM = forYouUM.portfolioReviewUM,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
SpacerH(48.dp)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun ForYouContent_Preview(@PreviewParameter(ForYouContentPreviewProvider::class) params: ForYouUM) {
|
||||
TangemThemePreviewRedesign {
|
||||
ForYouContent(
|
||||
forYouUM = params,
|
||||
bottomSheetState = remember { mutableStateOf(BottomSheetState.EXPANDED) },
|
||||
promoBannersBlockComponent = object : PromoBannersBlockComponent {
|
||||
@Composable
|
||||
override fun ContentWithPadding(horizontalItemPadding: Dp, modifier: Modifier) {
|
||||
}
|
||||
|
||||
override fun setVisibleOnScreen(isVisible: Boolean) {}
|
||||
},
|
||||
contentPadding = PaddingValues.Zero,
|
||||
modifier = Modifier.background(TangemTheme.colors3.bg.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ForYouContentPreviewProvider : PreviewParameterProvider<ForYouUM> {
|
||||
override val values: Sequence<ForYouUM>
|
||||
get() = sequenceOf(
|
||||
ForYouUM(
|
||||
walletListUM = WalletListUM(
|
||||
items = persistentListOf(
|
||||
WalletTabUM(
|
||||
text = stringReference("Wallet 1"),
|
||||
count = stringReference("1"),
|
||||
isSelected = true,
|
||||
onClick = {},
|
||||
deviceIcon = DeviceIconUM.Mobile,
|
||||
),
|
||||
),
|
||||
),
|
||||
portfolioReviewUM = ForYouPortfolioReviewPreviewData.reviewContent,
|
||||
),
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package com.tangem.features.foryou.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
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.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker
|
||||
import com.tangem.core.ui.ds2.badge.TangemBadge
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_chevron_down_16
|
||||
import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM
|
||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||
import com.tangem.features.foryou.impl.ui.components.ForYouMarketChartContent
|
||||
import com.tangem.features.foryou.impl.ui.components.ForYouPortfolioTokenList
|
||||
import com.tangem.features.foryou.impl.ui.preview.ForYouPortfolioReviewPreviewData
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Composable
|
||||
internal fun ForYouPortfolioReview(portfolioReviewUM: PortfolioReviewUM, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Portfolio Review", // TODO For You
|
||||
style = TangemTheme.typography3.heading.small,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
)
|
||||
TangemBadge(
|
||||
text = stringReference("All accounts"), // TODO For You
|
||||
variant = TangemBadge.Variant.Solid,
|
||||
size = TangemBadge.Size.X9,
|
||||
iconEnd = TangemIconUM.Icon(Icons.ic_chevron_down_16),
|
||||
)
|
||||
}
|
||||
SpacerH(16.dp)
|
||||
ForYouMarketChartContent(portfolioReviewUM)
|
||||
SpacerH(8.dp)
|
||||
when (portfolioReviewUM) {
|
||||
is PortfolioReviewUM.Content -> {
|
||||
TangemSegmentedPicker(
|
||||
tangemSegmentedPickerUM = portfolioReviewUM.periodPickerUM,
|
||||
onClick = portfolioReviewUM.onPeriodClick,
|
||||
)
|
||||
}
|
||||
is PortfolioReviewUM.Loading -> RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp),
|
||||
radius = 100.dp,
|
||||
)
|
||||
}
|
||||
|
||||
ForYouPortfolioTokenList(tokenList = portfolioReviewUM.tokenList)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun ForYouPortfolioReview_Review(
|
||||
@PreviewParameter(ForYouPortfolioReviewPreviewProvider::class) params: PortfolioReviewUM,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
ForYouPortfolioReview(
|
||||
portfolioReviewUM = params,
|
||||
modifier = Modifier.background(TangemTheme.colors3.bg.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ForYouPortfolioReviewPreviewProvider : PreviewParameterProvider<PortfolioReviewUM> {
|
||||
override val values: Sequence<PortfolioReviewUM>
|
||||
get() = sequenceOf(
|
||||
ForYouPortfolioReviewPreviewData.reviewContent,
|
||||
PortfolioReviewUM.Loading(
|
||||
tokenList = buildList {
|
||||
repeat(4) { index ->
|
||||
add(
|
||||
ForYouTokenListItemUM(
|
||||
tokenRowUM = TangemTokenRowUM.Loading(
|
||||
id = index.toString(),
|
||||
),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isExpandable = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.features.foryou.impl.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color.Companion.Cyan
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||
|
||||
@Composable
|
||||
internal fun ForYouMarketChartContent(portfolioReviewUM: PortfolioReviewUM) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(TangemTheme.colors3.bg.secondary)
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(
|
||||
top = 16.dp,
|
||||
end = 32.dp,
|
||||
start = 32.dp,
|
||||
bottom = 32.dp,
|
||||
)
|
||||
.background(Cyan, CircleShape)
|
||||
.size(200.dp),
|
||||
)
|
||||
SpacerH(16.dp)
|
||||
|
||||
when (portfolioReviewUM) {
|
||||
is PortfolioReviewUM.Content -> {
|
||||
Text(
|
||||
text = portfolioReviewUM.assetCount.resolveReference(),
|
||||
style = TangemTheme.typography3.heading.small,
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
)
|
||||
Text(
|
||||
text = portfolioReviewUM.topHoldingPercent.resolveReference(),
|
||||
style = TangemTheme.typography3.heading.small,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
)
|
||||
}
|
||||
is PortfolioReviewUM.Loading -> {
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography3.heading.small,
|
||||
modifier = Modifier.width(50.dp),
|
||||
)
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography3.heading.small,
|
||||
modifier = Modifier.width(75.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
package com.tangem.features.foryou.impl.ui.components
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.SharedTransitionScope.ResizeMode.Companion.scaleToBounds
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.animateIntAsState
|
||||
import androidx.compose.animation.core.snap
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInWindow
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.lerp
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.common.ui.tokens.SlideInItemVisibility
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.account.toBoxSize
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.currency.icon.TangemCurrencyIcon
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRow
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.ds.row.token.internal.TokenRowTitle
|
||||
import com.tangem.core.ui.ds2.row.TangemRow
|
||||
import com.tangem.core.ui.ds2.row.TangemRowVerticalAlignment
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_error_20
|
||||
import com.tangem.core.ui.utils.ProvideSharedTransitionScope
|
||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||
import com.tangem.core.ui.utils.sharedBoundsSafely
|
||||
import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM
|
||||
import com.tangem.utils.StringsSigns
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun ForYouPortfolioTokenList(tokenList: ImmutableList<ForYouTokenListItemUM>, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
val outerLastIndex = tokenList.lastIndex
|
||||
tokenList.fastForEachIndexed { index, listItem ->
|
||||
key(listItem.tokenRowUM.id) {
|
||||
PortfolioTokenItem(listItem = listItem, index = index, outerLastIndex = outerLastIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PortfolioTokenItem(listItem: ForYouTokenListItemUM, index: Int, outerLastIndex: Int) {
|
||||
PortfolioAssetItem(listItem = listItem, index = index, outerLastIndex = outerLastIndex)
|
||||
|
||||
val lastIndex = listItem.tokenList.lastIndex.inc()
|
||||
listItem.tokenList.fastForEachIndexed { tokenIndex, item ->
|
||||
SlideInItemVisibility(
|
||||
currentIndex = tokenIndex + 1,
|
||||
lastIndex = lastIndex,
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
radius = 24.dp,
|
||||
currentIndex = tokenIndex + 1,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = lastIndex,
|
||||
backgroundColor = TangemTheme.colors3.bg.secondary,
|
||||
),
|
||||
visible = listItem.isExpanded,
|
||||
) {
|
||||
val itemModifier = Modifier
|
||||
.semantics { lazyListItemPosition = tokenIndex + 1 }
|
||||
|
||||
var position by remember { mutableStateOf(Offset.Zero) }
|
||||
TangemTokenRow(
|
||||
tokenRowUM = item,
|
||||
isBalanceHidden = false, // TODO For You
|
||||
reorderableState = null,
|
||||
modifier = itemModifier
|
||||
.onGloballyPositioned {
|
||||
position = it.positionInWindow()
|
||||
}
|
||||
.conditionalCompose(item.onItemClick != null) {
|
||||
clickable(onClick = requireNotNull(item.onItemClick))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun PortfolioAssetItem(listItem: ForYouTokenListItemUM, index: Int, outerLastIndex: Int) {
|
||||
val itemBackgroundColor = TangemTheme.colors3.bg.secondary
|
||||
|
||||
ProvideSharedTransitionScope(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.semantics { lazyListItemPosition = index }
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = 0,
|
||||
radius = 24.dp,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = portfolioAssetExpandAnimation(listItem = listItem, outerLastIndex = outerLastIndex).value,
|
||||
backgroundColor = itemBackgroundColor,
|
||||
),
|
||||
) {
|
||||
val iconSharedContentState = rememberSharedContentState(key = "icon_${listItem.tokenRowUM.id}")
|
||||
val titleSharedContentState = rememberSharedContentState(key = "title_${listItem.tokenRowUM.id}")
|
||||
val boundsTransform = BoundsTransform { _, _ -> tween(250) }
|
||||
|
||||
AnimatedContent(
|
||||
targetState = listItem.isExpanded,
|
||||
transitionSpec = { portfolioAssetExpandFadeAnimation() },
|
||||
) { isExpandedWrapped ->
|
||||
val composables = remember {
|
||||
SharedTokenRowComposables(
|
||||
icon = { modifier ->
|
||||
PortfolioSharedAssetIcon(
|
||||
listItem = listItem,
|
||||
isExpandedWrapped = isExpandedWrapped,
|
||||
itemBackgroundColor = itemBackgroundColor,
|
||||
modifier = modifier.sharedBoundsSafely(
|
||||
sharedContentState = iconSharedContentState,
|
||||
animatedVisibilityScope = this,
|
||||
boundsTransform = boundsTransform,
|
||||
),
|
||||
)
|
||||
},
|
||||
title = { modifier ->
|
||||
PortfolioSharedAssetTitle(
|
||||
listItem = listItem,
|
||||
isExpandedWrapped = isExpandedWrapped,
|
||||
modifier = modifier.sharedBoundsSafely(
|
||||
sharedContentState = titleSharedContentState,
|
||||
animatedVisibilityScope = this,
|
||||
boundsTransform = boundsTransform,
|
||||
resizeMode = scaleToBounds(ContentScale.Fit, Alignment.CenterStart),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (isExpandedWrapped) {
|
||||
ForYouPortfolioListHeader(
|
||||
tokenRowUM = listItem.tokenRowUM,
|
||||
headComponent = composables.icon,
|
||||
titleComponent = composables.title,
|
||||
)
|
||||
} else {
|
||||
TangemTokenRow(
|
||||
tokenRowUM = listItem.tokenRowUM,
|
||||
headComponent = composables.icon,
|
||||
titleComponent = composables.title,
|
||||
isBalanceHidden = false, // todo For You
|
||||
reorderableState = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PortfolioSharedAssetIcon(
|
||||
listItem: ForYouTokenListItemUM,
|
||||
isExpandedWrapped: Boolean,
|
||||
itemBackgroundColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val headIcon = listItem.tokenRowUM.headIconUM
|
||||
|
||||
if (headIcon is TangemIconUM.Currency) {
|
||||
val size = if (isExpandedWrapped) {
|
||||
AccountIconSize.RedesignExtraSmall
|
||||
} else {
|
||||
AccountIconSize.RedesignedDefault
|
||||
}
|
||||
val currencyIconState = when (val currencyIconState = headIcon.currencyIconState) {
|
||||
is CurrencyIconState.CryptoPortfolio.Icon -> currencyIconState.copy(size = size)
|
||||
is CurrencyIconState.CryptoPortfolio.Letter -> currencyIconState.copy(size = size)
|
||||
else -> currencyIconState
|
||||
}
|
||||
TangemCurrencyIcon(
|
||||
state = currencyIconState,
|
||||
shouldDisplayNetwork = false,
|
||||
modifier = modifier
|
||||
.size(size.toBoxSize())
|
||||
// TODO For You replace with DC components
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
if (!isExpandedWrapped) {
|
||||
val offset = 34.dp.toPx()
|
||||
drawBadge(
|
||||
color = Color.Red,
|
||||
containerColor = itemBackgroundColor,
|
||||
offset = Offset(
|
||||
x = offset,
|
||||
y = offset,
|
||||
),
|
||||
size = 3.dp,
|
||||
padding = 1.dp,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PortfolioSharedAssetTitle(
|
||||
listItem: ForYouTokenListItemUM,
|
||||
isExpandedWrapped: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val targetAnimationFraction = if (isExpandedWrapped) 0f else 1f
|
||||
|
||||
val animationFraction = animateFloatAsState(
|
||||
targetValue = targetAnimationFraction,
|
||||
animationSpec = tween(durationMillis = 350),
|
||||
)
|
||||
|
||||
val startStyle = TangemTheme.typography3.subheading.medium
|
||||
val stopStyle = TangemTheme.typography3.body.medium
|
||||
|
||||
val textStyle by remember(animationFraction.value) {
|
||||
derivedStateOf { lerp(startStyle, stopStyle, animationFraction.value) }
|
||||
}
|
||||
|
||||
val resizedTitle = when (val titleUM = listItem.tokenRowUM.titleUM) {
|
||||
is TangemTokenRowUM.TitleUM.Content -> titleUM.copy(
|
||||
text = styledStringReference(
|
||||
titleUM.text.resolveReference(),
|
||||
{ textStyle.toSpanStyle() },
|
||||
),
|
||||
)
|
||||
else -> titleUM
|
||||
}
|
||||
|
||||
TokenRowTitle(
|
||||
titleUM = if (isExpandedWrapped) {
|
||||
(resizedTitle as? TangemTokenRowUM.TitleUM.Content)?.copy(badge = null) ?: resizedTitle
|
||||
} else {
|
||||
resizedTitle
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ForYouPortfolioListHeader(
|
||||
tokenRowUM: TangemTokenRowUM,
|
||||
headComponent: @Composable (Modifier) -> Unit,
|
||||
titleComponent: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
TangemRow(
|
||||
modifier = Modifier.background(TangemTheme.colors3.bg.secondary),
|
||||
divider = true,
|
||||
onClick = tokenRowUM.onItemClick,
|
||||
verticalAlignment = TangemRowVerticalAlignment.Center,
|
||||
startSlot = {
|
||||
headComponent(Modifier)
|
||||
},
|
||||
titleSlot = {
|
||||
titleComponent(Modifier)
|
||||
|
||||
val topEndUM = tokenRowUM.topEndContentUM
|
||||
val bottomEndUM = tokenRowUM.bottomEndContentUM
|
||||
when {
|
||||
topEndUM is TangemTokenRowUM.EndContentUM.Content &&
|
||||
bottomEndUM is TangemTokenRowUM.EndContentUM.Content -> {
|
||||
Text(
|
||||
text = annotatedReference {
|
||||
appendColored(StringsSigns.DOT, TangemTheme.colors3.icon.tertiary)
|
||||
appendSpace()
|
||||
append(topEndUM.text.resolveReference())
|
||||
appendSpace()
|
||||
appendColored(StringsSigns.DOT, TangemTheme.colors3.icon.tertiary)
|
||||
appendSpace()
|
||||
appendColored(bottomEndUM.text.resolveReference(), TangemTheme.colors3.text.secondary)
|
||||
}.resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography3.subheading.medium,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
topEndUM is TangemTokenRowUM.EndContentUM.Loading ||
|
||||
bottomEndUM is TangemTokenRowUM.EndContentUM.Loading -> {
|
||||
TextShimmer(style = TangemTheme.typography3.subheading.medium)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
SpacerWMax()
|
||||
Icon(
|
||||
imageVector = Icons.ic_error_20,
|
||||
tint = TangemTheme.colors3.icon.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun DrawScope.drawBadge(
|
||||
containerColor: Color,
|
||||
color: Color = TangemColorPalette.Azure,
|
||||
offset: Offset,
|
||||
size: Dp = 5.dp,
|
||||
padding: Dp = 2.dp,
|
||||
) {
|
||||
drawCircle(
|
||||
color = containerColor,
|
||||
center = offset,
|
||||
radius = size.toPx(),
|
||||
)
|
||||
drawCircle(
|
||||
color = color,
|
||||
center = offset,
|
||||
radius = (size - padding).toPx(),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun portfolioAssetExpandAnimation(listItem: ForYouTokenListItemUM, outerLastIndex: Int): State<Int> {
|
||||
// Snap immediately on expand; on collapse, hold the current value until all
|
||||
// child items finish their shrink animation, then snap to fully-rounded shape.
|
||||
return animateIntAsState(
|
||||
targetValue = if (listItem.isExpanded) outerLastIndex else 0,
|
||||
animationSpec = if (listItem.isExpanded) {
|
||||
snap()
|
||||
} else {
|
||||
snap(delayMillis = minOf(50 * maxOf(listItem.tokenList.lastIndex, 0), 250) + 150)
|
||||
},
|
||||
label = "lastIndex",
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun portfolioAssetExpandFadeAnimation(): ContentTransform {
|
||||
return fadeIn(animationSpec = tween(350, delayMillis = 90))
|
||||
.togetherWith(fadeOut(animationSpec = tween(350)))
|
||||
}
|
||||
|
||||
@Stable
|
||||
internal class SharedTokenRowComposables(
|
||||
val title: @Composable (Modifier) -> Unit,
|
||||
val icon: @Composable (Modifier) -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.features.foryou.impl.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.unit.dp
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletTabUM
|
||||
|
||||
/**
|
||||
* Horizontal wallet pill-tab strip for the For You screen.
|
||||
*
|
||||
* Faithful replica of the `WalletTabItem` / `walletListItem` reference in
|
||||
* `features/common-features/impl/.../choosetoken/ui/ChooseTokenScreen.kt` (which is `private`),
|
||||
* adapted to a standalone composable (For You renders inside a plain scrollable Column, not a LazyListScope).
|
||||
*/
|
||||
@Composable
|
||||
internal fun WalletTabsBlock(walletList: WalletListUM, modifier: Modifier = Modifier) {
|
||||
if (walletList.items.isEmpty()) return
|
||||
LazyRow(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
items(walletList.items) { um ->
|
||||
WalletTabItem(um)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletTabItem(state: WalletTabUM, modifier: Modifier = Modifier) {
|
||||
val isSelected = state.isSelected
|
||||
val backgroundColor = if (isSelected) TangemTheme.colors.button.primary else TangemTheme.colors.button.secondary
|
||||
val buttonTextColor = if (isSelected) TangemTheme.colors.text.primary2 else TangemTheme.colors.text.primary1
|
||||
val countTextColor = if (isSelected) TangemTheme.colors.text.primary2 else TangemTheme.colors.text.secondary
|
||||
val countBackground = if (isSelected) {
|
||||
TangemTheme.colors.button.secondary.copy(alpha = 0.2f)
|
||||
} else {
|
||||
TangemTheme.colors.button.primary.copy(alpha = 0.1f)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(percent = 50))
|
||||
.background(backgroundColor)
|
||||
.clickable(onClick = state.onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = state.text.resolveReference(),
|
||||
color = buttonTextColor,
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
)
|
||||
|
||||
val count = state.count
|
||||
if (count != null) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(countBackground, shape = CircleShape)
|
||||
.defaultMinSize(minWidth = 20.dp)
|
||||
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = count.resolveReference(),
|
||||
color = countTextColor,
|
||||
style = TangemTheme.typography.caption1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package com.tangem.features.foryou.impl.ui.preview
|
||||
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeColor
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeSize
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeType
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeUM
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM
|
||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal object ForYouPortfolioReviewPreviewData {
|
||||
|
||||
val reviewContent = PortfolioReviewUM.Content(
|
||||
assetCount = stringReference("5 assets"),
|
||||
topHoldingPercent = stringReference("Top holding 42%"),
|
||||
periodPickerUM = TangemSegmentedPickerUM(
|
||||
items = persistentListOf(
|
||||
TangemSegmentUM(id = "0", title = stringReference("Day")),
|
||||
TangemSegmentUM(id = "1", title = stringReference("Week")),
|
||||
TangemSegmentUM(id = "2", title = stringReference("Month")),
|
||||
),
|
||||
initialSelectedItem = TangemSegmentUM(id = "0", title = stringReference("Day")),
|
||||
isFixed = true,
|
||||
isAltSurface = true,
|
||||
),
|
||||
onPeriodClick = {},
|
||||
tokenList = persistentListOf(
|
||||
ForYouTokenListItemUM(
|
||||
tokenRowUM = TangemTokenRowUM.Content(
|
||||
id = "network_0",
|
||||
headIconUM = TangemIconUM.Currency(CurrencyIconState.Loading),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference("USDC"),
|
||||
badge = TangemBadgeUM(
|
||||
text = stringReference("Positive"),
|
||||
size = TangemBadgeSize.X4,
|
||||
type = TangemBadgeType.Tinted,
|
||||
color = TangemBadgeColor.Green,
|
||||
),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference("2 networks"),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("\$5,479"),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("54,8%"),
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = { _, _ -> },
|
||||
),
|
||||
tokenList = persistentListOf(
|
||||
TangemTokenRowUM.Content(
|
||||
id = "network_0_token_0",
|
||||
headIconUM = TangemIconUM.Currency(CurrencyIconState.Loading),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference("USDC"),
|
||||
badge = TangemBadgeUM(
|
||||
text = stringReference("Positive"),
|
||||
size = TangemBadgeSize.X4,
|
||||
type = TangemBadgeType.Tinted,
|
||||
color = TangemBadgeColor.Green,
|
||||
),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference("Solana $DOT 3,479 USDC"),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("\$3,479"),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("34,7%"),
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = { _, _ -> },
|
||||
),
|
||||
TangemTokenRowUM.Content(
|
||||
id = "network_0_token_1",
|
||||
headIconUM = TangemIconUM.Currency(CurrencyIconState.Loading),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference("USDC"),
|
||||
badge = TangemBadgeUM(
|
||||
text = stringReference("Positive"),
|
||||
size = TangemBadgeSize.X4,
|
||||
type = TangemBadgeType.Tinted,
|
||||
color = TangemBadgeColor.Green,
|
||||
),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference("Ethereum $DOT 2,000 USDC"),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("\$2,000"),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("20,1%"),
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = { _, _ -> },
|
||||
),
|
||||
),
|
||||
isExpanded = true,
|
||||
isExpandable = true,
|
||||
),
|
||||
ForYouTokenListItemUM(
|
||||
tokenRowUM = TangemTokenRowUM.Content(
|
||||
id = "network_1",
|
||||
headIconUM = TangemIconUM.Currency(CurrencyIconState.Loading),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference("Bitcoin"),
|
||||
badge = TangemBadgeUM(
|
||||
text = stringReference("Positive"),
|
||||
size = TangemBadgeSize.X4,
|
||||
type = TangemBadgeType.Tinted,
|
||||
color = TangemBadgeColor.Green,
|
||||
),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference("Main network"),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("\$849"),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("8,49%"),
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = { _, _ -> },
|
||||
),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isExpandable = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue