diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 67dfaf1848..b0dc85a2c9 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -315,6 +315,7 @@
час
Импортировать
В процессе
+ Недостаточный баланс
Позже
Узнать больше
Осталось %1$s
@@ -337,6 +338,7 @@
NFT
Нет
Нет адреса
+ Нет результата
Не добавлено
Не доступно
Не сейчас
@@ -484,6 +486,13 @@
Отправка средств в другой сети может повлечь потерю средств.
%s сеть
Отправляйте средства, используя только
+ Динамические адреса
+ Используйте новый адрес для каждой транзакции, чтобы снизить отслеживаемость и повысить конфиденциальность в блокчейне.
+ Повышенная конфиденциальность
+ Бесшовное получение
+ Включить динамические адреса
+ Динамические адреса недоступны
+ Сервис недоступен. Пожалуйста, попробуйте еще раз.
Лучшие возможности
Очистить фильтр
Список временно пуст — он обновляется. Пожалуйста, зайдите чуть позже.
@@ -701,6 +710,7 @@
Приложите, чтобы отсканировать
Приложите, чтобы подписать
Приложите карту или кольцо
+ Кошелёк успешно импортирован
Вы обновили данные биометрии, отсканируйте свою карту или кольцо для входа
Ваш баланс должен быть выше суммы комиссии для осуществления перевода
Недостаточно средств
@@ -756,10 +766,14 @@
Нет данных
Пульс рынка
Быстрые действия
+ Очистить всё
Поиск токенов
+ Недавние
+ В вашем портфеле
Результат
Токены с капитализацией меньше 100к USD
Показать токены
+ Криптовалюты, новости и не только
Нет результата
Выберите сеть
Выберите кошелек
@@ -817,6 +831,10 @@
Держатели
Изменение количества держателей токенов в течение выбранного периода времени
Держатели
+ Д
+ М
+ Н
+ Г
Инсайты
Ссылки
Ликвидность
@@ -835,6 +853,7 @@
Максимальное количество монет или токенов, которое может когда-либо существовать для выбранной криптовалюты
Максимальный объем
Метрики
+ Нет ограничений
Официальные ссылки
Динамика цены
Репозиторий
@@ -1121,6 +1140,14 @@
Настройки
Вы не предоставили доступ к вашей камере
Доступ к камере запрещен
+ Запрашиваемый токен не добавлен в ваш кошелёк. Пожалуйста, добавьте его и попробуйте снова.
+ Токен не добавлен
+ Извините, этот QR-код не удалось распознать.
+ Нераспознанный QR-код
+ Эта сеть не поддерживается ни одним из добавленных вами токенов. Добавьте поддерживаемый токен для отправки криптовалюты.
+ Поддерживаемые токены не найдены
+ Этот QR-код содержит параметры, которые не распознаны: %s. Некоторые данные платежа могут быть утеряны, если вы продолжите.
+ Неизвестные параметры
Memo не требуется
%1$s (%2$s) в сети %3$s
%1$s в %2$s сети
@@ -1239,6 +1266,12 @@
Мемо %s
Недопустимый Memo
Покрытие сетевой комиссии
+
+ - Токен %d не совместим с этим адресом
+
+ - Токены %d не совместимы с этим адресом
+
+
Nonce
Уникальный номер транзакции. Измените, чтобы ускорить отправку или отменить зависшую операцию.
Введите nonce…
@@ -1309,6 +1342,7 @@
К получению %s
Вы уверены, что хотите отменить конвертацию? Ваши предыдущие данные будут удалены.
Убрать конвертацию
+ Что-то пошло не так. Попробуйте еще раз.
Отправка с обменом
Транзакция отправлена
Подготовьтесь к сканированию кольца или карты, которую вы хотите настроить.
@@ -1497,14 +1531,18 @@
Ошибка расчета комиссии. Пожалуйста, отправьте информацию в поддержку.
Вы отправляете
Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.
+ Вы можете получить значительно меньше из-за низкой ликвидности. Попробуйте меньшую сумму или другого провайдера.
Высокое влияние на цену
Недостаточно средств
+ Недостаточно средств для завершения этой транзакции. Уменьшите сумму для получения или добавьте больше средств.
Дать разрешение
Обменять
Обмен…
Вы получите
Выберите токен
недоступен
+ Недостаточно ликвидности для этой сделки.\nУменьшите сумму или выберите другого провайдера.
+ Сумма сделки слишком велика
Будем рады вашей обратной связи
Tangem Pay в режиме beta
Карта заморожена
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index d3754c9d92..5b19f4c6fd 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -765,11 +765,13 @@
This section’s data is sourced from the following networks: %s
Unable to load the data…
No data
+ **Add to your portfolio** to start buying, exchanging or receiving this asset
+ In your portfolio
Market Pulse
Quick actions
Clear all
Search tokens
- Recents
+ Recent\'s
In your portfolio
Result
See tokens under 100k USD market cap
diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/AnnotatedStringExt.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/AnnotatedStringExt.kt
index e824de1e5d..8be4dc9343 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/extensions/AnnotatedStringExt.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/AnnotatedStringExt.kt
@@ -5,6 +5,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import org.intellij.markdown.MarkdownElementTypes
@@ -26,18 +27,23 @@ fun rememberMarkdownParser() = remember {
* @param node current processed node
*/
@Composable
-fun AnnotatedString.Builder.appendMarkdown(markdownText: String, node: ASTNode): AnnotatedString.Builder {
+fun AnnotatedString.Builder.appendMarkdown(
+ markdownText: String,
+ node: ASTNode,
+ boldColor: Color = Color.Unspecified,
+): AnnotatedString.Builder {
when (node.type) {
MarkdownElementTypes.MARKDOWN_FILE, MarkdownElementTypes.PARAGRAPH -> {
node.children.forEach { childNode ->
appendMarkdown(
markdownText = markdownText,
node = childNode,
+ boldColor = boldColor,
)
}
}
MarkdownElementTypes.STRONG -> {
- withStyle(SpanStyle(fontWeight = FontWeight.Medium)) {
+ withStyle(SpanStyle(fontWeight = FontWeight.Medium, color = boldColor)) {
node.children
.drop(2)
.dropLast(2)
@@ -45,6 +51,7 @@ fun AnnotatedString.Builder.appendMarkdown(markdownText: String, node: ASTNode):
appendMarkdown(
markdownText = markdownText,
node = childNode,
+ boldColor = boldColor,
)
}
}
@@ -56,6 +63,19 @@ fun AnnotatedString.Builder.appendMarkdown(markdownText: String, node: ASTNode):
return this
}
+/**
+ * Parses [rawString] as markdown and returns an [AnnotatedString] where bold (`**...**`) sections
+ * are styled with [boldColor].
+ */
+@Composable
+fun formatAnnotatedWithBoldColor(rawString: String, boldColor: Color): AnnotatedString {
+ val markdownParser = rememberMarkdownParser()
+ val parsedTree = markdownParser.parse(MarkdownElementTypes.MARKDOWN_FILE, rawString, true)
+ return buildAnnotatedString {
+ appendMarkdown(markdownText = rawString, node = parsedTree, boldColor = boldColor)
+ }
+}
+
/**
* Appends a single space character to the [AnnotatedString.Builder].
*/
diff --git a/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/portfolio/PortfolioComponent.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/portfolio/PortfolioComponent.kt
new file mode 100644
index 0000000000..16b7faf015
--- /dev/null
+++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/portfolio/PortfolioComponent.kt
@@ -0,0 +1,14 @@
+package com.tangem.features.feed.components.portfolio
+
+import com.tangem.core.decompose.factory.ComponentFactory
+import com.tangem.core.ui.decompose.ComposableContentComponent
+import com.tangem.domain.models.currency.CryptoCurrency
+
+interface PortfolioComponent : ComposableContentComponent {
+
+ data class Params(
+ val id: CryptoCurrency.ID,
+ )
+
+ interface Factory : ComponentFactory
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt
index 752b079b3f..a51d8689f7 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt
@@ -5,6 +5,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.navigation.Route
+import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.features.feed.components.earn.DefaultEarnComponent
import com.tangem.features.feed.components.feed.DefaultFeedComponent
@@ -12,6 +13,7 @@ import com.tangem.features.feed.components.feed.DefaultFeedComponent.FeedParams
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
+import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent
import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent
import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent
import com.tangem.features.feed.components.news.list.DefaultNewsListComponent
@@ -22,12 +24,15 @@ import com.tangem.features.promobanners.api.PromoBannersBlockComponent
import kotlinx.serialization.Serializable
import javax.inject.Inject
+@Suppress("LongParameterList")
internal class FeedEntryChildFactory @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
private val portfolioComponentFactory: MarketsPortfolioComponent.Factory,
+ private val portfolioBlockComponentFactory: PortfolioBlockComponent.Factory,
private val addToPortfolioPreselectedDataComponent: AddToPortfolioPreselectedDataComponent.Factory,
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
+ private val designFeatureToggles: DesignFeatureToggles,
) {
@Serializable
@@ -77,6 +82,8 @@ internal class FeedEntryChildFactory @Inject constructor(
params = child.params,
analyticsEventHandler = analyticsEventHandler,
portfolioComponentFactory = portfolioComponentFactory,
+ portfolioBlockComponentFactory = portfolioBlockComponentFactory,
+ designFeatureToggles = designFeatureToggles,
)
}
is Child.TokenList -> {
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt
index 3f9c69cdc3..a9c1b09117 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt
@@ -21,6 +21,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
+import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.components.haze.hazeEffectTangem
@@ -35,6 +36,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
+import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent
import com.tangem.features.feed.model.market.details.MarketsTokenDetailsModel
import com.tangem.features.feed.model.market.details.analytics.MarketDetailsAnalyticsEvent
import com.tangem.features.feed.model.market.details.state.TokenNetworksState
@@ -49,7 +51,9 @@ internal class DefaultMarketsTokenDetailsComponent(
appComponentContext: AppComponentContext,
val params: Params,
analyticsEventHandler: AnalyticsEventHandler,
+ designFeatureToggles: DesignFeatureToggles,
portfolioComponentFactory: MarketsPortfolioComponent.Factory,
+ portfolioBlockComponentFactory: PortfolioBlockComponent.Factory,
) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext {
// applying l2 compatibility
@@ -61,25 +65,41 @@ internal class DefaultMarketsTokenDetailsComponent(
private val analyticsParams = params.analyticsParams
private val model: MarketsTokenDetailsModel = getOrCreateModel(updatedParams)
- private val portfolioComponent: MarketsPortfolioComponent? = if (updatedParams.shouldShowPortfolio) {
- portfolioComponentFactory.create(
- context = child("my_portfolio"),
- params = MarketsPortfolioComponent.Params(
- updatedParams.token,
- analyticsParams = analyticsParams?.source?.let { MarketsPortfolioComponent.AnalyticsParams(it) },
- ),
- )
- } else {
- null
- }
+ private val portfolioComponent: MarketsPortfolioComponent? =
+ if (updatedParams.shouldShowPortfolio && !designFeatureToggles.isRedesignEnabled) {
+ portfolioComponentFactory.create(
+ context = child("my_portfolio"),
+ params = MarketsPortfolioComponent.Params(
+ updatedParams.token,
+ analyticsParams = analyticsParams?.source?.let { MarketsPortfolioComponent.AnalyticsParams(it) },
+ ),
+ )
+ } else {
+ null
+ }
+
+ private val portfolioBlockComponent: PortfolioBlockComponent? =
+ if (updatedParams.shouldShowPortfolio && designFeatureToggles.isRedesignEnabled) {
+ portfolioBlockComponentFactory.create(
+ context = child("portfolio_block"),
+ params = PortfolioBlockComponent.Params(updatedParams.token),
+ )
+ } else {
+ null
+ }
init {
-
componentScope.launch(dispatchers.default) {
model.networksState.collectLatest { state ->
when (state) {
- is TokenNetworksState.NetworksAvailable -> portfolioComponent?.setTokenNetworks(state.networks)
- TokenNetworksState.NoNetworksAvailable -> portfolioComponent?.setNoNetworksAvailable()
+ is TokenNetworksState.NetworksAvailable -> {
+ portfolioBlockComponent?.setTokenNetworks(state.networks)
+ portfolioComponent?.setTokenNetworks(state.networks)
+ }
+ TokenNetworksState.NoNetworksAvailable -> {
+ portfolioBlockComponent?.setNoNetworksAvailable()
+ portfolioComponent?.setNoNetworksAvailable()
+ }
else -> {}
}
}
@@ -192,6 +212,11 @@ internal class DefaultMarketsTokenDetailsComponent(
component.Content(blockModifier)
}
},
+ portfolioFloatingBlock = portfolioBlockComponent?.let { component ->
+ { blockModifier ->
+ component.Content(blockModifier)
+ }
+ },
)
}
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/api/MarketsPortfolioComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/api/MarketsPortfolioComponent.kt
index 18aa2ea032..9e689855ae 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/api/MarketsPortfolioComponent.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/api/MarketsPortfolioComponent.kt
@@ -7,6 +7,7 @@ import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.markets.TokenMarketParams
import kotlinx.serialization.Serializable
+@Deprecated("Will be removed in favor of PortfolioComponent with redesigned UI")
@Stable
interface MarketsPortfolioComponent : ComposableContentComponent {
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/PortfolioBlockComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/PortfolioBlockComponent.kt
new file mode 100644
index 0000000000..22f52fccc7
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/PortfolioBlockComponent.kt
@@ -0,0 +1,115 @@
+package com.tangem.features.feed.components.market.details.portfolioblock
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.extensions.compose.subscribeAsState
+import com.arkivanov.decompose.router.slot.childSlot
+import com.arkivanov.decompose.router.slot.dismiss
+import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.context.childByContext
+import com.tangem.core.decompose.model.getOrCreateModel
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
+import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
+import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
+import com.tangem.core.ui.decompose.ComposableContentComponent
+import com.tangem.domain.markets.TokenMarketInfo
+import com.tangem.domain.markets.TokenMarketParams
+import com.tangem.features.feed.components.market.details.portfolioblock.model.PortfolioBlockModel
+import com.tangem.features.feed.components.market.details.portfolioblock.model.PortfolioBlockRoute
+import com.tangem.features.feed.components.market.details.portfolioblock.ui.PortfolioBlock
+import com.tangem.features.feed.components.portfolio.PortfolioComponent
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.serialization.Serializable
+
+@Stable
+internal class PortfolioBlockComponent @AssistedInject constructor(
+ @Assisted context: AppComponentContext,
+ @Assisted private val params: Params,
+) : ComposableContentComponent, AppComponentContext by context {
+
+ private val portfolioComponentFactory: PortfolioComponent.Factory = object : PortfolioComponent.Factory {
+ override fun create(context: AppComponentContext, params: PortfolioComponent.Params): PortfolioComponent {
+ TODO("STUB. Will be implemented")
+ }
+ }
+
+ @Serializable
+ data class Params(
+ val token: TokenMarketParams,
+ )
+
+ private val model: PortfolioBlockModel = getOrCreateModel(params)
+
+ private val bottomSheetSlot = childSlot(
+ source = model.bottomSheetNavigation,
+ serializer = PortfolioBlockRoute.serializer(),
+ handleBackButton = true,
+ childFactory = ::createBottomSheetChild,
+ )
+
+ fun setTokenNetworks(networks: List) {
+ model.setTokenNetworks(networks)
+ }
+
+ fun setNoNetworksAvailable() {
+ model.setNoNetworksAvailable()
+ }
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ val state by model.state.collectAsStateWithLifecycle()
+ val bottomSheet by bottomSheetSlot.subscribeAsState()
+
+ PortfolioBlock(modifier = modifier, state = state)
+ bottomSheet.child?.instance?.BottomSheet()
+ }
+
+ @Suppress("UnusedParameter")
+ private fun createBottomSheetChild(
+ config: PortfolioBlockRoute,
+ componentContext: ComponentContext,
+ ): ComposableBottomSheetComponent {
+ val currencyId = model.cryptoCurrencyIdState.value ?: return ComposableBottomSheetComponent.EMPTY
+ val portfolioComponent = portfolioComponentFactory.create(
+ context = childByContext(componentContext),
+ params = PortfolioComponent.Params(id = currencyId),
+ )
+ return PortfolioBottomSheetWrapper(
+ portfolioComponent = portfolioComponent,
+ onDismiss = { model.bottomSheetNavigation.dismiss() },
+ )
+ }
+
+ private class PortfolioBottomSheetWrapper(
+ private val portfolioComponent: PortfolioComponent,
+ private val onDismiss: () -> Unit,
+ ) : ComposableBottomSheetComponent {
+
+ override fun dismiss() = onDismiss()
+
+ @Composable
+ override fun BottomSheet() {
+ TangemBottomSheet(
+ config = TangemBottomSheetConfig(
+ isShown = true,
+ onDismissRequest = ::dismiss,
+ content = TangemBottomSheetConfigContent.Empty,
+ ),
+ ) {
+ portfolioComponent.Content(modifier = Modifier)
+ }
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(context: AppComponentContext, params: Params): PortfolioBlockComponent
+ }
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/di/PortfolioBlockModelModule.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/di/PortfolioBlockModelModule.kt
new file mode 100644
index 0000000000..d2009383c0
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/di/PortfolioBlockModelModule.kt
@@ -0,0 +1,20 @@
+package com.tangem.features.feed.components.market.details.portfolioblock.di
+
+import com.tangem.core.decompose.di.ModelComponent
+import com.tangem.core.decompose.model.Model
+import com.tangem.features.feed.components.market.details.portfolioblock.model.PortfolioBlockModel
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+@InstallIn(ModelComponent::class)
+internal interface PortfolioBlockModelModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(PortfolioBlockModel::class)
+ fun providePortfolioBlockModel(model: PortfolioBlockModel): Model
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockModel.kt
new file mode 100644
index 0000000000..48901a9e3c
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockModel.kt
@@ -0,0 +1,188 @@
+package com.tangem.features.feed.components.market.details.portfolioblock.model
+
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.SpanStyle
+import com.arkivanov.decompose.router.slot.SlotNavigation
+import com.arkivanov.decompose.router.slot.activate
+import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
+import com.tangem.common.getTotalFiatAmount
+import com.tangem.core.decompose.di.ModelScoped
+import com.tangem.core.decompose.model.Model
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.core.ui.components.currency.icon.CurrencyIconState
+import com.tangem.core.ui.format.bigdecimal.fiat
+import com.tangem.core.ui.format.bigdecimal.formatStyled
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.domain.account.models.AccountStatusList
+import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
+import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
+import com.tangem.domain.appcurrency.model.AppCurrency
+import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
+import com.tangem.domain.markets.TokenMarketInfo
+import com.tangem.domain.models.account.filterCryptoPortfolio
+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.isMultiCurrency
+import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
+import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent
+import com.tangem.features.feed.components.market.details.portfolioblock.ui.state.PortfolioBlockUM
+import com.tangem.features.feed.impl.R
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.*
+import java.math.BigDecimal
+import javax.inject.Inject
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@Stable
+@ModelScoped
+internal class PortfolioBlockModel @Inject constructor(
+ override val dispatchers: CoroutineDispatcherProvider,
+ paramsContainer: ParamsContainer,
+ private val allAccountSupplier: MultiAccountStatusListSupplier,
+ private val getUserWalletUseCase: GetUserWalletUseCase,
+ private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
+ private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
+) : Model() {
+
+ val state: StateFlow
+ field = MutableStateFlow(PortfolioBlockUM.Loading)
+
+ val bottomSheetNavigation: SlotNavigation = SlotNavigation()
+
+ val cryptoCurrencyIdState: StateFlow
+ field = MutableStateFlow(null)
+
+ private val params = paramsContainer.require()
+ private val currencyRawId: CryptoCurrency.RawID = params.token.id
+
+ private val tokenIcon: CurrencyIconState = CurrencyIconState.CoinIcon(
+ url = params.token.imageUrl,
+ fallbackResId = R.drawable.ic_custom_token_44,
+ isGrayscale = false,
+ shouldShowCustomBadge = false,
+ )
+
+ private val availableNetworks = MutableSharedFlow>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+
+ init {
+ combineData()
+ .onEach { state.value = it }
+ .flowOn(dispatchers.default)
+ .launchIn(modelScope)
+ }
+
+ fun setTokenNetworks(networks: List) {
+ availableNetworks.tryEmit(networks)
+ }
+
+ fun setNoNetworksAvailable() {
+ availableNetworks.tryEmit(emptyList())
+ }
+
+ private fun combineData(): Flow {
+ return availableNetworks.transformLatest { networks ->
+ if (networks.isEmpty()) {
+ emit(PortfolioBlockUM.Hidden)
+ } else {
+ emitAll(portfolioFlow().distinctUntilChanged())
+ }
+ }.distinctUntilChanged()
+ }
+
+ private fun portfolioFlow(): Flow {
+ val portfolioDataFlow = allAccountSupplier().flatMapLatest { accountStatusLists ->
+ val walletFlows = accountStatusLists.map { accountStatusList ->
+ getUserWalletUseCase.invokeFlow(accountStatusList.userWalletId)
+ .mapNotNull { it.getOrNull() }
+ .map { wallet ->
+ WalletPortfolio(
+ userWallet = wallet,
+ currencies = accountStatusList.filterByRawId(),
+ )
+ }
+ }
+ if (walletFlows.isEmpty()) {
+ flowOf(emptyList())
+ } else {
+ combine(walletFlows) { it.toList() }
+ }
+ }.distinctUntilChanged()
+
+ val settingsFlow = combine(
+ getSelectedAppCurrencyUseCase.invokeOrDefault(),
+ getBalanceHidingSettingsUseCase.isBalanceHidden(),
+ ) { appCurrency, isBalanceHidden ->
+ SettingsBox(appCurrency, isBalanceHidden)
+ }.distinctUntilChanged()
+
+ return combine(portfolioDataFlow, settingsFlow) { portfolios, settings ->
+ buildState(portfolios, settings)
+ }.distinctUntilChanged()
+ }
+
+ private fun AccountStatusList.filterByRawId(): List {
+ return accountStatuses.filterCryptoPortfolio().flatMap { accountStatus ->
+ accountStatus.tokenList.flattenCurrencies().filter { status ->
+ val statusRawId = status.currency.id.rawCurrencyId ?: return@filter false
+ getTokenIdIfL2Network(statusRawId.value) == currencyRawId.value
+ }
+ }
+ }
+
+ private fun buildState(portfolios: List, settings: SettingsBox): PortfolioBlockUM {
+ val allCurrencies = portfolios.flatMap { it.currencies }
+ val hasMultiCurrencyWallet = portfolios.any { it.userWallet.isMultiCurrency }
+
+ if (allCurrencies.isEmpty()) {
+ return if (hasMultiCurrencyWallet) {
+ PortfolioBlockUM.AddToken(
+ tokenIcon = tokenIcon,
+ onClick = { bottomSheetNavigation.activate(PortfolioBlockRoute) },
+ )
+ } else {
+ PortfolioBlockUM.Hidden
+ }
+ }
+
+ cryptoCurrencyIdState.update {
+ it ?: allCurrencies.first().currency.id
+ }
+
+ val totalFiat = allCurrencies.mapNotNull { it.getTotalFiatAmount() }
+ .fold(BigDecimal.ZERO, BigDecimal::add)
+
+ val formattedBalance = totalFiat.formatStyled {
+ fiat(
+ fiatCurrencyCode = settings.appCurrency.code,
+ fiatCurrencySymbol = settings.appCurrency.symbol,
+ spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
+ )
+ }
+
+ return PortfolioBlockUM.Content(
+ totalBalance = formattedBalance,
+ tokensInPortfolioCount = allCurrencies.size,
+ tokenIcon = tokenIcon,
+ tokenName = allCurrencies.first().currency.name,
+ tokenSymbol = allCurrencies.first().currency.symbol,
+ isBalanceHidden = settings.isBalanceHidden,
+ onClick = { bottomSheetNavigation.activate(PortfolioBlockRoute) },
+ )
+ }
+}
+
+private data class WalletPortfolio(
+ val userWallet: UserWallet,
+ val currencies: List,
+)
+
+private data class SettingsBox(
+ val appCurrency: AppCurrency,
+ val isBalanceHidden: Boolean,
+)
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockRoute.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockRoute.kt
new file mode 100644
index 0000000000..4c1f19107b
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockRoute.kt
@@ -0,0 +1,7 @@
+package com.tangem.features.feed.components.market.details.portfolioblock.model
+
+import com.tangem.core.decompose.navigation.Route
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal data object PortfolioBlockRoute : Route
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/ui/PortfolioBlock.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/ui/PortfolioBlock.kt
new file mode 100644
index 0000000000..9f7cb34c98
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/ui/PortfolioBlock.kt
@@ -0,0 +1,266 @@
+package com.tangem.features.feed.components.market.details.portfolioblock.ui
+
+import android.content.res.Configuration
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.EnterExitState
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.BottomSheetDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.components.BottomFade
+import com.tangem.core.ui.components.SpacerW
+import com.tangem.core.ui.components.currency.icon.CurrencyIcon
+import com.tangem.core.ui.components.currency.icon.CurrencyIconState
+import com.tangem.core.ui.ds.button.*
+import com.tangem.core.ui.ds.row.TangemRowContainer
+import com.tangem.core.ui.ds.row.TangemRowLayoutId
+import com.tangem.core.ui.extensions.formatAnnotatedWithBoldColor
+import com.tangem.core.ui.extensions.orMaskWithStars
+import com.tangem.core.ui.extensions.resolveAnnotatedReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.extensions.stringReference
+import com.tangem.core.ui.extensions.stringResourceSafe
+import com.tangem.core.ui.res.LocalWindowSize
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreviewRedesign
+import com.tangem.features.feed.components.market.details.portfolioblock.ui.state.PortfolioBlockUM
+import com.tangem.features.feed.impl.R
+
+@Composable
+internal fun PortfolioBlock(state: PortfolioBlockUM, modifier: Modifier = Modifier) {
+ val isVisible = state is PortfolioBlockUM.AddToken || state is PortfolioBlockUM.Content
+ val screenHeightPx = with(LocalDensity.current) { LocalWindowSize.current.height.toPx() }
+
+ Box(modifier = modifier) {
+ AnimatedVisibility(
+ visible = isVisible,
+ enter = fadeIn(animationSpec = tween(durationMillis = 300)),
+ ) {
+ BottomFade(
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.BottomCenter),
+ )
+ }
+
+ AnimatedVisibility(
+ visible = isVisible,
+ enter = fadeIn(animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)),
+ exit = fadeOut(animationSpec = tween(durationMillis = 300)),
+ ) {
+ val slideOffset by transition.animateFloat(
+ transitionSpec = {
+ tween(
+ durationMillis = 600,
+ easing = CubicBezierEasing(a = 0.83f, b = 0f, c = 0.17f, d = 1f),
+ )
+ },
+ label = "slideOffset",
+ ) { enterExitState ->
+ when (enterExitState) {
+ EnterExitState.PreEnter -> screenHeightPx
+ EnterExitState.Visible -> 0f
+ EnterExitState.PostExit -> 0f
+ }
+ }
+
+ Box(modifier = Modifier.graphicsLayer { translationY = slideOffset }) {
+ when (state) {
+ is PortfolioBlockUM.AddToken -> AddTokenBlock(state)
+ is PortfolioBlockUM.Content -> ContentBlock(state)
+ is PortfolioBlockUM.Hidden,
+ is PortfolioBlockUM.Loading,
+ -> Unit
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ContentBlock(state: PortfolioBlockUM.Content, modifier: Modifier = Modifier) {
+ FloatingCard(
+ modifier = modifier,
+ onClick = state.onClick,
+ ) {
+ TangemRowContainer(
+ contentPadding = PaddingValues(0.dp),
+ ) {
+ CurrencyIcon(
+ modifier = Modifier
+ .padding(end = TangemTheme.dimens2.x3)
+ .layoutId(TangemRowLayoutId.HEAD),
+ state = state.tokenIcon,
+ )
+
+ Text(
+ modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
+ text = state.tokenName,
+ style = TangemTheme.typography2.bodyMedium16,
+ color = TangemTheme.colors2.text.neutral.primary,
+ maxLines = 1,
+ )
+
+ Text(
+ modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
+ text = stringResourceSafe(R.string.markets_portfolio_block_subtitle),
+ style = TangemTheme.typography2.captionMedium12,
+ color = TangemTheme.colors2.text.neutral.secondary,
+ maxLines = 1,
+ )
+
+ Text(
+ modifier = Modifier.layoutId(TangemRowLayoutId.END_TOP),
+ text = state.totalBalance.orMaskWithStars(state.isBalanceHidden).resolveAnnotatedReference(),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ color = TangemTheme.colors2.text.neutral.primary,
+ style = TangemTheme.typography2.bodyMedium16,
+ )
+
+ Text(
+ modifier = Modifier.layoutId(TangemRowLayoutId.END_BOTTOM),
+ text = state.tokenSymbol,
+ style = TangemTheme.typography2.captionMedium12,
+ color = TangemTheme.colors2.text.neutral.secondary,
+ maxLines = 1,
+ )
+
+ TangemButton(
+ modifier = Modifier
+ .padding(start = TangemTheme.dimens2.x3)
+ .layoutId(TangemRowLayoutId.TAIL),
+ buttonUM = TangemButtonUM(
+ type = TangemButtonType.Secondary,
+ iconRes = R.drawable.ic_chevron_24,
+ shape = TangemButtonShape.Rounded,
+ size = TangemButtonSize.X9,
+ onClick = state.onClick,
+ ),
+ )
+ }
+ }
+}
+
+@Composable
+private fun AddTokenBlock(state: PortfolioBlockUM.AddToken, modifier: Modifier = Modifier) {
+ FloatingCard(
+ modifier = modifier,
+ onClick = state.onClick,
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ CurrencyIcon(state.tokenIcon)
+
+ SpacerW(TangemTheme.dimens2.x3)
+
+ Text(
+ text = formatAnnotatedWithBoldColor(
+ rawString = stringResourceSafe(R.string.markets_portfolio_block_add_token_title),
+ boldColor = TangemTheme.colors2.text.neutral.primary,
+ ),
+ style = TangemTheme.typography2.captionMedium12,
+ color = TangemTheme.colors2.text.neutral.secondary,
+ modifier = Modifier.weight(1f),
+ maxLines = 2,
+ )
+
+ SpacerW(TangemTheme.dimens2.x2)
+
+ TangemButton(
+ buttonUM = TangemButtonUM(
+ type = TangemButtonType.Secondary,
+ text = resourceReference(R.string.common_add),
+ shape = TangemButtonShape.Rounded,
+ size = TangemButtonSize.X9,
+ onClick = state.onClick,
+ ),
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun FloatingCard(modifier: Modifier = Modifier, onClick: () -> Unit = {}, content: @Composable () -> Unit) {
+ Box(
+ modifier = modifier
+ .navigationBarsPadding()
+ .fillMaxWidth()
+ .widthIn(max = BottomSheetDefaults.SheetMaxWidth)
+ .padding(
+ start = TangemTheme.dimens2.x2,
+ end = TangemTheme.dimens2.x2,
+ bottom = TangemTheme.dimens2.x2,
+ )
+ .clip(RoundedCornerShape(size = TangemTheme.dimens2.x5))
+ .background(
+ color = TangemTheme.colors2.surface.level3,
+ shape = RoundedCornerShape(size = TangemTheme.dimens2.x5),
+ )
+ .clickable(onClick = onClick)
+ .padding(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x3),
+ ) {
+ content()
+ }
+}
+
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun ContentPreview() {
+ TangemThemePreviewRedesign {
+ PortfolioBlock(
+ state = PortfolioBlockUM.Content(
+ totalBalance = stringReference("$1,234.56"),
+ tokensInPortfolioCount = 3,
+ tokenIcon = previewCoinIcon,
+ tokenName = "Bitcoin",
+ tokenSymbol = "BTC",
+ isBalanceHidden = false,
+ onClick = {},
+ ),
+ )
+ }
+}
+
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun AddTokenPreview() {
+ TangemThemePreviewRedesign {
+ PortfolioBlock(
+ state = PortfolioBlockUM.AddToken(
+ tokenIcon = previewCoinIcon,
+ onClick = {},
+ ),
+ )
+ }
+}
+
+private val previewCoinIcon
+ get() = CurrencyIconState.CoinIcon(
+ url = null,
+ fallbackResId = com.tangem.core.ui.R.drawable.img_polygon_22,
+ isGrayscale = false,
+ shouldShowCustomBadge = false,
+ )
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/ui/state/PortfolioBlockUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/ui/state/PortfolioBlockUM.kt
new file mode 100644
index 0000000000..f61b90aa05
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/ui/state/PortfolioBlockUM.kt
@@ -0,0 +1,27 @@
+package com.tangem.features.feed.components.market.details.portfolioblock.ui.state
+
+import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.components.currency.icon.CurrencyIconState
+import com.tangem.core.ui.extensions.TextReference
+
+@Immutable
+internal sealed class PortfolioBlockUM {
+
+ data object Loading : PortfolioBlockUM()
+ data object Hidden : PortfolioBlockUM()
+
+ data class AddToken(
+ val tokenIcon: CurrencyIconState,
+ val onClick: () -> Unit,
+ ) : PortfolioBlockUM()
+
+ data class Content(
+ val totalBalance: TextReference,
+ val tokensInPortfolioCount: Int,
+ val tokenIcon: CurrencyIconState,
+ val tokenName: String,
+ val tokenSymbol: String,
+ val isBalanceHidden: Boolean,
+ val onClick: () -> Unit,
+ ) : PortfolioBlockUM()
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/MarketsTokenDetailsContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/MarketsTokenDetailsContent.kt
index 48b2e71786..195e45cf2f 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/MarketsTokenDetailsContent.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/MarketsTokenDetailsContent.kt
@@ -12,16 +12,22 @@ import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.components.SpacerH4
@@ -64,6 +70,7 @@ internal fun MarketsTokenDetailsContent(
backgroundColor: Color,
modifier: Modifier = Modifier,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
+ portfolioFloatingBlock: @Composable ((Modifier) -> Unit)?,
) {
Content(
contentPadding = contentPadding,
@@ -71,6 +78,7 @@ internal fun MarketsTokenDetailsContent(
backgroundColor = backgroundColor,
state = state,
portfolioBlock = portfolioBlock,
+ portfolioFloatingBlock = portfolioFloatingBlock,
)
when (state.bottomSheetConfig.content) {
@@ -80,7 +88,7 @@ internal fun MarketsTokenDetailsContent(
}
}
-@Suppress("LongParameterList")
+@Suppress("LongParameterList", "LongMethod")
@Composable
private fun Content(
contentPadding: PaddingValues,
@@ -88,6 +96,7 @@ private fun Content(
backgroundColor: Color,
modifier: Modifier = Modifier,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
+ portfolioFloatingBlock: @Composable ((Modifier) -> Unit)?,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
val density = LocalDensity.current
@@ -97,54 +106,71 @@ private fun Content(
lazyListState = lazyListState,
onShouldShowPriceSubtitleChange = state.onShouldShowPriceSubtitleChange,
)
+ var bottomSpacing by remember { mutableStateOf(0.dp) }
- Column(
- modifier = modifier
+ Box(
+ modifier
.drawBehind { drawRect(backgroundColor) }
.fillMaxSize(),
) {
- SpacerH4()
+ Column {
+ SpacerH4()
- LazyColumn(
- state = lazyListState,
- contentPadding = PaddingValues(bottom = bottomBarHeight, top = contentPadding.calculateTopPadding()),
- ) {
- item("header") {
- Header(
- state = state,
- modifier = Modifier
- .padding(horizontal = TangemTheme.dimens.spacing16)
- .fillMaxWidth(),
- )
- }
- item { SpacerH16() }
- item("intervalSelector") {
- IntervalSelector(
- trendInterval = state.selectedInterval,
- onIntervalClick = state.onSelectedIntervalChange,
- isEnabled = state.body !is MarketsTokenDetailsUM.Body.Nothing,
- modifier = Modifier
- .padding(horizontal = TangemTheme.dimens.spacing16)
- .fillMaxWidth(),
- )
- }
- item { SpacerH32() }
- item("chart") {
- MarketTokenDetailsChart(
- modifier = Modifier.fillMaxWidth(),
- backgroundColor = backgroundColor,
- state = state.chartState,
- )
- }
- item { SpacerH16() }
+ LazyColumn(
+ state = lazyListState,
+ contentPadding = PaddingValues(bottom = bottomBarHeight, top = contentPadding.calculateTopPadding()),
+ ) {
+ item("header") {
+ Header(
+ state = state,
+ modifier = Modifier
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .fillMaxWidth(),
+ )
+ }
+ item { SpacerH16() }
+ item("intervalSelector") {
+ IntervalSelector(
+ trendInterval = state.selectedInterval,
+ onIntervalClick = state.onSelectedIntervalChange,
+ isEnabled = state.body !is MarketsTokenDetailsUM.Body.Nothing,
+ modifier = Modifier
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .fillMaxWidth(),
+ )
+ }
+ item { SpacerH32() }
+ item("chart") {
+ MarketTokenDetailsChart(
+ modifier = Modifier.fillMaxWidth(),
+ backgroundColor = backgroundColor,
+ state = state.chartState,
+ )
+ }
+ item { SpacerH16() }
- tokenMarketDetailsBody(
- state = state.body,
- portfolioBlock = portfolioBlock,
- relatedNews = state.relatedNews,
- isRedesignEnabled = isRedesignEnabled,
- )
+ tokenMarketDetailsBody(
+ state = state.body,
+ portfolioBlock = portfolioBlock,
+ relatedNews = state.relatedNews,
+ isRedesignEnabled = isRedesignEnabled,
+ )
+
+ item { SpacerH(bottomSpacing) }
+ }
}
+
+ portfolioFloatingBlock?.invoke(
+ Modifier
+ .onSizeChanged { size ->
+ bottomSpacing = if (size.height > 0) {
+ with(density) { size.height.toDp() + 16.dp }
+ } else {
+ 0.dp
+ }
+ }
+ .align(Alignment.BottomCenter),
+ )
}
}
@@ -404,6 +430,7 @@ private fun MarketsTokenDetailsContent_Preview(
state = params,
backgroundColor = TangemTheme.colors.background.tertiary,
portfolioBlock = {},
+ portfolioFloatingBlock = null,
contentPadding = PaddingValues(),
)
}