Updated on 2026-08-14
This commit is contained in:
commit
a22e77ebe8
669 changed files with 27695 additions and 7905 deletions
|
|
@ -17,9 +17,9 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.PreselectedMarketsInterval
|
||||
import com.tangem.domain.markets.PreselectedMarketsOrder
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.models.earn.PreselectedEarnType
|
||||
import com.tangem.domain.news.model.NewsListConfig
|
||||
|
|
@ -32,8 +32,6 @@ import com.tangem.features.feed.entry.components.FeedEntryComponent
|
|||
import com.tangem.features.feed.entry.components.FeedEntryRoute
|
||||
import com.tangem.features.feed.model.FeedEntryModel
|
||||
import com.tangem.features.feed.model.feed.FeedModelClickIntents
|
||||
import com.tangem.domain.markets.PreselectedMarketsInterval
|
||||
import com.tangem.domain.markets.PreselectedMarketsOrder
|
||||
import com.tangem.features.feed.model.market.list.state.MarketsListUM
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.EntryContent
|
||||
|
|
@ -199,26 +197,21 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val background = TangemTheme.colors.background.tertiary
|
||||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
|
||||
) {
|
||||
val bottomSheetState = remember {
|
||||
derivedStateOf { BottomSheetState.EXPANDED }
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
router.pop()
|
||||
}
|
||||
|
||||
EntryContent(
|
||||
bottomSheetState = bottomSheetState,
|
||||
stackState = stack.subscribeAsState(),
|
||||
onHeaderSizeChange = {},
|
||||
onExpandSheet = {},
|
||||
isOpenedInBottomSheet = false,
|
||||
)
|
||||
val bottomSheetState = remember {
|
||||
derivedStateOf { BottomSheetState.EXPANDED }
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
router.pop()
|
||||
}
|
||||
|
||||
EntryContent(
|
||||
bottomSheetState = bottomSheetState,
|
||||
stackState = stack.subscribeAsState(),
|
||||
onHeaderSizeChange = {},
|
||||
onExpandSheet = {},
|
||||
isOpenedInBottomSheet = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onChildBack() {
|
||||
|
|
|
|||
|
|
@ -18,19 +18,16 @@ import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListCo
|
|||
import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent
|
||||
import com.tangem.features.feed.components.news.list.DefaultNewsListComponent
|
||||
import com.tangem.features.feed.components.search.DefaultSearchComponent
|
||||
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
|
||||
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 addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
|
||||
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
|
||||
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -121,7 +118,6 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
params = FeedParams(feedClickIntents = feedEntryClickIntents),
|
||||
addToPortfolioComponentFactory = addToPortfolioComponentFactory,
|
||||
promoBannersBlockComponentFactory = promoBannersBlockComponentFactory,
|
||||
newPromoBannersFeatureToggles = newPromoBannersFeatureToggles,
|
||||
)
|
||||
}
|
||||
is Child.Earn -> {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.features.feed.components.earn
|
||||
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -11,9 +9,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
|
|
@ -39,7 +38,6 @@ import com.tangem.features.feed.components.feed.FeedBottomSheetRoute
|
|||
import com.tangem.features.feed.model.earn.EarnModel
|
||||
import com.tangem.features.feed.ui.components.FeedSearchBar
|
||||
import com.tangem.features.feed.ui.earn.EarnContent
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal class DefaultEarnComponent(
|
||||
|
|
@ -65,16 +63,6 @@ internal class DefaultEarnComponent(
|
|||
FeedSearchBar(
|
||||
isSearchBarClickable = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||
feedListSearchBar = state.feedListSearchBar,
|
||||
modifier = Modifier
|
||||
.drawBehind { drawRect(background) }
|
||||
.hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .55f,
|
||||
endIntensity = 0f,
|
||||
preferPerformance = true,
|
||||
easing = EaseOut,
|
||||
)
|
||||
},
|
||||
startContent = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
|
||||
|
|
@ -82,10 +70,8 @@ internal class DefaultEarnComponent(
|
|||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x11)
|
||||
.background(
|
||||
color = TangemTheme.colors2.button.backgroundSecondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.hazeEffectTangem { blurRadius = 8.dp }
|
||||
.clickableSingle(
|
||||
onClick = state.onBackClick,
|
||||
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import com.tangem.features.feed.model.feed.FeedComponentModel
|
|||
import com.tangem.features.feed.model.feed.FeedModelClickIntents
|
||||
import com.tangem.features.feed.ui.feed.FeedList
|
||||
import com.tangem.features.feed.ui.feed.FeedListHeader
|
||||
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||
|
||||
internal class DefaultFeedComponent(
|
||||
|
|
@ -32,13 +31,11 @@ internal class DefaultFeedComponent(
|
|||
private val params: FeedParams,
|
||||
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
|
||||
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
|
||||
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
|
||||
) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val feedComponentModel = getOrCreateModel<FeedComponentModel, FeedParams>(params = params)
|
||||
|
||||
private val promoBannersBlockComponent: PromoBannersBlockComponent? by lazy {
|
||||
if (!newPromoBannersFeatureToggles.isNewPromoBannersEnabled) return@lazy null
|
||||
private val promoBannersBlockComponent: PromoBannersBlockComponent by lazy {
|
||||
promoBannersBlockComponentFactory.create(
|
||||
context = child("promoBannersBlockComponent"),
|
||||
params = PromoBannersBlockComponent.Params(
|
||||
|
|
@ -72,7 +69,7 @@ internal class DefaultFeedComponent(
|
|||
) {
|
||||
val isExpanded = bottomSheetState.value == BottomSheetState.EXPANDED
|
||||
LaunchedEffect(isExpanded) {
|
||||
promoBannersBlockComponent?.setVisibleOnScreen(isExpanded)
|
||||
promoBannersBlockComponent.setVisibleOnScreen(isExpanded)
|
||||
}
|
||||
|
||||
LifecycleStartEffect(Unit) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.features.feed.components.market.details
|
||||
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -12,8 +10,10 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
|
|
@ -48,9 +48,9 @@ import com.tangem.features.feed.components.market.details.portfolioblock.Portfol
|
|||
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
|
||||
import com.tangem.features.feed.ui.LocalIsOpenedInBottomSheet
|
||||
import com.tangem.features.feed.ui.market.detailed.MarketsTokenDetailsContent
|
||||
import com.tangem.features.feed.ui.market.detailed.MarketsTokenDetailsTopBar
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -161,14 +161,6 @@ internal class DefaultMarketsTokenDetailsComponent(
|
|||
val background = LocalMainBottomSheetColor.current.value
|
||||
if (LocalRedesignEnabled.current) {
|
||||
TangemTopBar(
|
||||
modifier = Modifier.hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .55f,
|
||||
endIntensity = 0f,
|
||||
preferPerformance = true,
|
||||
easing = EaseOut,
|
||||
)
|
||||
},
|
||||
startContent = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
|
||||
|
|
@ -176,10 +168,8 @@ internal class DefaultMarketsTokenDetailsComponent(
|
|||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x11)
|
||||
.background(
|
||||
color = TangemTheme.colors2.button.backgroundSecondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.hazeEffectTangem { blurRadius = 8.dp }
|
||||
.clickableSingle(
|
||||
onClick = { params.onBackClicked() },
|
||||
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||
|
|
@ -194,10 +184,8 @@ internal class DefaultMarketsTokenDetailsComponent(
|
|||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x11)
|
||||
.background(
|
||||
color = TangemTheme.colors2.button.backgroundSecondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.hazeEffectTangem { blurRadius = 8.dp }
|
||||
.clickableSingle(
|
||||
onClick = state.onShareClick,
|
||||
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||
|
|
@ -205,7 +193,11 @@ internal class DefaultMarketsTokenDetailsComponent(
|
|||
.padding(TangemTheme.dimens2.x2_5),
|
||||
)
|
||||
},
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
type = if (LocalIsOpenedInBottomSheet.current) {
|
||||
TangemTopBarType.BottomSheet
|
||||
} else {
|
||||
TangemTopBarType.Default
|
||||
},
|
||||
)
|
||||
} else {
|
||||
MarketsTokenDetailsTopBar(
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
|
|
@ -34,7 +35,6 @@ import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
|||
import com.tangem.features.feed.ui.components.FeedSearchBar
|
||||
import com.tangem.features.feed.ui.market.list.MarketsList
|
||||
import com.tangem.features.feed.ui.market.list.TopBarWithSearch
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal class DefaultMarketsTokenListComponent(
|
||||
|
|
@ -54,21 +54,13 @@ internal class DefaultMarketsTokenListComponent(
|
|||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val bsState by bottomSheetState
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
|
||||
if (LocalRedesignEnabled.current) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
FeedSearchBar(
|
||||
isSearchBarClickable = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||
feedListSearchBar = state.feedListSearchBar,
|
||||
modifier = Modifier
|
||||
.drawBehind { drawRect(background) }
|
||||
.hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .55f,
|
||||
endIntensity = .2f,
|
||||
preferPerformance = true,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.background(background.copy(alpha = .95f)),
|
||||
startContent = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
|
||||
|
|
@ -76,10 +68,8 @@ internal class DefaultMarketsTokenListComponent(
|
|||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x11)
|
||||
.background(
|
||||
color = TangemTheme.colors2.button.backgroundSecondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.hazeEffectTangem { blurRadius = 8.dp }
|
||||
.clickableSingle(
|
||||
onClick = clickIntents.onBackClicked,
|
||||
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.features.feed.components.news.details
|
||||
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -11,8 +9,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
|
|
@ -32,9 +32,9 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.news.model.NewsListConfig
|
||||
import com.tangem.features.feed.model.news.details.NewsDetailsModel
|
||||
import com.tangem.features.feed.ui.LocalIsOpenedInBottomSheet
|
||||
import com.tangem.features.feed.ui.news.details.NewsDetailsContent
|
||||
import com.tangem.features.feed.ui.news.details.state.ArticlesStateUM
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal class DefaultNewsDetailsComponent(
|
||||
|
|
@ -50,15 +50,11 @@ internal class DefaultNewsDetailsComponent(
|
|||
val state by newsDetailsModel.state.collectAsStateWithLifecycle()
|
||||
if (LocalRedesignEnabled.current) {
|
||||
TangemTopBar(
|
||||
modifier = Modifier.hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .55f,
|
||||
endIntensity = 0f,
|
||||
preferPerformance = true,
|
||||
easing = EaseOut,
|
||||
)
|
||||
type = if (LocalIsOpenedInBottomSheet.current) {
|
||||
TangemTopBarType.BottomSheet
|
||||
} else {
|
||||
TangemTopBarType.Default
|
||||
},
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
startContent = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
|
||||
|
|
@ -66,10 +62,8 @@ internal class DefaultNewsDetailsComponent(
|
|||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x11)
|
||||
.background(
|
||||
color = TangemTheme.colors2.button.backgroundSecondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.hazeEffectTangem { blurRadius = 8.dp }
|
||||
.clickableSingle(
|
||||
onClick = state.onBackClick,
|
||||
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||
|
|
@ -84,10 +78,8 @@ internal class DefaultNewsDetailsComponent(
|
|||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x11)
|
||||
.background(
|
||||
color = TangemTheme.colors2.button.backgroundSecondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.hazeEffectTangem { blurRadius = 8.dp }
|
||||
.clickableSingle(
|
||||
onClick = state.onShareClick,
|
||||
enabled = bottomSheetState.value == BottomSheetState.EXPANDED &&
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
|
|
@ -31,8 +33,8 @@ import com.tangem.core.ui.res.LocalRedesignEnabled
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.news.model.NewsListConfig
|
||||
import com.tangem.features.feed.model.news.list.NewsListModel
|
||||
import com.tangem.features.feed.ui.LocalIsOpenedInBottomSheet
|
||||
import com.tangem.features.feed.ui.news.list.NewsListContent
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal class DefaultNewsListComponent(
|
||||
|
|
@ -48,16 +50,13 @@ internal class DefaultNewsListComponent(
|
|||
val state by newsListModel.state.collectAsStateWithLifecycle()
|
||||
if (LocalRedesignEnabled.current) {
|
||||
TangemTopBar(
|
||||
modifier = Modifier.hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .55f,
|
||||
endIntensity = .2f,
|
||||
preferPerformance = true,
|
||||
)
|
||||
backgroundColor = background
|
||||
},
|
||||
modifier = Modifier.background(background.copy(alpha = .95f)),
|
||||
title = resourceReference(R.string.common_news),
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
type = if (LocalIsOpenedInBottomSheet.current) {
|
||||
TangemTopBarType.BottomSheet
|
||||
} else {
|
||||
TangemTopBarType.Default
|
||||
},
|
||||
startContent = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
|
||||
|
|
@ -65,10 +64,8 @@ internal class DefaultNewsListComponent(
|
|||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x11)
|
||||
.background(
|
||||
color = TangemTheme.colors2.button.backgroundSecondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.hazeEffectTangem { blurRadius = 8.dp }
|
||||
.clickableSingle(
|
||||
onClick = state.onBackClick,
|
||||
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.features.feed.components.search
|
||||
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -13,7 +12,6 @@ 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.state.BottomSheetState
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
||||
import com.tangem.core.ui.ds.field.search.TangemFieldShape
|
||||
|
|
@ -23,9 +21,9 @@ import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
|||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.features.feed.model.search.SearchModel
|
||||
import com.tangem.features.feed.ui.LocalIsOpenedInBottomSheet
|
||||
import com.tangem.features.feed.ui.search.SearchContent
|
||||
import com.tangem.features.feed.ui.search.state.SearchCallbacks
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
|
||||
internal class DefaultSearchComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
|
|
@ -53,15 +51,11 @@ internal class DefaultSearchComponent(
|
|||
}
|
||||
|
||||
TangemTopBar(
|
||||
modifier = Modifier.hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .55f,
|
||||
endIntensity = 0f,
|
||||
preferPerformance = true,
|
||||
easing = EaseOut,
|
||||
)
|
||||
type = if (LocalIsOpenedInBottomSheet.current) {
|
||||
TangemTopBarType.BottomSheet
|
||||
} else {
|
||||
TangemTopBarType.Default
|
||||
},
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
reserveSlotSpace = false,
|
||||
content = {
|
||||
TangemSearchField(
|
||||
|
|
@ -110,7 +104,6 @@ internal class DefaultSearchComponent(
|
|||
params = SearchTokenSelectorComponent.Params(
|
||||
entries = config.entries,
|
||||
appCurrency = config.appCurrency,
|
||||
isBalanceHidden = config.isBalanceHidden,
|
||||
onTokenSelected = config.onTokenSelected,
|
||||
onDismiss = config.onDismiss,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ internal sealed interface SearchBottomSheetRoute {
|
|||
data class TokenSelector(
|
||||
val entries: List<UserAssetEntry>,
|
||||
val appCurrency: AppCurrency,
|
||||
val isBalanceHidden: Boolean,
|
||||
val onTokenSelected: (UserAssetEntry) -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
) : SearchBottomSheetRoute
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ internal class SearchTokenSelectorComponent @AssistedInject constructor(
|
|||
data class Params(
|
||||
val entries: List<UserAssetEntry>,
|
||||
val appCurrency: AppCurrency,
|
||||
val isBalanceHidden: Boolean,
|
||||
val onTokenSelected: (UserAssetEntry) -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
getUserCountryUseCase: GetUserCountryUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
|
||||
addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
|
||||
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
|
||||
|
|
@ -247,6 +247,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
val state = MutableStateFlow(
|
||||
MarketsTokenDetailsUM(
|
||||
tokenName = params.token.name,
|
||||
symbol = params.token.symbol,
|
||||
priceText = params.token.tokenQuotes.currentPrice.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = currentAppCurrency.value.code,
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ internal class NewsListModel @Inject constructor(
|
|||
|
||||
init {
|
||||
observeNewsList()
|
||||
batchFlowManager.reload()
|
||||
loadCategories()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -229,7 +229,6 @@ internal class SearchModel @Inject constructor(
|
|||
SearchBottomSheetRoute.TokenSelector(
|
||||
entries = grouped.entries,
|
||||
appCurrency = currentAppCurrency.value,
|
||||
isBalanceHidden = isBalanceHidden.value,
|
||||
onTokenSelected = ::onTokenSelectedFromGroup,
|
||||
onDismiss = { bottomSheetNavigation.dismiss() },
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,22 +2,37 @@ package com.tangem.features.feed.model.search
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
|
||||
import com.tangem.features.feed.components.search.SearchTokenSelectorComponent
|
||||
import com.tangem.features.feed.model.search.state.TokenSelectorStateController
|
||||
import com.tangem.features.feed.model.search.state.transformers.BuildTokenSelectorSectionsTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class SearchTokenSelectorModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
private val stateController: TokenSelectorStateController,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val walletIconUMConverter: WalletIconUMConverter,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<SearchTokenSelectorComponent.Params>()
|
||||
|
|
@ -26,11 +41,35 @@ internal class SearchTokenSelectorModel @Inject constructor(
|
|||
get() = stateController.uiState
|
||||
|
||||
init {
|
||||
val requiredWalletIds = params.entries.map { it.userWalletId }.toSet()
|
||||
val walletIconsFlow = userWalletsListRepository.userWallets
|
||||
.filterNotNull()
|
||||
.map { wallets ->
|
||||
wallets
|
||||
.filter { it.walletId in requiredWalletIds }
|
||||
.associate { wallet ->
|
||||
wallet.walletId to walletIconUMConverter.convert(getWalletIconUseCase(wallet))
|
||||
}
|
||||
}
|
||||
|
||||
modelScope.launch(dispatchers.default) {
|
||||
combine(
|
||||
walletIconsFlow,
|
||||
getBalanceHidingSettingsUseCase.isBalanceHidden(),
|
||||
::Pair,
|
||||
).collect { (walletIcons, isBalanceHidden) ->
|
||||
rebuildSections(isBalanceHidden, walletIcons)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun rebuildSections(isBalanceHidden: Boolean, walletIcons: Map<UserWalletId, DeviceIconUM>) {
|
||||
stateController.update(
|
||||
BuildTokenSelectorSectionsTransformer(
|
||||
entries = params.entries,
|
||||
appCurrency = params.appCurrency,
|
||||
isBalanceHidden = params.isBalanceHidden,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
walletIcons = walletIcons,
|
||||
onTokenSelected = params.onTokenSelected,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@ import com.tangem.common.ui.account.toUM
|
|||
import com.tangem.common.ui.markets.tokenselector.AccountHeaderData
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorSectionUM
|
||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class BuildTokenSelectorSectionsTransformer(
|
||||
private val entries: List<UserAssetEntry>,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val walletIcons: Map<UserWalletId, DeviceIconUM>,
|
||||
private val onTokenSelected: (UserAssetEntry) -> Unit,
|
||||
) : TokenSelectorUMTransformer {
|
||||
|
||||
|
|
@ -30,11 +33,12 @@ internal class BuildTokenSelectorSectionsTransformer(
|
|||
val byWallet = entries.groupBy { it.userWalletId }
|
||||
val shouldShowWalletHeaders = byWallet.size > 1
|
||||
|
||||
for ((_, walletEntries) in byWallet) {
|
||||
for ((walletId, walletEntries) in byWallet) {
|
||||
if (shouldShowWalletHeaders) {
|
||||
sections.add(
|
||||
TokenSelectorSectionUM.WalletHeader(
|
||||
walletName = walletEntries.first().userWalletName,
|
||||
deviceIcon = walletIcons[walletId] ?: DeviceIconUM.Stub(cardsCount = 1),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,17 +18,29 @@ import com.arkivanov.decompose.extensions.compose.stack.Children
|
|||
import com.arkivanov.decompose.router.stack.ChildStack
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.components.topFade
|
||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
||||
import com.tangem.core.ui.extensions.conditionalCompose
|
||||
import com.tangem.core.ui.res.LocalHazeState
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
import com.tangem.features.feed.components.FeedEntryChildFactory
|
||||
import com.tangem.features.feed.ui.utils.FadeConstants.BASE_FADE_LEVEL
|
||||
import com.tangem.features.feed.ui.utils.contentFeedEntryStackAnimation
|
||||
import com.tangem.features.feed.ui.utils.topBarFeedEntryAnimatedContentTransitionSpec
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
|
||||
/**
|
||||
|
||||
* When the value is `null` (default), the fade covers `topBarHeight`. A screen with sticky chrome
|
||||
* (e.g. category chips, sort options) can set this to `0.dp` to disable the centralized fade and
|
||||
* apply its own fade on its inner haze source covering the full sticky header (topbar + chrome).
|
||||
*/
|
||||
internal val LocalContentTopFadeHeightOverride = compositionLocalOf<MutableState<Dp?>?> { null }
|
||||
|
||||
internal val LocalIsOpenedInBottomSheet = staticCompositionLocalOf { true }
|
||||
|
||||
@OptIn(ExperimentalDecomposeApi::class)
|
||||
@Composable
|
||||
internal fun EntryContent(
|
||||
|
|
@ -38,22 +50,22 @@ internal fun EntryContent(
|
|||
onExpandSheet: () -> Unit,
|
||||
isOpenedInBottomSheet: Boolean,
|
||||
) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
EntryContentV2(
|
||||
bottomSheetState = bottomSheetState,
|
||||
stackState = stackState,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
onExpandSheet = onExpandSheet,
|
||||
isOpenedInBottomSheet = isOpenedInBottomSheet,
|
||||
)
|
||||
} else {
|
||||
EntryContentV1(
|
||||
bottomSheetState = bottomSheetState,
|
||||
stackState = stackState,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
onExpandSheet = onExpandSheet,
|
||||
isOpenedInBottomSheet = isOpenedInBottomSheet,
|
||||
)
|
||||
CompositionLocalProvider(LocalIsOpenedInBottomSheet provides isOpenedInBottomSheet) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
EntryContentV2(
|
||||
bottomSheetState = bottomSheetState,
|
||||
stackState = stackState,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
onExpandSheet = onExpandSheet,
|
||||
)
|
||||
} else {
|
||||
EntryContentV1(
|
||||
bottomSheetState = bottomSheetState,
|
||||
stackState = stackState,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
onExpandSheet = onExpandSheet,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,11 +75,11 @@ private fun EntryContentV1(
|
|||
stackState: State<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>>,
|
||||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
onExpandSheet: () -> Unit,
|
||||
isOpenedInBottomSheet: Boolean,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val stackAnimation = remember { contentFeedEntryStackAnimation() }
|
||||
val isOpenedInBottomSheet = LocalIsOpenedInBottomSheet.current
|
||||
|
||||
Surface(contentColor = background) {
|
||||
Scaffold(
|
||||
|
|
@ -125,75 +137,120 @@ private fun EntryContentV2(
|
|||
stackState: State<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>>,
|
||||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
onExpandSheet: () -> Unit,
|
||||
isOpenedInBottomSheet: Boolean,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val animationContent = remember { contentFeedEntryStackAnimation() }
|
||||
val animationAppBar = remember(stackState) { topBarFeedEntryAnimatedContentTransitionSpec(stackState) }
|
||||
var topBarHeight by remember { mutableStateOf(0.dp) }
|
||||
val hazeState = rememberHazeState()
|
||||
val fadeHeightOverride = remember { mutableStateOf<Dp?>(null) }
|
||||
val statusBarInset = if (LocalIsOpenedInBottomSheet.current) {
|
||||
0.dp
|
||||
} else {
|
||||
WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
}
|
||||
val effectiveTopBarHeight = topBarHeight + statusBarInset
|
||||
val effectiveFadeHeight = fadeHeightOverride.value ?: effectiveTopBarHeight
|
||||
val isTopFadeSolid = LocalIsOpenedInBottomSheet.current && bottomSheetState.value == BottomSheetState.COLLAPSED
|
||||
|
||||
Surface(contentColor = background) {
|
||||
CompositionLocalProvider(LocalHazeState provides hazeState) {
|
||||
Surface(color = background, contentColor = background) {
|
||||
CompositionLocalProvider(
|
||||
LocalHazeState provides hazeState,
|
||||
LocalContentTopFadeHeightOverride provides fadeHeightOverride,
|
||||
LocalBottomSheetTopFadeSolid provides isTopFadeSolid,
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Children(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
stack = stackState.value,
|
||||
animation = animationContent,
|
||||
) { child ->
|
||||
child.instance.Content(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.conditionalCompose(
|
||||
condition = !isOpenedInBottomSheet,
|
||||
modifier = {
|
||||
padding(top = topBarHeight)
|
||||
},
|
||||
)
|
||||
.hazeSourceTangem(zIndex = 0f, state = hazeState),
|
||||
contentPadding = PaddingValues(top = topBarHeight),
|
||||
bottomSheetState = bottomSheetState,
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.then(
|
||||
if (!isOpenedInBottomSheet) {
|
||||
Modifier.statusBarsPadding()
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
if (coordinates.size.height > 0) {
|
||||
with(density) {
|
||||
val height = coordinates.size.height.toDp()
|
||||
topBarHeight = height
|
||||
onHeaderSizeChange(height)
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = stackState.value.active,
|
||||
transitionSpec = animationAppBar,
|
||||
contentKey = { it.key },
|
||||
label = "FeedEntryAppBar",
|
||||
) { state ->
|
||||
state.instance.Title(bottomSheetState)
|
||||
}
|
||||
CollapsedTitleClickOverlay(
|
||||
bottomSheetState = bottomSheetState,
|
||||
onExpandSheet = onExpandSheet,
|
||||
)
|
||||
}
|
||||
ContentBlock(
|
||||
bottomSheetState = bottomSheetState,
|
||||
effectiveFadeHeight = effectiveFadeHeight,
|
||||
stackState = stackState,
|
||||
topBarHeight = effectiveTopBarHeight,
|
||||
)
|
||||
TitleBlock(
|
||||
bottomSheetState = bottomSheetState,
|
||||
stackState = stackState,
|
||||
onTopBarHeightChang = { dp ->
|
||||
onHeaderSizeChange(dp)
|
||||
topBarHeight = dp
|
||||
},
|
||||
onExpandSheet = onExpandSheet,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.TitleBlock(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
stackState: State<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>>,
|
||||
onTopBarHeightChang: (Dp) -> Unit,
|
||||
onExpandSheet: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val animationAppBar = remember(stackState) { topBarFeedEntryAnimatedContentTransitionSpec(stackState) }
|
||||
val isOpenedInBottomSheet = LocalIsOpenedInBottomSheet.current
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.align(Alignment.TopStart)
|
||||
.then(if (!isOpenedInBottomSheet) Modifier.statusBarsPadding() else Modifier),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.onGloballyPositioned { coordinates ->
|
||||
if (coordinates.size.height > 0) {
|
||||
with(density) {
|
||||
val height = coordinates.size.height.toDp()
|
||||
onTopBarHeightChang(height)
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = stackState.value.active,
|
||||
transitionSpec = animationAppBar,
|
||||
contentKey = { it.key },
|
||||
label = "FeedEntryAppBar",
|
||||
) { state ->
|
||||
state.instance.Title(bottomSheetState)
|
||||
}
|
||||
CollapsedTitleClickOverlay(
|
||||
bottomSheetState = bottomSheetState,
|
||||
onExpandSheet = onExpandSheet,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.ContentBlock(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
effectiveFadeHeight: Dp,
|
||||
stackState: State<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>>,
|
||||
topBarHeight: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val animationContent = remember { contentFeedEntryStackAnimation() }
|
||||
|
||||
Children(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
stack = stackState.value,
|
||||
animation = animationContent,
|
||||
) { child ->
|
||||
child.instance.Content(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.hazeSourceTangem(zIndex = 0f, state = LocalHazeState.current)
|
||||
.topFade(
|
||||
height = effectiveFadeHeight,
|
||||
color = feedTopFadeColor(TangemTheme.colors2.surface.level2.copy(BASE_FADE_LEVEL)),
|
||||
solidStop = feedTopFadeSolidStop(),
|
||||
),
|
||||
contentPadding = PaddingValues(top = topBarHeight),
|
||||
bottomSheetState = bottomSheetState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.CollapsedTitleClickOverlay(bottomSheetState: State<BottomSheetState>, onExpandSheet: () -> Unit) {
|
||||
if (bottomSheetState.value == BottomSheetState.COLLAPSED) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.features.feed.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.features.feed.ui.utils.FadeConstants.FIRST_STEP
|
||||
import com.tangem.features.feed.ui.utils.FadeConstants.FIRST_STEP_FADE_LEVEL
|
||||
|
||||
/**
|
||||
* When `true`, top fade areas render as a solid color (no gradient) — used while the wallet
|
||||
* bottom sheet is collapsed so the peek header matches [LocalMainBottomSheetColor].
|
||||
*/
|
||||
internal val LocalBottomSheetTopFadeSolid = compositionLocalOf { false }
|
||||
|
||||
private const val EXPANDED_TOP_FADE_SOLID_STOP = 0.6f
|
||||
private const val COLLAPSED_TOP_FADE_SOLID_STOP = 1f
|
||||
|
||||
@Composable
|
||||
internal fun feedTopFadeSolidStop(): Float {
|
||||
return if (LocalBottomSheetTopFadeSolid.current) {
|
||||
COLLAPSED_TOP_FADE_SOLID_STOP
|
||||
} else {
|
||||
EXPANDED_TOP_FADE_SOLID_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun feedTopFadeColor(defaultFadeColor: Color): Color {
|
||||
return if (LocalBottomSheetTopFadeSolid.current) {
|
||||
LocalMainBottomSheetColor.current.value
|
||||
} else {
|
||||
defaultFadeColor
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun feedTopFadeColorStops(defaultFadeColor: Color): Array<Pair<Float, Color>> {
|
||||
if (LocalBottomSheetTopFadeSolid.current) {
|
||||
val solidColor = LocalMainBottomSheetColor.current.value
|
||||
return arrayOf(0f to solidColor, 1f to solidColor)
|
||||
}
|
||||
return arrayOf(
|
||||
0f to defaultFadeColor,
|
||||
FIRST_STEP to defaultFadeColor.copy(FIRST_STEP_FADE_LEVEL),
|
||||
1f to Color.Transparent,
|
||||
)
|
||||
}
|
||||
|
|
@ -16,12 +16,14 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.ui.LocalIsOpenedInBottomSheet
|
||||
import com.tangem.features.feed.ui.feed.state.FeedListSearchBar
|
||||
|
||||
@Composable
|
||||
|
|
@ -96,7 +98,7 @@ private fun FeedSearchBarV2(
|
|||
modifier = modifier,
|
||||
startContent = startContent,
|
||||
endContent = endContent,
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
type = if (LocalIsOpenedInBottomSheet.current) TangemTopBarType.BottomSheet else TangemTopBarType.Default,
|
||||
reserveSlotSpace = false,
|
||||
content = {
|
||||
Row(
|
||||
|
|
@ -107,7 +109,9 @@ private fun FeedSearchBarV2(
|
|||
end = if (endContent != null) TangemTheme.dimens2.x3 else 0.dp,
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.background(color = TangemTheme.colors2.button.backgroundSecondary)
|
||||
.hazeEffectTangem {
|
||||
blurRadius = 8.dp
|
||||
}
|
||||
.conditional(condition = isSearchBarClickable) {
|
||||
clickable(onClick = feedListSearchBar.onBarClick)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,13 +266,13 @@ private fun LazyListScope.bestOpportunitiesItemsV2(state: EarnBestOpportunitiesU
|
|||
is EarnBestOpportunitiesUM.Empty -> {
|
||||
item(key = "best_opportunities_empty") {
|
||||
SpacerH(12.dp)
|
||||
BestOpportunitiesEmpty() // TODO in [REDACTED_TASK_KEY]
|
||||
BestOpportunitiesEmpty()
|
||||
}
|
||||
}
|
||||
is EarnBestOpportunitiesUM.EmptyFiltered -> {
|
||||
item(key = "best_opportunities_empty_filtered") {
|
||||
SpacerH(12.dp)
|
||||
BestOpportunitiesEmptyFiltered(onClearFilterClick = state.onClearFilterClick) // TODO in [REDACTED_TASK_KEY]
|
||||
BestOpportunitiesEmptyFiltered(onClearFilterClick = state.onClearFilterClick)
|
||||
}
|
||||
}
|
||||
is EarnBestOpportunitiesUM.Content -> {
|
||||
|
|
@ -305,7 +305,7 @@ private fun LazyListScope.bestOpportunitiesItemsV2(state: EarnBestOpportunitiesU
|
|||
color = TangemTheme.colors2.surface.level3,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
|
||||
)
|
||||
.padding(vertical = 142.dp, horizontal = 114.dp),
|
||||
.padding(vertical = 142.dp, horizontal = 16.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
UnableToLoadData(onRetryClick = state.onRetryClicked)
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ private fun BestOpportunitiesEmptyV2(modifier: Modifier = Modifier) {
|
|||
Text(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x8),
|
||||
text = stringResourceSafe(R.string.earn_empty),
|
||||
style = TangemTheme.typography2.bodyRegular14,
|
||||
style = TangemTheme.typography2.subheadlineMedium14,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ private fun BestOpportunitiesEmptyFilteredV2(onClearFilterClick: () -> Unit, mod
|
|||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.earn_no_results),
|
||||
style = TangemTheme.typography2.bodyRegular14,
|
||||
style = TangemTheme.typography2.subheadlineMedium14,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
)
|
||||
SpacerH(TangemTheme.dimens2.x2)
|
||||
|
|
|
|||
|
|
@ -175,13 +175,15 @@ private fun NetworksTypesBlock(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
TangemCheckbox(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.layoutId(layoutId = TangemRowLayoutId.TAIL),
|
||||
isChecked = item.isSelected,
|
||||
onCheckedChange = { onOptionClick(item) },
|
||||
)
|
||||
if (item.isSelected) {
|
||||
TangemCheckbox(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.layoutId(layoutId = TangemRowLayoutId.TAIL),
|
||||
isChecked = true,
|
||||
onCheckedChange = { onOptionClick(item) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -233,13 +235,15 @@ private fun SpecificNetworksBlock(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
TangemCheckbox(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.layoutId(layoutId = TangemRowLayoutId.TAIL),
|
||||
isChecked = item.isSelected,
|
||||
onCheckedChange = { onOptionClick(item) },
|
||||
)
|
||||
if (item.isSelected) {
|
||||
TangemCheckbox(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.layoutId(layoutId = TangemRowLayoutId.TAIL),
|
||||
isChecked = true,
|
||||
onCheckedChange = { onOptionClick(item) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,8 @@ private fun EarnListItemV2(item: EarnListItemUM, modifier: Modifier = Modifier)
|
|||
tangemIconUM = TangemIconUM.Currency(item.currencyIconState),
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.HEAD)
|
||||
.padding(end = TangemTheme.dimens2.x2),
|
||||
.padding(end = TangemTheme.dimens2.x2)
|
||||
.size(TangemTheme.dimens2.x10),
|
||||
)
|
||||
|
||||
TokenTitle(
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.ds.opportunities.OpportunitiesBG
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.opportunities.OpportunitiesBG
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.*
|
||||
|
|
@ -53,10 +53,10 @@ private fun MostlyUsedCardV2(item: EarnListItemUM, onClick: () -> Unit, modifier
|
|||
) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
CurrencyIcon(
|
||||
modifier = Modifier.size(32.dp),
|
||||
state = item.currencyIconState,
|
||||
shouldDisplayNetwork = true,
|
||||
networkBadgeSize = 12.dp,
|
||||
networkBadgeSize = TangemTheme.dimens2.x4,
|
||||
iconSize = TangemTheme.dimens2.x10,
|
||||
networkBadgeBackground = TangemTheme.colors.background.action,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.features.feed.ui.feed
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
|
|
@ -16,9 +15,7 @@ import androidx.compose.ui.platform.testTag
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.conditionalCompose
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -31,7 +28,6 @@ import com.tangem.features.feed.ui.feed.preview.FeedListPreviewDataProvider.crea
|
|||
import com.tangem.features.feed.ui.feed.state.FeedListSearchBar
|
||||
import com.tangem.features.feed.ui.feed.state.FeedListUM
|
||||
import com.tangem.features.feed.ui.feed.state.GlobalFeedState
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
|
||||
@Composable
|
||||
internal fun FeedListHeader(
|
||||
|
|
@ -39,26 +35,10 @@ internal fun FeedListHeader(
|
|||
feedListSearchBar: FeedListSearchBar,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
FeedSearchBar(
|
||||
isSearchBarClickable = isSearchBarClickable,
|
||||
feedListSearchBar = feedListSearchBar,
|
||||
modifier = modifier
|
||||
.drawBehind { drawRect(background) }
|
||||
.conditionalCompose(
|
||||
condition = LocalRedesignEnabled.current,
|
||||
modifier = {
|
||||
hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .75f,
|
||||
endIntensity = 0f,
|
||||
preferPerformance = true,
|
||||
easing = EaseOut,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
.testTag(SEARCH_BAR),
|
||||
modifier = modifier.testTag(SEARCH_BAR),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,12 +118,7 @@ private fun Content(
|
|||
contentPadding = PaddingValues(bottom = bottomBarHeight, top = contentPadding.calculateTopPadding()),
|
||||
) {
|
||||
item("header") {
|
||||
Header(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
Header(state = state)
|
||||
}
|
||||
item {
|
||||
if (isRedesignEnabled) SpacerH(TangemTheme.dimens2.x3) else SpacerH16()
|
||||
|
|
@ -202,15 +197,33 @@ internal fun MarketsTokenDetailsTopBar(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Header(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier) {
|
||||
private fun Header(state: MarketsTokenDetailsUM) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
HeaderV2(
|
||||
modifier = Modifier
|
||||
.padding(TangemTheme.dimens2.x4)
|
||||
.fillMaxWidth(),
|
||||
state = state,
|
||||
)
|
||||
} else {
|
||||
HeaderV1(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HeaderV1(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
TokenPriceText(
|
||||
TokenPriceTextV1(
|
||||
price = state.priceText,
|
||||
priceAnnotated = state.priceAnnotated,
|
||||
triggerPriceChange = state.triggerPriceChange,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
|
||||
|
|
@ -240,23 +253,55 @@ private fun Header(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier)
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenPriceText(
|
||||
price: String,
|
||||
triggerPriceChange: StateEvent<PriceChangeType>,
|
||||
priceAnnotated: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
TokenPriceTextV2(
|
||||
priceAnnotated = priceAnnotated,
|
||||
triggerPriceChange = triggerPriceChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
TokenPriceTextV1(
|
||||
price = price,
|
||||
triggerPriceChange = triggerPriceChange,
|
||||
modifier = modifier,
|
||||
private fun HeaderV2(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
) {
|
||||
Text(
|
||||
text = state.tokenName,
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
Text(
|
||||
text = state.symbol,
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
)
|
||||
}
|
||||
SpacerH(TangemTheme.dimens2.x1)
|
||||
TokenPriceTextV2(
|
||||
priceAnnotated = state.priceAnnotated,
|
||||
triggerPriceChange = state.triggerPriceChange,
|
||||
)
|
||||
SpacerH(TangemTheme.dimens2.x4)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1)) {
|
||||
Text(
|
||||
text = state.dateTimeText.resolveReference(),
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
if (state.priceChangePercentText != null) {
|
||||
PriceChangeInPercent(
|
||||
valueInPercent = state.priceChangePercentText,
|
||||
type = state.priceChangeType,
|
||||
textStyle = TangemTheme.typography2.captionMedium12,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
SpacerW4()
|
||||
CoinIcon(
|
||||
modifier = Modifier.requiredSize(70.dp),
|
||||
url = state.iconUrl,
|
||||
alpha = 1f,
|
||||
colorFilter = null,
|
||||
fallbackResId = R.drawable.ic_custom_token_44,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -323,9 +368,9 @@ private fun TokenPriceTextV2(
|
|||
text = priceAnnotated.resolveAnnotatedReference(),
|
||||
modifier = modifier,
|
||||
color = color.value,
|
||||
autoSize = TextAutoSize.StepBased(maxFontSize = TangemTheme.typography2.headingBold34.fontSize),
|
||||
autoSize = TextAutoSize.StepBased(maxFontSize = TangemTheme.typography2.titleRegular44.fontSize),
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography2.headingBold34,
|
||||
style = TangemTheme.typography2.titleRegular44,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.shape.CircleShape
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -49,6 +50,7 @@ import com.tangem.core.ui.extensions.*
|
|||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.ui.market.detailed.state.ExchangeItemUM
|
||||
import com.tangem.features.feed.ui.market.detailed.state.ExchangesBottomSheetContent
|
||||
|
|
@ -273,7 +275,7 @@ private fun ErrorV2(content: ExchangesBottomSheetContent.Error, modifier: Modifi
|
|||
text = stringResourceSafe(id = content.message),
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography2.bodyRegular14,
|
||||
style = TangemTheme.typography2.subheadlineMedium14,
|
||||
)
|
||||
|
||||
SpacerH(8.dp)
|
||||
|
|
@ -369,6 +371,7 @@ private fun ExchangeItemRowPlaceholder(modifier: Modifier = Modifier) {
|
|||
.layoutId(TangemRowLayoutId.HEAD),
|
||||
)
|
||||
RectangleShimmer(
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
modifier = Modifier
|
||||
.width(108.dp)
|
||||
.height(20.dp)
|
||||
|
|
@ -376,6 +379,7 @@ private fun ExchangeItemRowPlaceholder(modifier: Modifier = Modifier) {
|
|||
.layoutId(TangemRowLayoutId.START_TOP),
|
||||
)
|
||||
RectangleShimmer(
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
modifier = Modifier
|
||||
.width(52.dp)
|
||||
.height(16.dp)
|
||||
|
|
@ -383,6 +387,7 @@ private fun ExchangeItemRowPlaceholder(modifier: Modifier = Modifier) {
|
|||
.layoutId(TangemRowLayoutId.START_BOTTOM),
|
||||
)
|
||||
RectangleShimmer(
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
modifier = Modifier
|
||||
.width(106.dp)
|
||||
.height(20.dp)
|
||||
|
|
@ -390,6 +395,7 @@ private fun ExchangeItemRowPlaceholder(modifier: Modifier = Modifier) {
|
|||
.layoutId(TangemRowLayoutId.END_TOP),
|
||||
)
|
||||
RectangleShimmer(
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
modifier = Modifier
|
||||
.width(52.dp)
|
||||
.height(16.dp)
|
||||
|
|
@ -428,6 +434,25 @@ private fun Preview_ExchangesBottomSheet(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ExchangesBottomSheetV2(
|
||||
@PreviewParameter(ExchangesBottomSheetContentProvider::class) content: ExchangesBottomSheetContent,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
CompositionLocalProvider(LocalRedesignEnabled provides true) {
|
||||
ExchangesBottomSheet(
|
||||
config = TangemBottomSheetConfig(
|
||||
onDismissRequest = {},
|
||||
content = content,
|
||||
isShown = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ExchangesBottomSheetContentProvider : CollectionPreviewParameterProvider<ExchangesBottomSheetContent>(
|
||||
listOf(
|
||||
ExchangesBottomSheetContent.Loading(exchangesCount = 13),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ internal object MarketsTokenDetailsPreview {
|
|||
|
||||
val loadingState = MarketsTokenDetailsUM(
|
||||
tokenName = "Token Name",
|
||||
symbol = "USDT",
|
||||
priceText = "$0.00000000324",
|
||||
dateTimeText = stringReference("Today"),
|
||||
priceChangePercentText = "52.00%",
|
||||
|
|
@ -55,6 +56,7 @@ internal object MarketsTokenDetailsPreview {
|
|||
|
||||
val contentState = MarketsTokenDetailsUM(
|
||||
tokenName = "Token Name",
|
||||
symbol = "USDT",
|
||||
priceText = "$0.00000000324",
|
||||
dateTimeText = stringReference("Today"),
|
||||
priceChangePercentText = "52.00%",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import java.math.BigDecimal
|
|||
|
||||
internal data class MarketsTokenDetailsUM(
|
||||
val tokenName: String,
|
||||
val symbol: String,
|
||||
val priceText: String,
|
||||
val priceAnnotated: TextReference,
|
||||
val iconUrl: String?,
|
||||
|
|
|
|||
|
|
@ -28,11 +28,9 @@ import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
|||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
import com.tangem.core.ui.components.fields.TangemSearchBarDefaults
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalHazeState
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -41,10 +39,11 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.model.market.list.state.*
|
||||
import com.tangem.features.feed.ui.feed.state.FeedListSearchBar
|
||||
import com.tangem.features.feed.ui.feedTopFadeColorStops
|
||||
import com.tangem.features.feed.ui.market.list.components.MarketsListLazyColumn
|
||||
import com.tangem.features.feed.ui.market.list.components.MarketsListSortByBottomSheet
|
||||
import com.tangem.features.feed.ui.market.list.components.Options
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import com.tangem.features.feed.ui.utils.FadeConstants.BASE_FADE_LEVEL
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
|
|
@ -108,19 +107,16 @@ internal fun TopBarWithSearch(
|
|||
@Composable
|
||||
internal fun MarketsList(contentPadding: PaddingValues, state: MarketsListUM, modifier: Modifier = Modifier) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
// should use here new overrided haze state cause on level upper already applied
|
||||
CompositionLocalProvider(LocalHazeState provides rememberHazeState()) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.drawBehind { drawRect(background) },
|
||||
) {
|
||||
Content(state = state, contentPadding = contentPadding)
|
||||
}
|
||||
MarketsListSortByBottomSheet(config = state.sortByBottomSheet)
|
||||
KeyboardEvents(isSortByBottomSheetShown = state.sortByBottomSheet.isShown)
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.drawBehind { drawRect(background) },
|
||||
) {
|
||||
Content(state = state, contentPadding = contentPadding)
|
||||
}
|
||||
MarketsListSortByBottomSheet(config = state.sortByBottomSheet)
|
||||
KeyboardEvents(isSortByBottomSheetShown = state.sortByBottomSheet.isShown)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
|
|
@ -205,21 +201,26 @@ private fun ColumnScope.ContentV2(contentPadding: PaddingValues, state: MarketsL
|
|||
val scrolledState = remember { mutableStateOf(false) }
|
||||
var optionsHeight by remember { mutableStateOf(0.dp) }
|
||||
val density = LocalDensity.current
|
||||
val fadeColor = TangemTheme.colors2.surface.level2.copy(BASE_FADE_LEVEL)
|
||||
val topPadding = contentPadding.calculateTopPadding()
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
ItemsList(
|
||||
topContentPadding = contentPadding.calculateTopPadding() + optionsHeight,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.hazeSourceTangem(zIndex = 1f),
|
||||
modifier = Modifier.align(Alignment.TopStart),
|
||||
topContentPadding = topPadding + optionsHeight,
|
||||
scrolledState = scrolledState,
|
||||
isInSearchMode = state.isInSearchMode,
|
||||
state = state.list,
|
||||
)
|
||||
TopFade(
|
||||
modifier = Modifier.padding(top = topPadding),
|
||||
colorStops = feedTopFadeColorStops(fadeColor),
|
||||
height = TangemTheme.dimens2.x4 + optionsHeight,
|
||||
)
|
||||
Options(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.padding(bottom = TangemTheme.dimens2.x4, top = contentPadding.calculateTopPadding())
|
||||
.padding(bottom = TangemTheme.dimens2.x4, top = topPadding)
|
||||
.padding(horizontal = TangemTheme.dimens2.x4)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
if (coordinates.size.height > 0) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.features.feed.ui.market.list.components
|
||||
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
|
|
@ -20,14 +19,12 @@ import com.tangem.core.ui.ds.image.TangemIconUM
|
|||
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.model.market.list.state.MarketsListUM
|
||||
import com.tangem.features.feed.model.market.list.state.SortByMenuUM
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import com.tangem.core.ui.ds.button.TangemButtonIconPosition as RedesignTangemButtonIconPosition
|
||||
|
||||
|
|
@ -120,7 +117,6 @@ private fun OptionsV2(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var isShowDropdownMenu by rememberSaveable { mutableStateOf(false) }
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
|
||||
val segmentItems = remember {
|
||||
persistentListOf(
|
||||
|
|
@ -147,16 +143,7 @@ private fun OptionsV2(
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Max)
|
||||
.hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .2f,
|
||||
endIntensity = 0f,
|
||||
easing = EaseOut,
|
||||
preferPerformance = true,
|
||||
)
|
||||
backgroundColor = background
|
||||
},
|
||||
.height(IntrinsicSize.Max),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
PrimaryInverseTangemButton(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,25 @@
|
|||
package com.tangem.features.feed.ui.market.list.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
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.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.ds.contextmenu.TangemContextMenu
|
||||
import com.tangem.core.ui.ds.contextmenu.TangemContextMenuCheckboxItem
|
||||
import com.tangem.core.ui.extensions.clickableSingle
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.model.market.list.state.SortByMenuUM
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
|
||||
|
|
@ -22,15 +36,61 @@ internal fun SortByMenu(
|
|||
offset = DpOffset.Zero,
|
||||
modifier = modifier,
|
||||
) {
|
||||
SortByTypeUM.entries.fastForEach { sortType ->
|
||||
TangemContextMenuCheckboxItem(
|
||||
title = sortType.text,
|
||||
isChecked = sortMenuUM.selectedOption == sortType,
|
||||
onClick = {
|
||||
sortMenuUM.onOptionClicked(sortType)
|
||||
onDropdownDismiss()
|
||||
},
|
||||
)
|
||||
SortByTypeUM.entries.fastForEachIndexed { index, sortType ->
|
||||
Column {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.widthIn(238.dp)
|
||||
.clickableSingle(
|
||||
onClick = {
|
||||
sortMenuUM.onOptionClicked(sortType)
|
||||
onDropdownDismiss()
|
||||
},
|
||||
)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens2.x5,
|
||||
horizontal = TangemTheme.dimens2.x4,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = sortType.text.resolveReference(),
|
||||
style = TangemTheme.typography2.headingSemibold17,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
)
|
||||
if (sortMenuUM.selectedOption == sortType) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(TangemTheme.dimens2.x0_5)
|
||||
.size(TangemTheme.dimens2.x5)
|
||||
.background(
|
||||
color = TangemTheme.colors2.graphic.neutral.primary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(
|
||||
ImageVector.vectorResource(R.drawable.ic_check_default_24),
|
||||
),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors2.graphic.neutral.primaryInverted,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.padding(TangemTheme.dimens2.x0_5)
|
||||
.size(TangemTheme.dimens2.x4),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index < SortByTypeUM.entries.size - 1) {
|
||||
HorizontalDivider(
|
||||
thickness = 0.5.dp,
|
||||
color = TangemTheme.colors2.border.neutral.quaternary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.features.feed.ui.news.list
|
||||
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
|
|
@ -15,20 +14,23 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.TopFade
|
||||
import com.tangem.core.ui.components.chip.Chip
|
||||
import com.tangem.core.ui.components.chip.entity.ChipUM
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemTab
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
|
||||
import com.tangem.features.feed.ui.feedTopFadeColorStops
|
||||
import com.tangem.features.feed.ui.news.list.components.NewsListLazyColumn
|
||||
import com.tangem.features.feed.ui.news.list.state.NewsListState
|
||||
import com.tangem.features.feed.ui.news.list.state.NewsListUM
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import com.tangem.features.feed.ui.utils.FadeConstants.BASE_FADE_LEVEL
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
|
||||
|
|
@ -38,6 +40,7 @@ internal fun NewsListContent(contentPadding: PaddingValues, state: NewsListUM, m
|
|||
NewsListContentV2(
|
||||
contentPadding = contentPadding,
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
NewsListContentV1(
|
||||
|
|
@ -87,50 +90,48 @@ internal fun NewsListContentV1(contentPadding: PaddingValues, state: NewsListUM,
|
|||
}
|
||||
|
||||
@Composable
|
||||
internal fun NewsListContentV2(contentPadding: PaddingValues, state: NewsListUM) {
|
||||
internal fun NewsListContentV2(contentPadding: PaddingValues, state: NewsListUM, modifier: Modifier = Modifier) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val lazyListState = rememberLazyListState()
|
||||
val chipsListState = rememberLazyListState()
|
||||
var chipsHeight by remember { mutableStateOf(0.dp) }
|
||||
val density = LocalDensity.current
|
||||
val topPadding = contentPadding.calculateTopPadding()
|
||||
val fadeColor = TangemTheme.colors2.surface.level2.copy(BASE_FADE_LEVEL)
|
||||
|
||||
ScrollChipsToSelected(state = state, chipsListState = chipsListState)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(background),
|
||||
) {
|
||||
NewsListLazyColumn(
|
||||
topContentPadding = contentPadding.calculateTopPadding() + 16.dp + chipsHeight,
|
||||
modifier = Modifier
|
||||
.hazeSourceTangem(zIndex = 0f)
|
||||
.align(Alignment.TopStart),
|
||||
topContentPadding = topPadding + TangemTheme.dimens2.x4 + chipsHeight,
|
||||
modifier = Modifier.align(Alignment.TopStart),
|
||||
newsListState = state.newsListState,
|
||||
listOfArticles = state.listOfArticles,
|
||||
lazyListState = lazyListState,
|
||||
onArticleClick = state.onArticleClick,
|
||||
)
|
||||
|
||||
TopFade(
|
||||
modifier = Modifier.padding(top = topPadding),
|
||||
colorStops = feedTopFadeColorStops(fadeColor),
|
||||
height = TangemTheme.dimens2.x4 + chipsHeight,
|
||||
)
|
||||
|
||||
LazyRow(
|
||||
state = chipsListState,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.padding(top = contentPadding.calculateTopPadding(), bottom = TangemTheme.dimens2.x4)
|
||||
.padding(top = topPadding, bottom = TangemTheme.dimens2.x4)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
if (coordinates.size.height > 0) {
|
||||
with(density) {
|
||||
chipsHeight = coordinates.size.height.toDp()
|
||||
}
|
||||
}
|
||||
}
|
||||
.hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .2f,
|
||||
endIntensity = 0f,
|
||||
easing = EaseOut,
|
||||
preferPerformance = true,
|
||||
)
|
||||
backgroundColor = background
|
||||
},
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.features.feed.ui.utils
|
||||
|
||||
internal object FadeConstants {
|
||||
const val BASE_FADE_LEVEL = .95f
|
||||
const val FIRST_STEP = .8f
|
||||
const val FIRST_STEP_FADE_LEVEL = .7f
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorSectionUM
|
||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||
import io.mockk.*
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
|
@ -215,7 +216,10 @@ class BuildTokenSelectorSectionsTransformerTest {
|
|||
|
||||
val prevStateWithSections = TokenSelectorContentUM(
|
||||
sections = persistentListOf(
|
||||
TokenSelectorSectionUM.WalletHeader(walletName = "Old Wallet"),
|
||||
TokenSelectorSectionUM.WalletHeader(
|
||||
walletName = "Old Wallet",
|
||||
deviceIcon = DeviceIconUM.Stub(cardsCount = 1),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -236,6 +240,7 @@ class BuildTokenSelectorSectionsTransformerTest {
|
|||
entries = entries,
|
||||
appCurrency = appCurrency,
|
||||
isBalanceHidden = false,
|
||||
walletIcons = emptyMap(),
|
||||
onTokenSelected = onTokenSelected,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue