Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-13 08:54:47 +02:00
parent 530b2d93ad
commit 822b82d1da
20 changed files with 211 additions and 123 deletions

View file

@ -5,6 +5,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.account.CryptoPortfolioIcon
@ -67,14 +68,20 @@ private fun tokenSelectorPreviewWithAccountHeaders(): TokenSelectorContentUM {
private fun tokenSelectorPreviewMultiWallet(): TokenSelectorContentUM {
return TokenSelectorContentUM(
sections = persistentListOf(
TokenSelectorSectionUM.WalletHeader(walletName = "Cold wallet"),
TokenSelectorSectionUM.WalletHeader(
walletName = "Cold wallet",
deviceIcon = DeviceIconUM.Stub(cardsCount = 2),
),
TokenSelectorSectionUM.TokenGroup(
accountHeader = null,
items = persistentListOf(
previewTokenItem(id = "btc_cold", name = "Bitcoin", symbol = "BTC"),
),
),
TokenSelectorSectionUM.WalletHeader(walletName = "Hot wallet"),
TokenSelectorSectionUM.WalletHeader(
walletName = "Hot wallet",
deviceIcon = DeviceIconUM.Mobile,
),
TokenSelectorSectionUM.TokenGroup(
accountHeader = null,
items = persistentListOf(

View file

@ -2,6 +2,7 @@ package com.tangem.common.ui.markets.tokenselector
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.account.CryptoPortfolioIcon
import kotlinx.collections.immutable.ImmutableList
@ -20,7 +21,10 @@ data class AccountHeaderData(
@Immutable
sealed interface TokenSelectorSectionUM {
data class WalletHeader(val walletName: String) : TokenSelectorSectionUM
data class WalletHeader(
val walletName: String,
val deviceIcon: DeviceIconUM,
) : TokenSelectorSectionUM
data class TokenGroup(
val accountHeader: AccountHeaderData?,

View file

@ -16,9 +16,9 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.common.ui.account.getResId
import com.tangem.common.ui.account.getUiColor
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.image.TangemDeviceIcon
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import kotlinx.collections.immutable.ImmutableList
@ -102,11 +102,9 @@ private fun WalletHeaderSection(section: TokenSelectorSectionUM.WalletHeader) {
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_key_card_20),
TangemDeviceIcon(
state = section.deviceIcon,
modifier = Modifier.size(TangemTheme.dimens2.x5),
tint = TangemTheme.colors2.graphic.neutral.tertiary,
contentDescription = null,
)
}
}

View file

@ -157,6 +157,7 @@ internal class AddToPortfolioModel @Inject constructor(
addToPortfolioManager.onSuccessAdded(result)
channel.close()
}
fun finishOnAddedTokenClick(result: AddToPortfolioManager.Result) {
addToPortfolioManager.onAddedTokenClick(result)
channel.close()

View file

@ -68,6 +68,7 @@ internal class TokenActionsModel @Inject constructor(
isBalanceHidden = isBalanceHidden,
)
}
.flowOn(dispatchers.default)
.stateIn(
scope = modelScope,
started = SharingStarted.Eagerly,
@ -79,7 +80,7 @@ internal class TokenActionsModel @Inject constructor(
analyticsEventHandler.send(event)
val isReceive = handledAction.action == TokenActionsBSContentUM.Action.Receive
if (!isReceive) return@launch
modelScope.launch {
modelScope.launch(dispatchers.default) {
val tokenConfig = receiveAddressesFactory.create(
status = handledAction.cryptoCurrencyData.status,
userWalletId = handledAction.cryptoCurrencyData.userWallet.walletId,

View file

@ -3,15 +3,18 @@ package com.tangem.features.commonfeatures.impl.addtoportfolio.model
import androidx.compose.ui.text.SpanStyle
import com.tangem.common.getTotalCryptoAmount
import com.tangem.common.getTotalFiatAmount
import com.tangem.common.ui.account.*
import com.tangem.common.ui.account.CryptoPortfolioIconConverter
import com.tangem.common.ui.account.getResId
import com.tangem.common.ui.account.getUiColor
import com.tangem.common.ui.account.toUM
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.markets.action.CryptoCurrencyData
import com.tangem.common.ui.markets.action.QuickActionsConverter.quickActions
import com.tangem.common.ui.markets.action.TokenActionsHandler
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.R
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.ds.badge.TangemBadgeIconPosition
import com.tangem.core.ui.ds.badge.TangemBadgeShape
@ -23,7 +26,9 @@ import com.tangem.core.ui.format.bigdecimal.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.features.commonfeatures.impl.addtoportfolio.TokenActionsComponent
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.PortfolioBadgeUM
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM
import java.math.BigDecimal
import javax.inject.Inject
@ -32,6 +37,8 @@ import javax.inject.Inject
internal class TokenActionsUiBuilder @Inject constructor(
paramsContainer: ParamsContainer,
private val designFeatureToggles: DesignFeatureToggles,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val walletIconUMConverter: WalletIconUMConverter,
) {
private val params = paramsContainer.require<TokenActionsComponent.Params>()
@ -112,46 +119,41 @@ internal class TokenActionsUiBuilder @Inject constructor(
params.callbacks.onLaterClick()
},
isBalancesHidden = isBalanceHidden,
portfolioBadge = createPortfolioBadge(cryptoCurrencyData),
portfolioBadge = createPortfolioBadge(cryptoCurrencyData = cryptoCurrencyData),
)
}
private fun createPortfolioBadge(cryptoCurrencyData: CryptoCurrencyData): TangemBadgeUM {
val icon: AccountIconUM?
val name = if (cryptoCurrencyData.isAccountMode) {
icon = CryptoPortfolioIconConverter.convert(cryptoCurrencyData.account.account.icon)
cryptoCurrencyData
private fun createPortfolioBadge(cryptoCurrencyData: CryptoCurrencyData): PortfolioBadgeUM {
return if (cryptoCurrencyData.isAccountMode) {
val icon = CryptoPortfolioIconConverter.convert(cryptoCurrencyData.account.account.icon)
val name = cryptoCurrencyData
.account
.account
.accountName
.toUM()
.value
PortfolioBadgeUM.Account(
badge = TangemBadgeUM(
text = name,
tangemIconUM = TangemIconUM.Icon(
iconRes = icon.value.getResId(),
tintReference = { icon.color.getUiColor() },
),
size = TangemBadgeSize.X6,
shape = TangemBadgeShape.Rounded,
iconPosition = TangemBadgeIconPosition.Start,
shouldRespectIconTint = true,
),
)
} else {
icon = null
stringReference(cryptoCurrencyData.userWallet.name)
val userWallet = cryptoCurrencyData.userWallet
PortfolioBadgeUM.Wallet(
name = stringReference(userWallet.name),
deviceIcon = walletIconUMConverter.convert(
getWalletIconUseCase(cryptoCurrencyData.userWallet),
),
)
}
return TangemBadgeUM(
text = name,
tangemIconUM = if (icon == null) {
TangemIconUM.Icon(
iconRes = R.drawable.ic_key_card_20,
tintReference = { TangemTheme.colors2.graphic.neutral.tertiary },
)
} else {
TangemIconUM.Icon(
iconRes = icon.value.getResId(),
tintReference = { icon.color.getUiColor() },
)
},
size = TangemBadgeSize.X6,
shape = TangemBadgeShape.Rounded,
iconPosition = if (cryptoCurrencyData.isAccountMode) {
TangemBadgeIconPosition.Start
} else {
TangemBadgeIconPosition.End
},
shouldRespectIconTint = cryptoCurrencyData.isAccountMode,
)
}
private fun createSubtitle2State(status: CryptoCurrencyStatus): TokenItemState.Subtitle2State? {

View file

@ -14,6 +14,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource
@ -29,11 +30,12 @@ import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.ds.badge.*
import com.tangem.core.ui.ds.badge.TangemBadge
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.ds.image.TangemDeviceIcon
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.*
@ -43,6 +45,7 @@ import com.tangem.core.ui.format.bigdecimal.price
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.*
import com.tangem.features.commonfeatures.impl.R
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.PortfolioBadgeUM
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM
import dev.chrisbanes.haze.rememberHazeState
import kotlinx.collections.immutable.persistentListOf
@ -166,7 +169,7 @@ private fun ActionRow(
private fun TokenHeader(
addedToken: TokenItemState,
isBalanceHidden: Boolean,
portfolioBadge: TangemBadgeUM?,
portfolioBadge: PortfolioBadgeUM,
modifier: Modifier = Modifier,
) {
Column(
@ -209,8 +212,38 @@ private fun TokenHeader(
SpacerH(TangemTheme.dimens2.x7)
if (portfolioBadge == null) return
TangemBadge(portfolioBadge)
when (portfolioBadge) {
is PortfolioBadgeUM.None -> Unit
is PortfolioBadgeUM.Account -> TangemBadge(portfolioBadge.badge)
is PortfolioBadgeUM.Wallet -> WalletPortfolioRow(
name = portfolioBadge.name,
deviceIcon = portfolioBadge.deviceIcon,
)
}
}
}
@Composable
private fun WalletPortfolioRow(name: TextReference, deviceIcon: DeviceIconUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.heightIn(TangemTheme.dimens2.x6)
.clip(RoundedCornerShape(TangemTheme.dimens2.x4))
.background(TangemTheme.colors2.markers.backgroundSolidGray)
.padding(start = TangemTheme.dimens2.x3, end = TangemTheme.dimens2.x2),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
Text(
text = name.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.markers.textGray,
maxLines = 1,
)
TangemDeviceIcon(
state = deviceIcon,
modifier = Modifier.size(TangemTheme.dimens2.x4),
)
}
}
@ -280,15 +313,9 @@ private class TokenActionsContentPreviewProviderV2 : PreviewParameterProvider<To
),
token = tokenState,
onLaterClick = {},
portfolioBadge = TangemBadgeUM(
text = stringReference("Wallet 2"),
tangemIconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_key_card_20,
tintReference = { TangemTheme.colors2.graphic.neutral.tertiary },
),
size = TangemBadgeSize.X6,
shape = TangemBadgeShape.Rounded,
iconPosition = TangemBadgeIconPosition.End,
portfolioBadge = PortfolioBadgeUM.Wallet(
name = stringReference("Wallet 2"),
deviceIcon = DeviceIconUM.Stub(cardsCount = 2),
),
),
)

View file

@ -1,13 +1,23 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.markets.action.QuickActions
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.ds.badge.TangemBadgeUM
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.TextReference
internal data class TokenActionsUM(
val token: TokenItemState,
val quickActions: QuickActions,
val onLaterClick: () -> Unit,
val isBalancesHidden: Boolean = false,
val portfolioBadge: TangemBadgeUM? = null,
)
val portfolioBadge: PortfolioBadgeUM = PortfolioBadgeUM.None,
)
@Immutable
internal sealed interface PortfolioBadgeUM {
data class Account(val badge: TangemBadgeUM) : PortfolioBadgeUM
data class Wallet(val name: TextReference, val deviceIcon: DeviceIconUM) : PortfolioBadgeUM
data object None : PortfolioBadgeUM
}

View file

@ -1,8 +1,10 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.state
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
import com.tangem.features.commonfeatures.api.addtoportfolio.AvailableToAddData
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM
@ -17,6 +19,8 @@ import kotlinx.coroutines.flow.*
internal class UserPortfolioStateController @AssistedInject constructor(
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val walletIconUMConverter: WalletIconUMConverter,
@Assisted private val modelScope: CoroutineScope,
@Assisted private val onTokenSelected: (AddToPortfolioManager.Result) -> Unit,
) {
@ -36,6 +40,9 @@ internal class UserPortfolioStateController @AssistedInject constructor(
rawCurrencyId = rawCurrencyId,
appCurrency = appCurrency,
isBalanceHidden = isBalanceHidden,
resolveWalletDeviceIcon = {
walletIconUMConverter.convert(getWalletIconUseCase(it))
},
onTokenSelected = onTokenSelected,
).transform()
}

View file

@ -6,6 +6,7 @@ import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToI
import com.tangem.common.ui.markets.tokenselector.*
import com.tangem.core.ui.components.marketprice.PriceChangeState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
@ -31,6 +32,7 @@ internal class UserPortfolioSectionsTransformer(
private val rawCurrencyId: CryptoCurrency.RawID,
private val appCurrency: AppCurrency,
private val isBalanceHidden: Boolean,
private val resolveWalletDeviceIcon: (UserWallet) -> DeviceIconUM,
private val onTokenSelected: (AddToPortfolioManager.Result) -> Unit,
) {
@ -68,8 +70,12 @@ internal class UserPortfolioSectionsTransformer(
for ((_, walletEntries) in byWallet) {
if (shouldShowWalletHeaders) {
val wallet = walletEntries.first().wallet
sections.add(
TokenSelectorSectionUM.WalletHeader(walletName = walletEntries.first().wallet.name),
TokenSelectorSectionUM.WalletHeader(
walletName = wallet.name,
deviceIcon = resolveWalletDeviceIcon(wallet),
),
)
}

View file

@ -2,10 +2,12 @@ package com.tangem.features.commonfeatures.impl.portfolioselector
import com.tangem.common.ui.account.AccountPortfolioItemUMConverter
import com.tangem.common.ui.userwallet.converter.UserWalletItemUMConverter
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
@ -15,6 +17,7 @@ import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioFetcher
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
import com.tangem.features.commonfeatures.impl.R
@ -33,6 +36,8 @@ internal class PortfolioSelectorModel @Inject constructor(
paramsContainer: ParamsContainer,
walletImageFetcher: UserWalletImageFetcher,
isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val getWalletIconUseCase: GetWalletIconUseCase,
private val walletIconUMConverter: WalletIconUMConverter,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
@ -173,6 +178,7 @@ internal class PortfolioSelectorModel @Inject constructor(
val walletTitle = PortfolioSelectorItemUM.GroupTitle(
id = "GroupTitle ${wallet.walletId.stringValue}",
name = stringReference(wallet.name),
deviceIcon = walletIconUMConverter.convert(getWalletIconUseCase(wallet)),
)
add(walletTitle)
@ -204,6 +210,7 @@ internal class PortfolioSelectorModel @Inject constructor(
val lockedWalletsTitle = PortfolioSelectorItemUM.GroupTitle(
id = "lockedWalletsTitleId",
name = resourceReference(R.string.common_locked_wallets),
deviceIcon = DeviceIconUM.Stub(cardsCount = 1),
)
return listOf(lockedWalletsTitle) + wallets

View file

@ -2,6 +2,7 @@ package com.tangem.features.commonfeatures.impl.portfolioselector.entity
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
@ -17,6 +18,7 @@ sealed interface PortfolioSelectorItemUM {
data class GroupTitle(
override val id: String,
val name: TextReference,
val deviceIcon: DeviceIconUM,
) : PortfolioSelectorItemUM
data class Portfolio(

View file

@ -28,6 +28,7 @@ import com.tangem.common.ui.account.AccountIconPreviewData
import com.tangem.common.ui.userwallet.UserWalletItemRow
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.common.ui.userwallet.state.UserWalletItemUM.ImageState
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
@ -172,12 +173,14 @@ internal object PortfolioSelectorPreviewData {
PortfolioSelectorItemUM.GroupTitle(
id = UUID.randomUUID().toString(),
name = stringReference("Tangem 2.0"),
deviceIcon = DeviceIconUM.Stub(cardsCount = 2),
),
PortfolioSelectorItemUM.Portfolio(accountItem, false),
PortfolioSelectorItemUM.Portfolio(lockedAccountItem, false),
PortfolioSelectorItemUM.GroupTitle(
id = UUID.randomUUID().toString(),
name = stringReference("Tangem White"),
deviceIcon = DeviceIconUM.Stub(cardsCount = 1),
),
PortfolioSelectorItemUM.Portfolio(accountItem, true),
)
@ -187,6 +190,7 @@ internal object PortfolioSelectorPreviewData {
PortfolioSelectorItemUM.GroupTitle(
id = UUID.randomUUID().toString(),
name = resourceReference(R.string.common_locked_wallets),
deviceIcon = DeviceIconUM.Stub(cardsCount = 1),
),
PortfolioSelectorItemUM.Portfolio(lockedWalletItem, false),
PortfolioSelectorItemUM.Portfolio(lockedWalletItem, false),
@ -204,6 +208,7 @@ internal object PortfolioSelectorPreviewData {
PortfolioSelectorItemUM.GroupTitle(
id = UUID.randomUUID().toString(),
name = resourceReference(R.string.common_locked_wallets),
deviceIcon = DeviceIconUM.Stub(cardsCount = 1),
),
PortfolioSelectorItemUM.Portfolio(lockedWalletItem, false),
PortfolioSelectorItemUM.Portfolio(lockedWalletItem, false),

View file

@ -8,16 +8,13 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -26,10 +23,10 @@ import com.tangem.common.ui.userwallet.CardImage
import com.tangem.common.ui.userwallet.getBalanceValueAndFlickerState
import com.tangem.common.ui.userwallet.getInformationValue
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.image.TangemDeviceIcon
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.conditional
@ -207,13 +204,11 @@ private fun WalletNameRow(model: PortfolioSelectorItemUM.GroupTitle, modifier: M
overflow = TextOverflow.Ellipsis,
)
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_key_card_20),
TangemDeviceIcon(
state = model.deviceIcon,
modifier = Modifier
.align(Alignment.Bottom)
.size(TangemTheme.dimens2.x5),
tint = TangemTheme.colors2.graphic.neutral.tertiary,
contentDescription = null,
)
}
}

View file

@ -87,6 +87,7 @@ internal class NewsListModel @Inject constructor(
init {
observeNewsList()
batchFlowManager.reload()
loadCategories()
}

View file

@ -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,
),
)
}
}
}

View file

@ -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),
),
)
}

View file

@ -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,
)
}

View file

@ -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(

View file

@ -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,
)
}