Updated on 2026-08-14
This commit is contained in:
parent
530b2d93ad
commit
822b82d1da
20 changed files with 211 additions and 123 deletions
|
|
@ -87,6 +87,7 @@ internal class NewsListModel @Inject constructor(
|
|||
|
||||
init {
|
||||
observeNewsList()
|
||||
batchFlowManager.reload()
|
||||
loadCategories()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,20 @@ package com.tangem.features.feed.model.search
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
|
||||
import com.tangem.features.feed.components.search.SearchTokenSelectorComponent
|
||||
import com.tangem.features.feed.model.search.state.TokenSelectorStateController
|
||||
import com.tangem.features.feed.model.search.state.transformers.BuildTokenSelectorSectionsTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
|
|
@ -18,6 +24,9 @@ internal class SearchTokenSelectorModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val stateController: TokenSelectorStateController,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val walletIconUMConverter: WalletIconUMConverter,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<SearchTokenSelectorComponent.Params>()
|
||||
|
|
@ -26,13 +35,25 @@ internal class SearchTokenSelectorModel @Inject constructor(
|
|||
get() = stateController.uiState
|
||||
|
||||
init {
|
||||
stateController.update(
|
||||
BuildTokenSelectorSectionsTransformer(
|
||||
entries = params.entries,
|
||||
appCurrency = params.appCurrency,
|
||||
isBalanceHidden = params.isBalanceHidden,
|
||||
onTokenSelected = params.onTokenSelected,
|
||||
),
|
||||
)
|
||||
modelScope.launch(dispatchers.default) {
|
||||
val requiredWalletIds = params.entries.map { it.userWalletId }.toSet()
|
||||
val walletIcons = userWalletsListRepository.userWallets
|
||||
.filterNotNull()
|
||||
.first()
|
||||
.filter { it.walletId in requiredWalletIds }
|
||||
.associate { wallet ->
|
||||
wallet.walletId to walletIconUMConverter.convert(getWalletIconUseCase(wallet))
|
||||
}
|
||||
|
||||
stateController.update(
|
||||
BuildTokenSelectorSectionsTransformer(
|
||||
entries = params.entries,
|
||||
appCurrency = params.appCurrency,
|
||||
isBalanceHidden = params.isBalanceHidden,
|
||||
walletIcons = walletIcons,
|
||||
onTokenSelected = params.onTokenSelected,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,14 +4,17 @@ import com.tangem.common.ui.account.toUM
|
|||
import com.tangem.common.ui.markets.tokenselector.AccountHeaderData
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorSectionUM
|
||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class BuildTokenSelectorSectionsTransformer(
|
||||
private val entries: List<UserAssetEntry>,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val walletIcons: Map<UserWalletId, DeviceIconUM>,
|
||||
private val onTokenSelected: (UserAssetEntry) -> Unit,
|
||||
) : TokenSelectorUMTransformer {
|
||||
|
||||
|
|
@ -30,11 +33,12 @@ internal class BuildTokenSelectorSectionsTransformer(
|
|||
val byWallet = entries.groupBy { it.userWalletId }
|
||||
val shouldShowWalletHeaders = byWallet.size > 1
|
||||
|
||||
for ((_, walletEntries) in byWallet) {
|
||||
for ((walletId, walletEntries) in byWallet) {
|
||||
if (shouldShowWalletHeaders) {
|
||||
sections.add(
|
||||
TokenSelectorSectionUM.WalletHeader(
|
||||
walletName = walletEntries.first().userWalletName,
|
||||
deviceIcon = walletIcons[walletId] ?: DeviceIconUM.Stub(cardsCount = 1),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
|||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.components.topFade
|
||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
||||
import com.tangem.core.ui.extensions.conditionalCompose
|
||||
import com.tangem.core.ui.res.LocalHazeState
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
|
|
@ -139,13 +138,18 @@ private fun EntryContentV2(
|
|||
isOpenedInBottomSheet: Boolean,
|
||||
) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
|
||||
var topBarHeight by remember { mutableStateOf(0.dp) }
|
||||
val hazeState = rememberHazeState()
|
||||
val fadeHeightOverride = remember { mutableStateOf<Dp?>(null) }
|
||||
val effectiveFadeHeight = fadeHeightOverride.value ?: topBarHeight
|
||||
val statusBarInset = if (isOpenedInBottomSheet) {
|
||||
0.dp
|
||||
} else {
|
||||
WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
}
|
||||
val effectiveTopBarHeight = topBarHeight + statusBarInset
|
||||
val effectiveFadeHeight = fadeHeightOverride.value ?: effectiveTopBarHeight
|
||||
|
||||
Surface(contentColor = background) {
|
||||
Surface(color = background, contentColor = background) {
|
||||
CompositionLocalProvider(
|
||||
LocalHazeState provides hazeState,
|
||||
LocalContentTopFadeHeightOverride provides fadeHeightOverride,
|
||||
|
|
@ -154,9 +158,8 @@ private fun EntryContentV2(
|
|||
ContentBlock(
|
||||
bottomSheetState = bottomSheetState,
|
||||
effectiveFadeHeight = effectiveFadeHeight,
|
||||
isOpenedInBottomSheet = isOpenedInBottomSheet,
|
||||
stackState = stackState,
|
||||
topBarHeight = topBarHeight,
|
||||
topBarHeight = effectiveTopBarHeight,
|
||||
)
|
||||
TitleBlock(
|
||||
bottomSheetState = bottomSheetState,
|
||||
|
|
@ -188,14 +191,10 @@ private fun BoxScope.TitleBlock(
|
|||
Box(
|
||||
modifier = modifier
|
||||
.align(Alignment.TopStart)
|
||||
.then(
|
||||
if (!isOpenedInBottomSheet) {
|
||||
Modifier.statusBarsPadding()
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
.then(if (!isOpenedInBottomSheet) Modifier.statusBarsPadding() else Modifier),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.onGloballyPositioned { coordinates ->
|
||||
if (coordinates.size.height > 0) {
|
||||
with(density) {
|
||||
val height = coordinates.size.height.toDp()
|
||||
|
|
@ -203,19 +202,20 @@ private fun BoxScope.TitleBlock(
|
|||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = stackState.value.active,
|
||||
transitionSpec = animationAppBar,
|
||||
contentKey = { it.key },
|
||||
label = "FeedEntryAppBar",
|
||||
) { state ->
|
||||
state.instance.Title(bottomSheetState)
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = stackState.value.active,
|
||||
transitionSpec = animationAppBar,
|
||||
contentKey = { it.key },
|
||||
label = "FeedEntryAppBar",
|
||||
) { state ->
|
||||
state.instance.Title(bottomSheetState)
|
||||
}
|
||||
CollapsedTitleClickOverlay(
|
||||
bottomSheetState = bottomSheetState,
|
||||
onExpandSheet = onExpandSheet,
|
||||
)
|
||||
}
|
||||
CollapsedTitleClickOverlay(
|
||||
bottomSheetState = bottomSheetState,
|
||||
onExpandSheet = onExpandSheet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +223,6 @@ private fun BoxScope.TitleBlock(
|
|||
private fun BoxScope.ContentBlock(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
effectiveFadeHeight: Dp,
|
||||
isOpenedInBottomSheet: Boolean,
|
||||
stackState: State<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>>,
|
||||
topBarHeight: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -238,26 +237,13 @@ private fun BoxScope.ContentBlock(
|
|||
child.instance.Content(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.conditionalCompose(
|
||||
condition = !isOpenedInBottomSheet,
|
||||
modifier = {
|
||||
padding(top = topBarHeight)
|
||||
},
|
||||
)
|
||||
.hazeSourceTangem(zIndex = 0f, state = LocalHazeState.current)
|
||||
.conditionalCompose(
|
||||
condition = isOpenedInBottomSheet,
|
||||
modifier = {
|
||||
topFade(
|
||||
height = effectiveFadeHeight,
|
||||
color = TangemTheme.colors2.surface.level2.copy(BASE_FADE_LEVEL),
|
||||
solidStop = .6f,
|
||||
)
|
||||
},
|
||||
.topFade(
|
||||
height = effectiveFadeHeight,
|
||||
color = TangemTheme.colors2.surface.level2.copy(BASE_FADE_LEVEL),
|
||||
solidStop = .6f,
|
||||
),
|
||||
contentPadding = PaddingValues(
|
||||
top = if (isOpenedInBottomSheet) topBarHeight else TangemTheme.dimens2.x2_5,
|
||||
),
|
||||
contentPadding = PaddingValues(top = topBarHeight),
|
||||
bottomSheetState = bottomSheetState,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,13 +127,12 @@ internal fun NewsListContentV2(contentPadding: PaddingValues, state: NewsListUM)
|
|||
)
|
||||
|
||||
TopFade(
|
||||
modifier = Modifier.padding(top = contentPadding.calculateTopPadding()),
|
||||
colorStops = arrayOf(
|
||||
0f to fadeColor,
|
||||
FIRST_STEP to fadeColor.copy(FIRST_STEP_FADE_LEVEL),
|
||||
1f to Color.Transparent,
|
||||
),
|
||||
height = 20.dp + chipsHeight,
|
||||
height = topPadding + TangemTheme.dimens2.x5 + chipsHeight,
|
||||
)
|
||||
|
||||
LazyRow(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorSectionUM
|
||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||
import io.mockk.*
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
|
@ -215,7 +216,10 @@ class BuildTokenSelectorSectionsTransformerTest {
|
|||
|
||||
val prevStateWithSections = TokenSelectorContentUM(
|
||||
sections = persistentListOf(
|
||||
TokenSelectorSectionUM.WalletHeader(walletName = "Old Wallet"),
|
||||
TokenSelectorSectionUM.WalletHeader(
|
||||
walletName = "Old Wallet",
|
||||
deviceIcon = DeviceIconUM.Stub(cardsCount = 1),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -236,6 +240,7 @@ class BuildTokenSelectorSectionsTransformerTest {
|
|||
entries = entries,
|
||||
appCurrency = appCurrency,
|
||||
isBalanceHidden = false,
|
||||
walletIcons = emptyMap(),
|
||||
onTokenSelected = onTokenSelected,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue