Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-17 15:37:41 +07:00
parent 7f587d170c
commit ddc5c4836b
17 changed files with 769 additions and 81 deletions

View file

@ -3,6 +3,7 @@ package com.tangem.common.ui.account
import com.tangem.common.ui.R
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.fiat
@ -56,8 +57,8 @@ class AccountCryptoPortfolioItemStateConverter(
)
}
private fun Account.CryptoPortfolio.mapToLoadingState(): TokenItemState.Loading {
return TokenItemState.Loading(
private fun Account.CryptoPortfolio.mapToLoadingState(): TokenItemState.Content {
return TokenItemState.Content(
id = account.accountId.value,
iconState = AccountIconItemStateConverter.convert(account),
titleState = TokenItemState.TitleState.Content(
@ -71,6 +72,10 @@ class AccountCryptoPortfolioItemStateConverter(
),
isAvailable = false,
),
fiatAmountState = FiatAmountState.Loading,
subtitle2State = Subtitle2State.Loading,
onItemLongClick = null,
onItemClick = onItemClick?.let { onItemClick -> { onItemClick(account) } },
)
}

View file

@ -6,6 +6,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
@ -29,6 +30,7 @@ fun AccountTitle(
accountTitleUM: AccountTitleUM,
modifier: Modifier = Modifier,
textStyle: TextStyle = TangemTheme.typography.subtitle2,
textColor: Color = TangemTheme.colors.text.tertiary,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
@ -40,19 +42,20 @@ fun AccountTitle(
Text(
text = accountTitleUM.prefixText.resolveReference(),
style = textStyle,
color = TangemTheme.colors.text.tertiary,
color = textColor,
)
AccountLabel(
name = accountTitleUM.name,
icon = accountTitleUM.icon,
iconSize = AccountIconSize.ExtraSmall,
nameStyle = textStyle,
nameColor = textColor,
)
}
is AccountTitleUM.Text -> Text(
text = accountTitleUM.title.resolveReference(),
style = textStyle,
color = TangemTheme.colors.text.tertiary,
color = textColor,
modifier = Modifier.testTag(SendScreenTestTags.AMOUNT_CONTAINER_TITLE),
)
}

View file

@ -895,7 +895,7 @@
<item quantity="other">最大%d日</item>
</plurals>
<string name="onramp_timing_minutes">%s分</string>
<string name="onramp_title_available_from___">下記より利用可能</string>
<string name="onramp_title_available_from">下記より利用可能</string>
<string name="onramp_title_you_get">以下が手に入ります。</string>
<string name="onramp_tos_external_providers">サービスは外部プロバイダーによって提供されます。 \nTangemは責任を負いません。</string>
<string name="onramp_transaction_status_footer_text">この画面を閉じて、トークンの詳細画面で取引状況を確認できます。</string>

View file

@ -617,6 +617,7 @@
<string name="manage_tokens_unavailable_description">The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it.</string>
<string name="manage_tokens_unavailable_vote">Upvote</string>
<string name="manage_tokens_wallet_support_only_one_network_title">The wallet doesn\'t support more than one network</string>
<string name="markets_about_coin_header">About coin</string>
<string name="markets_add_to_my_portfolio_description">To buy, exchange, or receive this asset, add it to your portfolio</string>
<string name="markets_add_to_my_portfolio_unavailable_description">This asset is currently not supported in the wallet</string>
<string name="markets_add_to_my_portfolio_unavailable_for_wallet_description">This asset is not available for this wallet</string>

View file

@ -13,6 +13,7 @@ import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.MarketsTokenDetailsComponent.Params
@ -33,6 +34,7 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Params,
analyticsEventHandler: AnalyticsEventHandler,
private val accountsFeatureToggles: AccountsFeatureToggles,
portfolioComponentFactory: MarketsPortfolioComponent.Factory,
) : AppComponentContext by appComponentContext, MarketsTokenDetailsComponent {
@ -114,6 +116,7 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
onBackClick = ::navigateBack,
backButtonEnabled = bsState == BottomSheetState.EXPANDED,
onHeaderSizeChange = onHeaderSizeChange,
isAccountEnabled = accountsFeatureToggles.isFeatureEnabled,
portfolioBlock = portfolioComponent?.let { component ->
{ blockModifier ->
component.Content(blockModifier)
@ -141,6 +144,7 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
onBackClick = ::navigateBack,
backButtonEnabled = true,
onHeaderSizeChange = {},
isAccountEnabled = accountsFeatureToggles.isFeatureEnabled,
portfolioBlock = portfolioComponent?.let { component ->
{ blockModifier ->
component.Content(blockModifier)

View file

@ -58,6 +58,7 @@ internal fun MarketsTokenDetailsContent(
onBackClick: () -> Unit,
onHeaderSizeChange: (Dp) -> Unit,
backButtonEnabled: Boolean,
isAccountEnabled: Boolean,
modifier: Modifier = Modifier,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
) {
@ -69,6 +70,7 @@ internal fun MarketsTokenDetailsContent(
onHeaderSizeChange = onHeaderSizeChange,
backButtonEnabled = backButtonEnabled,
portfolioBlock = portfolioBlock,
isAccountEnabled = isAccountEnabled,
addTopBarStatusBarInsets = addTopBarStatusBarPadding,
)
@ -88,6 +90,7 @@ private fun Content(
onBackClick: () -> Unit,
onHeaderSizeChange: (Dp) -> Unit,
backButtonEnabled: Boolean,
isAccountEnabled: Boolean,
modifier: Modifier = Modifier,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
) {
@ -153,6 +156,7 @@ private fun Content(
tokenMarketDetailsBody(
state = state.body,
isAccountEnabled = isAccountEnabled,
portfolioBlock = portfolioBlock,
)
}
@ -348,6 +352,7 @@ private fun Preview() {
backgroundColor = TangemTheme.colors.background.tertiary,
portfolioBlock = {},
backButtonEnabled = true,
isAccountEnabled = true,
addTopBarStatusBarPadding = false,
)
}

View file

@ -4,17 +4,21 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.items.DescriptionItem
import com.tangem.core.ui.components.items.DescriptionPlaceholder
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
import com.tangem.features.markets.impl.R
internal fun LazyListScope.tokenMarketDetailsBody(
state: MarketsTokenDetailsUM.Body,
isAccountEnabled: Boolean,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
) {
when (state) {
@ -29,6 +33,10 @@ internal fun LazyListScope.tokenMarketDetailsBody(
}
}
if (isAccountEnabled) {
aboutCoinHeader()
}
loadingInfoBlocks()
}
is MarketsTokenDetailsUM.Body.Content -> {
@ -42,6 +50,10 @@ internal fun LazyListScope.tokenMarketDetailsBody(
}
}
if (isAccountEnabled) {
aboutCoinHeader()
}
infoBlocksList(state.infoBlocks)
}
is MarketsTokenDetailsUM.Body.Error -> {
@ -69,6 +81,21 @@ private fun LazyListScope.error(state: MarketsTokenDetailsUM.Body.Error) {
}
}
private fun LazyListScope.aboutCoinHeader() {
item("aboutCoinHeader") {
Text(
modifier = Modifier.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing20,
),
text = stringResourceSafe(R.string.markets_about_coin_header),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h3,
)
}
}
private fun LazyListScope.description(description: MarketsTokenDetailsUM.Description) {
item("description") {
DescriptionItem(

View file

@ -14,9 +14,10 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.features.markets.portfolio.add.api.AddToPortfolioComponent
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
import com.tangem.features.markets.portfolio.impl.model.MarketsPortfolioModel
import com.tangem.features.markets.portfolio.impl.model.MarketsPortfolioRoute
import com.tangem.features.markets.portfolio.impl.ui.MyPortfolio
import com.tangem.features.tokenreceive.TokenReceiveComponent
import dagger.assisted.Assisted
@ -28,19 +29,25 @@ internal class DefaultMarketsPortfolioComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: MarketsPortfolioComponent.Params,
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
) : AppComponentContext by context, MarketsPortfolioComponent {
private val model: MarketsPortfolioModel = getOrCreateModel(params)
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = TokenReceiveConfig.serializer(),
serializer = MarketsPortfolioRoute.serializer(),
handleBackButton = false,
childFactory = ::bottomSheetChild,
)
override fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) = model.setTokenNetworks(networks)
override fun setNoNetworksAvailable() = model.setNoNetworksAvailable()
override fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) {
model.setTokenNetworks(networks)
}
override fun setNoNetworksAvailable() {
model.setNoNetworksAvailable()
}
@Composable
override fun Content(modifier: Modifier) {
@ -52,15 +59,24 @@ internal class DefaultMarketsPortfolioComponent @AssistedInject constructor(
}
private fun bottomSheetChild(
config: TokenReceiveConfig,
config: MarketsPortfolioRoute,
componentContext: ComponentContext,
): ComposableBottomSheetComponent = tokenReceiveComponentFactory.create(
context = childByContext(componentContext),
params = TokenReceiveComponent.Params(
config = config,
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
): ComposableBottomSheetComponent = when (config) {
MarketsPortfolioRoute.AddToPortfolio -> addToPortfolioComponentFactory.create(
context = childByContext(componentContext),
params = AddToPortfolioComponent.Params(
addToPortfolioManager = model.newAddToPortfolioManager!!,
callback = model.addToPortfolioCallback,
),
)
is MarketsPortfolioRoute.TokenReceive -> tokenReceiveComponentFactory.create(
context = childByContext(componentContext),
params = TokenReceiveComponent.Params(
config = config.config,
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
}
@AssistedFactory
interface Factory : MarketsPortfolioComponent.Factory {

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
@ -16,15 +17,14 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.managetokens.CheckCurrencyUnsupportedUseCase
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
import com.tangem.domain.markets.SaveMarketTokensUseCase
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
@ -32,11 +32,13 @@ import com.tangem.domain.transaction.usecase.ReceiveAddressesFactory
import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.features.markets.impl.R
import com.tangem.features.markets.portfolio.add.api.AddToPortfolioComponent
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
import com.tangem.features.markets.portfolio.impl.analytics.PortfolioAnalyticsEvent
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
import com.tangem.features.markets.portfolio.impl.loader.PortfolioDataLoader
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM.Tokens.AddButtonState
import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM
import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle
import com.tangem.features.wallet.utils.UserWalletImageFetcher
@ -49,6 +51,7 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import com.tangem.features.markets.portfolio.add.api.AddToPortfolioManager as NewAddToPortfolioManager
@Suppress("LongParameterList", "LargeClass")
@Stable
@ -69,6 +72,9 @@ internal class MarketsPortfolioModel @Inject constructor(
private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle,
private val userWalletImageFetcher: UserWalletImageFetcher,
private val receiveAddressesFactory: ReceiveAddressesFactory,
private val accountsFeatureToggles: AccountsFeatureToggles,
newAddToPortfolioManagerFactory: NewAddToPortfolioManager.Factory,
private val newMarketsPortfolioDelegateFactory: NewMarketsPortfolioDelegate.Factory,
) : Model() {
val state: StateFlow<MyPortfolioUM> get() = _state
@ -80,12 +86,40 @@ internal class MarketsPortfolioModel @Inject constructor(
source = params.analyticsParams?.source,
)
val newAddToPortfolioManager: NewAddToPortfolioManager?
val newMarketsPortfolioDelegate: NewMarketsPortfolioDelegate?
/** Multi-wallet [UserWalletId] that user uses to add new tokens in AddToPortfolio bottom sheet */
private val selectedMultiWalletIdFlow = MutableStateFlow<UserWalletId?>(value = null)
private val portfolioBSVisibilityModelFlow = MutableStateFlow(value = PortfolioBSVisibilityModel())
val bottomSheetNavigation: SlotNavigation<TokenReceiveConfig> = SlotNavigation()
val bottomSheetNavigation: SlotNavigation<MarketsPortfolioRoute> = SlotNavigation()
val addToPortfolioCallback = object : AddToPortfolioComponent.Callback {
override fun onDismiss() = bottomSheetNavigation.dismiss()
}
private val tokenActionsHandler = tokenActionsIntentsFactory.create(
currentAppCurrency = Provider { currentAppCurrency.value },
updateTokenReceiveBSConfig = { updateBlock ->
if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled.not()) {
updateTokensState {
it.copy(tokenReceiveBSConfig = updateBlock(it.tokenReceiveBSConfig))
}
}
},
onHandleQuickAction = { handledAction ->
analyticsEventHandler.send(
analyticsEventBuilder.quickActionClick(
actionUM = handledAction.action,
blockchainName = handledAction.cryptoCurrencyData.status.currency.network.name,
),
)
if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) {
configureReceiveAddresses(handledAction)
}
},
)
private val currentAppCurrency = getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->
@ -132,27 +166,7 @@ internal class MarketsPortfolioModel @Inject constructor(
},
),
currentState = Provider { _state.value },
tokenActionsHandler = tokenActionsIntentsFactory.create(
currentAppCurrency = Provider { currentAppCurrency.value },
updateTokenReceiveBSConfig = { updateBlock ->
if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled.not()) {
updateTokensState {
it.copy(tokenReceiveBSConfig = updateBlock(it.tokenReceiveBSConfig))
}
}
},
onHandleQuickAction = { handledAction ->
analyticsEventHandler.send(
analyticsEventBuilder.quickActionClick(
actionUM = handledAction.action,
blockchainName = handledAction.cryptoCurrencyData.status.currency.network.name,
),
)
if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) {
configureReceiveAddresses(handledAction)
}
},
),
tokenActionsHandler = tokenActionsHandler,
updateTokens = { updateBlock ->
updateTokensState { state ->
state.copy(tokens = updateBlock(state.tokens))
@ -161,18 +175,50 @@ internal class MarketsPortfolioModel @Inject constructor(
)
init {
// Subscribe on selected wallet flow to support actual selected wallet
subscribeOnSelectedMultiWalletUpdates()
if (accountsFeatureToggles.isFeatureEnabled) {
newAddToPortfolioManager = newAddToPortfolioManagerFactory
.create(
modelScope,
params.token,
params.analyticsParams,
)
newMarketsPortfolioDelegate = newMarketsPortfolioDelegateFactory.create(
scope = modelScope,
token = params.token,
tokenActionsHandler = tokenActionsHandler,
buttonState = newAddToPortfolioManager.state.map {
when (it) {
is NewAddToPortfolioManager.State.AvailableToAdd -> AddButtonState.Available
NewAddToPortfolioManager.State.Init -> AddButtonState.Loading
NewAddToPortfolioManager.State.NothingToAdd -> AddButtonState.Unavailable
}
},
onAddClick = { bottomSheetNavigation.activate(MarketsPortfolioRoute.AddToPortfolio) },
)
newMarketsPortfolioDelegate.combineData()
.onEach { _state.value = it }
.flowOn(dispatchers.default)
.launchIn(modelScope)
} else {
newAddToPortfolioManager = null
newMarketsPortfolioDelegate = null
// Subscribe on selected wallet flow to support actual selected wallet
subscribeOnSelectedMultiWalletUpdates()
subscribeOnStateUpdates()
subscribeOnStateUpdates()
}
}
fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) {
addToPortfolioManager.setAvailableNetworks(networks)
newAddToPortfolioManager?.setTokenNetworks(networks)
newMarketsPortfolioDelegate?.setTokenNetworks(networks)
}
fun setNoNetworksAvailable() {
addToPortfolioManager.setAvailableNetworks(emptyList())
newAddToPortfolioManager?.setTokenNetworks(emptyList())
newMarketsPortfolioDelegate?.setTokenNetworks(emptyList())
}
private fun subscribeOnSelectedMultiWalletUpdates() {
@ -378,7 +424,7 @@ internal class MarketsPortfolioModel @Inject constructor(
status = quickAction.cryptoCurrencyData.status,
userWalletId = quickAction.cryptoCurrencyData.userWallet.walletId,
) ?: return@launch
bottomSheetNavigation.activate(tokenConfig)
bottomSheetNavigation.activate(MarketsPortfolioRoute.TokenReceive(tokenConfig))
}
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.features.markets.portfolio.impl.model
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.models.TokenReceiveConfig
import kotlinx.serialization.Serializable
@Serializable
sealed interface MarketsPortfolioRoute : Route {
@Serializable
data object AddToPortfolio : MarketsPortfolioRoute
@Serializable
data class TokenReceive(
val config: TokenReceiveConfig,
) : MarketsPortfolioRoute
}

View file

@ -0,0 +1,325 @@
package com.tangem.features.markets.portfolio.impl.model
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.account.toUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM.Tokens.AddButtonState
import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioHeader
import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioListItem
import com.tangem.features.markets.portfolio.impl.ui.state.WalletHeader
import com.tangem.utils.extensions.isZero
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
@Suppress("LongParameterList")
internal class NewMarketsPortfolioDelegate @AssistedInject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val allAccountSupplier: MultiAccountStatusListSupplier,
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCaseV2,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
@Assisted private val scope: CoroutineScope,
@Assisted private val token: TokenMarketParams,
@Assisted private val tokenActionsHandler: TokenActionsHandler,
@Assisted private val buttonState: Flow<AddButtonState>,
@Assisted private val onAddClick: () -> Unit,
) {
private val currencyRawId: CryptoCurrency.RawID = token.id
private var expandedHolder: MutableStateFlow<Set<Pair<UserWalletId, CryptoCurrency.ID>>>? = null
private val settingsFlow: Flow<SettingsBox> = combine(
flow = getSelectedAppCurrencyUseCase.invokeOrDefault(),
flow2 = getBalanceHidingSettingsUseCase.isBalanceHidden(),
flow3 = isAccountsModeEnabledUseCase(),
transform = ::SettingsBox,
).shareIn(
replay = 1,
started = SharingStarted.Eagerly,
scope = scope,
).distinctUntilChanged()
private val availableNetworks = MutableSharedFlow<List<TokenMarketInfo.Network>>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) {
availableNetworks.tryEmit(networks)
}
fun combineData(): Flow<MyPortfolioUM> {
return availableNetworks.transformLatest { availableNetworks ->
when {
availableNetworks.isEmpty() -> emit(MyPortfolioUM.Unavailable)
else -> emitAll(onAvailableNetworksFlow().distinctUntilChanged())
}
}.distinctUntilChanged()
}
private fun onAvailableNetworksFlow(): Flow<MyPortfolioUM> =
portfolioWithThisCurrencyFLow().transformLatest { portfolioWithCurrency ->
fun addFirstTokenUM() = MyPortfolioUM.AddFirstToken(
onAddClick = onAddClick,
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
)
when (portfolioWithCurrency.flattenAddedCurrency.isEmpty()) {
false -> emitAll(contentFlow(portfolioWithCurrency).distinctUntilChanged())
true -> when (portfolioWithCurrency.hasMultiWallets) {
true -> emit(addFirstTokenUM())
false -> emit(MyPortfolioUM.UnavailableForWallet)
}
}
}
private fun contentFlow(portfolio: PortfoliosWithThisCurrency): Flow<MyPortfolioUM.Content> {
fun Portfolio.actionsFoAccountCurrencies(): List<Flow<Pair<CryptoCurrency, TokenActionsState>>> =
accountsWithAdded.map { account ->
fun CryptoCurrencyStatus.actionsFlow() = getCryptoCurrencyActionsUseCase(
accountId = account.accountStatus.account.accountId,
currency = this.currency,
).map { actionsState -> actionsState.cryptoCurrencyStatus.currency to actionsState }
account.addedCurrency.map { it.actionsFlow() }
}.flatten()
val allAddedTokenActions =
portfolio.portfolios.map { portfolio -> portfolio.actionsFoAccountCurrencies() }.flatten()
return combine(
flow = combine(allAddedTokenActions) { it.toMap() }.distinctUntilChanged(),
flow2 = buttonState.distinctUntilChanged(),
flow3 = getExpandedHolder(portfolio),
flow4 = settingsFlow.distinctUntilChanged(),
transform = { actions, addButtonState, expanded, settings ->
buildContentState(
portfolio = portfolio,
allActions = actions,
addButtonState = addButtonState,
expanded = expanded,
settings = settings,
)
},
)
}
private fun getExpandedHolder(
portfolio: PortfoliosWithThisCurrency,
): StateFlow<Set<Pair<UserWalletId, CryptoCurrency.ID>>> {
val expandedHolder = this.expandedHolder
if (expandedHolder != null) return expandedHolder
val allAddedCurrency = portfolio.flattenAddedCurrency
val shouldForceExpand = allAddedCurrency.size == 1 &&
allAddedCurrency.first().value.amount?.isZero() == true
val initValue = when {
shouldForceExpand -> {
val currency = allAddedCurrency.first()
// find userWallet than have this single added token
portfolio.portfolios
.find { it.accountsWithAdded.find { account -> account.addedCurrency.isNotEmpty() } != null }
?.userWallet
?.let { setOf(it.walletId to currency.currency.id) }
?: setOf()
}
else -> setOf()
}
return MutableStateFlow(initValue)
.also { this.expandedHolder = it }
}
private fun portfolioWithThisCurrencyFLow(): Flow<PortfoliosWithThisCurrency> =
allAccountSupplier(Unit).map { list -> list.map { it.addedAccountsFlow() } }.flatMapLatest { flows ->
combine(flows) {
PortfoliosWithThisCurrency(
currencyRawId = currencyRawId,
portfolios = it.toList(),
)
}
}.distinctUntilChanged()
private fun AccountStatusList.addedAccountsFlow(): Flow<Portfolio> =
getUserWalletUseCase.invokeFlow(this.userWalletId).mapNotNull { it.getOrNull() }.map { wallet ->
Portfolio(
userWallet = wallet,
accountStatusList = this,
accountsWithAdded = this.filterByRawID(),
)
}.distinctUntilChanged()
private fun AccountStatusList.filterByRawID(): List<AccountWithAdded> {
fun AccountStatus.filterByRawID(): List<CryptoCurrencyStatus> = when (this) {
is AccountStatus.CryptoPortfolio -> this.tokenList.flattenCurrencies()
.filter { status -> status.currency.id.rawCurrencyId == currencyRawId }
}
return accountStatuses.map { accountStatus ->
AccountWithAdded(
accountStatus = accountStatus,
addedCurrency = accountStatus.filterByRawID(),
)
}
}
private fun buildContentState(
portfolio: PortfoliosWithThisCurrency,
allActions: Map<CryptoCurrency, TokenActionsState>,
addButtonState: AddButtonState,
expanded: Set<Pair<UserWalletId, CryptoCurrency.ID>>,
settings: SettingsBox,
): MyPortfolioUM.Content {
val appCurrency = settings.appCurrency
val isBalanceHidden = settings.isBalanceHidden
val isAccountMode = settings.isAccountMode
val uiItems: MutableList<PortfolioListItem> = mutableListOf<PortfolioListItem>()
fun toggleQuickActions(key: Pair<UserWalletId, CryptoCurrency.ID>) = expandedHolder?.update { expanded ->
val isExpand = expanded.contains(key)
if (isExpand) expanded.minus(key) else expanded.plus(key)
}
val tokenUMConverter = PortfolioTokenUMConverter(
appCurrency = appCurrency,
isBalanceHidden = isBalanceHidden,
onTokenItemClick = { },
tokenActionsHandler = tokenActionsHandler,
)
portfolio.portfolios.forEach { portfolioItem ->
if (portfolioItem.flattenAddedCurrency.isEmpty()) return@forEach
val userWallet = portfolioItem.userWallet
if (isAccountMode) {
uiItems.add(portfolioItem.userWallet.toWalletHeader())
} else {
uiItems.add(portfolioItem.userWallet.toWalletPortfolioHeader())
}
portfolioItem.accountsWithAdded.forEach { accountWithAdded ->
if (accountWithAdded.addedCurrency.isEmpty()) return@forEach
if (isAccountMode) {
val account = accountWithAdded.accountStatus.account
uiItems.add(account.toAccountPortfolioHeader())
}
accountWithAdded.addedCurrency.forEach { currencyStatus ->
val actions = allActions[currencyStatus.currency]?.states
?: emptyList()
val value = PortfolioData.CryptoCurrencyData(
userWallet = userWallet,
status = currencyStatus,
actions = actions,
)
val expandedKey = portfolioItem.userWallet.walletId to currencyStatus.currency.id
val isExpand = expanded.contains(expandedKey)
val tokenItem = tokenUMConverter.convertV2(
onTokenItemClick = { wallet, status ->
toggleQuickActions(wallet.walletId to status.currency.id)
},
value = value,
isQuickActionsShown = isExpand,
)
uiItems.add(tokenItem)
}
}
}
return MyPortfolioUM.Content(
items = uiItems.toImmutableList(),
buttonState = addButtonState,
onAddClick = onAddClick,
)
}
private fun Account.toAccountPortfolioHeader(): PortfolioHeader = PortfolioHeader(
id = this.accountId.value,
state = AccountTitleUM.Account(
prefixText = TextReference.EMPTY,
name = this.accountName.toUM().value,
icon = when (this) {
is Account.CryptoPortfolio -> this.icon.toUM()
},
),
)
private fun UserWallet.toWalletPortfolioHeader(): PortfolioHeader = PortfolioHeader(
id = this.walletId.stringValue,
state = AccountTitleUM.Text(
title = stringReference(this.name),
),
)
private fun UserWallet.toWalletHeader(): WalletHeader = WalletHeader(
id = this.walletId.stringValue,
name = stringReference(this.name),
)
@Suppress("LongParameterList")
@AssistedFactory
interface Factory {
fun create(
scope: CoroutineScope,
token: TokenMarketParams,
tokenActionsHandler: TokenActionsHandler,
buttonState: Flow<AddButtonState>,
onAddClick: () -> Unit,
): NewMarketsPortfolioDelegate
}
}
private data class PortfoliosWithThisCurrency(
val currencyRawId: CryptoCurrency.RawID,
val portfolios: List<Portfolio>,
) {
val hasMultiWallets: Boolean = portfolios.any { it.userWallet.isMultiCurrency }
val flattenAddedCurrency: List<CryptoCurrencyStatus> =
portfolios.map { portfolio -> portfolio.flattenAddedCurrency }.flatten()
}
private data class Portfolio(
val userWallet: UserWallet,
val accountStatusList: AccountStatusList,
val accountsWithAdded: List<AccountWithAdded>,
) {
val flattenAddedCurrency: List<CryptoCurrencyStatus> =
accountsWithAdded.map { it.addedCurrency }.flatten()
}
private data class AccountWithAdded(
val addedCurrency: List<CryptoCurrencyStatus>,
val accountStatus: AccountStatus,
)
private data class SettingsBox(
val appCurrency: AppCurrency,
val isBalanceHidden: Boolean,
val isAccountMode: Boolean,
)

View file

@ -27,6 +27,24 @@ internal class PortfolioTokenUMConverter(
private val tokenActionsHandler: TokenActionsHandler,
) : Converter<PortfolioData.CryptoCurrencyData, PortfolioTokenUM> {
fun convertV2(
value: PortfolioData.CryptoCurrencyData,
isQuickActionsShown: Boolean,
onTokenItemClick: (UserWallet, CryptoCurrencyStatus) -> Unit,
): PortfolioTokenUM {
val tokenItemStateConverter = TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = { _, status -> onTokenItemClick(value.userWallet, status) },
)
return PortfolioTokenUM(
tokenItemState = tokenItemStateConverter.convert(value = value.status),
walletId = value.userWallet.walletId,
isBalanceHidden = isBalanceHidden,
isQuickActionsShown = isQuickActionsShown,
quickActions = quickActions(cryptoData = value, tokenActionsHandler = tokenActionsHandler),
)
}
override fun convert(value: PortfolioData.CryptoCurrencyData): PortfolioTokenUM {
val tokenItemStateConverter = TokenItemStateConverter(
appCurrency = appCurrency,

View file

@ -10,29 +10,43 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.common.ui.account.AccountTitle
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheet
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SmallButtonShimmer
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheet
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.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.markets.impl.R
import com.tangem.features.markets.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
import com.tangem.features.markets.portfolio.impl.ui.state.*
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM.Tokens.AddButtonState
@Composable
internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) {
if (state is MyPortfolioUM.Content) {
val contentModifier = Modifier.padding(
start = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing20,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing32,
)
PortfolioList(state, contentModifier)
return
}
InformationBlock(
modifier = modifier,
contentHorizontalPadding = TangemTheme.dimens.spacing0,
@ -55,6 +69,7 @@ internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) {
MyPortfolioUM.Loading -> LoadingPlaceholder(modifier = contentModifier)
MyPortfolioUM.Unavailable -> UnavailableAsset(modifier = contentModifier)
MyPortfolioUM.UnavailableForWallet -> UnavailableAssetForWallet(modifier = contentModifier)
is MyPortfolioUM.Content -> PortfolioList(state = state)
}
}
@ -119,6 +134,7 @@ private fun TokenList(state: MyPortfolioUM.Tokens, modifier: Modifier = Modifier
state.tokens.fastForEachIndexed { index, token ->
key(token.tokenItemState.id) {
PortfolioItem(
modifier = Modifier.background(color = TangemTheme.colors.background.action),
state = token,
lastInList = index == state.tokens.size - 1,
)
@ -129,6 +145,111 @@ private fun TokenList(state: MyPortfolioUM.Tokens, modifier: Modifier = Modifier
TokenReceiveBottomSheet(config = state.tokenReceiveBSConfig)
}
@Composable
private fun PortfolioList(state: MyPortfolioUM.Content, modifier: Modifier = Modifier) {
Column(modifier) {
key("PortfolioListHeader") {
Row(
modifier = Modifier.padding(horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
text = stringResourceSafe(R.string.markets_common_my_portfolio),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
AddButton(state = state.buttonState, onClick = state.onAddClick)
}
}
state.items.fastForEachIndexed { index, item ->
val previousItem = state.items.getOrNull(index.dec())
val nextItem = state.items.getOrNull(index.inc())
val itemModifier = Modifier
.fillMaxWidth()
.getOffsetModifier(item, previousItem)
.getBackgroundModifier(item, previousItem, nextItem)
key(item.id) {
PortfolioItem(
item = item,
modifier = itemModifier,
lastInList = index == state.items.size - 1,
)
}
}
}
}
@Composable
private fun Modifier.getBackgroundModifier(
item: PortfolioListItem,
previousItem: PortfolioListItem?,
nextItem: PortfolioListItem?,
): Modifier {
val color = TangemTheme.colors.background.action
val radius = 14.dp
val topRound = RoundedCornerShape(topStart = radius, topEnd = radius)
val bottomRound = RoundedCornerShape(bottomStart = radius, bottomEnd = radius)
val allRound = RoundedCornerShape(size = radius)
val backgroundModifier = when (item) {
is WalletHeader -> this
is PortfolioHeader -> this
.clip(topRound)
.background(color = color)
is PortfolioTokenUM -> when {
previousItem is PortfolioHeader && nextItem !is PortfolioTokenUM -> this
.clip(bottomRound)
.background(color = color)
previousItem is WalletHeader && nextItem !is PortfolioTokenUM -> this
.clip(allRound)
.background(color = color)
previousItem is PortfolioTokenUM && nextItem !is PortfolioTokenUM -> this
.clip(bottomRound)
.background(color = color)
else -> this.background(color = color)
}
}
return backgroundModifier
}
private fun Modifier.getOffsetModifier(item: PortfolioListItem, previousItem: PortfolioListItem?): Modifier = when {
item is WalletHeader -> this.padding(top = 20.dp, start = 4.dp, end = 4.dp)
item is PortfolioHeader && previousItem is PortfolioTokenUM -> this.padding(top = 12.dp)
item is PortfolioHeader && previousItem == null -> this.padding(top = 20.dp)
previousItem is WalletHeader -> this.padding(top = 12.dp)
previousItem is PortfolioTokenUM -> this
else -> this
}
@Composable
private fun PortfolioItem(item: PortfolioListItem, lastInList: Boolean, modifier: Modifier = Modifier) {
when (item) {
is PortfolioHeader -> AccountTitle(
modifier = modifier.padding(
start = 12.dp,
top = 12.dp,
bottom = 8.dp,
),
accountTitleUM = item.state,
textStyle = TangemTheme.typography.caption1,
textColor = TangemTheme.colors.text.primary1,
)
is PortfolioTokenUM -> PortfolioItem(
state = item,
modifier = modifier,
lastInList = lastInList,
)
is WalletHeader -> Text(
modifier = modifier,
text = item.name.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
}
}
@Composable
fun UnavailableAsset(modifier: Modifier = Modifier) {
UnavailableContent(
@ -199,8 +320,7 @@ private fun Preview(@PreviewParameter(PreviewMyPortfolioUMProvider::class) state
TangemThemePreview {
Box(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary)
.padding(TangemTheme.dimens.spacing8),
.background(TangemTheme.colors.background.tertiary),
) {
MyPortfolio(state)
}

View file

@ -45,7 +45,6 @@ internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifie
TokenItem(
state = tokenItemState,
isBalanceHidden = state.isBalanceHidden,
modifier = Modifier.background(color = TangemTheme.colors.background.action),
itemPaddingValues = PaddingValues(
start = TangemTheme.dimens.spacing10,
end = TangemTheme.dimens.spacing12,
@ -54,7 +53,6 @@ internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifie
PortfolioQuickActions(
modifier = Modifier
.background(color = TangemTheme.colors.background.action)
.padding(
bottom = if (lastInList) {
TangemTheme.dimens.spacing12
@ -82,6 +80,7 @@ private fun Preview(@PreviewParameter(PortfolioTokenUMProvider::class) tokenUM:
}
PortfolioItem(
modifier = Modifier.background(color = TangemTheme.colors.background.action),
state = tokenUM.copy(
tokenItemState = when (tokenUM.tokenItemState) {
is TokenItemState.Content -> tokenUM.tokenItemState.copy(onItemClick = { onItemClick() })

View file

@ -1,15 +1,19 @@
package com.tangem.features.markets.portfolio.impl.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.common.ui.account.AccountIconPreviewData
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioTokenUM
import com.tangem.features.markets.portfolio.impl.ui.state.QuickActionUM
import com.tangem.features.markets.portfolio.impl.ui.state.*
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM.Tokens
import kotlinx.collections.immutable.persistentListOf
import java.util.UUID
internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider<MyPortfolioUM> {
@ -40,35 +44,107 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider<MyPortfol
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
onAddClick = {},
),
MyPortfolioUM.Content(
items = persistentListOf(
walletPortfolioHeader,
accountToken,
accountToken,
),
buttonState = Tokens.AddButtonState.Available,
onAddClick = {},
),
MyPortfolioUM.Content(
items = persistentListOf(
walletHeader,
accountHeader,
accountToken,
accountToken,
),
buttonState = Tokens.AddButtonState.Available,
onAddClick = {},
),
MyPortfolioUM.Content(
items = persistentListOf(
walletHeader,
accountHeader,
accountToken.copy(isQuickActionsShown = true),
accountToken,
),
buttonState = Tokens.AddButtonState.Available,
onAddClick = {},
),
MyPortfolioUM.Loading,
MyPortfolioUM.Unavailable,
MyPortfolioUM.UnavailableForWallet,
)
val sampleToken = PortfolioTokenUM(
tokenItemState = TokenItemState.Content(
id = "",
iconState = CurrencyIconState.Locked,
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "My wallet")),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "486,65 \$"),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "733,71097 MATIC"),
subtitleState = TokenItemState.SubtitleState.TextContent(
value = stringReference(value = "XRP Ledger token"),
val walletHeader
get() = WalletHeader(
id = UUID.randomUUID().toString(),
name = stringReference("Wallet 1"),
)
val walletPortfolioHeader
get() = PortfolioHeader(
state = AccountTitleUM.Text(title = stringReference("Wallet 1")),
id = UUID.randomUUID().toString(),
)
val accountHeader
get() = PortfolioHeader(
state = AccountTitleUM.Account(
icon = AccountIconPreviewData.randomAccountIcon(),
name = stringReference("Main Account"),
prefixText = TextReference.EMPTY,
),
onItemClick = {},
onItemLongClick = {},
),
isQuickActionsShown = false,
quickActions = PortfolioTokenUM.QuickActions(
actions = persistentListOf(
QuickActionUM.Buy,
QuickActionUM.Exchange(showBadge = true),
QuickActionUM.Receive,
id = UUID.randomUUID().toString(),
)
val coinIconState
get() = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = com.tangem.core.ui.R.drawable.img_polygon_22,
isGrayscale = false,
shouldShowCustomBadge = false,
)
val accountToken
get() = sampleToken.copy(
tokenItemState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
iconState = coinIconState,
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
fiatAmountState = FiatAmountState.Content(text = "321 $"),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "5,412 MATIC"),
subtitleState = TokenItemState.SubtitleState.TextContent(stringReference(value = "Token")),
onItemClick = {},
onItemLongClick = {},
),
onQuickActionClick = {},
onQuickActionLongClick = {},
),
isBalanceHidden = false,
walletId = UserWalletId(""),
)
)
val sampleToken
get() = PortfolioTokenUM(
tokenItemState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
iconState = CurrencyIconState.Locked,
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "My wallet")),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "486,65 \$"),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "733,71097 MATIC"),
subtitleState = TokenItemState.SubtitleState.TextContent(
value = stringReference(value = "XRP Ledger token"),
),
onItemClick = {},
onItemLongClick = {},
),
isQuickActionsShown = false,
quickActions = PortfolioTokenUM.QuickActions(
actions = persistentListOf(
QuickActionUM.Buy,
QuickActionUM.Exchange(showBadge = true),
QuickActionUM.Receive,
),
onQuickActionClick = {},
onQuickActionLongClick = {},
),
isBalanceHidden = false,
walletId = UserWalletId(""),
)
}

View file

@ -24,6 +24,15 @@ internal sealed class MyPortfolioUM {
}
}
data class Content(
val items: ImmutableList<PortfolioListItem>,
val buttonState: Tokens.AddButtonState,
val onAddClick: () -> Unit,
) : MyPortfolioUM() {
override val addToPortfolioBSConfig: TangemBottomSheetConfig = TangemBottomSheetConfig.Empty
}
data class AddFirstToken(
override val addToPortfolioBSConfig: TangemBottomSheetConfig,
val onAddClick: () -> Unit,

View file

@ -1,16 +1,33 @@
package com.tangem.features.markets.portfolio.impl.ui.state
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.collections.immutable.ImmutableList
internal sealed interface PortfolioListItem {
val id: String
}
internal data class WalletHeader(
override val id: String,
val name: TextReference,
) : PortfolioListItem
internal data class PortfolioHeader(
override val id: String,
val state: AccountTitleUM,
) : PortfolioListItem
internal data class PortfolioTokenUM(
val tokenItemState: TokenItemState,
val walletId: UserWalletId,
val isBalanceHidden: Boolean,
val isQuickActionsShown: Boolean,
val quickActions: QuickActions,
) {
) : PortfolioListItem {
override val id: String = tokenItemState.id
data class QuickActions(
val actions: ImmutableList<QuickActionUM>,