Updated on 2026-08-14
This commit is contained in:
parent
843ec3d04e
commit
7a197ec6c9
24 changed files with 499 additions and 235 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<BottomSheetState>, modifier: Modifier)
|
||||
fun Content(bottomSheetState: State<BottomSheetState>, contentPadding: PaddingValues, modifier: Modifier)
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
package com.tangem.domain.search.model
|
||||
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
|
||||
data class SearchResult(
|
||||
val textHints: List<SearchTextHint>,
|
||||
val recentTokens: List<RecentSearchToken>,
|
||||
val userAssets: List<UserAssetSearchEntry>,
|
||||
val marketTokens: List<TokenMarket>,
|
||||
)
|
||||
|
|
@ -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<List<TokenMarket>> = flowOf(emptyList()),
|
||||
): Flow<SearchResult> {
|
||||
operator fun invoke(query: String): Flow<SearchResult> {
|
||||
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<List<TokenMarket>>): Flow<SearchResult> {
|
||||
return combine(
|
||||
observeUserAssets(query),
|
||||
marketTokens,
|
||||
) { userAssets, markets ->
|
||||
SearchResult(
|
||||
textHints = emptyList(),
|
||||
recentTokens = emptyList(),
|
||||
userAssets = userAssets,
|
||||
marketTokens = markets,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeUserAssets(query: String): Flow<List<UserAssetSearchEntry>> {
|
||||
private fun observeUserAssets(query: String): Flow<SearchResult> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<BottomSheetState>, modifier: Modifier) {
|
||||
override fun Content(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
val state by earnModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
EarnContent(
|
||||
contentPadding = contentPadding,
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<BottomSheetState>, modifier: Modifier) {
|
||||
override fun Content(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<BottomSheetState>, modifier: Modifier) {
|
||||
override fun Content(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<BottomSheetState>, modifier: Modifier) {
|
||||
override fun Content(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<BottomSheetState>, modifier: Modifier) {
|
||||
override fun Content(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val state by newsDetailsModel.state.collectAsStateWithLifecycle()
|
||||
NewsDetailsContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<BottomSheetState>, modifier: Modifier) {
|
||||
override fun Content(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val state by newsListModel.state.collectAsStateWithLifecycle()
|
||||
NewsListContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<BottomSheetState>, modifier: Modifier) {
|
||||
override fun Content(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}" +
|
||||
|
|
|
|||
|
|
@ -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<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>>,
|
||||
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<BottomSheetState>,
|
||||
stackState: State<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>>,
|
||||
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<BottomSheetState>,
|
||||
stackState: State<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>
|
||||
|
|
@ -20,12 +21,24 @@ internal typealias FeedEntryActiveChild =
|
|||
internal typealias FeedEntryChildStack =
|
||||
ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>
|
||||
|
||||
internal fun topBarFeedEntryAnimatedContentTransitionSpec(
|
||||
stackState: State<FeedEntryChildStack>,
|
||||
): AnimatedContentTransitionScope<FeedEntryActiveChild>.() -> 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<FeedEntryChildStack>,
|
||||
): AnimatedContentTransitionScope<FeedEntryActiveChild>.() -> ContentTransform =
|
||||
{ feedEntryAnimatedContentTransform(stackState.value) }
|
||||
|
|
@ -36,8 +49,8 @@ private fun AnimatedContentTransitionScope<FeedEntryActiveChild>.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<FeedEntryActiveChild>.feedEntrySlideT
|
|||
}
|
||||
}
|
||||
|
||||
private const val FEED_ENTRY_FADE_DURATION_MS = 300
|
||||
private const val FEED_ENTRY_SLIDE_DURATION_MS = 300
|
||||
private fun FeedEntryChildFactory.Child?.usesFadeStackTransition(): Boolean = when (this) {
|
||||
is FeedEntryChildFactory.Child.Search -> true
|
||||
is FeedEntryChildFactory.Child.TokenList -> params.shouldAlwaysShowSearchBar
|
||||
else -> false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue