From 7a197ec6c95d24359eac25b7fcde129b76c8476b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 6 Apr 2026 09:49:59 +0200 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Fade.kt | 22 +- ...sableModularBottomSheetContentComponent.kt | 5 +- .../domain/search/model/SearchResult.kt | 3 - .../search/usecase/GetSearchResultsUseCase.kt | 37 +-- .../components/earn/DefaultEarnComponent.kt | 22 +- .../components/feed/DefaultFeedComponent.kt | 12 +- .../DefaultMarketsTokenDetailsComponent.kt | 19 +- .../list/DefaultMarketsTokenListComponent.kt | 22 +- .../details/DefaultNewsDetailsComponent.kt | 20 +- .../news/list/DefaultNewsListComponent.kt | 19 +- .../search/DefaultSearchComponent.kt | 19 +- .../features/feed/model/search/SearchModel.kt | 16 +- .../tangem/features/feed/ui/EntryContent.kt | 133 +++++++++-- .../features/feed/ui/earn/EarnContent.kt | 18 +- .../tangem/features/feed/ui/feed/FeedList.kt | 35 ++- .../detailed/MarketsTokenDetailsContent.kt | 6 +- .../feed/ui/market/list/MarketsList.kt | 13 +- .../ui/news/details/NewsDetailsContent.kt | 12 +- .../news/details/components/ArticleDetail.kt | 217 +++++++++--------- .../components/NewsDetailsPlaceholder.kt | 17 +- .../feed/ui/news/list/NewsListContent.kt | 6 +- .../features/feed/ui/search/SearchContent.kt | 15 +- .../ui/search/preview/SearchContentPreview.kt | 2 + .../utils/EntryContentAnimationTransitions.kt | 44 ++-- 24 files changed, 499 insertions(+), 235 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Fade.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Fade.kt index f6e54c8b3b..fcc03b10e6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Fade.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Fade.kt @@ -12,9 +12,11 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.haze.hazeEffectTangem +import com.tangem.core.ui.res.LocalHazeState import com.tangem.core.ui.res.LocalPowerSavingState import com.tangem.core.ui.res.TangemTheme import dev.chrisbanes.haze.HazeProgressive +import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeStyle import dev.chrisbanes.haze.HazeTint @@ -62,7 +64,11 @@ fun BottomFade(gradientBrush: Brush, modifier: Modifier = Modifier) { * but with a vertical blur. */ @Composable -fun BottomFadeWithBlur(backgroundColor: Color, modifier: Modifier = Modifier) { +fun BottomFadeWithBlur( + backgroundColor: Color, + modifier: Modifier = Modifier, + hazeState: HazeState = LocalHazeState.current, +) { val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } val isPowerSavingState by LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsState() @@ -76,19 +82,9 @@ fun BottomFadeWithBlur(backgroundColor: Color, modifier: Modifier = Modifier) { .fillMaxWidth() .height(TangemTheme.dimens.size100 + bottomBarHeight) .hazeEffectTangem( - style = HazeStyle( - blurRadius = 20.dp, - tint = HazeTint( - brush = Brush.verticalGradient( - colors = listOf( - Color.Transparent, - backgroundColor, - ), - ), - ), - backgroundColor = Color.Transparent, - ), + state = hazeState, ) { + blurRadius = 20.dp progressive = HazeProgressive.verticalGradient( startIntensity = 0f, endIntensity = 1f, diff --git a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularBottomSheetContentComponent.kt b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularBottomSheetContentComponent.kt index 64495d533c..0200c621bc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularBottomSheetContentComponent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularBottomSheetContentComponent.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.decompose +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State @@ -27,7 +28,9 @@ interface ComposableModularBottomSheetContentComponent { * Renders the main content of the bottom sheet. * @param bottomSheetState The current state of the bottom sheet. Useful for tracking visibility * (e.g., for analytics or lifecycle effects when the sheet is [BottomSheetState.EXPANDED]). + * @param contentPadding Padding to apply as inner scroll offset in scrollable containers, + * allowing content to scroll under an overlaying top bar. Defaults to no padding. */ @Composable - fun Content(bottomSheetState: State, modifier: Modifier) + fun Content(bottomSheetState: State, contentPadding: PaddingValues, modifier: Modifier) } \ No newline at end of file diff --git a/domain/search/src/main/java/com/tangem/domain/search/model/SearchResult.kt b/domain/search/src/main/java/com/tangem/domain/search/model/SearchResult.kt index 784f334bdb..98a25e4996 100644 --- a/domain/search/src/main/java/com/tangem/domain/search/model/SearchResult.kt +++ b/domain/search/src/main/java/com/tangem/domain/search/model/SearchResult.kt @@ -1,10 +1,7 @@ package com.tangem.domain.search.model -import com.tangem.domain.markets.TokenMarket - data class SearchResult( val textHints: List, val recentTokens: List, val userAssets: List, - val marketTokens: List, ) \ No newline at end of file diff --git a/domain/search/src/main/java/com/tangem/domain/search/usecase/GetSearchResultsUseCase.kt b/domain/search/src/main/java/com/tangem/domain/search/usecase/GetSearchResultsUseCase.kt index 87e48a0215..b6167208a2 100644 --- a/domain/search/src/main/java/com/tangem/domain/search/usecase/GetSearchResultsUseCase.kt +++ b/domain/search/src/main/java/com/tangem/domain/search/usecase/GetSearchResultsUseCase.kt @@ -3,7 +3,6 @@ package com.tangem.domain.search.usecase import com.tangem.domain.account.models.AccountStatusList import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier import com.tangem.domain.common.wallets.UserWalletsListRepository -import com.tangem.domain.markets.TokenMarket import com.tangem.domain.models.account.filterCryptoPortfolio import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -13,15 +12,14 @@ import com.tangem.domain.search.model.UserAssetSearchEntry import com.tangem.domain.search.repository.SearchRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map /** * Primary search use case that produces [SearchResult] based on the current query. * * Behavior depends on the query: * - **Empty query** — returns search history: text hints and recently viewed tokens. - * - **Non-empty query** — performs the search across all unlocked user wallets, - * matching currencies by name or symbol, and combines the results with externally provided market tokens. + * - **Non-empty query** — performs the search across all unlocked user wallets, matching currencies by name or symbol. * * @property searchRepository local search history storage * @property multiAccountStatusListSupplier supplier for loaded account status lists across all wallets @@ -37,16 +35,12 @@ class GetSearchResultsUseCase( * Produces a [Flow] of [SearchResult] for the given [query]. * * @param query the search query string; blank means "show history" - * @param marketTokens external flow of market token search results (provided by presentation layer) */ - operator fun invoke( - query: String, - marketTokens: Flow> = flowOf(emptyList()), - ): Flow { + operator fun invoke(query: String): Flow { return if (query.isBlank()) { observeHistory() } else { - searchAssets(query, marketTokens) + observeUserAssets(query) } } @@ -59,26 +53,11 @@ class GetSearchResultsUseCase( textHints = hints, recentTokens = tokens, userAssets = emptyList(), - marketTokens = emptyList(), ) } } - private fun searchAssets(query: String, marketTokens: Flow>): Flow { - return combine( - observeUserAssets(query), - marketTokens, - ) { userAssets, markets -> - SearchResult( - textHints = emptyList(), - recentTokens = emptyList(), - userAssets = userAssets, - marketTokens = markets, - ) - } - } - - private fun observeUserAssets(query: String): Flow> { + private fun observeUserAssets(query: String): Flow { val lowerQuery = query.lowercase() return combine( multiAccountStatusListSupplier(), @@ -94,6 +73,12 @@ class GetSearchResultsUseCase( statusLists .filter { it.userWalletId in unlockedWallets } .flatMap { statusList -> extractMatchingAssets(statusList, unlockedWallets, lowerQuery) } + }.map { userAssets -> + SearchResult( + textHints = emptyList(), + recentTokens = emptyList(), + userAssets = userAssets, + ) } } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/earn/DefaultEarnComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/earn/DefaultEarnComponent.kt index b37c5b7dc7..ac8f5fffdc 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/earn/DefaultEarnComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/earn/DefaultEarnComponent.kt @@ -1,6 +1,8 @@ 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 import androidx.compose.foundation.shape.CircleShape @@ -23,6 +25,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM 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.extensions.clickableSingle @@ -35,6 +38,7 @@ import com.tangem.features.feed.components.market.details.portfolio.add.AddToPor 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 internal class DefaultEarnComponent( appComponentContext: AppComponentContext, @@ -59,7 +63,16 @@ internal class DefaultEarnComponent( FeedSearchBar( isSearchBarClickable = bottomSheetState.value == BottomSheetState.EXPANDED, feedListSearchBar = state.feedListSearchBar, - modifier = Modifier.drawBehind { drawRect(background) }, + 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), @@ -93,11 +106,16 @@ internal class DefaultEarnComponent( } @Composable - override fun Content(bottomSheetState: State, modifier: Modifier) { + override fun Content( + bottomSheetState: State, + contentPadding: PaddingValues, + modifier: Modifier, + ) { val bottomSheet by bottomSheetSlot.subscribeAsState() val state by earnModel.state.collectAsStateWithLifecycle() EarnContent( + contentPadding = contentPadding, state = state, modifier = modifier, ) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt index bd940eae65..4f06480af4 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt @@ -1,5 +1,6 @@ package com.tangem.features.feed.components.feed +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.getValue @@ -18,14 +19,14 @@ import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.decompose.EmptyComposableBottomSheetComponent -import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles -import com.tangem.features.promobanners.api.PromoBannersBlockComponent import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent.Params 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( appComponentContext: AppComponentContext, @@ -64,7 +65,11 @@ internal class DefaultFeedComponent( } @Composable - override fun Content(bottomSheetState: State, modifier: Modifier) { + override fun Content( + bottomSheetState: State, + contentPadding: PaddingValues, + modifier: Modifier, + ) { LifecycleStartEffect(Unit) { feedComponentModel.isVisibleOnScreen.value = true onStopOrDispose { @@ -78,6 +83,7 @@ internal class DefaultFeedComponent( modifier = modifier, state = state, promoBannersBlockComponent = promoBannersBlockComponent, + contentPadding = contentPadding, ) bottomSheet.child?.instance?.BottomSheet() } 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 dcf08d159a..3f9c69cdc3 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 @@ -1,6 +1,8 @@ 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 import androidx.compose.foundation.shape.CircleShape @@ -21,6 +23,7 @@ import com.tangem.core.decompose.context.child import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.R import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +import com.tangem.core.ui.components.haze.hazeEffectTangem import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.ds.topbar.TangemTopBar import com.tangem.core.ui.ds.topbar.TangemTopBarType @@ -37,6 +40,7 @@ import com.tangem.features.feed.model.market.details.analytics.MarketDetailsAnal import com.tangem.features.feed.model.market.details.state.TokenNetworksState 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 @@ -101,6 +105,14 @@ 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), @@ -153,7 +165,11 @@ internal class DefaultMarketsTokenDetailsComponent( } @Composable - override fun Content(bottomSheetState: State, modifier: Modifier) { + override fun Content( + bottomSheetState: State, + contentPadding: PaddingValues, + modifier: Modifier, + ) { LifecycleStartEffect(Unit) { model.isVisibleOnScreen.value = true onStopOrDispose { @@ -167,6 +183,7 @@ internal class DefaultMarketsTokenDetailsComponent( } MarketsTokenDetailsContent( + contentPadding = contentPadding, modifier = modifier, backgroundColor = LocalMainBottomSheetColor.current.value, state = state, diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/list/DefaultMarketsTokenListComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/list/DefaultMarketsTokenListComponent.kt index 81f649ec89..0fd788caa3 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/list/DefaultMarketsTokenListComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/list/DefaultMarketsTokenListComponent.kt @@ -1,6 +1,8 @@ package com.tangem.features.feed.components.market.list +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 import androidx.compose.foundation.shape.CircleShape @@ -19,6 +21,7 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.R import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +import com.tangem.core.ui.components.haze.hazeEffectTangem import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.extensions.clickableSingle import com.tangem.core.ui.res.LocalMainBottomSheetColor @@ -31,6 +34,7 @@ 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( @@ -56,7 +60,16 @@ internal class DefaultMarketsTokenListComponent( FeedSearchBar( isSearchBarClickable = bottomSheetState.value == BottomSheetState.EXPANDED, feedListSearchBar = state.feedListSearchBar, - modifier = Modifier.drawBehind { drawRect(background) }, + 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), @@ -87,7 +100,11 @@ internal class DefaultMarketsTokenListComponent( } @Composable - override fun Content(bottomSheetState: State, modifier: Modifier) { + override fun Content( + bottomSheetState: State, + contentPadding: PaddingValues, + modifier: Modifier, + ) { LifecycleStartEffect(Unit) { model.isVisibleOnScreen.value = true onStopOrDispose { @@ -103,6 +120,7 @@ internal class DefaultMarketsTokenListComponent( } MarketsList( + contentPadding = contentPadding, modifier = modifier, state = state, ) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/details/DefaultNewsDetailsComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/details/DefaultNewsDetailsComponent.kt index 7b0b6465db..47732db33e 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/details/DefaultNewsDetailsComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/details/DefaultNewsDetailsComponent.kt @@ -1,6 +1,8 @@ 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 import androidx.compose.foundation.shape.CircleShape @@ -18,6 +20,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +import com.tangem.core.ui.components.haze.hazeEffectTangem import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.ds.topbar.TangemTopBar import com.tangem.core.ui.ds.topbar.TangemTopBarType @@ -31,6 +34,7 @@ import com.tangem.domain.news.model.NewsListConfig import com.tangem.features.feed.model.news.details.NewsDetailsModel 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( @@ -46,6 +50,14 @@ 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 = TangemTopBarType.BottomSheet, startContent = { Icon( @@ -88,7 +100,6 @@ internal class DefaultNewsDetailsComponent( } else { TangemTopAppBar( containerColor = background, - title = null, startButton = TopAppBarButtonUM.Icon( iconRes = R.drawable.ic_back_24, onClicked = state.onBackClick, @@ -105,11 +116,16 @@ internal class DefaultNewsDetailsComponent( } @Composable - override fun Content(bottomSheetState: State, modifier: Modifier) { + override fun Content( + bottomSheetState: State, + contentPadding: PaddingValues, + modifier: Modifier, + ) { val state by newsDetailsModel.state.collectAsStateWithLifecycle() NewsDetailsContent( state = state, modifier = modifier, + contentPadding = contentPadding, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt index 13505b2b0a..58a30e4a3a 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt @@ -1,6 +1,8 @@ package com.tangem.features.feed.components.news.list +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 import androidx.compose.foundation.shape.CircleShape @@ -18,6 +20,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +import com.tangem.core.ui.components.haze.hazeEffectTangem import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.ds.topbar.TangemTopBar import com.tangem.core.ui.ds.topbar.TangemTopBarType @@ -30,6 +33,7 @@ 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.news.list.NewsListContent +import dev.chrisbanes.haze.HazeProgressive import kotlinx.serialization.Serializable internal class DefaultNewsListComponent( @@ -45,6 +49,14 @@ internal class DefaultNewsListComponent( val state by newsListModel.state.collectAsStateWithLifecycle() if (LocalRedesignEnabled.current) { TangemTopBar( + modifier = Modifier.hazeEffectTangem { + progressive = HazeProgressive.verticalGradient( + startIntensity = .55f, + endIntensity = 0f, + preferPerformance = true, + easing = EaseOut, + ) + }, title = resourceReference(R.string.common_news), type = TangemTopBarType.BottomSheet, startContent = { @@ -80,11 +92,16 @@ internal class DefaultNewsListComponent( } @Composable - override fun Content(bottomSheetState: State, modifier: Modifier) { + override fun Content( + bottomSheetState: State, + contentPadding: PaddingValues, + modifier: Modifier, + ) { val state by newsListModel.state.collectAsStateWithLifecycle() NewsListContent( state = state, modifier = modifier, + contentPadding = contentPadding, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/search/DefaultSearchComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/search/DefaultSearchComponent.kt index b9cf5d8787..b8a8ac1119 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/search/DefaultSearchComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/search/DefaultSearchComponent.kt @@ -1,5 +1,7 @@ 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 import androidx.compose.ui.focus.FocusRequester @@ -7,6 +9,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext 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.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.ds.field.search.TangemFieldShape import com.tangem.core.ui.ds.field.search.TangemSearchField @@ -15,6 +18,7 @@ import com.tangem.core.ui.ds.topbar.TangemTopBarType import com.tangem.features.feed.model.search.SearchModel 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, @@ -35,6 +39,14 @@ internal class DefaultSearchComponent( } TangemTopBar( + modifier = Modifier.hazeEffectTangem { + progressive = HazeProgressive.verticalGradient( + startIntensity = .55f, + endIntensity = 0f, + preferPerformance = true, + easing = EaseOut, + ) + }, type = TangemTopBarType.BottomSheet, reserveSlotSpace = false, content = { @@ -50,7 +62,11 @@ internal class DefaultSearchComponent( } @Composable - override fun Content(bottomSheetState: State, modifier: Modifier) { + override fun Content( + bottomSheetState: State, + contentPadding: PaddingValues, + modifier: Modifier, + ) { val state by model.state.collectAsStateWithLifecycle() val searchCallbacks = remember { SearchCallbacks( @@ -64,6 +80,7 @@ internal class DefaultSearchComponent( modifier = modifier, content = state.content, searchCallbacks = searchCallbacks, + contentPadding = contentPadding, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/search/SearchModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/search/SearchModel.kt index fb91bf9fd9..a21b032562 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/search/SearchModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/search/SearchModel.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject private const val UPDATE_QUOTES_TIMER_MILLIS = 60000L +private const val MARKET_SEARCH_DEBOUNCE_MS = 500L @Suppress("LongParameterList") @ModelScoped @@ -52,6 +53,7 @@ internal class SearchModel @Inject constructor( private val updateQuotesJob = JobHolder() private val searchResultsJob = JobHolder() + private val marketSearchDebounceJob = JobHolder() private var shouldShowAllTokensIncludingUnder100k = false private val currentAppCurrency = getSelectedAppCurrencyUseCase().map { maybeAppCurrency -> @@ -145,13 +147,20 @@ internal class SearchModel @Inject constructor( .onEach { query -> shouldShowAllTokensIncludingUnder100k = false if (query.isEmpty()) { + marketSearchDebounceJob.cancel() searchMarketsListManager.clearStateAndStopAllActions() updateQuotesJob.cancel() loadHistory() } else { stateController.update(SetSearchResultsLoadingTransformer()) - searchMarketsListManager.reload(searchText = query) subscribeToSearchResults(query) + modelScope.launch(dispatchers.default) { + delay(MARKET_SEARCH_DEBOUNCE_MS) + val latestQuery = stateController.value.searchBar.query.trim() + if (latestQuery.isNotEmpty()) { + searchMarketsListManager.reload(searchText = latestQuery) + } + }.saveIn(marketSearchDebounceJob) } } .launchIn(modelScope) @@ -159,10 +168,7 @@ internal class SearchModel @Inject constructor( private fun subscribeToSearchResults(query: String) { modelScope.launch { - getSearchResultsUseCase( - query = query, - marketTokens = searchMarketsListManager.rawItems, - ).collectLatest { searchResult -> + getSearchResultsUseCase(query = query).collectLatest { searchResult -> val userAssets = searchResult.userAssets.map { entry -> UserAssetItemUM( id = "${entry.userWalletId.stringValue}_${entry.accountId.value}" + diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/EntryContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/EntryContent.kt index 4f1527ad16..eaf7f1665d 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/EntryContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/EntryContent.kt @@ -1,26 +1,30 @@ package com.tangem.features.feed.ui import androidx.compose.animation.AnimatedContent -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.* import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.remember +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import com.arkivanov.decompose.ExperimentalDecomposeApi +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.decompose.ComposableModularBottomSheetContentComponent +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.utils.WindowInsetsZero import com.tangem.features.feed.components.FeedEntryChildFactory -import com.tangem.features.feed.ui.utils.contentFeedEntryAnimatedContentTransitionSpec +import com.tangem.features.feed.ui.utils.contentFeedEntryStackAnimation import com.tangem.features.feed.ui.utils.topBarFeedEntryAnimatedContentTransitionSpec +import dev.chrisbanes.haze.rememberHazeState @OptIn(ExperimentalDecomposeApi::class) @Composable @@ -29,18 +33,41 @@ internal fun EntryContent( stackState: State>, onHeaderSizeChange: (Dp) -> Unit, isOpenedInBottomSheet: Boolean, +) { + if (LocalRedesignEnabled.current) { + EntryContentV2( + bottomSheetState = bottomSheetState, + stackState = stackState, + onHeaderSizeChange = onHeaderSizeChange, + isOpenedInBottomSheet = isOpenedInBottomSheet, + ) + } else { + EntryContentV1( + bottomSheetState = bottomSheetState, + stackState = stackState, + onHeaderSizeChange = onHeaderSizeChange, + isOpenedInBottomSheet = isOpenedInBottomSheet, + ) + } +} + +@Composable +private fun EntryContentV1( + bottomSheetState: State, + stackState: State>, + onHeaderSizeChange: (Dp) -> Unit, + isOpenedInBottomSheet: Boolean, ) { val density = LocalDensity.current val background = LocalMainBottomSheetColor.current.value - val animationContent = remember(stackState) { contentFeedEntryAnimatedContentTransitionSpec(stackState) } - val animationAppBar = remember(stackState) { topBarFeedEntryAnimatedContentTransitionSpec(stackState) } + val stackAnimation = remember { contentFeedEntryStackAnimation() } Surface(contentColor = background) { Scaffold( containerColor = background, contentWindowInsets = WindowInsetsZero, topBar = { - AnimatedContent( + Children( modifier = Modifier .then( if (!isOpenedInBottomSheet) { @@ -56,6 +83,77 @@ internal fun EntryContent( } } }, + stack = stackState.value, + animation = stackAnimation, + ) { child -> + child.instance.Title(bottomSheetState) + } + }, + content = { contentPadding -> + Children( + stack = stackState.value, + animation = stackAnimation, + ) { child -> + child.instance.Content( + modifier = Modifier.padding(contentPadding), + bottomSheetState = bottomSheetState, + contentPadding = PaddingValues(), + ) + } + }, + ) + } +} + +@Composable +private fun EntryContentV2( + bottomSheetState: State, + stackState: State>, + onHeaderSizeChange: (Dp) -> 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() + + Surface(contentColor = background) { + CompositionLocalProvider(LocalHazeState provides hazeState) { + Box(modifier = Modifier.fillMaxSize()) { + Children( + modifier = Modifier.fillMaxSize(), + stack = stackState.value, + animation = animationContent, + ) { child -> + child.instance.Content( + modifier = Modifier + .fillMaxSize() + .hazeSourceTangem(zIndex = 0f, state = hazeState), + contentPadding = PaddingValues(top = topBarHeight), + bottomSheetState = bottomSheetState, + ) + } + AnimatedContent( + 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) + } + } + }, targetState = stackState.value.active, transitionSpec = animationAppBar, contentKey = { it.key }, @@ -63,20 +161,7 @@ internal fun EntryContent( ) { state -> state.instance.Title(bottomSheetState) } - }, - content = { contentPadding -> - AnimatedContent( - targetState = stackState.value.active, - transitionSpec = animationContent, - contentKey = { it.key }, - label = "FeedEntryContent", - ) { state -> - state.instance.Content( - modifier = Modifier.padding(contentPadding), - bottomSheetState = bottomSheetState, - ) - } - }, - ) + } + } } } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/EarnContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/EarnContent.kt index 86ca75fa5b..d3800af21f 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/EarnContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/EarnContent.kt @@ -22,11 +22,7 @@ import com.tangem.core.ui.components.* import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.list.InfiniteListHandler import com.tangem.core.ui.decorations.roundedShapeItemDecoration -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.conditional -import com.tangem.core.ui.extensions.conditionalCompose -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.* import com.tangem.features.feed.ui.earn.components.* import com.tangem.features.feed.ui.earn.state.* @@ -36,7 +32,7 @@ import kotlinx.collections.immutable.persistentListOf private const val EARN_LOAD_MORE_BUFFER = 3 @Composable -internal fun EarnContent(state: EarnUM, modifier: Modifier = Modifier) { +internal fun EarnContent(contentPadding: PaddingValues, state: EarnUM, modifier: Modifier = Modifier) { val background = LocalMainBottomSheetColor.current.value val density = LocalDensity.current val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() } @@ -55,7 +51,7 @@ internal fun EarnContent(state: EarnUM, modifier: Modifier = Modifier) { modifier = modifier .fillMaxSize() .background(background), - contentPadding = PaddingValues(bottom = bottomBarHeight), + contentPadding = PaddingValues(bottom = bottomBarHeight, top = contentPadding.calculateTopPadding()), ) { item(key = "mostly_used_header") { SectionHeader( @@ -395,6 +391,7 @@ private fun EarnContentPreviewV1() { LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, ) { EarnContent( + contentPadding = PaddingValues(), state = previewEarnUM( mostlyUsed = EarnListUM.Content( items = persistentListOf( @@ -437,6 +434,7 @@ private fun EarnContentPreviewV2() { LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, ) { EarnContent( + contentPadding = PaddingValues(), state = previewEarnUM( mostlyUsed = EarnListUM.Content( items = persistentListOf( @@ -479,6 +477,7 @@ private fun EarnContentLoadingPreviewV1() { LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, ) { EarnContent( + contentPadding = PaddingValues(), state = previewEarnUM( mostlyUsed = EarnListUM.Error(onRetryClicked = {}), bestOpportunities = EarnBestOpportunitiesUM.Loading, @@ -498,6 +497,7 @@ private fun EarnContentLoadingPreviewV2() { LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, ) { EarnContent( + contentPadding = PaddingValues(), state = previewEarnUM( mostlyUsed = EarnListUM.Error(onRetryClicked = {}), bestOpportunities = EarnBestOpportunitiesUM.Loading, @@ -517,6 +517,7 @@ private fun EarnContentErrorPreviewV1() { LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, ) { EarnContent( + contentPadding = PaddingValues(), state = previewEarnUM( mostlyUsed = EarnListUM.Content( items = persistentListOf( @@ -545,6 +546,7 @@ private fun EarnContentErrorPreviewV2() { LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, ) { EarnContent( + contentPadding = PaddingValues(), state = previewEarnUM( mostlyUsed = EarnListUM.Content( items = persistentListOf( @@ -573,6 +575,7 @@ private fun EarnContentEmptyPreviewV1() { LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, ) { EarnContent( + contentPadding = PaddingValues(), state = previewEarnUM( mostlyUsed = EarnListUM.Content( items = persistentListOf( @@ -601,6 +604,7 @@ private fun EarnContentEmptyPreviewV2() { LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, ) { EarnContent( + contentPadding = PaddingValues(), state = previewEarnUM( mostlyUsed = EarnListUM.Content( items = persistentListOf( diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt index 26f97c3c93..1c459a5e09 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt @@ -1,6 +1,7 @@ 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 @@ -14,8 +15,11 @@ 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 import com.tangem.core.ui.test.BaseSearchBarTestTags.SEARCH_BAR import com.tangem.features.feed.model.market.list.state.SortByTypeUM @@ -25,6 +29,7 @@ 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( @@ -38,12 +43,26 @@ internal fun FeedListHeader( 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), ) } @Composable internal fun FeedList( + contentPadding: PaddingValues, state: FeedListUM, modifier: Modifier = Modifier, promoBannersBlockComponent: ComposableContentComponent? = null, @@ -59,19 +78,23 @@ internal fun FeedList( FeedListLoading( modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) - .drawBehind { drawRect(background) }, + .drawBehind { drawRect(background) } + .padding(top = contentPadding.calculateTopPadding()) + .verticalScroll(rememberScrollState()), ) } is GlobalFeedState.Error -> { FeedListGlobalError( onRetryClick = animatedState.onRetryClicked, - modifier = Modifier.drawBehind { drawRect(background) }, + modifier = Modifier + .drawBehind { drawRect(background) } + .padding(top = contentPadding.calculateTopPadding()), currentDate = state.currentDate, ) } is GlobalFeedState.Content -> { FeedListContent( + contentPadding = contentPadding, modifier = Modifier, state = state, promoBannersBlockComponent = promoBannersBlockComponent, @@ -83,6 +106,7 @@ internal fun FeedList( @Composable private fun FeedListContent( + contentPadding: PaddingValues, state: FeedListUM, modifier: Modifier = Modifier, promoBannersBlockComponent: ComposableContentComponent? = null, @@ -95,6 +119,9 @@ private fun FeedListContent( .padding(bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()) .drawBehind { drawRect(background) }, ) { + if (LocalRedesignEnabled.current) { + SpacerH(contentPadding.calculateTopPadding()) + } DateBlock(state.currentDate) SpacerH(32.dp) @@ -127,6 +154,6 @@ private fun FeedListContent( @Composable private fun FeedListPreview() { TangemThemePreview { - FeedList(state = createFeedPreviewState()) + FeedList(state = createFeedPreviewState(), contentPadding = PaddingValues()) } } \ 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 8cbb7fb5e1..68076673c5 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 @@ -58,12 +58,14 @@ import com.tangem.core.ui.R as CoreR @Suppress("LongParameterList") @Composable internal fun MarketsTokenDetailsContent( + contentPadding: PaddingValues, state: MarketsTokenDetailsUM, backgroundColor: Color, modifier: Modifier = Modifier, portfolioBlock: @Composable ((Modifier) -> Unit)?, ) { Content( + contentPadding = contentPadding, modifier = modifier, backgroundColor = backgroundColor, state = state, @@ -80,6 +82,7 @@ internal fun MarketsTokenDetailsContent( @Suppress("LongParameterList") @Composable private fun Content( + contentPadding: PaddingValues, state: MarketsTokenDetailsUM, backgroundColor: Color, modifier: Modifier = Modifier, @@ -103,7 +106,7 @@ private fun Content( LazyColumn( state = lazyListState, - contentPadding = PaddingValues(bottom = bottomBarHeight), + contentPadding = PaddingValues(bottom = bottomBarHeight, top = contentPadding.calculateTopPadding()), ) { item("header") { Header( @@ -343,6 +346,7 @@ private fun MarketsTokenDetailsContent_Preview( state = params, backgroundColor = TangemTheme.colors.background.tertiary, portfolioBlock = {}, + contentPadding = PaddingValues(), ) } } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/list/MarketsList.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/list/MarketsList.kt index 8a1330df5d..cafab387b7 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/list/MarketsList.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/list/MarketsList.kt @@ -17,9 +17,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.common.ui.markets.preview.MarketChartListItemPreviewDataProvider -import com.tangem.core.ui.components.Keyboard -import com.tangem.core.ui.components.SpacerH12 -import com.tangem.core.ui.components.SpacerH8 +import com.tangem.core.ui.components.* import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState @@ -27,7 +25,6 @@ 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.components.keyboardAsState import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.conditionalCompose import com.tangem.core.ui.extensions.resourceReference @@ -105,7 +102,7 @@ internal fun TopBarWithSearch( } @Composable -internal fun MarketsList(state: MarketsListUM, modifier: Modifier = Modifier) { +internal fun MarketsList(contentPadding: PaddingValues, state: MarketsListUM, modifier: Modifier = Modifier) { val background = LocalMainBottomSheetColor.current.value Column( modifier = modifier @@ -113,7 +110,7 @@ internal fun MarketsList(state: MarketsListUM, modifier: Modifier = Modifier) { .imePadding() .drawBehind { drawRect(background) }, ) { - Content(state = state) + Content(state = state, contentPadding = contentPadding) } MarketsListSortByBottomSheet(config = state.sortByBottomSheet) KeyboardEvents(isSortByBottomSheetShown = state.sortByBottomSheet.isShown) @@ -121,7 +118,7 @@ internal fun MarketsList(state: MarketsListUM, modifier: Modifier = Modifier) { @Suppress("LongMethod") @Composable -private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modifier) { +private fun ColumnScope.Content(contentPadding: PaddingValues, state: MarketsListUM, modifier: Modifier = Modifier) { val isRedesignEnabled = LocalRedesignEnabled.current val hazeState = rememberHazeState() @@ -135,6 +132,7 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif val scrolledState = remember { mutableStateOf(false) } Column(modifier.padding(horizontal = TangemTheme.dimens.size16)) { + SpacerH(contentPadding.calculateTopPadding()) AnimatedVisibility( visible = scrolledState.value.not() && state.isInSearchMode && @@ -276,6 +274,7 @@ private fun Preview() { LocalMainBottomSheetColor provides remember { mutableStateOf(primaryBackground) }, ) { MarketsList( + contentPadding = PaddingValues(), state = MarketsListUM( list = ListUM.Content( items = MarketChartListItemPreviewDataProvider().values diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt index e592094a1a..9b7905ade3 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt @@ -24,7 +24,7 @@ import com.tangem.features.feed.ui.news.details.state.MockArticlesFactory import com.tangem.features.feed.ui.news.details.state.NewsDetailsUM @Composable -internal fun NewsDetailsContent(state: NewsDetailsUM, modifier: Modifier = Modifier) { +internal fun NewsDetailsContent(state: NewsDetailsUM, contentPadding: PaddingValues, modifier: Modifier = Modifier) { val background = LocalMainBottomSheetColor.current.value AnimatedContent( targetState = state.articlesStateUM, @@ -32,15 +32,16 @@ internal fun NewsDetailsContent(state: NewsDetailsUM, modifier: Modifier = Modif ) { animatedState -> when (animatedState) { ArticlesStateUM.Content -> { - Content(state = state, background = background) + Content(state = state, background = background, contentPadding = contentPadding) } ArticlesStateUM.Loading -> { - NewsDetailsPlaceholder(background = background) + NewsDetailsPlaceholder(background = background, contentPadding = contentPadding) } is ArticlesStateUM.LoadingError -> { Box( modifier = Modifier .fillMaxSize() + .padding(top = contentPadding.calculateTopPadding()) .background(background), contentAlignment = Alignment.Center, ) { @@ -57,7 +58,7 @@ internal fun NewsDetailsContent(state: NewsDetailsUM, modifier: Modifier = Modif } @Composable -private fun Content(state: NewsDetailsUM, background: Color) { +private fun Content(contentPadding: PaddingValues, state: NewsDetailsUM, background: Color) { val isRedesignEnabled = LocalRedesignEnabled.current val pagerState = rememberPagerState( initialPage = state.selectedArticleIndex, @@ -96,6 +97,7 @@ private fun Content(state: NewsDetailsUM, background: Color) { modifier = Modifier.fillMaxSize(), onLikeClick = { state.onLikeClick(article.id) }, relatedTokensUM = state.relatedTokensUM, + contentPadding = contentPadding, ) } if (state.articles.size > 1) { @@ -142,6 +144,7 @@ private fun PreviewNewsDetailsContent() { onBackClick = {}, onArticleIndexChanged = {}, ), + contentPadding = PaddingValues(), ) } } @@ -166,6 +169,7 @@ private fun PreviewNewsDetailsContentV2() { onBackClick = {}, onArticleIndexChanged = {}, ), + contentPadding = PaddingValues(), ) } } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/components/ArticleDetail.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/components/ArticleDetail.kt index 12a7153f8d..7c7c47c46d 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/components/ArticleDetail.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/components/ArticleDetail.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.HorizontalDivider 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 @@ -28,6 +29,7 @@ import com.tangem.core.ui.ds.button.TangemButtonShape import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.LocalHazeState import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.LocalRedesignEnabled import com.tangem.core.ui.res.TangemTheme @@ -35,9 +37,11 @@ import com.tangem.features.feed.ui.feed.components.articles.ArticleHeader import com.tangem.features.feed.ui.news.details.state.ArticleUM import com.tangem.features.feed.ui.news.details.state.RelatedArticleUM import com.tangem.features.feed.ui.news.details.state.RelatedTokensUM +import dev.chrisbanes.haze.rememberHazeState @Composable internal fun ArticleDetail( + contentPadding: PaddingValues, article: ArticleUM, onLikeClick: () -> Unit, relatedTokensUM: RelatedTokensUM, @@ -45,6 +49,7 @@ internal fun ArticleDetail( ) { if (LocalRedesignEnabled.current) { ArticleDetailV2( + contentPadding = contentPadding, article = article, onLikeClick = onLikeClick, relatedTokensUM = relatedTokensUM, @@ -190,6 +195,7 @@ private fun ArticleDetailV1( @Suppress("LongMethod") @Composable internal fun ArticleDetailV2( + contentPadding: PaddingValues, article: ArticleUM, onLikeClick: () -> Unit, relatedTokensUM: RelatedTokensUM, @@ -199,126 +205,127 @@ internal fun ArticleDetailV2( val density = LocalDensity.current val background = LocalMainBottomSheetColor.current.value val pagerHeight = 32.dp - val contentPadding = pagerHeight + 56.dp + with(density) { + val bottomPadding = pagerHeight + 56.dp + with(density) { WindowInsets.navigationBars.getBottom(this).div(this.density) }.dp - Box(modifier = modifier) { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .hazeSourceTangem(zIndex = 0f) - .background(background), - contentPadding = PaddingValues(bottom = contentPadding), - ) { - item("content") { - ArticleHeader( - title = article.title, - createdAt = article.createdAt.resolveReference(), - score = article.score, - tags = article.tags, - isTrending = article.isTrending, - modifier = Modifier - .padding(top = 16.dp) - .padding(horizontal = 16.dp), - ) - - if (article.shortContent.isNotEmpty()) { - QuickRecap( - content = article.shortContent, + CompositionLocalProvider(LocalHazeState provides rememberHazeState()) { + Box(modifier = modifier) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .hazeSourceTangem(zIndex = -1f) + .background(background), + contentPadding = PaddingValues(bottom = bottomPadding, top = contentPadding.calculateTopPadding()), + ) { + item("content") { + ArticleHeader( + title = article.title, + createdAt = article.createdAt.resolveReference(), + score = article.score, + tags = article.tags, + isTrending = article.isTrending, modifier = Modifier - .padding(top = 32.dp) + .padding(top = 16.dp) .padding(horizontal = 16.dp), ) - } - Text( - text = article.content, - style = TangemTheme.typography2.bodyRegular16, - color = TangemTheme.colors2.text.neutral.primary, - modifier = Modifier - .padding(top = 12.dp) - .padding(horizontal = 16.dp), - ) - - SpacerH(24.dp) - - HorizontalDivider( - modifier = Modifier.padding(horizontal = 24.dp), - color = TangemTheme.colors2.border.neutral.primary, - ) - - SpacerH(20.dp) - - SecondaryTangemButton( - modifier = Modifier.padding(horizontal = 24.dp), - text = resourceReference(R.string.news_like), - size = com.tangem.core.ui.ds.button.TangemButtonSize.X9, - iconRes = if (article.isLiked) { - R.drawable.ic_like_20 - } else { - R.drawable.ic_heart_20 - }, - onClick = { - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - onLikeClick() - }, - shape = TangemButtonShape.Rounded, - ) - - RelatedTokensBlock( - relatedTokensUM = relatedTokensUM, - onItemClick = when (relatedTokensUM) { - is RelatedTokensUM.Content -> relatedTokensUM.onTokenClick - else -> null - }, - modifier = Modifier.padding(horizontal = 16.dp), - ) - - if (article.relatedArticles.isNotEmpty()) { - SpacerH(24.dp) - Row( - modifier = Modifier.padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Text( - text = stringResourceSafe(R.string.news_sources), - style = TangemTheme.typography2.headingSemibold20, - color = TangemTheme.colors2.text.neutral.primary, + if (article.shortContent.isNotEmpty()) { + QuickRecap( + content = article.shortContent, + modifier = Modifier + .padding(top = 32.dp) + .padding(horizontal = 16.dp), ) } - } - } - if (article.relatedArticles.isNotEmpty()) { - item("relatedArticles") { - LazyRow( + Text( + text = article.content, + style = TangemTheme.typography2.bodyRegular16, + color = TangemTheme.colors2.text.neutral.primary, modifier = Modifier - .padding(vertical = 12.dp), - state = rememberLazyListState(), - contentPadding = PaddingValues(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - items( - items = article.relatedArticles, - key = RelatedArticleUM::id, - ) { article -> - RelatedNewsItem( - relatedArticle = article, - modifier = Modifier.fillParentMaxHeight(), + .padding(top = 12.dp) + .padding(horizontal = 16.dp), + ) + + SpacerH(24.dp) + + HorizontalDivider( + modifier = Modifier.padding(horizontal = 24.dp), + color = TangemTheme.colors2.border.neutral.primary, + ) + + SpacerH(20.dp) + + SecondaryTangemButton( + modifier = Modifier.padding(horizontal = 24.dp), + text = resourceReference(R.string.news_like), + size = com.tangem.core.ui.ds.button.TangemButtonSize.X9, + iconRes = if (article.isLiked) { + R.drawable.ic_like_20 + } else { + R.drawable.ic_heart_20 + }, + onClick = { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + onLikeClick() + }, + shape = TangemButtonShape.Rounded, + ) + + RelatedTokensBlock( + relatedTokensUM = relatedTokensUM, + onItemClick = when (relatedTokensUM) { + is RelatedTokensUM.Content -> relatedTokensUM.onTokenClick + else -> null + }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + if (article.relatedArticles.isNotEmpty()) { + SpacerH(24.dp) + Row( + modifier = Modifier.padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = stringResourceSafe(R.string.news_sources), + style = TangemTheme.typography2.headingSemibold20, + color = TangemTheme.colors2.text.neutral.primary, ) } } } - } - } - BottomFadeWithBlur( - modifier = Modifier - .align(Alignment.BottomCenter) - .height(80.dp) - .fillMaxWidth(), - backgroundColor = background, - ) + if (article.relatedArticles.isNotEmpty()) { + item("relatedArticles") { + LazyRow( + modifier = Modifier.padding(vertical = 12.dp), + state = rememberLazyListState(), + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + items( + items = article.relatedArticles, + key = RelatedArticleUM::id, + ) { article -> + RelatedNewsItem( + relatedArticle = article, + modifier = Modifier.fillParentMaxHeight(), + ) + } + } + } + } + } + + BottomFadeWithBlur( + modifier = Modifier + .align(Alignment.BottomCenter) + .height(80.dp) + .fillMaxWidth(), + backgroundColor = background, + ) + } } } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/components/NewsDetailsPlaceholder.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/components/NewsDetailsPlaceholder.kt index a814d22eed..dc9107030f 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/components/NewsDetailsPlaceholder.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/components/NewsDetailsPlaceholder.kt @@ -17,9 +17,9 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreviewRedesign @Composable -fun NewsDetailsPlaceholder(background: Color, modifier: Modifier = Modifier) { +fun NewsDetailsPlaceholder(contentPadding: PaddingValues, background: Color, modifier: Modifier = Modifier) { if (LocalRedesignEnabled.current) { - NewsDetailsPlaceholderV2(background, modifier) + NewsDetailsPlaceholderV2(contentPadding, background, modifier) } else { NewsDetailsPlaceholderV1(background, modifier) } @@ -103,13 +103,14 @@ private fun NewsDetailsPlaceholderV1(background: Color, modifier: Modifier = Mod @Suppress("LongMethod") @Composable -private fun NewsDetailsPlaceholderV2(background: Color, modifier: Modifier = Modifier) { +private fun NewsDetailsPlaceholderV2(contentPadding: PaddingValues, background: Color, modifier: Modifier = Modifier) { Column( modifier = modifier .fillMaxSize() .background(background) .padding(16.dp), ) { + SpacerH(contentPadding.calculateTopPadding()) Row( modifier = Modifier.height(50.dp), horizontalArrangement = Arrangement.spacedBy(30.dp), @@ -214,7 +215,10 @@ private fun NewsDetailsPlaceholderV2(background: Color, modifier: Modifier = Mod @Composable private fun NewsDetailsPlaceholderPreviewV1() { TangemThemePreview { - NewsDetailsPlaceholder(background = TangemTheme.colors.background.tertiary) + NewsDetailsPlaceholder( + background = TangemTheme.colors.background.tertiary, + contentPadding = PaddingValues(), + ) } } @@ -223,6 +227,9 @@ private fun NewsDetailsPlaceholderPreviewV1() { @Composable private fun NewsDetailsPlaceholderPreviewV2() { TangemThemePreviewRedesign { - NewsDetailsPlaceholder(background = TangemTheme.colors2.surface.level3) + NewsDetailsPlaceholder( + background = TangemTheme.colors2.surface.level3, + contentPadding = PaddingValues(), + ) } } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt index ced6feef80..933d5fe2a9 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.chip.Chip import com.tangem.core.ui.components.chip.entity.ChipUM @@ -22,6 +21,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.LocalRedesignEnabled import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM 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 @@ -29,7 +29,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableSet @Composable -internal fun NewsListContent(state: NewsListUM, modifier: Modifier = Modifier) { +internal fun NewsListContent(contentPadding: PaddingValues, state: NewsListUM, modifier: Modifier = Modifier) { val background = LocalMainBottomSheetColor.current.value val isRedesignEnabled = LocalRedesignEnabled.current val lazyListState = rememberLazyListState() @@ -39,6 +39,7 @@ internal fun NewsListContent(state: NewsListUM, modifier: Modifier = Modifier) { .fillMaxSize() .background(background), ) { + SpacerH(contentPadding.calculateTopPadding()) LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -149,6 +150,7 @@ private fun NewsListContentPreview() { onArticleClick = {}, onBackClick = {}, ), + contentPadding = PaddingValues(), ) } } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/SearchContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/SearchContent.kt index d1f01ad1cc..b2da4c8e5f 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/SearchContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/SearchContent.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.vectorResource @@ -34,6 +35,7 @@ import com.tangem.core.ui.ds.image.TangemIconUM import com.tangem.core.ui.extensions.clickableSingle import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.TangemTheme import com.tangem.features.feed.ui.search.state.* @@ -41,17 +43,26 @@ private const val PLACEHOLDER_COUNT = 10 private const val LOAD_MORE_THRESHOLD = 5 @Composable -internal fun SearchContent(content: SearchContentUM, searchCallbacks: SearchCallbacks, modifier: Modifier = Modifier) { +internal fun SearchContent( + content: SearchContentUM, + searchCallbacks: SearchCallbacks, + contentPadding: PaddingValues, + modifier: Modifier = Modifier, +) { val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } val lazyListState = rememberLazyListState() + val background = LocalMainBottomSheetColor.current.value LazyColumn( state = lazyListState, - modifier = modifier.fillMaxSize(), + modifier = modifier + .fillMaxSize() + .drawBehind { drawRect(background) }, contentPadding = PaddingValues( start = TangemTheme.dimens2.x4, end = TangemTheme.dimens2.x4, bottom = bottomBarHeight, + top = contentPadding.calculateTopPadding(), ), ) { when (content) { diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/preview/SearchContentPreview.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/preview/SearchContentPreview.kt index 227ab6c942..b8f37312a4 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/preview/SearchContentPreview.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/preview/SearchContentPreview.kt @@ -3,6 +3,7 @@ package com.tangem.features.feed.ui.search.preview import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -408,6 +409,7 @@ private fun SearchContentPreviewHost( content = scenario.content, searchCallbacks = SearchContentPreviewCallbacks, modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(), ) } } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/EntryContentAnimationTransitions.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/EntryContentAnimationTransitions.kt index 02de097c9a..2bbfa496e2 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/EntryContentAnimationTransitions.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/EntryContentAnimationTransitions.kt @@ -4,15 +4,16 @@ import androidx.compose.animation.* import androidx.compose.animation.core.tween import androidx.compose.runtime.State import com.arkivanov.decompose.Child +import com.arkivanov.decompose.FaultyDecomposeApi +import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation +import com.arkivanov.decompose.extensions.compose.stack.animation.fade +import com.arkivanov.decompose.extensions.compose.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation import com.arkivanov.decompose.router.stack.ChildStack import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.features.feed.components.FeedEntryChildFactory -private fun FeedEntryChildFactory.Child?.usesFadeStackTransition(): Boolean = when (this) { - is FeedEntryChildFactory.Child.Search -> true - is FeedEntryChildFactory.Child.TokenList -> params.shouldAlwaysShowSearchBar - else -> false -} +private const val FEED_ENTRY_SLIDE_DURATION_MS = 300 internal typealias FeedEntryActiveChild = Child.Created @@ -20,12 +21,24 @@ internal typealias FeedEntryActiveChild = internal typealias FeedEntryChildStack = ChildStack -internal fun topBarFeedEntryAnimatedContentTransitionSpec( - stackState: State, -): AnimatedContentTransitionScope.() -> ContentTransform = - { feedEntryAnimatedContentTransform(stackState.value) } +@OptIn(FaultyDecomposeApi::class) +internal fun contentFeedEntryStackAnimation(): StackAnimation< + FeedEntryChildFactory.Child, + ComposableModularBottomSheetContentComponent, + > = + stackAnimation { to, from, _ -> + val isSearchToTokenList = + (to.configuration as? FeedEntryChildFactory.Child.TokenList)?.params?.shouldAlwaysShowSearchBar == true + val isFromSearchTokenList = + (from.configuration as? FeedEntryChildFactory.Child.TokenList)?.params?.shouldAlwaysShowSearchBar == true + if (isSearchToTokenList || isFromSearchTokenList) { + fade() + } else { + slide() + } + } -internal fun contentFeedEntryAnimatedContentTransitionSpec( +internal fun topBarFeedEntryAnimatedContentTransitionSpec( stackState: State, ): AnimatedContentTransitionScope.() -> ContentTransform = { feedEntryAnimatedContentTransform(stackState.value) } @@ -36,8 +49,8 @@ private fun AnimatedContentTransitionScope.feedEntryAnimat val shouldUseFade = initialState.configuration.usesFadeStackTransition() || targetState.configuration.usesFadeStackTransition() return if (shouldUseFade) { - fadeIn(animationSpec = tween(FEED_ENTRY_FADE_DURATION_MS)) togetherWith - fadeOut(animationSpec = tween(FEED_ENTRY_FADE_DURATION_MS)) + // No transition: fade/slide both fight with haze during the animation frame window. + EnterTransition.None togetherWith ExitTransition.None } else { feedEntrySlideTransform(stack) } @@ -66,5 +79,8 @@ private fun AnimatedContentTransitionScope.feedEntrySlideT } } -private const val FEED_ENTRY_FADE_DURATION_MS = 300 -private const val FEED_ENTRY_SLIDE_DURATION_MS = 300 \ No newline at end of file +private fun FeedEntryChildFactory.Child?.usesFadeStackTransition(): Boolean = when (this) { + is FeedEntryChildFactory.Child.Search -> true + is FeedEntryChildFactory.Child.TokenList -> params.shouldAlwaysShowSearchBar + else -> false +} \ No newline at end of file