Updated on 2026-08-14
This commit is contained in:
parent
7f587d170c
commit
ddc5c4836b
17 changed files with 769 additions and 81 deletions
|
|
@ -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) } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() })
|
||||
|
|
|
|||
|
|
@ -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(""),
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue