Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-17 00:31:23 +04:00
parent 0680fb9e7e
commit 84198eb103
84 changed files with 601 additions and 7129 deletions

View file

@ -5,7 +5,6 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.navigation.Route
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
@ -15,7 +14,6 @@ import com.tangem.features.feed.components.earn.DefaultEarnComponent
import com.tangem.features.feed.components.feed.DefaultFeedComponent
import com.tangem.features.feed.components.feed.DefaultFeedComponent.FeedParams
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent
import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent
import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent
@ -32,12 +30,10 @@ import javax.inject.Inject
@Suppress("LongParameterList")
internal class FeedEntryChildFactory @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
private val portfolioComponentFactory: MarketsPortfolioComponent.Factory,
private val portfolioBlockComponentFactory: PortfolioBlockComponent.Factory,
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
private val manageFundsComponentFactory: ManageFundsComponent.Factory,
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
private val designFeatureToggles: DesignFeatureToggles,
private val forYouComponentFactory: ForYouComponent.Factory,
private val tokenSummaryComponentFactory: TokenSummaryComponent.Factory,
private val marketingBannerComponentFactory: MarketingBannerComponent.Factory,
@ -100,9 +96,7 @@ internal class FeedEntryChildFactory @Inject constructor(
appComponentContext = appComponentContext,
params = child.params,
analyticsEventHandler = analyticsEventHandler,
portfolioComponentFactory = portfolioComponentFactory,
portfolioBlockComponentFactory = portfolioBlockComponentFactory,
designFeatureToggles = designFeatureToggles,
addToPortfolioComponentFactory = addToPortfolioComponentFactory,
manageFundsComponentFactory = manageFundsComponentFactory,
marketingBannerComponentFactory = marketingBannerComponentFactory,

View file

@ -21,8 +21,6 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.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
@ -31,9 +29,6 @@ import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
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.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.earn.PreselectedEarnType
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
@ -60,44 +55,31 @@ internal class DefaultEarnComponent(
@Composable
override fun Title(bottomSheetState: State<BottomSheetState>) {
val background = LocalMainBottomSheetColor.current.value
val state by earnModel.state.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) {
TangemTopBar(
title = resourceReference(R.string.earn_title),
type = if (LocalIsOpenedInBottomSheet.current) {
TangemTopBarType.BottomSheet
} else {
TangemTopBarType.Default
},
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = state.onBackClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
)
} else {
TangemTopAppBar(
containerColor = background,
title = stringResourceSafe(R.string.earn_title),
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_back_24,
onClicked = state.onBackClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
),
)
}
TangemTopBar(
title = resourceReference(R.string.earn_title),
type = if (LocalIsOpenedInBottomSheet.current) {
TangemTopBarType.BottomSheet
} else {
TangemTopBarType.Default
},
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = state.onBackClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
)
}
@Composable

View file

@ -18,7 +18,6 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
@ -29,7 +28,6 @@ import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
import com.tangem.features.commonfeatures.api.managefunds.ManageFundsComponent
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent
import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockParentClickIntents
import com.tangem.features.feed.model.market.details.MarketsTokenDetailsModel
@ -46,8 +44,6 @@ import kotlinx.serialization.Serializable
internal class DefaultMarketsTokenDetailsComponent(
appComponentContext: AppComponentContext,
analyticsEventHandler: AnalyticsEventHandler,
designFeatureToggles: DesignFeatureToggles,
portfolioComponentFactory: MarketsPortfolioComponent.Factory,
portfolioBlockComponentFactory: PortfolioBlockComponent.Factory,
val params: Params,
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
@ -69,21 +65,8 @@ internal class DefaultMarketsTokenDetailsComponent(
params = MarketingBannerComponent.Params.Standalone(requestFlow = model.marketingRequest),
)
private val portfolioComponent: MarketsPortfolioComponent? =
if (updatedParams.shouldShowPortfolio && !designFeatureToggles.isRedesignEnabled) {
portfolioComponentFactory.create(
context = child("my_portfolio"),
params = MarketsPortfolioComponent.Params(
updatedParams.token,
analyticsParams = analyticsParams?.source?.let { MarketsPortfolioComponent.AnalyticsParams(it) },
),
)
} else {
null
}
private val portfolioBlockComponent: PortfolioBlockComponent? =
if (updatedParams.shouldShowPortfolio && designFeatureToggles.isRedesignEnabled) {
if (updatedParams.shouldShowPortfolio) {
portfolioBlockComponentFactory.create(
context = child("portfolio_block"),
params = PortfolioBlockComponent.Params(token = updatedParams.token),
@ -126,11 +109,9 @@ internal class DefaultMarketsTokenDetailsComponent(
when (state) {
is TokenNetworksState.NetworksAvailable -> {
portfolioBlockComponent?.setTokenNetworks(state.networks)
portfolioComponent?.setTokenNetworks(state.networks)
}
TokenNetworksState.NoNetworksAvailable -> {
portfolioBlockComponent?.setNoNetworksAvailable()
portfolioComponent?.setNoNetworksAvailable()
}
else -> {}
}
@ -180,7 +161,6 @@ internal class DefaultMarketsTokenDetailsComponent(
val state by model.state.collectAsStateWithLifecycle()
MarketsTokenDetailsTitle(
state = state,
backgroundColor = LocalMainBottomSheetColor.current.value,
isBackButtonEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
onBackClick = { params.onBackClicked() },
)
@ -211,11 +191,6 @@ internal class DefaultMarketsTokenDetailsComponent(
modifier = modifier,
backgroundColor = LocalMainBottomSheetColor.current.value,
state = state,
portfolioBlock = portfolioComponent?.let { component ->
{ blockModifier ->
component.Content(blockModifier)
}
},
portfolioFloatingBlock = portfolioBlockComponent?.let { component ->
{ blockModifier ->
component.Content(blockModifier)

View file

@ -1,30 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.api
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.markets.TokenMarketParams
import kotlinx.serialization.Serializable
@Deprecated("Will be removed in favor of PortfolioComponent with redesigned UI")
@Stable
interface MarketsPortfolioComponent : ComposableContentComponent {
@Serializable
data class Params(
val token: TokenMarketParams,
val analyticsParams: AnalyticsParams?,
)
@Serializable
data class AnalyticsParams(
val source: String,
)
fun setTokenNetworks(networks: List<TokenMarketInfo.Network>)
fun setNoNetworksAvailable()
interface Factory : ComponentFactory<Params, MarketsPortfolioComponent>
}

View file

@ -1,87 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.impl.model.MarketsPortfolioModel
import com.tangem.features.feed.components.market.details.portfolio.impl.model.MarketsPortfolioRoute
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.MyPortfolio
import com.tangem.features.tokenreceive.TokenReceiveComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Stable
internal class DefaultMarketsPortfolioComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: MarketsPortfolioComponent.Params,
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
) : AppComponentContext by context, MarketsPortfolioComponent {
private val model: MarketsPortfolioModel = getOrCreateModel(params)
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = MarketsPortfolioRoute.serializer(),
handleBackButton = false,
childFactory = ::bottomSheetChild,
)
override fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) {
model.setTokenNetworks(networks)
}
override fun setNoNetworksAvailable() {
model.setNoNetworksAvailable()
}
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState()
MyPortfolio(modifier = modifier, state = state)
bottomSheet.child?.instance?.BottomSheet()
}
private fun bottomSheetChild(
config: MarketsPortfolioRoute,
componentContext: ComponentContext,
): ComposableBottomSheetComponent = when (config) {
MarketsPortfolioRoute.AddToPortfolio -> addToPortfolioComponentFactory.create(
context = childByContext(componentContext),
params = AddToPortfolioComponent.Params(
addToPortfolioManager = model.addToPortfolioManager,
),
)
is MarketsPortfolioRoute.TokenReceive -> tokenReceiveComponentFactory.create(
context = childByContext(componentContext),
params = TokenReceiveComponent.Params(
config = config.config,
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
}
@AssistedFactory
interface Factory : MarketsPortfolioComponent.Factory {
override fun create(
context: AppComponentContext,
params: MarketsPortfolioComponent.Params,
): DefaultMarketsPortfolioComponent
}
}

View file

@ -1,41 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.analytics
import com.tangem.common.ui.markets.action.TokenActionsBSContentUM
import com.tangem.core.analytics.models.AnalyticsEvent
internal class PortfolioAnalyticsEvent(
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category = "Markets / Chart", event = event, params = params) {
data class EventBuilder(
val tokenSymbol: String,
val source: String?,
) {
fun addToPortfolioClicked() = PortfolioAnalyticsEvent(
event = "Button - Add To Portfolio",
params = buildMap {
put("Token", tokenSymbol)
if (source != null) put("Source", source)
},
)
fun quickActionClick(actionUM: TokenActionsBSContentUM.Action, blockchainName: String) =
PortfolioAnalyticsEvent(
event = when (actionUM) {
TokenActionsBSContentUM.Action.Buy -> "Button - Buy"
TokenActionsBSContentUM.Action.Receive -> "Button - Receive"
TokenActionsBSContentUM.Action.Exchange -> "Button - Swap"
TokenActionsBSContentUM.Action.Stake -> "Button - Stake"
TokenActionsBSContentUM.Action.YieldMode -> "Button - Yield Mode"
else -> "error"
},
params = buildMap {
put("Token", tokenSymbol)
if (source != null) put("Source", source)
put("blockchain", blockchainName)
},
)
}
}

View file

@ -1,20 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.di
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.impl.DefaultMarketsPortfolioComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ComponentModule {
@Binds
@Singleton
fun bindMarketsPortfolioComponent(
factory: DefaultMarketsPortfolioComponent.Factory,
): MarketsPortfolioComponent.Factory
}

View file

@ -1,20 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.feed.components.market.details.portfolio.impl.model.MarketsPortfolioModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(ModelComponent::class)
internal interface ModelModule {
@Binds
@IntoMap
@ClassKey(MarketsPortfolioModel::class)
fun provideMarketsPortfolioModel(model: MarketsPortfolioModel): Model
}

View file

@ -1,347 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.model
import arrow.core.getOrElse
import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.account.CryptoPortfolioIconConverter
import com.tangem.common.ui.account.toUM
import com.tangem.common.ui.markets.action.CryptoCurrencyData
import com.tangem.common.ui.markets.action.TokenActionsHandler
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.yield.supply.models.YieldSupplyAvailability
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetAvailabilityUseCase
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.MyPortfolioUM
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.PortfolioHeader
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.PortfolioListItem
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.WalletHeader
import com.tangem.utils.extensions.isZero
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("LongParameterList")
internal class MarketsPortfolioDelegate @AssistedInject constructor(
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val allAccountSupplier: MultiAccountStatusListSupplier,
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCaseV2,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val yieldSupplyGetAvailabilityUseCase: YieldSupplyGetAvailabilityUseCase,
private val designFeatureToggles: DesignFeatureToggles,
isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
@Assisted private val scope: CoroutineScope,
@Assisted private val token: TokenMarketParams,
@Assisted private val tokenActionsHandler: TokenActionsHandler,
@Assisted private val buttonState: Flow<MyPortfolioUM.Tokens.AddButtonState>,
@Assisted private val onAddClick: () -> Unit,
) {
private val currencyRawId: CryptoCurrency.RawID = token.id
private var expandedHolder: MutableStateFlow<Set<Pair<UserWalletId, CryptoCurrency.ID>>>? = null
private val settingsFlow: Flow<SettingsBox> = combine(
flow = getSelectedAppCurrencyUseCase.invokeOrDefault(),
flow2 = getBalanceHidingSettingsUseCase.isBalanceHidden(),
flow3 = isAccountsModeEnabledUseCase(),
transform = ::SettingsBox,
).shareIn(
replay = 1,
started = SharingStarted.Eagerly,
scope = scope,
).distinctUntilChanged()
private val availableNetworks = MutableSharedFlow<List<TokenMarketInfo.Network>>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) {
availableNetworks.tryEmit(networks)
}
fun combineData(): Flow<MyPortfolioUM> {
return availableNetworks.transformLatest { availableNetworks ->
when {
availableNetworks.isEmpty() -> emit(MyPortfolioUM.Unavailable)
else -> emitAll(onAvailableNetworksFlow().distinctUntilChanged())
}
}.distinctUntilChanged()
}
private fun onAvailableNetworksFlow(): Flow<MyPortfolioUM> =
portfolioWithThisCurrencyFLow().transformLatest { portfolioWithCurrency ->
when (portfolioWithCurrency.flattenAddedCurrency.isEmpty()) {
false -> emitAll(contentFlow(portfolioWithCurrency).distinctUntilChanged())
true -> when (portfolioWithCurrency.hasMultiWallets) {
true -> emitAll(addFirstTokenFlow())
false -> emit(MyPortfolioUM.UnavailableForWallet)
}
}
}
private fun addFirstTokenFlow(): Flow<MyPortfolioUM> = buttonState.map { state ->
when (state) {
MyPortfolioUM.Tokens.AddButtonState.Loading -> MyPortfolioUM.Loading
MyPortfolioUM.Tokens.AddButtonState.Available -> MyPortfolioUM.AddFirstToken(onAddClick = onAddClick)
MyPortfolioUM.Tokens.AddButtonState.Unavailable -> MyPortfolioUM.Unavailable
}
}
private fun contentFlow(portfoliosWithThisCurrency: PortfoliosWithThisCurrency): Flow<MyPortfolioUM.Content> {
fun Portfolio.actionsFoAccountCurrencies(): List<Flow<Pair<CryptoCurrency, TokenActionsState>>> =
accountsWithAdded.map { account ->
fun CryptoCurrencyStatus.actionsFlow(): Flow<Pair<CryptoCurrency, TokenActionsState>> = flow {
val yieldSupplyAvailability = yieldSupplyGetAvailabilityUseCase(this@actionsFlow.currency)
.getOrElse { YieldSupplyAvailability.Unavailable }
emitAll(
getCryptoCurrencyActionsUseCase(
accountId = account.accountStatus.account.accountId,
currency = this@actionsFlow.currency,
yieldSupplyAvailability = yieldSupplyAvailability,
).map { actionsState -> actionsState.cryptoCurrencyStatus.currency to actionsState },
)
}
account.addedCurrency.map { it.actionsFlow() }
}.flatten()
val allAddedTokenActions =
portfoliosWithThisCurrency.portfolios.map { portfolio -> portfolio.actionsFoAccountCurrencies() }.flatten()
return combine(
flow = combine(allAddedTokenActions) { it.toMap() }.distinctUntilChanged(),
flow2 = buttonState.distinctUntilChanged(),
flow3 = getExpandedHolder(portfoliosWithThisCurrency),
flow4 = settingsFlow.distinctUntilChanged(),
transform = { actions, addButtonState, expanded, settings ->
buildContentState(
portfolio = portfoliosWithThisCurrency,
allActions = actions,
addButtonState = addButtonState,
expanded = expanded,
settings = settings,
)
},
)
}
private fun getExpandedHolder(
portfolio: PortfoliosWithThisCurrency,
): StateFlow<Set<Pair<UserWalletId, CryptoCurrency.ID>>> {
val expandedHolder = this.expandedHolder
if (expandedHolder != null) return expandedHolder
val allAddedCurrency = portfolio.flattenAddedCurrency
val shouldForceExpand = allAddedCurrency.size == 1 &&
allAddedCurrency.first().value.amount?.isZero() == true
val initValue = when {
shouldForceExpand -> {
val currency = allAddedCurrency.first()
// find userWallet than have this single added token
portfolio.portfolios
.find { it.accountsWithAdded.any { account -> account.addedCurrency.isNotEmpty() } }
?.userWallet
?.let { setOf(it.walletId to currency.currency.id) }
.orEmpty()
}
else -> emptySet()
}
return MutableStateFlow(initValue)
.also { this.expandedHolder = it }
}
private fun portfolioWithThisCurrencyFLow(): Flow<PortfoliosWithThisCurrency> =
allAccountSupplier().map { list -> list.map { it.addedAccountsFlow() } }.flatMapLatest { flows ->
combine(flows) { portfolios ->
PortfoliosWithThisCurrency(
currencyRawId = currencyRawId,
portfolios = portfolios.toList(),
)
}
}.distinctUntilChanged()
private fun AccountStatusList.addedAccountsFlow(): Flow<Portfolio> =
getUserWalletUseCase.invokeFlow(this.userWalletId).mapNotNull { it.getOrNull() }.map { wallet ->
Portfolio(
userWallet = wallet,
accountStatusList = this,
accountsWithAdded = this.filterByRawID(),
)
}.distinctUntilChanged()
private fun AccountStatusList.filterByRawID(): List<AccountWithAdded> {
fun AccountStatus.CryptoPortfolio.filterByRawID(): List<CryptoCurrencyStatus> = tokenList.flattenCurrencies()
.filter { status ->
val currencyId = status.currency.id.rawCurrencyId ?: return@filter false
getTokenIdIfL2Network(currencyId.value) == currencyRawId.value
}
return accountStatuses.filterCryptoPortfolio().map { accountStatus ->
AccountWithAdded(
accountStatus = accountStatus,
addedCurrency = accountStatus.filterByRawID(),
)
}
}
private fun buildContentState(
portfolio: PortfoliosWithThisCurrency,
allActions: Map<CryptoCurrency, TokenActionsState>,
addButtonState: MyPortfolioUM.Tokens.AddButtonState,
expanded: Set<Pair<UserWalletId, CryptoCurrency.ID>>,
settings: SettingsBox,
): MyPortfolioUM.Content {
val appCurrency = settings.appCurrency
val isBalanceHidden = settings.isBalanceHidden
val isAccountMode = settings.isAccountMode
val uiItems: MutableList<PortfolioListItem> = mutableListOf()
fun toggleQuickActions(key: Pair<UserWalletId, CryptoCurrency.ID>) = expandedHolder?.update { expanded ->
val isExpand = expanded.contains(key)
if (isExpand) expanded.minus(key) else expanded.plus(key)
}
val tokenUMConverter = PortfolioTokenUMConverter(
appCurrency = appCurrency,
isBalanceHidden = isBalanceHidden,
onTokenItemClick = { },
tokenActionsHandler = tokenActionsHandler,
isRedesignEnabled = designFeatureToggles.isRedesignEnabled,
)
portfolio.portfolios.forEach { portfolioItem ->
if (portfolioItem.flattenAddedCurrency.isEmpty()) return@forEach
val userWallet = portfolioItem.userWallet
if (isAccountMode) {
uiItems.add(portfolioItem.userWallet.toWalletHeader())
} else {
uiItems.add(portfolioItem.userWallet.toWalletPortfolioHeader())
}
portfolioItem.accountsWithAdded.forEach { accountWithAdded ->
if (accountWithAdded.addedCurrency.isEmpty()) return@forEach
if (isAccountMode) {
val account = accountWithAdded.accountStatus.account
uiItems.add(account.toAccountPortfolioHeader())
}
accountWithAdded.addedCurrency.forEach { currencyStatus ->
val actions = allActions[currencyStatus.currency]?.states.orEmpty()
val value = CryptoCurrencyData(
userWallet = userWallet,
status = currencyStatus,
actions = actions,
isAccountMode = isAccountMode,
account = accountWithAdded.accountStatus,
)
val expandedKey = portfolioItem.userWallet.walletId to currencyStatus.currency.id
val isExpand = expanded.contains(expandedKey)
val tokenItem = tokenUMConverter.convertV2(
onTokenItemClick = { wallet, status ->
toggleQuickActions(wallet.walletId to status.currency.id)
},
value = value,
isQuickActionsShown = isExpand,
)
uiItems.add(tokenItem)
}
}
}
return MyPortfolioUM.Content(
items = uiItems.toImmutableList(),
buttonState = addButtonState,
onAddClick = onAddClick,
)
}
private fun Account.CryptoPortfolio.toAccountPortfolioHeader(): PortfolioHeader = PortfolioHeader(
id = this.accountId.value,
state = AccountTitleUM.Account(
prefixText = TextReference.EMPTY,
name = this.accountName.toUM().value,
icon = CryptoPortfolioIconConverter.convert(this.icon),
),
)
private fun UserWallet.toWalletPortfolioHeader(): PortfolioHeader = PortfolioHeader(
id = this.walletId.stringValue,
state = AccountTitleUM.Text(
title = stringReference(this.name),
),
)
private fun UserWallet.toWalletHeader(): WalletHeader = WalletHeader(
id = this.walletId.stringValue,
name = stringReference(this.name),
)
@Suppress("LongParameterList")
@AssistedFactory
interface Factory {
fun create(
scope: CoroutineScope,
token: TokenMarketParams,
tokenActionsHandler: TokenActionsHandler,
buttonState: Flow<MyPortfolioUM.Tokens.AddButtonState>,
onAddClick: () -> Unit,
): MarketsPortfolioDelegate
}
}
private data class PortfoliosWithThisCurrency(
val currencyRawId: CryptoCurrency.RawID,
val portfolios: List<Portfolio>,
) {
val hasMultiWallets: Boolean = portfolios.any { it.userWallet.isMultiCurrency }
val flattenAddedCurrency: List<CryptoCurrencyStatus> =
portfolios.map { portfolio -> portfolio.flattenAddedCurrency }.flatten()
}
private data class Portfolio(
val userWallet: UserWallet,
val accountStatusList: AccountStatusList,
val accountsWithAdded: List<AccountWithAdded>,
) {
val flattenAddedCurrency: List<CryptoCurrencyStatus> =
accountsWithAdded.map { it.addedCurrency }.flatten()
}
private data class AccountWithAdded(
val addedCurrency: List<CryptoCurrencyStatus>,
val accountStatus: AccountStatus.CryptoPortfolio,
)
private data class SettingsBox(
val appCurrency: AppCurrency,
val isBalanceHidden: Boolean,
val isAccountMode: Boolean,
)

View file

@ -1,170 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.markets.action.TokenActionsBSContentUM
import com.tangem.common.ui.markets.action.TokenActionsHandler
import com.tangem.core.analytics.api.AnalyticsEventHandler
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.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.transaction.usecase.ReceiveAddressesFactory
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.impl.analytics.PortfolioAnalyticsEvent
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.MyPortfolioUM
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
@Stable
@ModelScoped
internal class MarketsPortfolioModel @Inject constructor(
paramsContainer: ParamsContainer,
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
private val marketsPortfolioDelegateFactory: MarketsPortfolioDelegate.Factory,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val tokenActionsHandlerFactory: TokenActionsHandler.Factory,
private val receiveAddressesFactory: ReceiveAddressesFactory,
private val analyticsEventHandler: AnalyticsEventHandler,
private val appRouter: AppRouter,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
val state: StateFlow<MyPortfolioUM>
field = MutableStateFlow<MyPortfolioUM>(value = MyPortfolioUM.Loading)
private val params = paramsContainer.require<MarketsPortfolioComponent.Params>()
private val analyticsEventBuilder = PortfolioAnalyticsEvent.EventBuilder(
tokenSymbol = params.token.symbol,
source = params.analyticsParams?.source,
)
private val currentAppCurrency = createAppCurrencyFlow()
private val tokenActionsHandler = createTokenActionsHandler()
val addToPortfolioManager: AddToPortfolioManager = createAddToPortfolioManager()
private val marketsPortfolioDelegate: MarketsPortfolioDelegate = createMarketsPortfolioDelegate()
val bottomSheetNavigation: SlotNavigation<MarketsPortfolioRoute> = SlotNavigation()
init {
marketsPortfolioDelegate.combineData()
.onEach { state.value = it }
.flowOn(dispatchers.default)
.launchIn(modelScope)
addToPortfolioManager.onDismiss.receiveAsFlow()
.onEach { bottomSheetNavigation.dismiss() }
.launchIn(modelScope)
addToPortfolioManager.onSuccessAdded.receiveAsFlow()
.onEach { bottomSheetNavigation.dismiss() }
.launchIn(modelScope)
addToPortfolioManager.onAddedTokenClick.receiveAsFlow()
.onEach { result ->
bottomSheetNavigation.dismiss()
appRouter.push(
AppRoute.CurrencyDetails(
userWalletId = result.wallet.walletId,
currency = result.addedCurrency.currency,
),
)
}
.launchIn(modelScope)
}
fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) {
addToPortfolioManager.setTokenNetworks(networks)
marketsPortfolioDelegate.setTokenNetworks(networks)
}
fun setNoNetworksAvailable() {
addToPortfolioManager.setTokenNetworks(emptyList())
marketsPortfolioDelegate.setTokenNetworks(emptyList())
}
private fun createAddToPortfolioManager(): AddToPortfolioManager {
return addToPortfolioManagerFactory.create(
scope = modelScope,
settings = AddToPortfolioManager.Settings.DefaultMarket,
analyticsParams = AddToPortfolioManager.AnalyticsParams(params.analyticsParams?.source),
).also { addToPortfolioManager -> addToPortfolioManager.setTokenParams(params.token) }
}
private fun createMarketsPortfolioDelegate(): MarketsPortfolioDelegate {
return marketsPortfolioDelegateFactory.create(
scope = modelScope,
token = params.token,
tokenActionsHandler = tokenActionsHandler,
buttonState = addToPortfolioManager.state.map { state ->
when (state) {
is AddToPortfolioManager.State.Ready -> {
if (state.isAvailableToAdd) {
MyPortfolioUM.Tokens.AddButtonState.Available
} else {
MyPortfolioUM.Tokens.AddButtonState.Unavailable
}
}
AddToPortfolioManager.State.Loading -> MyPortfolioUM.Tokens.AddButtonState.Loading
}
},
onAddClick = {
analyticsEventHandler.send(analyticsEventBuilder.addToPortfolioClicked())
bottomSheetNavigation.activate(MarketsPortfolioRoute.AddToPortfolio)
},
)
}
private fun createAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->
maybeAppCurrency.getOrElse { AppCurrency.Default }
}
.stateIn(
scope = modelScope,
started = SharingStarted.Eagerly,
initialValue = AppCurrency.Default,
)
}
private fun createTokenActionsHandler(): TokenActionsHandler {
return tokenActionsHandlerFactory.create(
currentAppCurrency = Provider { currentAppCurrency.value },
onHandleQuickAction = { handledAction, _ ->
val currency = handledAction.cryptoCurrencyData.status.currency
analyticsEventHandler.send(
analyticsEventBuilder.quickActionClick(
actionUM = handledAction.action,
blockchainName = currency.network.name,
),
)
configureReceiveAddresses(handledAction)
},
coroutineScope = modelScope,
)
}
private fun configureReceiveAddresses(quickAction: TokenActionsHandler.HandledQuickAction) {
val isNewReceive = quickAction.action == TokenActionsBSContentUM.Action.Receive
if (isNewReceive) {
modelScope.launch {
val tokenConfig = receiveAddressesFactory.create(
status = quickAction.cryptoCurrencyData.status,
userWalletId = quickAction.cryptoCurrencyData.userWallet.walletId,
) ?: return@launch
bottomSheetNavigation.activate(MarketsPortfolioRoute.TokenReceive(tokenConfig))
}
}
}
}

View file

@ -1,17 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.model
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.models.TokenReceiveConfig
import kotlinx.serialization.Serializable
@Serializable
sealed interface MarketsPortfolioRoute : Route {
@Serializable
data object AddToPortfolio : MarketsPortfolioRoute
@Serializable
data class TokenReceive(
val config: TokenReceiveConfig,
) : MarketsPortfolioRoute
}

View file

@ -1,72 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.model
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.tokens.TokenItemStateConverter
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.PortfolioTokenUM
import com.tangem.utils.converter.Converter
/**
* Converter from [UserWallet] and [CryptoCurrencyStatus] to [PortfolioTokenUM]
*
[REDACTED_AUTHOR]
*/
internal class PortfolioTokenUMConverter(
private val appCurrency: AppCurrency,
private val isBalanceHidden: Boolean,
private val onTokenItemClick: (CryptoCurrencyStatus) -> Unit,
private val tokenActionsHandler: TokenActionsHandler,
private val isRedesignEnabled: Boolean,
) : Converter<CryptoCurrencyData, PortfolioTokenUM> {
fun convertV2(
value: CryptoCurrencyData,
isQuickActionsShown: Boolean,
onTokenItemClick: (UserWallet, CryptoCurrencyStatus) -> Unit,
): PortfolioTokenUM {
val tokenItemStateConverter = TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = { _, status -> onTokenItemClick(value.userWallet, status) },
)
return PortfolioTokenUM(
tokenItemState = tokenItemStateConverter.convert(value = value.status),
walletId = value.userWallet.walletId,
isBalanceHidden = isBalanceHidden,
isQuickActionsShown = isQuickActionsShown,
quickActions = quickActions(
cryptoData = value,
tokenActionsHandler = tokenActionsHandler,
isRedesignEnabled = isRedesignEnabled,
),
)
}
override fun convert(value: CryptoCurrencyData): PortfolioTokenUM {
val tokenItemStateConverter = TokenItemStateConverter(
appCurrency = appCurrency,
titleStateProvider = { TokenItemState.TitleState.Content(text = stringReference(value.userWallet.name)) },
subtitleStateProvider = {
TokenItemState.SubtitleState.TextContent(value = stringReference(value.status.currency.name))
},
onItemClick = { _, status -> onTokenItemClick(status) },
)
return PortfolioTokenUM(
tokenItemState = tokenItemStateConverter.convert(value = value.status),
walletId = value.userWallet.walletId,
isBalanceHidden = isBalanceHidden,
isQuickActionsShown = false,
quickActions = quickActions(
cryptoData = value,
tokenActionsHandler = tokenActionsHandler,
isRedesignEnabled = isRedesignEnabled,
),
)
}
}

View file

@ -1,333 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui
import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.common.ui.account.AccountTitle
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SmallButtonShimmer
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
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.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.*
import com.tangem.features.feed.impl.R
@Composable
internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) {
if (state is MyPortfolioUM.Content) {
val contentModifier = Modifier.padding(
start = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing20,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing32,
)
PortfolioList(state, contentModifier)
return
}
InformationBlock(
modifier = modifier,
contentHorizontalPadding = TangemTheme.dimens.spacing0,
title = { Title() },
action = {
if (state !is MyPortfolioUM.Tokens) return@InformationBlock
AddButton(state = state.buttonState, onClick = state.onAddClick)
},
) {
val contentModifier = Modifier.padding(
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing12,
)
when (state) {
is MyPortfolioUM.Tokens -> TokenList(state = state)
is MyPortfolioUM.AddFirstToken -> AddFirstTokenContent(state = state, modifier = contentModifier)
MyPortfolioUM.Loading -> LoadingPlaceholder(modifier = contentModifier)
MyPortfolioUM.Unavailable -> UnavailableAsset(modifier = contentModifier)
MyPortfolioUM.UnavailableForWallet -> UnavailableAssetForWallet(modifier = contentModifier)
is MyPortfolioUM.Content -> PortfolioList(state = state)
}
}
}
@Composable
private fun Title() {
Text(
text = stringResourceSafe(R.string.markets_common_my_portfolio),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
}
@Composable
private fun AddButton(state: MyPortfolioUM.Tokens.AddButtonState, onClick: () -> Unit) {
when (state) {
MyPortfolioUM.Tokens.AddButtonState.Loading -> {
Box {
SmallButtonShimmer(
modifier = Modifier.width(width = TangemTheme.dimens.size63),
shape = RoundedCornerShape(TangemTheme.dimens.radius3),
withIcon = true,
)
Box(
Modifier
.matchParentSize()
.background(TangemTheme.colors.background.action),
)
RectangleShimmer(
modifier = Modifier
.align(Alignment.Center)
.size(width = TangemTheme.dimens.size63, height = TangemTheme.dimens.size18),
radius = TangemTheme.dimens.radius3,
)
}
}
MyPortfolioUM.Tokens.AddButtonState.Available,
MyPortfolioUM.Tokens.AddButtonState.Unavailable,
-> {
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(R.string.markets_add_token),
icon = TangemButtonIconPosition.Start(R.drawable.ic_plus_24),
onClick = onClick,
isEnabled = state == MyPortfolioUM.Tokens.AddButtonState.Available,
),
)
}
}
}
@Composable
private fun TokenList(state: MyPortfolioUM.Tokens, modifier: Modifier = Modifier) {
Column(modifier) {
state.tokens.fastForEachIndexed { index, token ->
key(token.tokenItemState.id) {
PortfolioItem(
modifier = Modifier.background(color = TangemTheme.colors.background.action),
state = token,
lastInList = index == state.tokens.size - 1,
)
}
}
}
}
@Composable
private fun PortfolioList(state: MyPortfolioUM.Content, modifier: Modifier = Modifier) {
Column(modifier) {
key("PortfolioListHeader") {
Row(
modifier = Modifier.padding(horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
text = stringResourceSafe(R.string.markets_common_my_portfolio),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
AddButton(state = state.buttonState, onClick = state.onAddClick)
}
}
state.items.fastForEachIndexed { index, item ->
val previousItem = state.items.getOrNull(index.dec())
val nextItem = state.items.getOrNull(index.inc())
val itemModifier = Modifier
.fillMaxWidth()
.getOffsetModifier(item, previousItem)
.getBackgroundModifier(item, previousItem, nextItem)
key(item.id) {
PortfolioItem(
item = item,
modifier = itemModifier,
lastInList = index == state.items.size - 1,
)
}
}
}
}
@Composable
private fun Modifier.getBackgroundModifier(
item: PortfolioListItem,
previousItem: PortfolioListItem?,
nextItem: PortfolioListItem?,
): Modifier {
val color = TangemTheme.colors.background.action
val radius = 14.dp
val topRound = RoundedCornerShape(topStart = radius, topEnd = radius)
val bottomRound = RoundedCornerShape(bottomStart = radius, bottomEnd = radius)
val allRound = RoundedCornerShape(size = radius)
val backgroundModifier = when (item) {
is WalletHeader -> this
is PortfolioHeader -> this
.clip(topRound)
.background(color = color)
is PortfolioTokenUM -> when {
previousItem is PortfolioHeader && nextItem !is PortfolioTokenUM -> this
.clip(bottomRound)
.background(color = color)
previousItem is WalletHeader && nextItem !is PortfolioTokenUM -> this
.clip(allRound)
.background(color = color)
previousItem is PortfolioTokenUM && nextItem !is PortfolioTokenUM -> this
.clip(bottomRound)
.background(color = color)
else -> this.background(color = color)
}
}
return backgroundModifier
}
private fun Modifier.getOffsetModifier(item: PortfolioListItem, previousItem: PortfolioListItem?): Modifier = when {
item is WalletHeader -> this.padding(top = 20.dp, start = 4.dp, end = 4.dp)
item is PortfolioHeader && previousItem is PortfolioTokenUM -> this.padding(top = 12.dp)
item is PortfolioHeader && previousItem == null -> this.padding(top = 20.dp)
previousItem is WalletHeader -> this.padding(top = 12.dp)
previousItem is PortfolioTokenUM -> this
else -> this
}
@Composable
private fun PortfolioItem(item: PortfolioListItem, lastInList: Boolean, modifier: Modifier = Modifier) {
when (item) {
is PortfolioHeader -> AccountTitle(
modifier = modifier.padding(
start = 12.dp,
top = 12.dp,
bottom = 8.dp,
),
accountTitleUM = item.state,
textStyle = TangemTheme.typography.caption1,
textColor = TangemTheme.colors.text.primary1,
)
is PortfolioTokenUM -> PortfolioItem(
state = item,
modifier = modifier,
lastInList = lastInList,
)
is WalletHeader -> Text(
modifier = modifier,
text = item.name.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
}
}
@Composable
fun UnavailableAsset(modifier: Modifier = Modifier) {
UnavailableContent(
textId = R.string.markets_add_to_my_portfolio_unavailable_description,
modifier = modifier,
)
}
@Composable
fun UnavailableAssetForWallet(modifier: Modifier = Modifier) {
UnavailableContent(
textId = R.string.markets_add_to_my_portfolio_unavailable_for_wallet_description,
modifier = modifier,
)
}
@Composable
private fun UnavailableContent(@StringRes textId: Int, modifier: Modifier = Modifier) {
Text(
modifier = modifier,
text = stringResourceSafe(textId),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
@Composable
private fun AddFirstTokenContent(state: MyPortfolioUM.AddFirstToken, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
Text(
text = stringResourceSafe(R.string.markets_add_to_my_portfolio_description),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResourceSafe(R.string.common_add_to_portfolio),
onClick = state.onAddClick,
)
}
}
@Composable
private fun LoadingPlaceholder(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
TextShimmer(
modifier = Modifier.fillMaxWidth(),
style = TangemTheme.typography.body2,
textSizeHeight = true,
)
TextShimmer(
modifier = Modifier.fillMaxWidth(fraction = 0.7f),
style = TangemTheme.typography.body2,
textSizeHeight = true,
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview(@PreviewParameter(PreviewMyPortfolioUMProvider::class) state: MyPortfolioUM) {
TangemThemePreview {
Box(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary),
) {
MyPortfolio(state)
}
}
}
@Preview
@Composable
private fun PreviewRtl(@PreviewParameter(PreviewMyPortfolioUMProvider::class) state: MyPortfolioUM) {
TangemThemePreview(rtl = true) {
Box(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary)
.padding(TangemTheme.dimens.spacing8),
) {
MyPortfolio(state)
}
}
}

View file

@ -1,156 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.icons.IconTint
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.PortfolioTokenUM
import com.tangem.features.feed.impl.R
import com.tangem.utils.StringsSigns.DASH_SIGN
import kotlinx.collections.immutable.persistentListOf
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState as TokenFiatAmountState
@Composable
internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifier: Modifier = Modifier) {
Column(modifier) {
val hapticManager = LocalHapticManager.current
val tokenItemState = remember(state.tokenItemState) {
when (state.tokenItemState) {
is TokenItemState.Content -> state.tokenItemState.copy(
onItemClick = { tokenItemState ->
val onClick = state.tokenItemState.onItemClick
if (onClick != null) {
hapticManager.perform(TangemHapticEffect.View.ContextClick)
onClick.invoke(tokenItemState)
}
},
)
else -> state.tokenItemState
}
}
TokenItem(
state = tokenItemState,
isBalanceHidden = state.isBalanceHidden,
itemPaddingValues = PaddingValues(
start = TangemTheme.dimens.spacing10,
end = TangemTheme.dimens.spacing12,
),
modifier = Modifier.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_TOKEN_ITEM),
)
PortfolioQuickActions(
modifier = Modifier
.padding(
bottom = if (lastInList) {
TangemTheme.dimens.spacing12
} else {
TangemTheme.dimens.spacing24
},
),
actions = state.quickActions.actions,
isVisible = state.isQuickActionsShown,
onActionClick = state.quickActions.onQuickActionClick,
onActionLongClick = state.quickActions.onQuickActionLongClick,
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview(@PreviewParameter(PortfolioTokenUMProvider::class) tokenUM: PortfolioTokenUM) {
TangemThemePreview {
var isQuickActionsShown by remember { mutableStateOf(value = false) }
val onItemClick = {
isQuickActionsShown = isQuickActionsShown.not()
}
PortfolioItem(
modifier = Modifier.background(color = TangemTheme.colors.background.action),
state = tokenUM.copy(
tokenItemState = when (tokenUM.tokenItemState) {
is TokenItemState.Content -> tokenUM.tokenItemState.copy(onItemClick = { onItemClick() })
is TokenItemState.Unreachable -> tokenUM.tokenItemState.copy(onItemClick = { onItemClick() })
else -> tokenUM.tokenItemState
},
isQuickActionsShown = isQuickActionsShown,
),
lastInList = true,
)
}
}
private class PortfolioTokenUMProvider : CollectionPreviewParameterProvider<PortfolioTokenUM>(
collection = listOf(
tokenUM.copy(
tokenItemState = (tokenUM.tokenItemState as TokenItemState.Content).copy(
fiatAmountState = contentFiatAmount?.copy(
icons = persistentListOf(
TokenFiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_staking_24,
tint = IconTint.Accent,
),
),
),
),
),
tokenUM.copy(
tokenItemState = tokenUM.tokenItemState.copy(
fiatAmountState = contentFiatAmount?.copy(text = DASH_SIGN),
subtitle2State = (tokenUM.tokenItemState.subtitle2State as? TokenItemState.Subtitle2State.TextContent)
?.copy(text = DASH_SIGN),
),
),
tokenUM.copy(isBalanceHidden = true),
tokenUM.copy(
tokenItemState = TokenItemState.Unreachable(
id = tokenUM.tokenItemState.id,
iconState = tokenUM.tokenItemState.iconState,
titleState = tokenUM.tokenItemState.titleState,
subtitleState = tokenUM.tokenItemState.subtitleState,
onItemClick = {},
onItemLongClick = {},
),
),
tokenUM.copy(
tokenItemState = TokenItemState.NoAddress(
id = tokenUM.tokenItemState.id,
iconState = tokenUM.tokenItemState.iconState,
titleState = tokenUM.tokenItemState.titleState,
subtitleState = tokenUM.tokenItemState.subtitleState,
onItemLongClick = {},
),
),
tokenUM.copy(
tokenItemState = TokenItemState.Loading(
id = tokenUM.tokenItemState.id,
iconState = tokenUM.tokenItemState.iconState,
titleState = tokenUM.tokenItemState.titleState as TokenItemState.TitleState.Content,
subtitleState = tokenUM.tokenItemState.subtitleState,
),
),
),
) {
companion object {
val tokenUM = PreviewMyPortfolioUMProvider().sampleToken
val contentFiatAmount = tokenUM.tokenItemState.fiatAmountState as? TokenFiatAmountState.Content
}
}

View file

@ -1,263 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui
import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.common.ui.markets.action.QuickActionUM
import com.tangem.core.ui.components.SpacerH4
import com.tangem.core.ui.components.icons.badge.drawBadge
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun PortfolioQuickActions(
actions: ImmutableList<QuickActionUM>,
isVisible: Boolean,
onActionClick: (QuickActionUM) -> Unit,
onActionLongClick: (QuickActionUM) -> Unit,
modifier: Modifier = Modifier,
) {
if (actions.isEmpty()) return
AnimatedVisibility(
modifier = modifier,
visible = isVisible,
enter = expandVertically(expandFrom = Alignment.Top),
exit = shrinkVertically(shrinkTowards = Alignment.Top),
) {
Column {
actions.fastForEach { action ->
LineSeparator()
QuickActionItem(
state = action,
onClick = { onActionClick(action) },
onLongClick = { onActionLongClick(action) }.takeIf { action.isLongClickAvailable },
)
}
}
}
}
@Composable
private fun AnimatedVisibilityScope.LineSeparator(modifier: Modifier = Modifier) {
val lineColor = TangemTheme.colors.stroke.primary
val strokeWidth = TangemTheme.dimens.size1
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val startPadding = TangemTheme.dimens.spacing30
val height = TangemTheme.dimens.size16
Canvas(
modifier = modifier
.animateEnterExit(
enter = expandVertically(
animationSpec = spring(
stiffness = Spring.StiffnessLow,
),
expandFrom = Alignment.Top,
) + fadeIn(),
exit = shrinkVertically(
spring(
stiffness = Spring.StiffnessLow,
),
shrinkTowards = Alignment.Top,
) + fadeOut(),
)
.fillMaxWidth()
.height(height),
) {
val x = if (isLtr) startPadding.toPx() else size.width - startPadding.toPx()
drawLine(
color = lineColor,
start = Offset(x, 0f),
end = Offset(x, size.height),
strokeWidth = strokeWidth.toPx(),
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun AnimatedVisibilityScope.QuickActionItem(
state: QuickActionUM,
onClick: () -> Unit,
onLongClick: (() -> Unit)?,
modifier: Modifier = Modifier,
) {
val hapticManager = LocalHapticManager.current
val onLongClickInternal: (() -> Unit)? = if (onLongClick != null) {
{
hapticManager.perform(TangemHapticEffect.View.LongPress)
onLongClick()
}
} else {
null
}
Row(
modifier = modifier
.fillMaxWidth()
.combinedClickable(
onLongClick = onLongClickInternal,
onClick = {
hapticManager.perform(TangemHapticEffect.View.SegmentTick)
onClick()
},
)
.padding(horizontal = TangemTheme.dimens.spacing14, vertical = TangemTheme.dimens.spacing4)
.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
) {
QuickActionIcon(state)
Column(
modifier = Modifier
.animateEnterExit(
enter = fadeIn(),
exit = fadeOut(),
),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
) {
Text(
text = state.title.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON_TITLE),
)
Text(
text = state.description.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Composable
private fun AnimatedVisibilityScope.QuickActionIcon(state: QuickActionUM) {
val containerColor = TangemTheme.colors.background.action
Box(
Modifier
.animateEnterExit(
enter = scaleIn(),
exit = scaleOut(),
)
.background(
color = TangemTheme.colors.button.secondary,
shape = CircleShape,
)
.size(TangemTheme.dimens.size32)
.semantics {
contentDescription = if (state is QuickActionUM.V1.Exchange && state.shouldShowBadge) {
"Badge shown"
} else {
"Badge hidden"
}
}
.drawWithContent {
drawContent()
if (state is QuickActionUM.V1.Exchange && state.shouldShowBadge) {
drawBadge(containerColor = containerColor, offset = 4.dp)
}
},
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier
.requiredSize(TangemTheme.dimens.size16)
.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON_ICON),
imageVector = ImageVector.vectorResource(id = state.icon),
contentDescription = null,
tint = TangemTheme.colors.button.primary,
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview() {
TangemThemePreview {
var isVisible by remember { mutableStateOf(true) }
Column(
modifier = Modifier
.fillMaxWidth()
.height(680.dp),
) {
Button(
onClick = { isVisible = !isVisible },
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
) {
Text(text = "Toggle")
}
SpacerH4()
Box(
modifier = Modifier.background(color = TangemTheme.colors.background.action),
) {
PortfolioQuickActions(
actions = persistentListOf(
QuickActionUM.V1.Buy,
QuickActionUM.V1.Exchange(shouldShowBadge = true),
QuickActionUM.V1.Receive,
),
isVisible = isVisible,
onActionClick = {},
onActionLongClick = {},
)
}
}
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewRtl() {
TangemThemePreview(rtl = true) {
Box(modifier = Modifier.background(color = TangemTheme.colors.background.action)) {
PortfolioQuickActions(
actions = persistentListOf(
QuickActionUM.V1.Buy,
QuickActionUM.V1.Exchange(shouldShowBadge = true),
QuickActionUM.V1.Receive,
),
isVisible = true,
onActionClick = {},
onActionLongClick = {},
)
}
}
}

View file

@ -1,142 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.common.ui.account.AccountIconPreviewData
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.markets.action.QuickActionUM
import com.tangem.common.ui.markets.action.QuickActions
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.*
import kotlinx.collections.immutable.persistentListOf
import java.util.UUID
@Suppress("PropertyUsedBeforeDeclaration")
internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider<MyPortfolioUM> {
override val values: Sequence<MyPortfolioUM>
get() = sequenceOf(
MyPortfolioUM.Tokens(
tokens = persistentListOf(sampleToken, sampleToken),
buttonState = MyPortfolioUM.Tokens.AddButtonState.Available,
onAddClick = {},
),
MyPortfolioUM.Tokens(
tokens = persistentListOf(sampleToken, sampleToken.copy(isQuickActionsShown = true)),
buttonState = MyPortfolioUM.Tokens.AddButtonState.Unavailable,
onAddClick = {},
),
MyPortfolioUM.Tokens(
tokens = persistentListOf(sampleToken.copy(isQuickActionsShown = true), sampleToken),
buttonState = MyPortfolioUM.Tokens.AddButtonState.Loading,
onAddClick = {},
),
MyPortfolioUM.AddFirstToken(onAddClick = {}),
MyPortfolioUM.Content(
items = persistentListOf(
walletPortfolioHeader,
accountToken,
accountToken,
),
buttonState = MyPortfolioUM.Tokens.AddButtonState.Available,
onAddClick = {},
),
MyPortfolioUM.Content(
items = persistentListOf(
walletHeader,
accountHeader,
accountToken,
accountToken,
),
buttonState = MyPortfolioUM.Tokens.AddButtonState.Available,
onAddClick = {},
),
MyPortfolioUM.Content(
items = persistentListOf(
walletHeader,
accountHeader,
accountToken.copy(isQuickActionsShown = true),
accountToken,
),
buttonState = MyPortfolioUM.Tokens.AddButtonState.Available,
onAddClick = {},
),
MyPortfolioUM.Loading,
MyPortfolioUM.Unavailable,
MyPortfolioUM.UnavailableForWallet,
)
val walletHeader
get() = WalletHeader(
id = UUID.randomUUID().toString(),
name = stringReference("Wallet 1"),
)
val walletPortfolioHeader
get() = PortfolioHeader(
state = AccountTitleUM.Text(title = stringReference("Wallet 1")),
id = UUID.randomUUID().toString(),
)
val accountHeader
get() = PortfolioHeader(
state = AccountTitleUM.Account(
icon = AccountIconPreviewData.randomAccountIcon(),
name = stringReference("Main Account"),
prefixText = TextReference.EMPTY,
),
id = UUID.randomUUID().toString(),
)
val coinIconState
get() = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = com.tangem.core.ui.R.drawable.img_polygon_22,
isGrayscale = false,
shouldShowCustomBadge = false,
)
val accountToken
get() = sampleToken.copy(
tokenItemState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
iconState = coinIconState,
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
fiatAmountState = FiatAmountState.Content(text = "321 $"),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "5,412 MATIC"),
subtitleState = TokenItemState.SubtitleState.TextContent(stringReference(value = "Token")),
onItemClick = {},
onItemLongClick = {},
),
)
val sampleToken
get() = PortfolioTokenUM(
tokenItemState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
iconState = CurrencyIconState.Locked,
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "My wallet")),
fiatAmountState = FiatAmountState.Content(text = "486,65 \$"),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "733,71097 MATIC"),
subtitleState = TokenItemState.SubtitleState.TextContent(
value = stringReference(value = "XRP Ledger token"),
),
onItemClick = {},
onItemLongClick = {},
),
isQuickActionsShown = false,
quickActions = QuickActions(
actions = persistentListOf(
QuickActionUM.V1.Buy,
QuickActionUM.V1.Exchange(shouldShowBadge = true),
QuickActionUM.V1.Receive,
),
onQuickActionClick = {},
onQuickActionLongClick = {},
),
isBalanceHidden = false,
walletId = UserWalletId(""),
)
}

View file

@ -1,37 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui.state
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal sealed class MyPortfolioUM {
data class Tokens(
val tokens: ImmutableList<PortfolioTokenUM>,
val buttonState: AddButtonState,
val onAddClick: () -> Unit,
) : MyPortfolioUM() {
enum class AddButtonState {
Loading,
Available,
Unavailable,
}
}
data class Content(
val items: ImmutableList<PortfolioListItem>,
val buttonState: Tokens.AddButtonState,
val onAddClick: () -> Unit,
) : MyPortfolioUM()
data class AddFirstToken(
val onAddClick: () -> Unit,
) : MyPortfolioUM()
data object Loading : MyPortfolioUM()
data object Unavailable : MyPortfolioUM()
data object UnavailableForWallet : MyPortfolioUM()
}

View file

@ -1,33 +0,0 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.markets.action.QuickActions
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.wallet.UserWalletId
@Immutable
internal sealed interface PortfolioListItem {
val id: String
}
internal data class WalletHeader(
override val id: String,
val name: TextReference,
) : PortfolioListItem
internal data class PortfolioHeader(
override val id: String,
val state: AccountTitleUM,
) : PortfolioListItem
internal data class PortfolioTokenUM(
val tokenItemState: TokenItemState,
val walletId: UserWalletId,
val isBalanceHidden: Boolean,
val isQuickActionsShown: Boolean,
val quickActions: QuickActions,
) : PortfolioListItem {
override val id: String = tokenItemState.id
}

View file

@ -27,7 +27,6 @@ import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarketParams
@ -36,7 +35,6 @@ import com.tangem.features.feed.model.market.list.state.MarketsListUM
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.feed.ui.components.FeedSearchBar
import com.tangem.features.feed.ui.market.list.MarketsList
import com.tangem.features.feed.ui.market.list.TopBarWithSearch
import kotlinx.serialization.Serializable
internal class DefaultMarketsTokenListComponent(
@ -55,41 +53,31 @@ internal class DefaultMarketsTokenListComponent(
@Composable
override fun Title(bottomSheetState: State<BottomSheetState>) {
val state by model.state.collectAsStateWithLifecycle()
val bsState by bottomSheetState
if (LocalRedesignEnabled.current) {
val bottomSheetColor = LocalMainBottomSheetColor.current.value
FeedSearchBar(
isSearchBarClickable = bottomSheetState.value == BottomSheetState.EXPANDED,
feedListSearchBar = state.feedListSearchBar,
modifier = Modifier.conditional(bottomSheetColor.isSpecified) {
background(bottomSheetColor.copy(alpha = .95f))
},
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = clickIntents.onBackClicked,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
)
} else {
TopBarWithSearch(
onBackClick = clickIntents.onBackClicked,
onSearchClick = state.onSearchClicked,
marketsSearchBar = state.marketsSearchBar,
bottomSheetState = bsState,
)
}
val bottomSheetColor = LocalMainBottomSheetColor.current.value
FeedSearchBar(
isSearchBarClickable = bottomSheetState.value == BottomSheetState.EXPANDED,
feedListSearchBar = state.feedListSearchBar,
modifier = Modifier.conditional(bottomSheetColor.isSpecified) {
background(bottomSheetColor.copy(alpha = .95f))
},
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = clickIntents.onBackClicked,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
)
}
@Composable

View file

@ -17,16 +17,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.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
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarketParams
@ -46,65 +42,47 @@ internal class DefaultNewsDetailsComponent(
@Composable
override fun Title(bottomSheetState: State<BottomSheetState>) {
val background = LocalMainBottomSheetColor.current.value
val state by newsDetailsModel.state.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) {
TangemTopBar(
type = if (LocalIsOpenedInBottomSheet.current) {
TangemTopBarType.BottomSheet
} else {
TangemTopBarType.Default
},
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = state.onBackClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
endContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_share_new_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = state.onShareClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED &&
state.articlesStateUM is ArticlesStateUM.Content,
)
.padding(TangemTheme.dimens2.x2_5),
)
},
)
} else {
TangemTopAppBar(
containerColor = background,
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_back_24,
onClicked = state.onBackClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
),
endButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_share_24,
onClicked = state.onShareClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED &&
state.articlesStateUM is ArticlesStateUM.Content,
),
)
}
TangemTopBar(
type = if (LocalIsOpenedInBottomSheet.current) {
TangemTopBarType.BottomSheet
} else {
TangemTopBarType.Default
},
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = state.onBackClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
endContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_share_new_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = state.onShareClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED &&
state.articlesStateUM is ArticlesStateUM.Content,
)
.padding(TangemTheme.dimens2.x2_5),
)
},
)
}
@Composable

View file

@ -18,8 +18,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.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
@ -27,9 +25,7 @@ import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
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.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.news.model.NewsListConfig
import com.tangem.features.feed.model.news.list.NewsListModel
@ -48,43 +44,31 @@ internal class DefaultNewsListComponent(
override fun Title(bottomSheetState: State<BottomSheetState>) {
val background = LocalMainBottomSheetColor.current.value
val state by newsListModel.state.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) {
TangemTopBar(
modifier = Modifier.background(background.copy(alpha = .95f)),
title = resourceReference(R.string.common_news),
type = if (LocalIsOpenedInBottomSheet.current) {
TangemTopBarType.BottomSheet
} else {
TangemTopBarType.Default
},
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = state.onBackClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
)
} else {
TangemTopAppBar(
containerColor = background,
title = stringResourceSafe(R.string.common_news),
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_back_24,
onClicked = state.onBackClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
),
)
}
TangemTopBar(
modifier = Modifier.background(background.copy(alpha = .95f)),
title = resourceReference(R.string.common_news),
type = if (LocalIsOpenedInBottomSheet.current) {
TangemTopBarType.BottomSheet
} else {
TangemTopBarType.Default
},
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.hazeEffectTangem { blurRadius = 8.dp }
.clickableSingle(
onClick = state.onBackClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
)
}
@Composable

View file

@ -13,7 +13,6 @@ import com.tangem.core.analytics.models.AnalyticsParam
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.DesignFeatureToggles
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -60,7 +59,6 @@ internal class FeedComponentModel @Inject constructor(
private val fetchTopEarnTokensUseCase: FetchTopEarnTokensUseCase,
private val getTopEarnTokensUseCase: GetTopEarnTokensUseCase,
private val appRouter: AppRouter,
private val designFeatureToggles: DesignFeatureToggles,
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
private val forYouFeatureToggles: ForYouFeatureToggles,
getTopFiveMarketTokenUseCase: GetTopFiveMarketTokenUseCase,
@ -229,19 +227,11 @@ internal class FeedComponentModel @Inject constructor(
currentDate = getCurrentDate(),
feedListSearchBar = FeedListSearchBar(
placeholderText = resourceReference(
id = if (designFeatureToggles.isRedesignEnabled) {
R.string.markets_search_title_placeholder
} else {
R.string.markets_search_header_title
},
id = R.string.markets_search_title_placeholder,
),
onBarClick = {
analyticsEventHandler.send(FeedAnalyticsEvent.TokenSearchedClicked())
if (designFeatureToggles.isRedesignEnabled) {
params.feedClickIntents.openSearch(AnalyticsParam.ScreensSources.Markets.value)
} else {
params.feedClickIntents.onMarketOpenClick(null)
}
params.feedClickIntents.openSearch(AnalyticsParam.ScreensSources.Markets.value)
},
),
feedListCallbacks = FeedListCallbacks(

View file

@ -21,7 +21,6 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.marketprice.PriceChangeType
@ -59,7 +58,6 @@ import com.tangem.features.feed.impl.R
import com.tangem.features.feed.model.converter.ShortArticleToArticleConfigUMConverter
import com.tangem.features.feed.model.market.details.analytics.MarketDetailsAnalyticsEvent
import com.tangem.features.feed.model.market.details.converter.DescriptionConverter
import com.tangem.features.feed.model.market.details.converter.ExchangeItemStateConverter
import com.tangem.features.feed.model.market.details.converter.ExchangeItemStateConverterV2
import com.tangem.features.feed.model.market.details.converter.TokenMarketInfoConverter
import com.tangem.features.feed.model.market.details.formatter.*
@ -92,7 +90,6 @@ internal class MarketsTokenDetailsModel @Inject constructor(
getUserCountryUseCase: GetUserCountryUseCase,
paramsContainer: ParamsContainer,
addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
private val designFeatureToggles: DesignFeatureToggles,
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase,
@ -165,7 +162,6 @@ internal class MarketsTokenDetailsModel @Inject constructor(
needApplyFCARestrictions = Provider {
userCountry.needApplyFCARestrictions()
},
isRedesignEnabled = designFeatureToggles.isRedesignEnabled,
// ==================
)
@ -203,7 +199,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
marketChartLook.copy(
type = percentChangeType.toChartType(),
isMinMaxLook = designFeatureToggles.isRedesignEnabled,
isMinMaxLook = true,
xAxisFormatter = MarketsDateTimeFormatters.getChartXFormatterByInterval(PriceChangeInterval.H24),
yAxisFormatter = { value ->
value.format {
@ -244,7 +240,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
val addFundsSheetNavigation = SlotNavigation<AddFundsSlotRoute>()
private val isAddToPortfolioAvailable: Boolean =
params.shouldShowPortfolio && designFeatureToggles.isRedesignEnabled
params.shouldShowPortfolio
val addToPortfolioManager: AddToPortfolioManager = addToPortfolioManagerFactory.create(
scope = modelScope,
settings = AddToPortfolioManager.Settings(
@ -846,15 +842,9 @@ internal class MarketsTokenDetailsModel @Inject constructor(
ExchangesBottomSheetContent.Error(onRetryClick = { onListedOnClick(exchangesCount) })
},
ifRight = { list ->
if (designFeatureToggles.isRedesignEnabled) {
ExchangesBottomSheetContent.ContentV2(
exchangeItemsV2 = ExchangeItemStateConverterV2.convertList(list).toImmutableList(),
)
} else {
ExchangesBottomSheetContent.ContentV1(
exchangeItems = ExchangeItemStateConverter.convertList(list).toImmutableList(),
)
}
ExchangesBottomSheetContent.ContentV2(
exchangeItemsV2 = ExchangeItemStateConverterV2.convertList(list).toImmutableList(),
)
},
)

View file

@ -25,7 +25,6 @@ internal class MetricsConverter(
private val appCurrency: Provider<AppCurrency>,
private val tokenSymbol: String,
private val onInfoClick: (InfoBottomSheetContent) -> Unit,
private val isRedesignEnabled: Boolean,
) : Converter<TokenMarketInfo.Metrics, MetricsUM> {
@Suppress("LongMethod")
@ -118,11 +117,7 @@ internal class MetricsConverter(
},
),
),
metricsV2 = if (isRedesignEnabled) {
convertToMetricsV2UM(value)
} else {
null
},
metricsV2 = convertToMetricsV2UM(value),
)
}
}

View file

@ -15,7 +15,6 @@ import com.tangem.utils.converter.Converter
@Stable
@Suppress("LongParameterList")
internal class TokenMarketInfoConverter(
private val isRedesignEnabled: Boolean,
private val appCurrency: Provider<AppCurrency>,
private val needApplyFCARestrictions: Provider<Boolean>,
private val onInfoClick: (TangemBottomSheetConfigContent) -> Unit,
@ -48,7 +47,6 @@ internal class TokenMarketInfoConverter(
tokenSymbol = value.symbol,
appCurrency = appCurrency,
onInfoClick = onInfoClick,
isRedesignEnabled = isRedesignEnabled,
)
val exchangesAmount = value.exchangesAmount

View file

@ -9,7 +9,6 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
@ -54,7 +53,6 @@ internal class NewsDetailsModel @Inject constructor(
private val markArticleAsViewedUseCase: MarkArticleAsViewedUseCase,
private val toggleArticleLikedUseCase: ToggleArticleLikedUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val designFeatureToggles: DesignFeatureToggles,
paramsContainer: ParamsContainer,
) : Model() {
@ -70,7 +68,6 @@ internal class NewsDetailsModel @Inject constructor(
dispatchers = dispatchers,
observeNewsDetailsUseCase = observeNewsDetailsUseCase,
prefetchedIds = params.preselectedArticlesId.toSet(),
isRedesignEnabled = designFeatureToggles.isRedesignEnabled,
)
}

View file

@ -19,13 +19,11 @@ internal class NewsDetailsPaginationManager(
currentCategoryIds: Provider<List<Int>>,
modelScope: CoroutineScope,
prefetchedIds: Set<Int>,
isRedesignEnabled: Boolean,
) : NewsListBatchFlowManager(
getNewsListBatchFlowUseCase = getNewsListBatchFlowUseCase,
currentCategoryIds = currentCategoryIds,
modelScope = modelScope,
dispatchers = dispatchers,
isRedesignEnabled = isRedesignEnabled,
) {
private val _cachedPrefetchedIds = MutableStateFlow(prefetchedIds)

View file

@ -4,7 +4,6 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
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.DesignFeatureToggles
import com.tangem.core.ui.components.chip.entity.ChipUM
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
@ -36,7 +35,6 @@ internal class NewsListModel @Inject constructor(
private val getNewsCategoriesUseCase: GetNewsCategoriesUseCase,
private val getNewsListBatchFlowUseCase: GetNewsListBatchFlowUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val designFeatureToggles: DesignFeatureToggles,
paramsContainer: ParamsContainer,
) : Model() {
@ -59,7 +57,6 @@ internal class NewsListModel @Inject constructor(
},
modelScope = modelScope,
dispatchers = dispatchers,
isRedesignEnabled = designFeatureToggles.isRedesignEnabled,
)
}

View file

@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
internal open class NewsListBatchFlowManager(
private val isRedesignEnabled: Boolean,
getNewsListBatchFlowUseCase: GetNewsListBatchFlowUseCase,
private val currentCategoryIds: Provider<List<Int>>,
protected val modelScope: CoroutineScope,
@ -30,11 +29,7 @@ internal open class NewsListBatchFlowManager(
private val actionsFlow = MutableSharedFlow<BatchAction<Int, NewsListConfig, Nothing>>()
private val converter by lazy {
ShortArticleToArticleConfigUMConverter(
isTrending = if (isRedesignEnabled) {
null
} else {
false
},
isTrending = null,
)
}

View file

@ -3,7 +3,6 @@ package com.tangem.features.feed.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -22,9 +21,7 @@ import com.tangem.core.ui.components.topFade
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.res.TangemTheme
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.features.feed.components.FeedEntryChildFactory
import com.tangem.features.feed.ui.utils.FadeConstants.BASE_FADE_LEVEL
import com.tangem.features.feed.ui.utils.contentFeedEntryStackAnimation
@ -51,82 +48,11 @@ internal fun EntryContent(
isOpenedInBottomSheet: Boolean,
) {
CompositionLocalProvider(LocalIsOpenedInBottomSheet provides isOpenedInBottomSheet) {
if (LocalRedesignEnabled.current) {
EntryContentV2(
bottomSheetState = bottomSheetState,
stackState = stackState,
onHeaderSizeChange = onHeaderSizeChange,
onExpandSheet = onExpandSheet,
)
} else {
EntryContentV1(
bottomSheetState = bottomSheetState,
stackState = stackState,
onHeaderSizeChange = onHeaderSizeChange,
onExpandSheet = onExpandSheet,
)
}
}
}
@Composable
private fun EntryContentV1(
bottomSheetState: State<BottomSheetState>,
stackState: State<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>>,
onHeaderSizeChange: (Dp) -> Unit,
onExpandSheet: () -> Unit,
) {
val density = LocalDensity.current
val background = LocalMainBottomSheetColor.current.value
val stackAnimation = remember { contentFeedEntryStackAnimation() }
val isOpenedInBottomSheet = LocalIsOpenedInBottomSheet.current
Surface(contentColor = background) {
Scaffold(
containerColor = background,
contentWindowInsets = WindowInsetsZero,
topBar = {
Box(
modifier = Modifier
.then(
if (!isOpenedInBottomSheet) {
Modifier.statusBarsPadding()
} else {
Modifier
},
)
.onGloballyPositioned { coordinates ->
if (coordinates.size.height > 0) {
with(density) {
onHeaderSizeChange(coordinates.size.height.toDp())
}
}
},
) {
Children(
stack = stackState.value,
animation = stackAnimation,
) { child ->
child.instance.Title(bottomSheetState)
}
CollapsedTitleClickOverlay(
bottomSheetState = bottomSheetState,
onExpandSheet = onExpandSheet,
)
}
},
content = { contentPadding ->
Children(
stack = stackState.value,
animation = stackAnimation,
) { child ->
child.instance.Content(
modifier = Modifier.padding(contentPadding),
bottomSheetState = bottomSheetState,
contentPadding = PaddingValues(),
)
}
},
EntryContentV2(
bottomSheetState = bottomSheetState,
stackState = stackState,
onHeaderSizeChange = onHeaderSizeChange,
onExpandSheet = onExpandSheet,
)
}
}

View file

@ -1,10 +1,8 @@
package com.tangem.features.feed.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -21,7 +19,6 @@ import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.LocalIsOpenedInBottomSheet
import com.tangem.features.feed.ui.feed.state.FeedListSearchBar
@ -34,56 +31,13 @@ internal fun FeedSearchBar(
startContent: @Composable (() -> Unit)? = null,
endContent: @Composable (() -> Unit)? = null,
) {
if (LocalRedesignEnabled.current) {
FeedSearchBarV2(
isSearchBarClickable = isSearchBarClickable,
feedListSearchBar = feedListSearchBar,
modifier = modifier,
startContent = startContent,
endContent = endContent,
)
} else {
FeedSearchBarV1(
isSearchBarClickable = isSearchBarClickable,
feedListSearchBar = feedListSearchBar,
modifier = modifier,
)
}
}
@Composable
private fun FeedSearchBarV1(
isSearchBarClickable: Boolean,
feedListSearchBar: FeedListSearchBar,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(36.dp))
.background(color = TangemTheme.colors.field.focused)
.conditional(condition = isSearchBarClickable) {
clickable(onClick = feedListSearchBar.onBarClick)
}
.padding(14.dp),
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
imageVector = ImageVector.vectorResource(id = R.drawable.ic_search_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
SpacerW(14.dp)
Text(
text = feedListSearchBar.placeholderText.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
FeedSearchBarV2(
isSearchBarClickable = isSearchBarClickable,
feedListSearchBar = feedListSearchBar,
modifier = modifier,
startContent = startContent,
endContent = endContent,
)
}
@Composable

View file

@ -23,7 +23,6 @@ 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.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.res.*
@ -40,7 +39,6 @@ internal fun EarnContent(contentPadding: PaddingValues, state: EarnUM, modifier:
val density = LocalDensity.current
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
val listState = rememberLazyListState()
val isRedesignEnabled = LocalRedesignEnabled.current
if (state.bestOpportunities is EarnBestOpportunitiesUM.Content) {
PaginationHandler(
@ -60,7 +58,7 @@ internal fun EarnContent(contentPadding: PaddingValues, state: EarnUM, modifier:
SectionHeader(
title = stringResourceSafe(R.string.earn_mostly_used),
modifier = Modifier.padding(
top = if (isRedesignEnabled) 10.dp else 16.dp,
top = 10.dp,
),
)
}
@ -76,7 +74,7 @@ internal fun EarnContent(contentPadding: PaddingValues, state: EarnUM, modifier:
SectionHeader(
title = stringResourceSafe(R.string.earn_best_opportunities),
modifier = Modifier.padding(
top = if (isRedesignEnabled) 32.dp else 20.dp,
top = 32.dp,
),
)
}
@ -91,17 +89,12 @@ internal fun EarnContent(contentPadding: PaddingValues, state: EarnUM, modifier:
)
}
if (isRedesignEnabled) {
bestOpportunitiesItemsV2(state = state.bestOpportunities)
} else {
bestOpportunitiesItemsV1(state = state.bestOpportunities)
}
bestOpportunitiesItemsV2(state = state.bestOpportunities)
}
}
@Composable
private fun MostlyUsedContent(state: EarnListUM, onScroll: () -> Unit) {
val isRedesignEnabled = LocalRedesignEnabled.current
AnimatedContent(
targetState = state,
contentKey = { it::class.java },
@ -136,20 +129,9 @@ private fun MostlyUsedContent(state: EarnListUM, onScroll: () -> Unit) {
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp)
.conditionalCompose(
condition = isRedesignEnabled,
modifier = {
background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x4),
)
},
otherModifier = {
background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
},
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x4),
)
.padding(vertical = 32.dp, horizontal = 12.dp),
contentAlignment = Alignment.Center,
@ -177,76 +159,6 @@ private fun BestOpportunitiesFilters(
}
}
private fun LazyListScope.bestOpportunitiesItemsV1(state: EarnBestOpportunitiesUM) {
when (state) {
is EarnBestOpportunitiesUM.Loading -> {
val lastIndex = PLACEHOLDER_ITEMS_COUNT - 1
items(
count = PLACEHOLDER_ITEMS_COUNT,
key = { "placeholder_$it" },
) { index ->
EarnItemPlaceholderV1(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors.background.action,
),
)
}
}
is EarnBestOpportunitiesUM.Empty -> {
item(key = "best_opportunities_empty") {
SpacerH(12.dp)
BestOpportunitiesEmpty()
}
}
is EarnBestOpportunitiesUM.EmptyFiltered -> {
item(key = "best_opportunities_empty_filtered") {
SpacerH(12.dp)
BestOpportunitiesEmptyFiltered(onClearFilterClick = state.onClearFilterClick)
}
}
is EarnBestOpportunitiesUM.Content -> {
if (state.items.isNotEmpty()) {
val lastIndex = state.items.lastIndex
itemsIndexed(
items = state.items,
key = { _, item -> "${item.tokenName}-${item.network}" },
) { index, item ->
EarnListItem(
item = item,
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors.background.action,
),
)
}
}
}
is EarnBestOpportunitiesUM.Error -> {
item(key = "best_opportunities_error") {
SpacerH(12.dp)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(vertical = 32.dp, horizontal = 12.dp),
contentAlignment = Alignment.Center,
) {
UnableToLoadData(onRetryClick = state.onRetryClicked)
}
}
}
}
}
private fun LazyListScope.bestOpportunitiesItemsV2(state: EarnBestOpportunitiesUM) {
when (state) {
is EarnBestOpportunitiesUM.Loading -> {
@ -320,53 +232,32 @@ private fun LazyListScope.bestOpportunitiesItemsV2(state: EarnBestOpportunitiesU
@Composable
private fun FilterButtonsShimmer(modifier: Modifier = Modifier) {
Row(modifier = modifier.padding(horizontal = 16.dp, vertical = 4.dp)) {
if (LocalRedesignEnabled.current) {
RectangleShimmer(
modifier = Modifier
.width(130.dp)
.height(36.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerWMax()
RectangleShimmer(
modifier = Modifier
.width(106.dp)
.height(36.dp),
radius = TangemTheme.dimens2.x25,
)
} else {
SmallButtonShimmer(
modifier = Modifier.width(110.dp),
)
SpacerWMax()
SmallButtonShimmer(
modifier = Modifier.width(90.dp),
)
}
RectangleShimmer(
modifier = Modifier
.width(130.dp)
.height(36.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerWMax()
RectangleShimmer(
modifier = Modifier
.width(106.dp)
.height(36.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
@Composable
private fun SectionHeader(title: String, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
Text(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 24.dp),
text = title,
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
)
} else {
Text(
modifier = modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 16.dp),
text = title,
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
}
Text(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 24.dp),
text = title,
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
)
}
@Composable

View file

@ -10,55 +10,16 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun BestOpportunitiesEmpty(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
BestOpportunitiesEmptyV2(modifier)
} else {
BestOpportunitiesEmptyV1(modifier)
}
}
@Composable
private fun BestOpportunitiesEmptyV1(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(vertical = 32.dp, horizontal = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
modifier = Modifier
.fillMaxWidth(),
painter = painterResource(R.drawable.ic_empty_64),
contentDescription = null,
tint = Color.Unspecified,
)
SpacerH(TangemTheme.dimens2.x6)
Text(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens2.x8),
text = stringResourceSafe(R.string.earn_empty),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
}
BestOpportunitiesEmptyV2(modifier)
}
@Composable

View file

@ -9,24 +9,16 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun BestOpportunitiesEmptyFiltered(onClearFilterClick: () -> Unit, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
BestOpportunitiesEmptyFilteredV2(onClearFilterClick, modifier)
} else {
BestOpportunitiesEmptyFilteredV1(onClearFilterClick, modifier)
}
BestOpportunitiesEmptyFilteredV2(onClearFilterClick, modifier)
}
@Composable
@ -58,32 +50,4 @@ private fun BestOpportunitiesEmptyFilteredV2(onClearFilterClick: () -> Unit, mod
),
)
}
}
@Composable
private fun BestOpportunitiesEmptyFilteredV1(onClearFilterClick: () -> Unit, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(vertical = 32.dp, horizontal = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(R.string.earn_no_results),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
SpacerH(12.dp)
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(R.string.earn_clear_filter),
onClick = onClearFilterClick,
),
)
}
}

View file

@ -3,39 +3,25 @@ package com.tangem.features.feed.ui.earn.components
import android.content.res.Configuration
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.inputrow.InputRowChecked
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.components.rows.RowContentContainer
import com.tangem.core.ui.components.rows.RowText
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.checkbox.TangemCheckbox
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.TextReference
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.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -48,62 +34,12 @@ import kotlinx.collections.immutable.toImmutableList
@Composable
internal fun EarnFilterByNetworkBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
EarnFilterByNetworkBottomSheetV2(config = config)
} else {
EarnFilterByNetworkBottomSheetV1(config = config)
}
}
@Composable
private fun EarnFilterByNetworkBottomSheetV1(config: TangemBottomSheetConfig) {
TangemBottomSheet<EarnFilterByNetworkBottomSheetContentUM>(
config = config,
titleText = resourceReference(R.string.earn_filter_by),
containerColor = TangemTheme.colors.background.tertiary,
content = { ContentV1(it) },
)
}
@Composable
private fun EarnFilterByNetworkBottomSheetV2(config: TangemBottomSheetConfig) {
EarnFilterBottomSheet<EarnFilterByNetworkBottomSheetContentUM>(
config = config,
content = { ContentV2(it) },
)
}
@Composable
private fun ContentV1(content: EarnFilterByNetworkBottomSheetContentUM) {
val allMyNetworks = remember(content) {
content.networks.filterIsInstance<EarnFilterNetworkUM.AllNetworks>() +
content.networks.filterIsInstance<EarnFilterNetworkUM.MyNetworks>()
}
val specificNetworks = remember(content) { content.networks.filterIsInstance<EarnFilterNetworkUM.Network>() }
LazyColumn(
contentPadding = PaddingValues(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing16,
),
) {
allMyNetworksList(
allMyNetworks = allMyNetworks,
onOptionClicked = content.onOptionClick,
)
if (specificNetworks.isNotEmpty()) {
networksHeader()
specificNetworksList(
specificNetworks = specificNetworks,
onOptionClicked = content.onOptionClick,
)
}
}
}
@Composable
private fun ContentV2(content: EarnFilterByNetworkBottomSheetContentUM) {
val allMyNetworks = remember(content) {
@ -250,125 +186,6 @@ private fun SpecificNetworksBlock(
}
}
private fun LazyListScope.allMyNetworksList(
allMyNetworks: List<EarnFilterNetworkUM>,
onOptionClicked: (EarnFilterNetworkUM) -> Unit,
) {
itemsIndexed(
items = allMyNetworks,
key = { _, item ->
when (item) {
is EarnFilterNetworkUM.AllNetworks -> "all_networks"
is EarnFilterNetworkUM.MyNetworks -> "my_networks"
is EarnFilterNetworkUM.Network -> item.id
}
},
) { index, item ->
DividerContainer(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = allMyNetworks.lastIndex,
addDefaultPadding = false,
)
.background(TangemTheme.colors.background.action)
.clickable { onOptionClicked(item) },
showDivider = index != allMyNetworks.lastIndex,
) {
InputRowChecked(
text = when (item) {
is EarnFilterNetworkUM.AllNetworks -> TextReference.Res(R.string.earn_filter_all_networks)
is EarnFilterNetworkUM.MyNetworks -> TextReference.Res(R.string.earn_filter_my_networks)
is EarnFilterNetworkUM.Network -> TextReference.Str(item.text)
},
checked = item.isSelected,
)
}
}
}
private fun LazyListScope.networksHeader() {
item(key = "networks_header") {
SpacerH(TangemTheme.dimens.spacing16)
Text(
modifier = Modifier
.fillMaxWidth()
.background(
color = TangemTheme.colors.background.action,
shape = RoundedCornerShape(
topStart = 16.dp,
topEnd = 16.dp,
),
)
.padding(
start = 12.dp,
end = 12.dp,
top = 12.dp,
bottom = 4.dp,
),
text = stringResourceSafe(id = R.string.earn_filter_networks),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
private fun LazyListScope.specificNetworksList(
specificNetworks: List<EarnFilterNetworkUM.Network>,
onOptionClicked: (EarnFilterNetworkUM) -> Unit,
) {
itemsIndexed(
items = specificNetworks,
key = { _, item -> item.id },
) { index, item ->
DividerContainer(
modifier = Modifier
.height(52.dp)
.roundedShapeItemDecoration(
currentIndex = index + 1,
lastIndex = specificNetworks.lastIndex + 1,
addDefaultPadding = false,
)
.background(TangemTheme.colors.background.action)
.clickable { onOptionClicked(item) },
showDivider = false,
) {
RowContentContainer(
modifier = Modifier
.heightIn(52.dp)
.padding(horizontal = 12.dp),
icon = {
Image(
modifier = Modifier.size(22.dp),
imageVector = ImageVector.vectorResource(item.iconRes),
contentDescription = item.symbol,
)
},
text = {
RowText(
mainText = item.text,
secondText = item.symbol,
accentMainText = true,
accentSecondText = false,
)
},
action = {
if (item.isSelected) {
Icon(
painter = painterResource(R.drawable.ic_check_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
}
},
)
}
}
}
@Preview(widthDp = 360, heightDp = 800)
@Preview(widthDp = 360, heightDp = 800, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable

View file

@ -4,7 +4,6 @@ import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
@ -15,81 +14,25 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.inputrow.InputRowChecked
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.checkbox.TangemCheckbox
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.earn.state.EarnFilterByTypeBottomSheetContentUM
import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM
@Composable
internal fun EarnFilterByTypeBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
EarnFilterByTypeBottomSheetV2(config)
} else {
EarnFilterByTypeBottomSheetV1(config)
}
}
@Composable
private fun EarnFilterByTypeBottomSheetV1(config: TangemBottomSheetConfig) {
TangemBottomSheet<EarnFilterByTypeBottomSheetContentUM>(
config = config,
titleText = resourceReference(R.string.earn_filter_by),
containerColor = TangemTheme.colors.background.tertiary,
content = { ContentV1(it) },
)
}
@Composable
private fun EarnFilterByTypeBottomSheetV2(config: TangemBottomSheetConfig) {
EarnFilterBottomSheet<EarnFilterByTypeBottomSheetContentUM>(
config = config,
content = { ContentV2(it) },
)
}
@Composable
private fun ContentV1(content: EarnFilterByTypeBottomSheetContentUM) {
Column(
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing16,
),
) {
EarnFilterTypeUM.entries.forEachIndexed { index, type ->
DividerContainer(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = EarnFilterTypeUM.entries.lastIndex,
addDefaultPadding = false,
)
.background(TangemTheme.colors.background.action)
.clickable { content.onOptionClick(type) },
showDivider = index != EarnFilterTypeUM.entries.lastIndex,
) {
InputRowChecked(
text = type.text,
checked = type == content.selectedOption,
)
}
}
}
}
@Composable
private fun ContentV2(content: EarnFilterByTypeBottomSheetContentUM) {
CardFilterBlock(

View file

@ -9,8 +9,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource
@ -20,7 +18,6 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
@ -35,74 +32,10 @@ import com.tangem.features.feed.ui.earn.state.EarnListItemUM
@Composable
internal fun EarnListItem(item: EarnListItemUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
EarnListItemV2(
item = item,
modifier = modifier,
)
} else {
EarnListItemV1(
item = item,
modifier = modifier,
)
}
}
@Composable
private fun EarnListItemV1(item: EarnListItemUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.clip(RectangleShape)
.clickable { item.onItemClick() }
.padding(
horizontal = 12.dp,
vertical = 15.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
CurrencyIcon(
modifier = Modifier.size(36.dp),
state = item.currencyIconState,
shouldDisplayNetwork = true,
networkBadgeBackground = TangemTheme.colors.background.action,
)
SpacerW(12.dp)
Column(modifier = Modifier.weight(1f)) {
TokenTitle(
name = item.tokenName.resolveReference(),
symbol = item.symbol.resolveReference(),
)
SpacerH(2.dp)
Text(
text = item.network.resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
SpacerW(8.dp)
Column(horizontalAlignment = Alignment.End) {
Text(
text = item.earnValue.resolveReference(),
color = TangemTheme.colors.text.accent,
style = TangemTheme.typography.body2,
maxLines = 1,
)
SpacerH(2.dp)
Text(
text = item.earnTypeTitle.resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
maxLines = 1,
)
}
}
EarnListItemV2(
item = item,
modifier = modifier,
)
}
@Composable
@ -158,47 +91,25 @@ private fun EarnListItemV2(item: EarnListItemUM, modifier: Modifier = Modifier)
@Composable
private fun TokenTitle(name: String, symbol: String, modifier: Modifier = Modifier) {
Row(modifier = modifier) {
if (LocalRedesignEnabled.current) {
Text(
modifier = Modifier
.weight(1f, fill = false)
.alignByBaseline(),
text = name,
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodySemibold16,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SpacerW(4.dp)
Text(
modifier = Modifier.alignByBaseline(),
text = symbol,
color = TangemTheme.colors2.text.neutral.secondary,
style = TangemTheme.typography2.captionSemibold12,
maxLines = 1,
overflow = TextOverflow.Visible,
)
} else {
Text(
modifier = Modifier
.weight(1f, fill = false)
.alignByBaseline(),
text = name,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SpacerW(4.dp)
Text(
modifier = Modifier.alignByBaseline(),
text = symbol,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
overflow = TextOverflow.Visible,
)
}
Text(
modifier = Modifier
.weight(1f, fill = false)
.alignByBaseline(),
text = name,
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodySemibold16,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SpacerW(4.dp)
Text(
modifier = Modifier.alignByBaseline(),
text = symbol,
color = TangemTheme.colors2.text.neutral.secondary,
style = TangemTheme.typography2.captionSemibold12,
maxLines = 1,
overflow = TextOverflow.Visible,
)
}
}

View file

@ -10,71 +10,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
internal fun EarnItemPlaceholderV1(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(
horizontal = 16.dp,
vertical = 12.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
CircleShimmer(modifier = Modifier.size(36.dp))
SpacerW(12.dp)
Column(
modifier = Modifier
.weight(1f)
.padding(top = 4.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
) {
RectangleShimmer(
modifier = Modifier
.width(70.dp)
.height(12.dp),
radius = 4.dp,
)
SpacerWMax()
RectangleShimmer(
modifier = Modifier
.width(40.dp)
.height(12.dp),
radius = 4.dp,
)
}
SpacerH(8.dp)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
RectangleShimmer(
modifier = Modifier
.width(52.dp)
.height(12.dp),
radius = 4.dp,
)
SpacerWMax()
RectangleShimmer(
modifier = Modifier
.width(40.dp)
.height(12.dp),
radius = 4.dp,
)
}
}
}
}
@Composable
internal fun EarnItemPlaceholderV2(modifier: Modifier = Modifier) {
Row(
@ -127,22 +64,6 @@ internal fun EarnItemPlaceholderV2(modifier: Modifier = Modifier) {
private const val PLACEHOLDER_ITEMS_COUNT = 8
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EarnListPlaceholderPreviewV1() {
TangemThemePreview {
Column(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary),
) {
repeat(PLACEHOLDER_ITEMS_COUNT) {
EarnItemPlaceholderV1()
}
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable

View file

@ -7,15 +7,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.ds.button.PrimaryInverseTangemButton
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.extensions.TextReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.features.feed.ui.earn.state.EarnFilterUM
@ -27,57 +23,12 @@ internal fun FilterButtons(
onTypeFilterClick: () -> Unit,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
FilterButtonsV2(
earnFilterUM = earnFilterUM,
onNetworkFilterClick = onNetworkFilterClick,
onTypeFilterClick = onTypeFilterClick,
modifier = modifier,
)
} else {
FilterButtonsV1(
earnFilterUM = earnFilterUM,
onNetworkFilterClick = onNetworkFilterClick,
onTypeFilterClick = onTypeFilterClick,
modifier = modifier,
)
}
}
@Composable
private fun FilterButtonsV1(
earnFilterUM: EarnFilterUM,
onNetworkFilterClick: () -> Unit,
onTypeFilterClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier.padding(horizontal = 16.dp),
) {
SecondarySmallButton(
config = SmallButtonConfig(
text = when (earnFilterUM.selectedNetworkFilter) {
is EarnFilterNetworkUM.AllNetworks -> TextReference.Res(R.string.earn_filter_all_networks)
is EarnFilterNetworkUM.MyNetworks -> TextReference.Res(R.string.earn_filter_my_networks)
is EarnFilterNetworkUM.Network -> TextReference.Str(earnFilterUM.selectedNetworkFilter.text)
},
onClick = onNetworkFilterClick,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
isEnabled = earnFilterUM.isNetworkFilterEnabled,
),
)
SpacerWMax()
SecondarySmallButton(
config = SmallButtonConfig(
text = earnFilterUM.selectedTypeFilter.text,
onClick = onTypeFilterClick,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
isEnabled = earnFilterUM.isTypeFilterEnabled,
),
)
}
FilterButtonsV2(
earnFilterUM = earnFilterUM,
onNetworkFilterClick = onNetworkFilterClick,
onTypeFilterClick = onTypeFilterClick,
modifier = modifier,
)
}
@Composable

View file

@ -25,19 +25,11 @@ import com.tangem.features.feed.ui.earn.state.EarnListItemUM
@Composable
internal fun MostlyUsedCard(item: EarnListItemUM, onClick: () -> Unit, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
MostlyUsedCardV2(
modifier = modifier,
item = item,
onClick = onClick,
)
} else {
MostlyUsedCardV1(
modifier = modifier,
item = item,
onClick = onClick,
)
}
MostlyUsedCardV2(
modifier = modifier,
item = item,
onClick = onClick,
)
}
@Composable
@ -91,84 +83,6 @@ private fun MostlyUsedCardV2(item: EarnListItemUM, onClick: () -> Unit, modifier
}
}
@Composable
private fun MostlyUsedCardV1(item: EarnListItemUM, onClick: () -> Unit, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.width(148.dp)
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.clickable(onClick = onClick)
.padding(12.dp),
) {
CurrencyIcon(
modifier = Modifier.size(32.dp),
state = item.currencyIconState,
shouldDisplayNetwork = true,
networkBadgeSize = 12.dp,
networkBadgeBackground = TangemTheme.colors.background.action,
)
SpacerH(8.dp)
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(weight = 1f, fill = false),
text = item.tokenName.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
SpacerW(4.dp)
Text(
text = item.symbol.resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
maxLines = 1,
)
}
SpacerH(2.dp)
Text(
text = item.earnValue.resolveReference(),
color = TangemTheme.colors.text.accent,
style = TangemTheme.typography.caption1,
maxLines = 1,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun EarnListItemPreviewV1() {
TangemThemePreview {
MostlyUsedCardV1(
EarnListItemUM(
network = stringReference("Ethereum"),
symbol = stringReference("USDT"),
tokenName = stringReference("Tether"),
currencyIconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_eth_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 6.54%"),
earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {},
),
onClick = {},
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun EarnListItemPreviewV2() {

View file

@ -12,7 +12,6 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -31,48 +30,11 @@ internal fun MostlyUsedPlaceholder(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(PLACEHOLDER_ITEMS_COUNT) {
if (LocalRedesignEnabled.current) {
MostlyUsedItemPlaceholderV2()
} else {
MostlyUsedItemPlaceholderV1()
}
MostlyUsedItemPlaceholderV2()
}
}
}
@Composable
private fun MostlyUsedItemPlaceholderV1(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.width(148.dp)
.height(102.dp)
.background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(12.dp),
) {
CircleShimmer(
modifier = Modifier
.size(32.dp),
)
SpacerH(8.dp)
RectangleShimmer(
modifier = Modifier
.width(74.dp)
.height(20.dp),
radius = 4.dp,
)
SpacerH(2.dp)
RectangleShimmer(
modifier = Modifier
.width(84.dp)
.height(16.dp),
radius = 4.dp,
)
}
}
@Composable
private fun MostlyUsedItemPlaceholderV2(modifier: Modifier = Modifier) {
Column(

View file

@ -10,7 +10,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.platform.testTag
@ -106,12 +105,10 @@ private fun FeedListContent(
.padding(bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding())
.drawBehind { drawRect(background) },
) {
if (LocalRedesignEnabled.current) {
SpacerH(contentPadding.calculateTopPadding())
}
SpacerH(contentPadding.calculateTopPadding())
DateBlock(state.currentDate)
if (state.forYouBannerUM is ForYouBannerUM.Content && LocalRedesignEnabled.current) {
if (state.forYouBannerUM is ForYouBannerUM.Content) {
SpacerH(16.dp)
TangemMessageBanner(
title = resourceReference(R.string.for_you_title),
@ -164,17 +161,7 @@ private fun FeedListContent(
@Preview(showBackground = true, heightDp = 1500)
@Composable
private fun FeedListPreview() {
TangemThemePreview {
TangemThemePreviewRedesign {
FeedList(state = createFeedPreviewState(), contentPadding = PaddingValues())
}
}
@Preview(showBackground = true, heightDp = 1500)
@Composable
private fun FeedListPreviewV2() {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
FeedList(state = createFeedPreviewState(), contentPadding = PaddingValues())
}
}
}

View file

@ -16,11 +16,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
@Composable
@ -31,10 +27,7 @@ internal fun ColumnScope.Header(
modifier: Modifier = Modifier,
title: @Composable () -> Unit,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
if (isRedesignEnabled) {
SpacerH(12.dp)
}
SpacerH(12.dp)
AnimatedContent(
targetState = isLoading,
modifier = modifier,
@ -45,45 +38,32 @@ internal fun ColumnScope.Header(
verticalAlignment = Alignment.CenterVertically,
) {
if (animatedState) {
if (isRedesignEnabled) {
RectangleShimmer(
modifier = Modifier.size(width = 130.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
} else {
RectangleShimmer(modifier = Modifier.size(width = 104.dp, height = 18.dp))
}
RectangleShimmer(
modifier = Modifier.size(width = 130.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
} else {
Box(modifier = Modifier.weight(1f)) {
title()
}
SpacerW(8.dp)
AnimatedVisibility(shouldShowSeeAll) {
if (isRedesignEnabled) {
Row(
modifier = Modifier
.padding(start = 8.dp)
.clickable(onClick = onSeeAllClick),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResourceSafe(R.string.common_see_all),
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodySemibold16,
)
Icon(
modifier = Modifier.size(24.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_small_right_24),
tint = TangemTheme.colors2.markers.iconGray,
contentDescription = null,
)
}
} else {
SecondarySmallButton(
config = SmallButtonConfig(
text = TextReference.Res(R.string.common_see_all),
onClick = onSeeAllClick,
),
Row(
modifier = Modifier
.padding(start = 8.dp)
.clickable(onClick = onSeeAllClick),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResourceSafe(R.string.common_see_all),
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodySemibold16,
)
Icon(
modifier = Modifier.size(24.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_small_right_24),
tint = TangemTheme.colors2.markers.iconGray,
contentDescription = null,
)
}
}

View file

@ -1,51 +0,0 @@
package com.tangem.features.feed.ui.feed.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
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.unit.dp
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
@Composable
internal fun ChartsFilterChip(sortByTypeUM: SortByTypeUM, isSelected: Boolean, onClick: () -> Unit) {
Box(
modifier = Modifier
.clip(shape = RoundedCornerShape(12.dp))
.background(
color = if (isSelected) {
TangemTheme.colors.button.primary
} else {
TangemTheme.colors.button.secondary
},
)
.clickable(
onClick = onClick,
indication = ripple(),
interactionSource = remember { MutableInteractionSource() },
)
.padding(vertical = 8.dp, horizontal = 24.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = sortByTypeUM.text.resolveReference(),
style = TangemTheme.typography.button,
color = if (isSelected) {
TangemTheme.colors.text.primary2
} else {
TangemTheme.colors.text.primary1
},
)
}
}

View file

@ -5,45 +5,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun DateBlock(currentDate: String) {
if (LocalRedesignEnabled.current) {
DateBlockV2(currentDate)
} else {
DateBlockV1(currentDate)
}
}
@Composable
private fun DateBlockV1(currentDate: String) {
SpacerH(20.dp)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
text = stringResourceSafe(R.string.feed_market_and_news),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
text = currentDate,
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.tertiary,
)
}
@Composable
private fun DateBlockV2(currentDate: String) {
SpacerH(TangemTheme.dimens2.x1)
Text(
modifier = Modifier

View file

@ -9,17 +9,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.earn.components.EarnItemPlaceholderV1
import com.tangem.features.feed.ui.earn.components.EarnListItem
import com.tangem.features.feed.ui.earn.components.MostlyUsedCard
import com.tangem.features.feed.ui.earn.components.MostlyUsedPlaceholder
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
@ -29,56 +25,7 @@ import kotlinx.collections.immutable.ImmutableList
@Composable
internal fun EarnBlock(onSeeAllClick: () -> Unit, earnListUM: EarnListUM, modifier: Modifier = Modifier) {
if (earnListUM is EarnListUM.Empty) return
if (LocalRedesignEnabled.current) {
EarnBlockV2(onSeeAllClick = onSeeAllClick, earnListUM = earnListUM, modifier = modifier)
} else {
EarnBlockV1(onSeeAllClick = onSeeAllClick, earnListUM = earnListUM, modifier = modifier)
}
}
@Composable
private fun EarnBlockV1(onSeeAllClick: () -> Unit, earnListUM: EarnListUM, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Header(
title = {
Text(
text = stringResourceSafe(R.string.markets_earn_common_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
},
onSeeAllClick = onSeeAllClick,
isLoading = earnListUM is EarnListUM.Loading,
shouldShowSeeAll = earnListUM is EarnListUM.Content,
modifier = Modifier.padding(horizontal = 20.dp),
)
SpacerH(12.dp)
BlockCard(
modifier = Modifier.padding(horizontal = 16.dp),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
AnimatedContent(
targetState = earnListUM,
contentKey = { it::class.java },
) { earnListUM ->
when (earnListUM) {
is EarnListUM.Content -> EarnContentBlock(items = earnListUM.items)
is EarnListUM.Error -> EarnErrorBlock(onRetryClick = earnListUM.onRetryClicked)
EarnListUM.Loading -> {
repeat(PLACEHOLDER_ITEM_COUNT) {
EarnItemPlaceholderV1()
}
}
EarnListUM.Empty -> Unit
}
}
}
SpacerH(32.dp)
}
EarnBlockV2(onSeeAllClick = onSeeAllClick, earnListUM = earnListUM, modifier = modifier)
}
@Composable
@ -143,32 +90,22 @@ private fun EarnErrorBlock(onRetryClick: () -> Unit) {
@Composable
private fun EarnContentBlock(items: ImmutableList<EarnListItemUM>) {
if (LocalRedesignEnabled.current) {
LazyRow(
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = 8.dp,
),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(
items = items,
key = { item -> item.id },
) { item ->
MostlyUsedCard(
item = item,
onClick = item.onItemClick,
)
}
}
} else {
Column(modifier = Modifier.fillMaxWidth()) {
items.fastForEach { item ->
EarnListItem(item = item)
}
LazyRow(
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = 8.dp,
),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(
items = items,
key = { item -> item.id },
) { item ->
MostlyUsedCard(
item = item,
onClick = item.onItemClick,
)
}
}
}
private const val PLACEHOLDER_ITEM_COUNT = 5
}

View file

@ -13,9 +13,8 @@ import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.feed.ui.feed.components.articles.DefaultLoadingArticle
import com.tangem.features.feed.ui.feed.components.articles.TrendingLoadingArticle
@ -36,7 +35,48 @@ internal fun FeedListLoading(modifier: Modifier = Modifier) {
@Composable
internal fun MarketLoadingBlock() {
if (LocalRedesignEnabled.current) {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
.size(width = 132.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(20.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(40.dp)
}
@Composable
internal fun MarketPulseLoadingBlock() {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
.size(width = 132.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(15.dp)
LazyRow(
modifier = Modifier.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
RectangleShimmer(
modifier = Modifier.size(width = 112.dp, height = 36.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
SpacerH(16.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(32.dp)
}
@Composable
internal fun NewsLoadingBlock() {
Column {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
@ -44,133 +84,28 @@ internal fun MarketLoadingBlock() {
radius = TangemTheme.dimens2.x25,
)
SpacerH(20.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
TrendingLoadingArticle(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(12.dp)
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
DefaultLoadingArticle()
}
}
SpacerH(40.dp)
} else {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp)
.size(width = 104.dp, height = 18.dp),
)
SpacerH(15.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(35.dp)
}
}
@Composable
internal fun MarketPulseLoadingBlock() {
if (LocalRedesignEnabled.current) {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
.size(width = 132.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(15.dp)
LazyRow(
modifier = Modifier.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
RectangleShimmer(
modifier = Modifier.size(width = 112.dp, height = 36.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
SpacerH(16.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(32.dp)
} else {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp)
.size(width = 104.dp, height = 18.dp),
)
SpacerH(15.dp)
LazyRow(
modifier = Modifier.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
RectangleShimmer(modifier = Modifier.size(width = 124.dp, height = 36.dp))
}
}
SpacerH(16.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(32.dp)
}
}
@Composable
internal fun NewsLoadingBlock() {
if (LocalRedesignEnabled.current) {
Column {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
.size(width = 132.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(20.dp)
TrendingLoadingArticle(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(12.dp)
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
DefaultLoadingArticle()
}
}
SpacerH(40.dp)
}
} else {
Column {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp)
.size(width = 104.dp, height = 18.dp),
)
SpacerH(15.dp)
TrendingLoadingArticle(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(12.dp)
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
DefaultLoadingArticle()
}
}
SpacerH(35.dp)
}
}
}
@Composable
private fun ChartsLoading(modifier: Modifier = Modifier) {
val isRedesignEnabled = LocalRedesignEnabled.current
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(
containerColor = if (isRedesignEnabled) {
TangemTheme.colors2.surface.level3
} else {
TangemTheme.colors.background.action
},
containerColor = TangemTheme.colors2.surface.level3,
),
) {
Column(
@ -190,7 +125,7 @@ private const val DEFAULT_CHART_SIZE_IN_MARKET = 5
@Preview(showBackground = true)
@Composable
private fun FeedListLoadingPreview() {
TangemThemePreview {
TangemThemePreviewRedesign {
Column {
MarketLoadingBlock()
NewsLoadingBlock()

View file

@ -28,7 +28,6 @@ import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.ds.tabs.TangemTab
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.feed.ui.feed.state.FeedListCallbacks
@ -42,7 +41,6 @@ internal fun MarketBlock(
onItemClick: (MarketsListItemUM) -> Unit,
modifier: Modifier = Modifier,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
AnimatedContent(
targetState = marketChart,
label = "MarketBlockChartAnimation",
@ -58,16 +56,8 @@ internal fun MarketBlock(
title = {
Text(
text = stringResourceSafe(R.string.markets_common_title),
style = if (isRedesignEnabled) {
TangemTheme.typography2.headingSemibold20
} else {
TangemTheme.typography.h3
},
color = if (isRedesignEnabled) {
TangemTheme.colors2.text.neutral.primary
} else {
TangemTheme.colors.text.primary1
},
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
@ -80,7 +70,7 @@ internal fun MarketBlock(
modifier = Modifier.padding(horizontal = MARKET_BLOCK_HEADER_PADDING),
)
SpacerH(if (isRedesignEnabled) 20.dp else 12.dp)
SpacerH(20.dp)
Charts(
onItemClick = onItemClick,
@ -96,7 +86,6 @@ internal fun MarketBlock(
@Suppress("LongMethod")
@Composable
internal fun ColumnScope.MarketPulseBlock(marketChartConfig: MarketChartConfig, feedListCallbacks: FeedListCallbacks) {
val isRedesignEnabled = LocalRedesignEnabled.current
val onSeeAllClick by rememberUpdatedState {
feedListCallbacks.onMarketOpenClick(marketChartConfig.currentSortByType)
}
@ -105,16 +94,8 @@ internal fun ColumnScope.MarketPulseBlock(marketChartConfig: MarketChartConfig,
title = {
Text(
text = stringResourceSafe(R.string.markets_pulse_common_title),
style = if (isRedesignEnabled) {
TangemTheme.typography2.headingSemibold20
} else {
TangemTheme.typography.h3
},
color = if (isRedesignEnabled) {
TangemTheme.colors2.text.neutral.primary
} else {
TangemTheme.colors.text.primary1
},
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
@ -128,11 +109,7 @@ internal fun ColumnScope.MarketPulseBlock(marketChartConfig: MarketChartConfig,
LazyRow(
modifier = Modifier.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
contentPadding = if (isRedesignEnabled) {
PaddingValues(horizontal = 16.dp, vertical = 6.dp)
} else {
PaddingValues(16.dp)
},
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 6.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
state = rememberLazyListState(),
) {
@ -140,19 +117,11 @@ internal fun ColumnScope.MarketPulseBlock(marketChartConfig: MarketChartConfig,
items = marketChartConfig.getFilterPreset(),
key = SortByTypeUM::name,
) { sortByTypeUM ->
if (isRedesignEnabled) {
TangemTab(
text = sortByTypeUM.text,
isChecked = sortByTypeUM == marketChartConfig.currentSortByType,
onCheckedChange = { feedListCallbacks.onSortTypeClick(sortByTypeUM) },
)
} else {
ChartsFilterChip(
sortByTypeUM = sortByTypeUM,
isSelected = sortByTypeUM == marketChartConfig.currentSortByType,
onClick = { feedListCallbacks.onSortTypeClick(sortByTypeUM) },
)
}
TangemTab(
text = sortByTypeUM.text,
isChecked = sortByTypeUM == marketChartConfig.currentSortByType,
onCheckedChange = { feedListCallbacks.onSortTypeClick(sortByTypeUM) },
)
}
}
@ -179,21 +148,12 @@ private fun Charts(
onItemClick: (MarketsListItemUM) -> Unit,
modifier: Modifier = Modifier,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(
containerColor = if (isRedesignEnabled) {
TangemTheme.colors2.surface.level3
} else {
TangemTheme.colors.background.action
},
containerColor = TangemTheme.colors2.surface.level3,
),
shape = if (isRedesignEnabled) {
RoundedCornerShape(TangemTheme.dimens2.x6)
} else {
TangemTheme.shapes.roundedCornersXMedium
},
shape = RoundedCornerShape(TangemTheme.dimens2.x6),
) {
Column(modifier = Modifier.fillMaxWidth()) {
when (marketChart) {

View file

@ -1,12 +1,10 @@
package com.tangem.features.feed.ui.feed.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
@ -26,7 +24,6 @@ import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
@ -38,9 +35,6 @@ private const val GRADIENT_END = 0.5f
private const val LINEAR_GRADIENT_FIRST_PART_V2 = 0xFF7B78FF
private const val LINEAR_GRADIENT_SECOND_PART_V2 = 0xFFC56BCD
private const val LINEAR_GRADIENT_FIRST_PART_V1 = 0xFF635EEC
private const val LINEAR_GRADIENT_SECOND_PART_V1 = 0xFFE05AED
private const val MAGIC_ICON_COLOR = 0xFF7D78FF
@Composable
@ -65,22 +59,8 @@ internal fun NewsBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM, trend
@Suppress("LongMethod")
@Composable
private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM, trendingArticle: ArticleConfigUM?) {
val isRedesignEnabled = LocalRedesignEnabled.current
val gradientStart = remember(isRedesignEnabled) {
if (isRedesignEnabled) {
Color(LINEAR_GRADIENT_FIRST_PART_V2)
} else {
Color(LINEAR_GRADIENT_FIRST_PART_V1)
}
}
val gradientEnd = remember(isRedesignEnabled) {
if (isRedesignEnabled) {
Color(LINEAR_GRADIENT_SECOND_PART_V2)
} else {
Color(LINEAR_GRADIENT_SECOND_PART_V1)
}
}
val gradientStart = Color(LINEAR_GRADIENT_FIRST_PART_V2)
val gradientEnd = Color(LINEAR_GRADIENT_SECOND_PART_V2)
Column {
Header(
@ -88,33 +68,18 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM,
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResourceSafe(R.string.common_news),
style = if (isRedesignEnabled) {
TangemTheme.typography2.headingSemibold20
} else {
TangemTheme.typography.h3
},
color = if (isRedesignEnabled) {
TangemTheme.colors2.text.neutral.primary
} else {
TangemTheme.colors.text.primary1
},
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
)
SpacerW(4.dp)
if (isRedesignEnabled) {
Icon(
modifier = Modifier.size(TangemTheme.dimens2.x6),
tint = Color(MAGIC_ICON_COLOR),
imageVector = ImageVector.vectorResource(R.drawable.ic_magic_28),
contentDescription = null,
)
} else {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_stars_20),
contentDescription = null,
)
}
Icon(
modifier = Modifier.size(TangemTheme.dimens2.x6),
tint = Color(MAGIC_ICON_COLOR),
imageVector = ImageVector.vectorResource(R.drawable.ic_magic_28),
contentDescription = null,
)
SpacerW(2.dp)
@ -131,11 +96,7 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM,
append(stringResourceSafe(R.string.feed_tangem_ai))
}
},
style = if (isRedesignEnabled) {
TangemTheme.typography2.bodyRegular16.copy(fontWeight = FontWeight.Medium)
} else {
TangemTheme.typography.subtitle1
},
style = TangemTheme.typography2.bodyRegular16.copy(fontWeight = FontWeight.Medium),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
@ -156,7 +117,6 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM,
.padding(horizontal = 16.dp),
articleConfigUM = trendingArticle,
onArticleClick = { feedListCallbacks.onArticleClick(trendingArticle.id) },
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
)
SpacerH(12.dp)
}

View file

@ -10,9 +10,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ShowMoreArticlesCard
import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
@ -48,7 +46,6 @@ internal fun NewsSlider(newsSliderConfig: NewsSliderConfig) {
.width(280.dp)
.heightIn(min = 172.dp)
.fillMaxHeight(),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
)
}

View file

@ -1,30 +1,13 @@
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.material3.CardColors
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.LocalRedesignEnabled
@Composable
fun ArticleCard(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
modifier: Modifier = Modifier,
colors: CardColors = TangemBlockCardColors,
) {
if (LocalRedesignEnabled.current) {
ArticleCardV2(
articleConfigUM = articleConfigUM,
onArticleClick = onArticleClick,
modifier = modifier,
)
} else {
ArticleCardV1(
articleConfigUM = articleConfigUM,
onArticleClick = onArticleClick,
modifier = modifier,
colors = colors,
)
}
fun ArticleCard(articleConfigUM: ArticleConfigUM, onArticleClick: () -> Unit, modifier: Modifier = Modifier) {
ArticleCardV2(
articleConfigUM = articleConfigUM,
onArticleClick = onArticleClick,
modifier = modifier,
)
}

View file

@ -1,273 +0,0 @@
package com.tangem.features.feed.ui.feed.components.articles
import android.content.res.Configuration
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CardColors
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
@Composable
internal fun ArticleCardV1(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
modifier: Modifier = Modifier,
colors: CardColors = TangemBlockCardColors,
) {
if (articleConfigUM.isTrending) {
TrendingArticle(
articleConfigUM = articleConfigUM,
modifier = modifier,
onArticleClick = onArticleClick,
colors = colors,
)
} else {
DefaultArticle(
articleConfigUM = articleConfigUM,
modifier = modifier,
onArticleClick = onArticleClick,
colors = colors,
)
}
}
@Composable
private fun TrendingArticle(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
colors: CardColors,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(colors.containerColor)
.clickable { onArticleClick() },
) {
Image(
modifier = Modifier.matchParentSize(),
painter = painterResource(R.drawable.bg_trending_article),
contentDescription = null,
contentScale = ContentScale.FillWidth,
)
Column(
modifier = Modifier.padding(vertical = 24.dp, horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier
.background(
color = TangemTheme.colors.icon.accent.copy(alpha = 0.1f),
shape = RoundedCornerShape(8.dp),
)
.padding(horizontal = 8.dp, vertical = 4.dp),
text = stringResourceSafe(R.string.feed_trending_now),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.icon.accent,
)
SpacerH(12.dp)
Text(
text = articleConfigUM.title,
color = if (articleConfigUM.isViewed) {
TangemTheme.colors.text.tertiary
} else {
TangemTheme.colors.text.primary1
},
style = TangemTheme.typography.h3,
textAlign = TextAlign.Center,
)
SpacerH(8.dp)
ArticleInfo(
score = articleConfigUM.score,
createdAt = articleConfigUM.createdAt.resolveReference(),
)
SpacerH(32.dp)
Tags(
modifier = Modifier.padding(horizontal = 46.dp),
tags = articleConfigUM.tags.toImmutableList(),
)
}
}
}
@Composable
internal fun ShowMoreArticlesCardV1(modifier: Modifier = Modifier, onClick: () -> Unit) {
BlockCard(
modifier = modifier,
onClick = onClick,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(vertical = 31.dp, horizontal = 12.dp),
) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_show_more_news_48),
contentDescription = stringResourceSafe(R.string.common_show_more),
)
SpacerH(16.dp)
Text(
text = stringResourceSafe(R.string.news_all_news),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
Text(
text = stringResourceSafe(R.string.news_stay_in_the_loop),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Composable
private fun DefaultArticle(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
colors: CardColors,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(colors.containerColor)
.clickable { onArticleClick() }
.padding(12.dp),
) {
ArticleInfo(
score = articleConfigUM.score,
createdAt = articleConfigUM.createdAt.resolveReference(),
)
SpacerH(8.dp)
Text(
text = articleConfigUM.title,
color = if (articleConfigUM.isViewed) {
TangemTheme.colors.text.tertiary
} else {
TangemTheme.colors.text.primary1
},
style = TangemTheme.typography.subtitle1,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
SpacerH(16.dp)
SpacerHMax()
Tags(tags = articleConfigUM.tags.toImmutableList())
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TagsPreview() {
TangemThemePreview {
Tags(
tags = persistentListOf(
LabelUM(TextReference.Str("Hype")),
LabelUM(TextReference.Str("BTC")),
LabelUM(TextReference.Str("Supply")),
LabelUM(TextReference.Str("Demand")),
LabelUM(TextReference.Str("Best rate")),
LabelUM(TextReference.Str("Breaking news")),
),
)
}
}
@Preview(showBackground = true, widthDp = 360, backgroundColor = 0xFF000000)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ArticleCardsPreview() {
val tags = listOf(
LabelUM(TextReference.Str("Hype")),
LabelUM(TextReference.Str("BTC")),
LabelUM(TextReference.Str("Supply")),
LabelUM(TextReference.Str("Demand")),
LabelUM(TextReference.Str("Breaking news")),
).toImmutableSet()
val config = ArticleConfigUM(
id = 1,
title = "Bitcoin ETFs log 4th straight day of inflows (+\$550M)",
score = 9.5f,
createdAt = TextReference.Str("1h ago"),
isTrending = true,
tags = tags,
isViewed = false,
)
TangemThemePreview {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
ArticleCard(
articleConfigUM = config,
onArticleClick = {},
)
SpacerH(20.dp)
ArticleCard(
articleConfigUM = config.copy(isViewed = true),
onArticleClick = {},
)
SpacerH(20.dp)
ArticleCard(
articleConfigUM = config.copy(isTrending = false),
onArticleClick = {},
)
SpacerH(20.dp)
ArticleCard(
articleConfigUM = config.copy(isTrending = false, isViewed = true),
onArticleClick = {},
)
}
}
}

View file

@ -11,14 +11,11 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.label.Label
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.ds.badge.*
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.utils.StringsSigns
import kotlinx.collections.immutable.ImmutableList
@ -34,63 +31,14 @@ fun ArticleHeader(
tags: ImmutableList<LabelUM>,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
ArticleHeaderV2(
isTrending = isTrending,
title = title,
createdAt = createdAt,
score = score,
tags = tags,
modifier = modifier,
)
} else {
ArticleHeaderV1(
title = title,
createdAt = createdAt,
score = score,
tags = tags,
modifier = modifier,
)
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun ArticleHeaderV1(
title: String,
createdAt: String,
score: Float,
tags: ImmutableList<LabelUM>,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier) {
ArticleInfo(
score = score,
createdAt = createdAt,
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = title,
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
if (tags.isNotEmpty()) {
Spacer(modifier = Modifier.height(20.dp))
FlowRow(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
tags.forEach { tag ->
Label(
state = tag,
)
}
}
}
}
ArticleHeaderV2(
isTrending = isTrending,
title = title,
createdAt = createdAt,
score = score,
tags = tags,
modifier = modifier,
)
}
@Composable
@ -220,21 +168,7 @@ private fun labelLeadingBadgeIconPosition(content: LabelLeadingContentUM): Tange
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun ArticleHeaderPreviewV1() {
TangemThemePreview {
ArticleHeader(
title = "Something going good!",
createdAt = "1 hour ago",
score = 5.5f,
tags = persistentListOf(),
isTrending = true,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun ArticleHeaderPreviewV2() {
private fun ArticleHeaderPreview() {
TangemThemePreviewRedesign {
ArticleHeader(
title = "Something going good!",

View file

@ -1,61 +0,0 @@
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = Modifier) {
val dotColor = TangemTheme.colors.text.secondary
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_start_circle_12),
contentDescription = null,
)
Text(
text = score.toString(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.secondary,
)
Spacer(
modifier = Modifier
.size(4.dp)
.drawWithCache {
val radius = size.minDimension / 2f
onDrawBehind {
drawCircle(
color = dotColor,
radius = radius,
)
}
},
)
Text(
text = createdAt,
style = TangemTheme.typography.subtitle2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.secondary,
)
}
}

View file

@ -11,47 +11,12 @@ import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
fun TrendingLoadingArticle(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
TrendingLoadingArticleV2(modifier)
} else {
TrendingLoadingArticleV1(modifier)
}
}
@Composable
private fun TrendingLoadingArticleV1(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp, horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
RectangleShimmer(modifier = Modifier.size(width = 96.dp, height = 24.dp), radius = 8.dp)
SpacerH(12.dp)
RectangleShimmer(modifier = Modifier.size(width = 285.dp, height = 18.dp), radius = 4.dp)
SpacerH(6.dp)
RectangleShimmer(modifier = Modifier.size(width = 190.dp, height = 18.dp), radius = 4.dp)
SpacerH(14.dp)
RectangleShimmer(modifier = Modifier.size(width = 110.dp, height = 18.dp), radius = 4.dp)
SpacerH(32.dp)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 24.dp), radius = 8.dp)
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 24.dp), radius = 8.dp)
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 24.dp), radius = 8.dp)
}
}
}
TrendingLoadingArticleV2(modifier)
}
@Composable
@ -96,51 +61,7 @@ private fun TrendingLoadingArticleV2(modifier: Modifier = Modifier) {
@Composable
fun DefaultLoadingArticle(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
DefaultLoadingArticleV2(modifier)
} else {
DefaultLoadingArticleV1(modifier)
}
}
@Composable
private fun DefaultLoadingArticleV1(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
RectangleShimmer(modifier = Modifier.size(width = 46.dp, height = 16.dp), radius = TangemTheme.dimens2.x25)
SpacerH(8.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(28.dp)
RectangleShimmer(modifier = Modifier.size(width = 46.dp, height = 16.dp), radius = TangemTheme.dimens2.x25)
SpacerH(8.dp)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
RectangleShimmer(
modifier = Modifier.size(width = 72.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier.size(width = 62.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier.size(width = 32.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
}
DefaultLoadingArticleV2(modifier)
}
@Composable
@ -186,16 +107,7 @@ private fun DefaultLoadingArticleV2(modifier: Modifier = Modifier) {
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TrendingLoadingArticlePreviewV1() {
TangemThemePreview {
TrendingLoadingArticle()
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TrendingLoadingArticlePreviewV2() {
private fun TrendingLoadingArticlePreview() {
TangemThemePreviewRedesign {
TrendingLoadingArticle()
}
@ -204,16 +116,7 @@ private fun TrendingLoadingArticlePreviewV2() {
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun DefaultLoadingArticlePreviewV1() {
TangemThemePreview {
DefaultLoadingArticle()
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun DefaultLoadingArticlePreviewV2() {
private fun DefaultLoadingArticlePreview() {
TangemThemePreviewRedesign {
DefaultLoadingArticle()
}

View file

@ -2,14 +2,8 @@ package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.LocalRedesignEnabled
@Composable
fun ShowMoreArticlesCard(modifier: Modifier = Modifier, onClick: () -> Unit) {
val isRedesignEnabled: Boolean = LocalRedesignEnabled.current
if (isRedesignEnabled) {
ShowMoreArticlesCardV2(modifier = modifier, onClick = onClick)
} else {
ShowMoreArticlesCardV1(modifier = modifier, onClick = onClick)
}
ShowMoreArticlesCardV2(modifier = modifier, onClick = onClick)
}

View file

@ -8,13 +8,11 @@ import androidx.compose.ui.layout.SubcomposeMeasureScope
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.label.Label
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.ds.badge.*
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.utils.StringsSigns
import kotlinx.collections.immutable.ImmutableList
@ -36,28 +34,24 @@ internal fun Tags(tags: ImmutableList<LabelUM>, modifier: Modifier = Modifier) {
val tagPlaceables = subcompose(ContentSlot.Tags) {
tags.forEach { tag ->
if (LocalRedesignEnabled.current) {
TangemBadge(
text = tag.text,
tangemIconUM = when (val content = tag.leadingContent) {
LabelLeadingContentUM.None -> null
is LabelLeadingContentUM.Token -> TangemIconUM.Url(
url = content.iconUrl,
fallbackRes = R.drawable.ic_alert_24,
)
},
shape = TangemBadgeShape.Rounded,
size = TangemBadgeSize.X6,
type = TangemBadgeType.Tinted,
color = TangemBadgeColor.Gray,
iconPosition = when (tag.leadingContent) {
LabelLeadingContentUM.None -> TangemBadgeIconPosition.None
is LabelLeadingContentUM.Token -> TangemBadgeIconPosition.Start
},
)
} else {
Label(state = tag)
}
TangemBadge(
text = tag.text,
tangemIconUM = when (val content = tag.leadingContent) {
LabelLeadingContentUM.None -> null
is LabelLeadingContentUM.Token -> TangemIconUM.Url(
url = content.iconUrl,
fallbackRes = R.drawable.ic_alert_24,
)
},
shape = TangemBadgeShape.Rounded,
size = TangemBadgeSize.X6,
type = TangemBadgeType.Tinted,
color = TangemBadgeColor.Gray,
iconPosition = when (tag.leadingContent) {
LabelLeadingContentUM.None -> TangemBadgeIconPosition.None
is LabelLeadingContentUM.Token -> TangemBadgeIconPosition.Start
},
)
}
}.map { it.measure(constraints) }
@ -134,22 +128,13 @@ private fun SubcomposeMeasureScope.calculateLayoutInfo(
@Composable
private fun OverflowLabel(count: Int) {
if (LocalRedesignEnabled.current) {
TangemBadge(
text = TextReference.Str("${StringsSigns.PLUS}$count"),
shape = TangemBadgeShape.Rounded,
size = TangemBadgeSize.X6,
type = TangemBadgeType.Tinted,
color = TangemBadgeColor.Gray,
)
} else {
Label(
state = LabelUM(
text = TextReference.Str("${StringsSigns.PLUS}$count"),
maxLines = 1,
),
)
}
TangemBadge(
text = TextReference.Str("${StringsSigns.PLUS}$count"),
shape = TangemBadgeShape.Rounded,
size = TangemBadgeSize.X6,
type = TangemBadgeType.Tinted,
color = TangemBadgeColor.Gray,
)
}
private fun calculateRowWidth(placeables: List<Placeable>, spacingPx: Int): Int {

View file

@ -24,9 +24,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
import com.tangem.core.ui.components.currency.icon.CoinIcon
import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
import com.tangem.core.ui.components.marketprice.PriceChangeType
@ -38,7 +35,6 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketsTestTags
@ -50,10 +46,8 @@ import com.tangem.features.feed.ui.market.detailed.state.ExchangesBottomSheetCon
import com.tangem.features.feed.ui.market.detailed.state.InfoBottomSheetContent
import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM
import com.tangem.features.feed.ui.market.detailed.state.SecurityScoreBottomSheetContent
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.distinctUntilChanged
import com.tangem.core.ui.R as CoreR
@Suppress("LongParameterList")
@Composable
@ -62,7 +56,6 @@ internal fun MarketsTokenDetailsContent(
state: MarketsTokenDetailsUM,
backgroundColor: Color,
modifier: Modifier = Modifier,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
portfolioFloatingBlock: @Composable ((Modifier) -> Unit)?,
marketingBanner: @Composable (Modifier) -> Unit,
) {
@ -71,7 +64,6 @@ internal fun MarketsTokenDetailsContent(
modifier = modifier,
backgroundColor = backgroundColor,
state = state,
portfolioBlock = portfolioBlock,
portfolioFloatingBlock = portfolioFloatingBlock,
marketingBanner = marketingBanner,
)
@ -90,11 +82,9 @@ private fun Content(
state: MarketsTokenDetailsUM,
backgroundColor: Color,
modifier: Modifier = Modifier,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
portfolioFloatingBlock: @Composable ((Modifier) -> Unit)?,
marketingBanner: @Composable (Modifier) -> Unit,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
val density = LocalDensity.current
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
val lazyListState = rememberLazyListState()
@ -128,20 +118,19 @@ private fun Content(
Header(state = state)
}
item {
if (isRedesignEnabled) SpacerH(TangemTheme.dimens2.x3) else SpacerH16()
SpacerH(TangemTheme.dimens2.x3)
}
item("intervalSelector") {
IntervalSelector(
trendInterval = state.selectedInterval,
onIntervalClick = state.onSelectedIntervalChange,
isEnabled = state.body !is MarketsTokenDetailsUM.Body.Nothing,
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
)
}
item {
if (isRedesignEnabled) SpacerH(TangemTheme.dimens2.x3) else SpacerH32()
SpacerH(TangemTheme.dimens2.x3)
}
item("chart") {
MarketTokenDetailsChart(
@ -153,9 +142,7 @@ private fun Content(
item { SpacerH16() }
tokenMarketDetailsBody(
state = state.body,
portfolioBlock = portfolioBlock,
relatedNews = state.relatedNews,
isRedesignEnabled = isRedesignEnabled,
marketingBanner = marketingBanner,
)
item { SpacerH(bottomSpacing) }
@ -176,86 +163,14 @@ private fun Content(
}
}
@Suppress("LongParameterList")
@Composable
internal fun MarketsTokenDetailsTopBar(
backgroundColor: Color,
shouldShowPriceSubtitle: Boolean,
tokenName: String,
tokenPrice: String,
isBackButtonEnabled: Boolean,
onBackClick: () -> Unit,
onShareClick: () -> Unit,
) {
TangemTopAppBar(
modifier = Modifier.drawBehind { drawRect(backgroundColor) },
title = tokenName,
subtitle = if (shouldShowPriceSubtitle) tokenPrice else null,
startButton = TopAppBarButtonUM.Back(
onBackClicked = onBackClick,
enabled = isBackButtonEnabled,
),
endButton = TopAppBarButtonUM.Icon(
iconRes = CoreR.drawable.ic_share_24,
onClicked = onShareClick,
),
)
}
@Composable
private fun Header(state: MarketsTokenDetailsUM) {
if (LocalRedesignEnabled.current) {
HeaderV2(
modifier = Modifier
.padding(TangemTheme.dimens2.x4)
.fillMaxWidth(),
state = state,
)
} else {
HeaderV1(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
state = state,
)
}
}
@Composable
private fun HeaderV1(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Column(modifier = Modifier.weight(1f)) {
TokenPriceTextV1(
price = state.priceText,
triggerPriceChange = state.triggerPriceChange,
)
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
Text(
text = state.dateTimeText.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
if (state.priceChangePercentText != null) {
PriceChangeInPercent(
valueInPercent = state.priceChangePercentText,
type = state.priceChangeType,
textStyle = TangemTheme.typography.caption2,
)
}
}
}
SpacerW4()
CoinIcon(
modifier = Modifier.requiredSize(TangemTheme.dimens.size48),
url = state.iconUrl,
alpha = 1f,
colorFilter = null,
fallbackResId = R.drawable.ic_custom_token_44,
)
}
HeaderV2(
modifier = Modifier
.padding(TangemTheme.dimens2.x4)
.fillMaxWidth(),
state = state,
)
}
@Composable
@ -324,40 +239,6 @@ private fun HeaderV2(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier
}
}
@Composable
private fun TokenPriceTextV1(
price: String,
triggerPriceChange: StateEvent<PriceChangeType>,
modifier: Modifier = Modifier,
) {
val growColor = TangemTheme.colors.text.accent
val fallColor = TangemTheme.colors.text.warning
val generalColor = TangemTheme.colors.text.primary1
val color = remember(generalColor) { Animatable(generalColor) }
EventEffect(triggerPriceChange) { priceChangeType ->
val nextColor = when (priceChangeType) {
PriceChangeType.UP,
-> growColor
PriceChangeType.DOWN -> fallColor
PriceChangeType.NEUTRAL -> return@EventEffect
}
color.animateTo(nextColor, snap())
color.animateTo(generalColor, tween(durationMillis = 500))
}
Text(
text = price,
modifier = modifier,
color = color.value,
autoSize = TextAutoSize.StepBased(maxFontSize = TangemTheme.typography.head.fontSize),
maxLines = 1,
style = TangemTheme.typography.head,
)
}
@Composable
private fun TokenPriceTextV2(
priceAnnotated: TextReference,
@ -395,66 +276,24 @@ private fun TokenPriceTextV2(
@Composable
private fun IntervalSelector(
trendInterval: PriceChangeInterval,
isEnabled: Boolean,
onIntervalClick: (PriceChangeInterval) -> Unit,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
val items = remember {
PriceChangeInterval.entries
.map { TangemSegmentUM(it.toString(), it.getText()) }.toImmutableList()
}
val selectedItem = remember(trendInterval) {
items.firstOrNull { it.id == trendInterval.toString() }
}
TangemSegmentedPicker(
items = items,
initialSelectedItem = selectedItem,
isFixed = true,
modifier = modifier,
onClick = { onIntervalClick(PriceChangeInterval.valueOf(it.id)) },
)
return
val items = remember {
PriceChangeInterval.entries
.map { TangemSegmentUM(it.toString(), it.getText()) }.toImmutableList()
}
val selectedItem = remember(trendInterval) {
items.firstOrNull { it.id == trendInterval.toString() }
}
SegmentedButtons(
config = persistentListOf(
PriceChangeInterval.H24,
PriceChangeInterval.WEEK,
PriceChangeInterval.MONTH,
PriceChangeInterval.MONTH3,
PriceChangeInterval.MONTH6,
PriceChangeInterval.YEAR,
PriceChangeInterval.ALL_TIME,
),
color = TangemTheme.colors.button.secondary,
initialSelectedItem = trendInterval,
onClick = onIntervalClick,
isEnabled = isEnabled,
TangemSegmentedPicker(
items = items,
initialSelectedItem = selectedItem,
isFixed = true,
modifier = modifier,
) {
Box(
Modifier
.fillMaxSize()
.align(Alignment.Center)
.padding(
vertical = TangemTheme.dimens.spacing4,
),
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = it.getText().resolveReference(),
style = TangemTheme.typography.caption1,
color = if (isEnabled) {
TangemTheme.colors.text.primary1
} else {
TangemTheme.colors.text.disabled
},
)
}
}
onClick = { onIntervalClick(PriceChangeInterval.valueOf(it.id)) },
)
}
@Composable
@ -493,7 +332,6 @@ private fun MarketsTokenDetailsContent_Preview(
MarketsTokenDetailsContent(
state = params,
backgroundColor = TangemTheme.colors.background.tertiary,
portfolioBlock = {},
portfolioFloatingBlock = null,
marketingBanner = {},
contentPadding = PaddingValues(),

View file

@ -11,7 +11,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.Dp
@ -21,7 +20,6 @@ import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_sign_plus_24
@ -31,29 +29,16 @@ import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM
@Composable
internal fun MarketsTokenDetailsTitle(
state: MarketsTokenDetailsUM,
backgroundColor: Color,
isBackButtonEnabled: Boolean,
onBackClick: () -> Unit,
) {
if (LocalRedesignEnabled.current) {
MarketsTokenDetailsRedesignTopBar(
isAddToPortfolioButtonVisible = state.isAddToPortfolioButtonVisible,
onAddToPortfolioClick = state.onAddToPortfolioClick,
onShareClick = state.onShareClick,
isBackButtonEnabled = isBackButtonEnabled,
onBackClick = onBackClick,
)
} else {
MarketsTokenDetailsTopBar(
onBackClick = onBackClick,
isBackButtonEnabled = isBackButtonEnabled,
shouldShowPriceSubtitle = state.shouldShowPriceSubtitle,
tokenName = state.tokenName,
tokenPrice = state.priceText,
backgroundColor = backgroundColor,
onShareClick = state.onShareClick,
)
}
MarketsTokenDetailsRedesignTopBar(
isAddToPortfolioButtonVisible = state.isAddToPortfolioButtonVisible,
onAddToPortfolioClick = state.onAddToPortfolioClick,
onShareClick = state.onShareClick,
isBackButtonEnabled = isBackButtonEnabled,
onBackClick = onBackClick,
)
}
@Composable

View file

@ -5,13 +5,11 @@ import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -29,16 +27,9 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.audits.AuditLabelUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.button.*
@ -48,10 +39,8 @@ import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.state.ExchangeItemUM
@ -68,56 +57,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet as TangemBot
*/
@Composable
internal fun ExchangesBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
ExchangesBottomSheetV2(config)
} else {
ExchangesBottomSheetV1(config)
}
}
@Composable
private fun ExchangesBottomSheetV1(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheet<ExchangesBottomSheetContent>(
config = config,
addBottomInsets = false,
title = { Title(textResId = it.titleResId, onBackClick = config.onDismissRequest) },
content = { content ->
Box {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = bottomBarHeight),
) {
item(key = "subtitle") {
Subtitle(
subtitleRes = content.subtitleResId,
volumeReference = content.volumeReference,
modifier = Modifier.padding(
start = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing8,
),
)
}
items(
items = content.exchangeItems,
key = TokenItemState::id,
itemContent = { TokenItem(state = it, isBalanceHidden = false) },
)
}
if (content is ExchangesBottomSheetContent.Error) {
Error(
content = content,
modifier = Modifier.align(Alignment.Center),
)
}
}
},
)
ExchangesBottomSheetV2(config)
}
@Composable
@ -191,14 +131,6 @@ private fun ExchangesBottomSheetV2(config: TangemBottomSheetConfig) {
)
}
@Composable
private fun Title(@StringRes textResId: Int, onBackClick: () -> Unit) {
TangemTopAppBar(
title = stringResourceSafe(id = textResId),
startButton = TopAppBarButtonUM.Back(onBackClicked = onBackClick),
)
}
@Composable
private fun Subtitle(@StringRes subtitleRes: Int, volumeReference: TextReference, modifier: Modifier = Modifier) {
Row(
@ -213,57 +145,18 @@ private fun Subtitle(@StringRes subtitleRes: Int, volumeReference: TextReference
@Composable
private fun SubtitleText(textReference: TextReference) {
if (LocalRedesignEnabled.current) {
Text(
text = textReference.resolveReference(),
color = TangemTheme.colors2.text.neutral.secondary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography2.captionSemibold12,
)
} else {
Text(
text = textReference.resolveReference(),
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.body2,
)
}
Text(
text = textReference.resolveReference(),
color = TangemTheme.colors2.text.neutral.secondary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography2.captionSemibold12,
)
}
@Composable
private fun Error(content: ExchangesBottomSheetContent.Error, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
ErrorV2(content, modifier)
} else {
ErrorV1(content, modifier)
}
}
@Composable
private fun ErrorV1(content: ExchangesBottomSheetContent.Error, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(id = content.message),
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.caption1,
)
SpacerH12()
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(id = R.string.alert_button_try_again),
onClick = content.onRetryClick,
),
)
}
ErrorV2(content, modifier)
}
@Composable
@ -427,10 +320,10 @@ private fun getColorByTrustValue(type: AuditLabelUM.Type): Color {
@Preview
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_ExchangesBottomSheet(
private fun Preview_ExchangesBottomSheetV2(
@PreviewParameter(ExchangesBottomSheetContentProvider::class) content: ExchangesBottomSheetContent,
) {
TangemThemePreview {
TangemThemePreviewRedesign {
ExchangesBottomSheet(
config = TangemBottomSheetConfig(
onDismissRequest = {},
@ -441,25 +334,6 @@ private fun Preview_ExchangesBottomSheet(
}
}
@Preview
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_ExchangesBottomSheetV2(
@PreviewParameter(ExchangesBottomSheetContentProvider::class) content: ExchangesBottomSheetContent,
) {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
ExchangesBottomSheet(
config = TangemBottomSheetConfig(
onDismissRequest = {},
content = content,
isShown = true,
),
)
}
}
}
private class ExchangesBottomSheetContentProvider : CollectionPreviewParameterProvider<ExchangesBottomSheetContent>(
listOf(
ExchangesBottomSheetContent.Loading(exchangesCount = 13),

View file

@ -17,19 +17,13 @@ import androidx.compose.ui.res.vectorResource
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetTitle
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.state.InfoBottomSheetContent
@ -38,50 +32,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet as TangemBot
@Composable
internal fun InfoBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
InfoBottomSheetV2(config)
} else {
InfoBottomSheetV1(config)
}
}
@Composable
private fun InfoBottomSheetV1(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheet<InfoBottomSheetContent>(
config = config,
addBottomInsets = false,
title = { TangemBottomSheetTitle(title = it.title) },
content = { content ->
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
MarkdownText(
markdown = content.body.resolveReference(),
disableLinkMovementMethod = true,
linkifyMask = 0,
syntaxHighlightColor = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.body2.copy(
color = TangemTheme.colors.text.secondary,
),
)
if (content.generatedAINotificationUM != null) {
AdditionalInfoNotification(
onClick = content.generatedAINotificationUM.onClick,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
)
}
SpacerH(bottomBarHeight)
}
},
)
InfoBottomSheetV2(config)
}
@Suppress("LongMethod")
@ -165,20 +116,4 @@ private fun InfoBottomSheetV2(config: TangemBottomSheetConfig) {
}
},
)
}
@Composable
private fun AdditionalInfoNotification(onClick: () -> Unit, modifier: Modifier = Modifier) {
Notification(
config = NotificationConfig(
subtitle = TextReference.Res(id = R.string.information_generated_with_ai),
iconResId = R.drawable.ic_magic_28,
onClick = onClick,
shouldShowArrowIcon = false,
),
modifier = modifier,
subtitleColor = TangemTheme.colors.text.primary1,
containerColor = TangemTheme.colors.button.disabled,
iconTint = TangemTheme.colors.icon.accent,
)
}

View file

@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -20,75 +19,16 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW4
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.text.TooltipText
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUM
@Composable
internal fun InfoPoint(infoPointUM: InfoPointUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
InfoPointV2(infoPointUM, modifier)
} else {
InfoPointV1(infoPointUM, modifier)
}
}
@Composable
private fun InfoPointV1(infoPointUM: InfoPointUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(vertical = TangemTheme.dimens.spacing8),
horizontalAlignment = Alignment.Start,
) {
if (infoPointUM.onInfoClick != null) {
TooltipText(
text = infoPointUM.title,
onInfoClick = infoPointUM.onInfoClick,
textStyle = TangemTheme.typography.caption2,
)
} else {
Text(
text = infoPointUM.title.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Row {
Text(
text = infoPointUM.value,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
if (infoPointUM.change != null) {
SpacerW4()
Icon(
modifier = Modifier
.size(TangemTheme.dimens.size8)
.align(Alignment.CenterVertically),
imageVector = ImageVector.vectorResource(
id = when (infoPointUM.change) {
InfoPointUM.ChangeType.UP -> R.drawable.ic_arrow_up_8
InfoPointUM.ChangeType.DOWN -> R.drawable.ic_arrow_down_8
},
),
tint = when (infoPointUM.change) {
InfoPointUM.ChangeType.UP -> TangemTheme.colors.icon.accent
InfoPointUM.ChangeType.DOWN -> TangemTheme.colors.icon.warning
},
contentDescription = null,
)
}
}
}
InfoPointV2(infoPointUM, modifier)
}
@Composable
@ -154,46 +94,8 @@ private fun InfoPointV2(infoPointUM: InfoPointUM, modifier: Modifier = Modifier)
}
@Composable
internal fun InfoPointShimmer(modifier: Modifier = Modifier, withTooltip: Boolean = false) {
if (LocalRedesignEnabled.current) {
InfoPointShimmerV2(modifier)
} else {
InfoPointShimmerV1(modifier, withTooltip)
}
}
@Composable
private fun InfoPointShimmerV1(modifier: Modifier = Modifier, withTooltip: Boolean = false) {
Column(
modifier = modifier.padding(vertical = TangemTheme.dimens.spacing8),
horizontalAlignment = Alignment.Start,
) {
if (withTooltip) {
Box(
modifier = Modifier
.requiredHeight(TangemTheme.dimens.size16)
.fillMaxWidth(),
contentAlignment = Alignment.CenterStart,
) {
TextShimmer(
modifier = Modifier.fillMaxWidth(),
style = TangemTheme.typography.caption2,
textSizeHeight = false,
)
}
} else {
TextShimmer(
modifier = Modifier.fillMaxWidth(),
style = TangemTheme.typography.caption2,
textSizeHeight = true,
)
}
TextShimmer(
modifier = Modifier.fillMaxWidth(fraction = 0.5f),
style = TangemTheme.typography.body1,
textSizeHeight = true,
)
}
internal fun InfoPointShimmer(modifier: Modifier = Modifier) {
InfoPointShimmerV2(modifier)
}
@Composable
@ -223,8 +125,8 @@ private fun InfoPointShimmerV2(modifier: Modifier = Modifier) {
@Preview
@Preview("Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ContentPreviewV1() {
TangemThemePreview {
private fun ContentPreviewV2() {
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.width(150.dp)
@ -261,85 +163,4 @@ private fun ContentPreviewV1() {
)
}
}
}
@Preview
@Preview("Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ContentPreviewV2() {
CompositionLocalProvider(
LocalRedesignEnabled provides true,
) {
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.width(150.dp)
.background(TangemTheme.colors.background.tertiary),
) {
InfoPoint(
infoPointUM = InfoPointUM(
title = stringReference("Market Cap"),
value = "$1,000,000,000",
),
)
InfoPoint(
infoPointUM = InfoPointUM(
title = stringReference("Market Cap"),
value = "$1,000,000,000",
onInfoClick = { },
),
)
InfoPoint(
infoPointUM = InfoPointUM(
title = stringReference("Market Cap"),
value = "$1,000,000",
change = InfoPointUM.ChangeType.UP,
onInfoClick = { },
),
)
InfoPoint(
infoPointUM = InfoPointUM(
title = stringReference("Market Cap"),
value = "$1,000,000",
change = InfoPointUM.ChangeType.DOWN,
onInfoClick = { },
),
)
}
}
}
}
@Preview
@Preview("Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewShimmer() {
TangemThemePreview {
PreviewShimmerContainer(
shimmerContent = {
Column(
modifier = Modifier
.width(150.dp)
.background(TangemTheme.colors.background.tertiary),
) {
InfoPointShimmer(modifier = Modifier.fillMaxWidth())
InfoPointShimmer(
modifier = Modifier.fillMaxWidth(),
withTooltip = true,
)
InfoPointShimmer(
modifier = Modifier.fillMaxWidth(),
withTooltip = true,
)
InfoPointShimmer(
modifier = Modifier.fillMaxWidth(),
withTooltip = true,
)
}
},
actualContent = {
ContentPreviewV1()
},
)
}
}

View file

@ -6,29 +6,21 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.block.information.GridItems
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
import com.tangem.core.ui.components.text.TooltipText
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.domain.markets.PriceChangeInterval
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.components.TokenMarketInformationBlock
import com.tangem.features.feed.ui.market.detailed.getText
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUM
import com.tangem.features.feed.ui.market.detailed.state.InsightsUM
import kotlinx.collections.immutable.persistentListOf
@ -36,77 +28,7 @@ import kotlinx.collections.immutable.toImmutableList
@Composable
internal fun InsightsBlock(state: InsightsUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
InsightsBlockV2(state, modifier)
} else {
InsightsBlockV1(state, modifier)
}
}
@Composable
private fun InsightsBlockV1(state: InsightsUM, modifier: Modifier = Modifier) {
var currentInterval by remember { mutableStateOf(PriceChangeInterval.H24) }
InformationBlock(
modifier = modifier,
title = {
TooltipText(
text = resourceReference(R.string.markets_token_details_insights),
textStyle = TangemTheme.typography.subtitle2,
onInfoClick = state.onInfoClick,
)
},
action = {
SegmentedButtons(
config = persistentListOf(
PriceChangeInterval.H24,
PriceChangeInterval.WEEK,
PriceChangeInterval.MONTH,
),
initialSelectedItem = PriceChangeInterval.H24,
onClick = { interval ->
currentInterval = interval
state.onIntervalChanged(interval)
},
modifier = Modifier.width(IntrinsicSize.Min),
) {
Box(
Modifier
.fillMaxSize()
.align(Alignment.Center)
.padding(
horizontal = 14.dp,
vertical = 4.dp,
),
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = it.getText().resolveReference(),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.primary1,
)
}
}
},
content = {
val infoPoints = when (currentInterval) {
PriceChangeInterval.H24 -> state.h24Info
PriceChangeInterval.WEEK -> state.weekInfo
PriceChangeInterval.MONTH -> state.monthInfo
else -> state.h24Info
}
GridItems(
items = infoPoints,
itemContent = { infoPointUM ->
InfoPoint(
modifier = Modifier.align(Alignment.CenterStart),
infoPointUM = infoPointUM,
)
},
)
},
)
InsightsBlockV2(state, modifier)
}
@Composable
@ -180,41 +102,7 @@ private fun InsightsBlockV2(state: InsightsUM, modifier: Modifier = Modifier) {
@Composable
internal fun InsightsBlockPlaceholder(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
InsightsBlockPlaceholderV2(modifier)
} else {
InsightsBlockPlaceholderV1(modifier)
}
}
@Composable
internal fun InsightsBlockPlaceholderV1(modifier: Modifier = Modifier) {
val subtitle2dp = with(LocalDensity.current) { TangemTheme.typography.subtitle2.lineHeight.toDp() }
val caption1dp = with(LocalDensity.current) { TangemTheme.typography.caption1.lineHeight.toDp() }
val headerHeight = maxOf(subtitle2dp, caption1dp) + TangemTheme.dimens.spacing4
InformationBlock(
modifier = modifier,
title = {
RectangleShimmer(
modifier = Modifier
.height(headerHeight)
.fillMaxWidth(),
radius = TangemTheme.dimens.radius3,
)
},
content = {
GridItems(
items = List(size = 4) { it }.toImmutableList(),
horizontalArragement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
itemContent = {
InfoPointShimmer(
modifier = Modifier.fillMaxWidth(),
)
},
)
},
)
InsightsBlockPlaceholderV2(modifier)
}
@Composable
@ -257,8 +145,8 @@ internal fun InsightsBlockPlaceholderV2(modifier: Modifier = Modifier) {
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ContentPreviewV1() {
TangemThemePreview {
private fun ContentPreviewV2() {
TangemThemePreviewRedesign {
InsightsBlock(
state = InsightsUM(
h24Info = persistentListOf(
@ -325,95 +213,11 @@ private fun ContentPreviewV1() {
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ContentPreviewV2() {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
TangemThemePreviewRedesign {
InsightsBlock(
state = InsightsUM(
h24Info = persistentListOf(
InfoPointUM(
title = resourceReference(R.string.markets_token_details_experienced_buyers),
value = "1 000 000 000",
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_buy_pressure),
value = "1 000 000 000",
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_holders),
value = "1 000 000 000",
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_liquidity),
value = "1 000 000 000",
),
),
weekInfo = persistentListOf(
InfoPointUM(
title = resourceReference(R.string.markets_token_details_experienced_buyers),
value = "1 000 000",
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_buy_pressure),
value = "1 000 000",
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_holders),
value = "1 000 000",
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_liquidity),
value = "1 000 000",
),
),
monthInfo = persistentListOf(
InfoPointUM(
title = resourceReference(R.string.markets_token_details_experienced_buyers),
value = "1 000",
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_buy_pressure),
value = "1 000",
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_holders),
value = "1 000",
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_liquidity),
value = "1 000",
),
),
onInfoClick = {},
onIntervalChanged = {},
),
)
}
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewPlaceholderV1() {
TangemThemePreview {
private fun PreviewPlaceholderV2() {
TangemThemePreviewRedesign {
PreviewShimmerContainer(
actualContent = { ContentPreviewV1() },
actualContent = { ContentPreviewV2() },
shimmerContent = { InsightsBlockPlaceholder() },
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewPlaceholderV2() {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
PreviewShimmerContainer(
actualContent = { ContentPreviewV2() },
shimmerContent = { InsightsBlockPlaceholder() },
)
}
}
}

View file

@ -11,9 +11,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.components.ChipShimmer
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.buttons.chip.Chip
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonIconPosition
import com.tangem.core.ui.ds.button.TangemButtonShape
@ -21,11 +18,8 @@ import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.state.LinksUM
import kotlinx.collections.immutable.ImmutableList
@ -33,53 +27,7 @@ import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun LinksBlock(state: LinksUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
LinksBlockV2(state, modifier)
} else {
LinksBlockV1(state, modifier)
}
}
@Composable
private fun LinksBlockV1(state: LinksUM, modifier: Modifier = Modifier) {
InformationBlock(
modifier = modifier,
contentHorizontalPadding = 0.dp,
title = {
Text(
text = stringResourceSafe(id = R.string.markets_token_details_links),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
content = {
Column {
SubBlockV1(
title = stringResourceSafe(id = R.string.markets_token_details_official_links),
links = state.officialLinks,
onLinkClick = state.onLinkClick,
)
SubBlockV1(
title = stringResourceSafe(id = R.string.markets_token_details_social),
links = state.social,
onLinkClick = state.onLinkClick,
)
SubBlockV1(
title = stringResourceSafe(id = R.string.markets_token_details_repository),
links = state.repository,
onLinkClick = state.onLinkClick,
)
SubBlockV1(
title = stringResourceSafe(id = R.string.markets_token_details_blockchain_site),
links = state.blockchainSite,
onLinkClick = state.onLinkClick,
lastBlock = true,
)
}
},
)
LinksBlockV2(state, modifier)
}
@Composable
@ -108,48 +56,6 @@ private fun LinksBlockV2(state: LinksUM, modifier: Modifier = Modifier) {
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun SubBlockV1(
links: ImmutableList<LinksUM.Link>,
onLinkClick: (LinksUM.Link) -> Unit,
modifier: Modifier = Modifier,
lastBlock: Boolean = false,
title: String = "Official links",
) {
if (links.isEmpty()) return
DividerContainer(
modifier = modifier,
showDivider = !lastBlock,
) {
Column(
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
Text(
text = title,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
FlowRow(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
links.fastForEach { link ->
Chip(
text = stringReference(link.title),
iconResId = link.iconRes,
onClick = { onLinkClick(link) },
)
}
}
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun SubBlockV2(
@ -196,32 +102,7 @@ private fun SubBlockV2(
@Composable
fun LinksBlockPlaceholder(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
LinksBlockPlaceholderV2(modifier)
} else {
LinksBlockPlaceholderV1(modifier)
}
}
@Composable
private fun LinksBlockPlaceholderV1(modifier: Modifier = Modifier) {
InformationBlock(
modifier = modifier,
contentHorizontalPadding = 0.dp,
title = {
TextShimmer(
modifier = Modifier.fillMaxWidth(),
style = TangemTheme.typography.subtitle2,
)
},
content = {
Column {
SubBlockPlaceholderV1()
SubBlockPlaceholderV1()
SubBlockPlaceholderV1(lastBlock = true)
}
},
)
LinksBlockPlaceholderV2(modifier)
}
@Composable
@ -233,33 +114,6 @@ private fun LinksBlockPlaceholderV2(modifier: Modifier = Modifier) {
}
}
@Composable
private fun SubBlockPlaceholderV1(modifier: Modifier = Modifier, lastBlock: Boolean = false) {
DividerContainer(
modifier = modifier,
showDivider = !lastBlock,
) {
Column(
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
TextShimmer(
modifier = Modifier.width(78.dp),
style = TangemTheme.typography.caption2,
)
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
repeat(times = 3) {
ChipShimmer(
modifier = Modifier.weight(1f),
)
}
}
}
}
}
@Composable
private fun SubBlockPlaceholderV2(modifier: Modifier = Modifier) {
Column(
@ -285,56 +139,6 @@ private fun SubBlockPlaceholderV2(modifier: Modifier = Modifier) {
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ContentPreviewV1() {
TangemThemePreview {
LinksBlockV1(
state = LinksUM(
officialLinks = persistentListOf(
LinksUM.Link(
title = "Website",
iconRes = R.drawable.ic_plus_24,
url = "https://tangem.com",
),
LinksUM.Link(
title = "Website",
iconRes = R.drawable.ic_plus_24,
url = "https://tangem.com",
),
LinksUM.Link(
title = "Website",
iconRes = R.drawable.ic_plus_24,
url = "https://tangem.com",
),
),
social = persistentListOf(
LinksUM.Link(
title = "Twitter",
iconRes = R.drawable.ic_plus_24,
url = "https://tangem.com",
),
LinksUM.Link(
title = "Facebook",
iconRes = R.drawable.ic_plus_24,
url = "https://tangem.com",
),
),
repository = persistentListOf(
LinksUM.Link(
title = "Github",
iconRes = R.drawable.ic_plus_24,
url = "https://tangem.com",
),
),
blockchainSite = persistentListOf(),
onLinkClick = {},
),
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@ -385,18 +189,6 @@ private fun ContentPreviewV2() {
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PlaceholderPreviewV1() {
TangemThemePreview {
PreviewShimmerContainer(
shimmerContent = { LinksBlockPlaceholderV1() },
actualContent = { ContentPreviewV1() },
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable

View file

@ -1,6 +1,5 @@
package com.tangem.features.feed.ui.market.detailed.components
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
@ -8,28 +7,19 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.*
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.platform.testTag
import androidx.compose.ui.res.painterResource
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
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.R
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketsTestTags
import com.tangem.features.feed.ui.components.TokenMarketInformationBlock
import com.tangem.features.feed.ui.market.detailed.state.ListedOnUM
import kotlinx.coroutines.delay
/**
* "Listed on" block
@ -40,50 +30,7 @@ import kotlinx.coroutines.delay
*/
@Composable
internal fun ListedOnBlock(state: ListedOnUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
ListedOnBlockV2(state, modifier)
} else {
ListedOnBlockV1(state, modifier)
}
}
@Composable
private fun ListedOnBlockV1(state: ListedOnUM, modifier: Modifier = Modifier) {
Box(modifier = modifier) {
InformationBlock(
title = {
Text(
text = state.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
modifier = Modifier
.clip(shape = TangemTheme.shapes.roundedCornersXMedium)
.clickable(enabled = state is ListedOnUM.Content) {
(state as? ListedOnUM.Content)?.onClick?.invoke()
}
.testTag(MarketsTestTags.LISTED_ON_BLOCK),
) {
Description(
state = state,
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
)
}
if (state is ListedOnUM.Content) {
Icon(
painter = painterResource(id = R.drawable.ic_chevron_right_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = TangemTheme.dimens.spacing12),
)
}
}
ListedOnBlockV2(state, modifier)
}
@Composable
@ -129,31 +76,7 @@ private fun ListedOnBlockV2(state: ListedOnUM, modifier: Modifier = Modifier) {
@Composable
internal fun ListedOnBlockPlaceholder(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
ListedOnBlockPlaceholderV2(modifier)
} else {
ListedOnBlockPlaceholderV1(modifier)
}
}
@Composable
internal fun ListedOnBlockPlaceholderV1(modifier: Modifier = Modifier) {
InformationBlock(
title = {
TextShimmer(
style = TangemTheme.typography.subtitle2,
modifier = Modifier.fillMaxWidth(fraction = 0.5f),
)
},
modifier = modifier,
) {
TextShimmer(
style = TangemTheme.typography.body2,
modifier = Modifier
.fillMaxWidth(fraction = 0.3f)
.padding(bottom = TangemTheme.dimens.spacing12),
)
}
ListedOnBlockPlaceholderV2(modifier)
}
@Composable
@ -175,51 +98,4 @@ internal fun ListedOnBlockPlaceholderV2(modifier: Modifier = Modifier) {
}
},
)
}
@Composable
private fun Description(state: ListedOnUM, modifier: Modifier = Modifier) {
Text(
text = state.description.resolveReference(),
modifier = modifier.testTag(MarketsTestTags.LISTED_ON_EXCHANGES_COUNT),
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.body2,
)
}
@Preview(widthDp = 328, heightDp = 68)
@Preview(name = "Dark Theme", widthDp = 328, heightDp = 68, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_ListedOnBlock(@PreviewParameter(ListenOnUMProvider::class) state: ListedOnUM?) {
TangemThemePreview {
if (state == null) {
ListedOnBlockPlaceholder()
} else {
ListedOnBlock(state = state)
}
}
}
@Preview
@Composable
private fun Preview_ListedOnBlock_StateChanging() {
var state by remember { mutableStateOf<ListedOnUM?>(value = null) }
Preview_ListedOnBlock(state = state)
LaunchedEffect(key1 = null) {
delay(timeMillis = 3000)
state = ListedOnUM.Empty
}
}
private class ListenOnUMProvider : CollectionPreviewParameterProvider<ListedOnUM?>(
collection = listOf(
ListedOnUM.Empty,
ListedOnUM.Content(onClick = {}, amount = 5),
null,
),
)
}

View file

@ -1,84 +1,23 @@
package com.tangem.features.feed.ui.market.detailed.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.TextButton
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.GridItems
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.components.MetricsCard
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUM
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUMV2
import com.tangem.features.feed.ui.market.detailed.state.MetricsUM
import com.tangem.features.feed.ui.market.detailed.state.MetricsV2UM
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
const val MAX_METRICS_COUNT = 6
@Composable
internal fun MetricsBlock(state: MetricsUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
state.metricsV2?.let {
MetricsBlockV2(it, modifier)
}
} else {
MetricsBlockV1(state, modifier)
state.metricsV2?.let {
MetricsBlockV2(it, modifier)
}
}
@Composable
private fun MetricsBlockV1(state: MetricsUM, modifier: Modifier = Modifier) {
var isExpanded by remember { mutableStateOf(false) }
InformationBlock(
modifier = modifier,
title = {
Text(
text = stringResourceSafe(id = R.string.markets_token_details_metrics),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
action = {
if (state.metrics.size > MAX_METRICS_COUNT) {
ShowLessMoreButton(expanded = isExpanded, onClick = { isExpanded = !isExpanded })
}
},
content = {
val metrics = if (isExpanded) {
state.metrics
} else {
state.metrics.take(MAX_METRICS_COUNT).toImmutableList()
}
GridItems(
items = metrics,
itemContent = {
InfoPoint(infoPointUM = it)
},
)
},
)
}
@Composable
private fun MetricsBlockV2(state: MetricsV2UM, modifier: Modifier = Modifier) {
Column(
@ -123,60 +62,9 @@ private fun MetricCard(item: InfoPointUMV2) {
}
}
// TODO make TextButton clickable area smaller and remove paddings for an action in InformationBlock
@Composable
private fun ShowLessMoreButton(expanded: Boolean, onClick: () -> Unit) {
// FIXME add string resources
val text = if (expanded) {
"See less"
} else {
"See more"
}
TextButton(
text = text,
onClick = onClick,
colors = TangemButtonsDefaults.positiveButtonColors,
textStyle = TangemTheme.typography.body2,
)
}
@Composable
internal fun MetricsBlockPlaceholder(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
MetricsBlockPlaceholderV2(modifier)
} else {
MetricsBlockPlaceholderV1(modifier)
}
}
@Composable
private fun MetricsBlockPlaceholderV1(modifier: Modifier = Modifier) {
InformationBlock(
modifier = modifier,
title = {
TextShimmer(
modifier = Modifier.fillMaxWidth(),
radius = TangemTheme.dimens.radius3,
style = TangemTheme.typography.subtitle2,
)
},
action = {
Box(Modifier)
},
content = {
GridItems(
items = List(size = 6) { it }.toImmutableList(),
horizontalArragement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
itemContent = {
InfoPointShimmer(
modifier = Modifier.fillMaxWidth(),
withTooltip = true,
)
},
)
},
)
MetricsBlockPlaceholderV2(modifier)
}
@Composable
@ -279,61 +167,4 @@ private fun CirculatingSupplyCardPlaceholder() {
)
},
)
}
@Preview
@Preview("Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BlockPreview() {
TangemThemePreview {
MetricsBlock(
state = MetricsUM(
metrics = persistentListOf(
InfoPointUM(
title = resourceReference(R.string.markets_token_details_market_capitalization),
value = "1.2T",
onInfoClick = {},
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_market_rating),
value = "A",
onInfoClick = {},
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_trading_volume),
value = "1.2T",
onInfoClick = {},
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_fully_diluted_valuation),
value = "1.2T",
onInfoClick = {},
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_circulating_supply),
value = "1.2T",
onInfoClick = {},
),
InfoPointUM(
title = resourceReference(R.string.markets_token_details_total_supply),
value = "1.2T",
onInfoClick = {},
),
),
metricsV2 = null,
),
)
}
}
@Preview
@Preview("Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewPlaceholder() {
TangemThemePreview {
PreviewShimmerContainer(
actualContent = { BlockPreview() },
shimmerContent = { MetricsBlockPlaceholder() },
)
}
}

View file

@ -7,7 +7,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
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,7 +27,6 @@ import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.feed.impl.R
@ -380,115 +378,113 @@ private const val BRONZE_PLACE_COLOR_LIGHT = 0xFFCC7F66
@Preview(widthDp = 360, heightDp = 1500, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun MetricsCardsPreview() {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level2)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
MarketCapCard(
item = InfoPointUMV2.MarketCap(
capitalizationValue = stringReference("$ 1.2 T"),
onInfoClick = {},
),
)
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level2)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
MarketCapCard(
item = InfoPointUMV2.MarketCap(
capitalizationValue = stringReference("$ 1.2 T"),
onInfoClick = {},
),
)
TradingVolumeCard(
item = InfoPointUMV2.TradingVolume(
tradingValue = stringReference("$ 45.2 M"),
liquidity = 0.75f,
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.HIGH,
onInfoClick = {},
),
)
TradingVolumeCard(
item = InfoPointUMV2.TradingVolume(
tradingValue = stringReference("$ 45.2 M"),
liquidity = 0.75f,
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.HIGH,
onInfoClick = {},
),
)
TradingVolumeCard(
item = InfoPointUMV2.TradingVolume(
tradingValue = stringReference("$ 12.1 M"),
liquidity = 0.45f,
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.MEDIUM,
onInfoClick = {},
),
)
TradingVolumeCard(
item = InfoPointUMV2.TradingVolume(
tradingValue = stringReference("$ 12.1 M"),
liquidity = 0.45f,
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.MEDIUM,
onInfoClick = {},
),
)
TradingVolumeCard(
item = InfoPointUMV2.TradingVolume(
tradingValue = stringReference("$ 2.3 M"),
liquidity = 0.15f,
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.LOW,
onInfoClick = {},
),
)
TradingVolumeCard(
item = InfoPointUMV2.TradingVolume(
tradingValue = stringReference("$ 2.3 M"),
liquidity = 0.15f,
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.LOW,
onInfoClick = {},
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = stringReference("1"),
rangeValue = 0.02f,
marketRatingType = MarketRatingType.GOLD,
onInfoClick = {},
marketRatingChange24H = MarketRatingChange24H.NoChanges,
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = stringReference("1"),
rangeValue = 0.02f,
marketRatingType = MarketRatingType.GOLD,
onInfoClick = {},
marketRatingChange24H = MarketRatingChange24H.NoChanges,
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = stringReference("2"),
rangeValue = 0.05f,
marketRatingChange24H = MarketRatingChange24H.Up(1),
marketRatingType = MarketRatingType.SILVER,
onInfoClick = {},
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = stringReference("2"),
rangeValue = 0.05f,
marketRatingChange24H = MarketRatingChange24H.Up(1),
marketRatingType = MarketRatingType.SILVER,
onInfoClick = {},
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = null,
rangeValue = null,
marketRatingType = MarketRatingType.OTHER,
onInfoClick = {},
marketRatingChange24H = MarketRatingChange24H.NoChanges,
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = null,
rangeValue = null,
marketRatingType = MarketRatingType.OTHER,
onInfoClick = {},
marketRatingChange24H = MarketRatingChange24H.NoChanges,
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = stringReference("42"),
rangeValue = 0.42f,
marketRatingType = MarketRatingType.OTHER,
onInfoClick = {},
marketRatingChange24H = MarketRatingChange24H.Down(15),
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = stringReference("42"),
rangeValue = 0.42f,
marketRatingType = MarketRatingType.OTHER,
onInfoClick = {},
marketRatingChange24H = MarketRatingChange24H.Down(15),
),
)
FDVCard(
item = InfoPointUMV2.FullyDilutedValuation(
value = stringReference("$ 1.5 T"),
fullyDilutedValuationChange24 = stringReference("$ 2.44 M in total"),
onInfoClick = {},
),
)
FDVCard(
item = InfoPointUMV2.FullyDilutedValuation(
value = stringReference("$ 1.5 T"),
fullyDilutedValuationChange24 = stringReference("$ 2.44 M in total"),
onInfoClick = {},
),
)
CirculatingSupplyCard(
item = InfoPointUMV2.CirculatingSupply(
currentValue = stringReference("12.5 B POL"),
maxValue = stringReference("21 B POL"),
fillValue = 0.6f,
onInfoClick = {},
),
)
CirculatingSupplyCard(
item = InfoPointUMV2.CirculatingSupply(
currentValue = stringReference("12.5 B POL"),
maxValue = stringReference("21 B POL"),
fillValue = 0.6f,
onInfoClick = {},
),
)
CirculatingSupplyCard(
item = InfoPointUMV2.CirculatingSupply(
currentValue = stringReference("18.9 M ETH"),
maxValue = null,
fillValue = null,
onInfoClick = {},
),
)
}
CirculatingSupplyCard(
item = InfoPointUMV2.CirculatingSupply(
currentValue = stringReference("18.9 M ETH"),
maxValue = null,
fillValue = null,
onInfoClick = {},
),
)
}
}
}

View file

@ -1,269 +0,0 @@
package com.tangem.features.feed.ui.market.detailed.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
import com.tangem.core.ui.components.progressbar.TangemLinearProgressIndicator
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemAnimations
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.domain.markets.PriceChangeInterval
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.getText
import com.tangem.features.feed.ui.market.detailed.state.PricePerformanceUM
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun PricePerformanceBlock(state: PricePerformanceUM, modifier: Modifier = Modifier) {
var currentInterval by remember { mutableStateOf(PriceChangeInterval.H24) }
InformationBlock(
modifier = modifier,
title = {
Text(
text = stringResourceSafe(id = R.string.markets_token_details_price_performance),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
action = {
SegmentedButtons(
config = persistentListOf(
PriceChangeInterval.H24,
PriceChangeInterval.MONTH,
PriceChangeInterval.ALL_TIME,
),
initialSelectedItem = PriceChangeInterval.H24,
onClick = { interval ->
currentInterval = interval
state.onIntervalChanged(interval)
},
modifier = Modifier.width(IntrinsicSize.Min),
) {
Box(
Modifier
.fillMaxSize()
.align(Alignment.Center)
.padding(
horizontal = 14.dp,
vertical = TangemTheme.dimens.spacing4,
),
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = it.getText().resolveReference(),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.primary1,
)
}
}
},
content = {
val value = when (currentInterval) {
PriceChangeInterval.H24 -> state.h24
PriceChangeInterval.MONTH -> state.month
PriceChangeInterval.ALL_TIME -> state.all
else -> error("")
}
Content(
modifier = Modifier.fillMaxWidth(),
state = value,
)
},
)
}
@Composable
private fun Content(state: PricePerformanceUM.Value, modifier: Modifier = Modifier) {
val animatedIndicatorFraction by TangemAnimations.horizontalIndicatorAsState(
targetFraction = state.indicatorFraction,
)
Column(
modifier = modifier
.padding(vertical = TangemTheme.dimens.spacing8),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResourceSafe(R.string.markets_token_details_low),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
SpacerW8()
Text(
text = stringResourceSafe(R.string.markets_token_details_high),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
TangemLinearProgressIndicator(
modifier = Modifier
.height(TangemTheme.dimens.size6)
.fillMaxWidth(),
progress = { animatedIndicatorFraction },
color = TangemTheme.colors.text.accent,
backgroundColor = TangemTheme.colors.background.tertiary,
strokeCap = StrokeCap.Round,
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
Text(
modifier = Modifier.weight(1f),
text = state.low,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
Text(
modifier = Modifier.weight(1f),
text = state.high,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.End,
)
}
}
}
@Composable
internal fun PricePerformanceBlockPlaceholder(modifier: Modifier = Modifier) {
val subtitle2dp = with(LocalDensity.current) { TangemTheme.typography.subtitle2.lineHeight.toDp() }
val caption1dp = with(LocalDensity.current) { TangemTheme.typography.caption1.lineHeight.toDp() }
val headerHeight = maxOf(subtitle2dp, caption1dp) + TangemTheme.dimens.spacing4
InformationBlock(
modifier = modifier,
title = {
RectangleShimmer(
modifier = Modifier
.height(headerHeight)
.fillMaxWidth(),
radius = TangemTheme.dimens.radius3,
)
},
content = {
Column(
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
TextShimmer(
modifier = Modifier.width(35.dp),
style = TangemTheme.typography.caption2,
)
SpacerW8()
TextShimmer(
modifier = Modifier.width(35.dp),
style = TangemTheme.typography.caption2,
)
}
RectangleShimmer(
modifier = Modifier
.height(TangemTheme.dimens.size6)
.fillMaxWidth(),
radius = 27.dp,
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
TextShimmer(
modifier = Modifier.width(TangemTheme.dimens.size56),
style = TangemTheme.typography.body1,
)
SpacerW8()
TextShimmer(
modifier = Modifier.width(TangemTheme.dimens.size56),
style = TangemTheme.typography.body1,
)
}
}
},
)
}
@Preview
@Preview("Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ContentPreview() {
TangemThemePreview {
PricePerformanceBlock(
modifier = Modifier,
state = PricePerformanceUM(
h24 = PricePerformanceUM.Value(
low = "\$38,5K",
high = "\$58,5K",
indicatorFraction = 0.5f,
),
month = PricePerformanceUM.Value(
low = "\$500,5K",
high = "\$5800,5K",
indicatorFraction = 0.8f,
),
all = PricePerformanceUM.Value(
low = "\$58,52",
high = "\$580,5M",
indicatorFraction = 0.2f,
),
onIntervalChanged = {},
),
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PlaceholderPreview() {
TangemThemePreview {
PreviewShimmerContainer(
shimmerContent = {
PricePerformanceBlockPlaceholder()
},
actualContent = {
ContentPreview()
},
)
}
}

View file

@ -3,7 +3,6 @@ package com.tangem.features.feed.ui.market.detailed.components
import androidx.annotation.FloatRange
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -15,10 +14,7 @@ import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.impl.R
import kotlin.math.round
@ -26,52 +22,7 @@ import kotlin.math.round
private const val STARS_COUNT = 5
@Composable
internal fun ScoreStarsBlock(
score: Float,
horizontalSpacing: Dp,
scoreTextStyle: TextStyle,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
ScoreStarsBlockV2(
score = score,
modifier = modifier,
)
} else {
ScoreStarsBlockV1(
score = score,
horizontalSpacing = horizontalSpacing,
scoreTextStyle = scoreTextStyle,
modifier = modifier,
)
}
}
@Composable
private fun ScoreStarsBlockV1(
score: Float,
horizontalSpacing: Dp,
scoreTextStyle: TextStyle,
modifier: Modifier = Modifier,
) {
val rounded = score.roundTo1decimal()
val percentage = rounded / STARS_COUNT
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = rounded.toString(),
style = scoreTextStyle,
color = TangemTheme.colors.text.primary1,
)
Stars(fraction = percentage)
}
}
@Composable
private fun ScoreStarsBlockV2(score: Float, modifier: Modifier = Modifier) {
internal fun ScoreStarsBlock(score: Float, modifier: Modifier = Modifier) {
val rounded = score.roundTo1decimal()
val percentage = rounded / STARS_COUNT
Box(
@ -85,59 +36,6 @@ private fun ScoreStarsBlockV2(score: Float, modifier: Modifier = Modifier) {
@Suppress("MagicNumber")
@Composable
private fun Stars(@FloatRange(0.0, 1.0) fraction: Float = 0f) {
if (LocalRedesignEnabled.current) {
StarsV2(fraction)
} else {
StarsV1(fraction)
}
}
@Suppress("MagicNumber")
@Composable
private fun StarsV1(@FloatRange(0.0, 1.0) fraction: Float = 0f) {
val grayColor = TangemTheme.colors.icon.inactive
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
verticalAlignment = Alignment.CenterVertically,
) {
repeat(times = STARS_COUNT) { i ->
Box(
modifier = Modifier.size(TangemTheme.dimens.size16),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier
.requiredSize(16.dp)
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
.drawWithCache {
onDrawWithContent {
val starFraction = ((fraction - i * 0.2) / 0.2).coerceIn(0.0, 1.0)
val starFractionFloat = starFraction
.toFloat()
.roundTo1decimal()
drawContent()
drawRect(
color = grayColor,
topLeft = Offset(x = size.width * starFractionFloat, y = 0f),
size = Size(size.width * (1 - starFractionFloat), size.height),
blendMode = BlendMode.SrcIn,
)
}
},
imageVector = ImageVector.vectorResource(R.drawable.ic_star_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
}
}
}
}
@Suppress("MagicNumber")
@Composable
private fun StarsV2(@FloatRange(0.0, 1.0) fraction: Float = 0f) {
val grayColor = TangemTheme.colors2.markers.iconGray
Row(

View file

@ -6,77 +6,27 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
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.clip
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.text.style.TextOverflow
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.TextShimmer
import com.tangem.core.ui.components.text.TooltipText
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.components.TokenMarketInformationBlock
import com.tangem.features.feed.ui.market.detailed.state.SecurityScoreUM
@Composable
internal fun SecurityScoreBlock(state: SecurityScoreUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
SecurityScoreBlockV2(state, modifier)
} else {
SecurityScoreBlockV1(state, modifier)
}
}
@Composable
private fun SecurityScoreBlockV1(state: SecurityScoreUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.fillMaxWidth()
.heightIn(max = TangemTheme.dimens.size72)
.padding(all = TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier
.weight(1F)
.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween,
) {
TooltipText(
text = resourceReference(R.string.markets_token_details_security_score),
onInfoClick = state.onInfoClick,
textStyle = TangemTheme.typography.subtitle2,
)
Text(
text = state.description.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
ScoreStarsBlock(
score = state.score,
scoreTextStyle = TangemTheme.typography.body1,
horizontalSpacing = TangemTheme.dimens.spacing8,
)
}
SecurityScoreBlockV2(state, modifier)
}
@Composable
@ -111,8 +61,6 @@ private fun SecurityScoreBlockV2(state: SecurityScoreUM, modifier: Modifier = Mo
ScoreStarsBlock(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_TOP),
score = state.score,
scoreTextStyle = TangemTheme.typography.body1,
horizontalSpacing = TangemTheme.dimens.spacing8,
)
}
},
@ -121,11 +69,7 @@ private fun SecurityScoreBlockV2(state: SecurityScoreUM, modifier: Modifier = Mo
@Composable
internal fun SecurityScoreBlockPlaceholder(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
SecurityScoreBlockPlaceholderV2(modifier)
} else {
SecurityScoreBlockPlaceholderV1(modifier)
}
SecurityScoreBlockPlaceholderV2(modifier)
}
@Composable
@ -167,103 +111,26 @@ private fun SecurityScoreBlockPlaceholderV2(modifier: Modifier = Modifier) {
}
}
@Composable
private fun SecurityScoreBlockPlaceholderV1(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.primary)
.fillMaxWidth()
.heightIn(max = TangemTheme.dimens.size72)
.padding(all = TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Column(
modifier = Modifier
.fillMaxWidth(fraction = 0.4f)
.padding(vertical = TangemTheme.dimens.spacing2)
.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween,
) {
TextShimmer(
modifier = Modifier.fillMaxWidth(),
style = TangemTheme.typography.subtitle2,
textSizeHeight = true,
)
TextShimmer(
modifier = Modifier.fillMaxWidth(),
style = TangemTheme.typography.body2,
textSizeHeight = true,
)
}
TextShimmer(
modifier = Modifier.fillMaxWidth(fraction = 0.5f),
style = TangemTheme.typography.body2,
textSizeHeight = true,
)
}
}
@Preview(widthDp = 328, showBackground = true)
@Preview(widthDp = 328, showBackground = true, locale = "ru")
@Preview(widthDp = 328, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ContentPreviewV1() {
TangemThemePreview {
SecurityScoreBlock(
state = SecurityScoreUM(
score = 3.5f,
description = stringReference("Based on 3 ratings"),
onInfoClick = {},
),
)
}
}
@Preview(widthDp = 328, showBackground = true)
@Preview(widthDp = 328, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewPlaceholderV1() {
TangemThemePreview {
PreviewShimmerContainer(
shimmerContent = {
SecurityScoreBlockPlaceholder(
modifier = Modifier.fillMaxWidth(),
)
},
actualContent = {
ContentPreviewV1()
},
)
}
}
@Preview(widthDp = 328, showBackground = true)
@Preview(widthDp = 328, showBackground = true, locale = "ru")
@Preview(widthDp = 328, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ContentPreviewV2() {
CompositionLocalProvider(
LocalRedesignEnabled provides true,
) {
TangemThemePreviewRedesign {
Column(modifier = Modifier.background(TangemTheme.colors2.surface.level2)) {
SecurityScoreBlock(
state = SecurityScoreUM(
score = 3.5f,
description = stringReference("Based on 3 ratings"),
onInfoClick = {},
),
)
TangemThemePreviewRedesign {
Column(modifier = Modifier.background(TangemTheme.colors2.surface.level2)) {
SecurityScoreBlock(
state = SecurityScoreUM(
score = 3.5f,
description = stringReference("Based on 3 ratings"),
onInfoClick = {},
),
)
SpacerH(10.dp)
SpacerH(10.dp)
SecurityScoreBlockPlaceholder(
modifier = Modifier.fillMaxWidth(),
)
}
SecurityScoreBlockPlaceholder(
modifier = Modifier.fillMaxWidth(),
)
}
}
}

View file

@ -2,16 +2,12 @@ package com.tangem.features.feed.ui.market.detailed.components
import android.content.res.Configuration
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -25,30 +21,22 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetTitle
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.preview.SecurityScorePreviewData
@ -57,58 +45,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet as TangemBot
@Composable
internal fun SecurityScoreBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
SecurityScoreBottomSheetV2(config)
} else {
SecurityScoreBottomSheetV1(config)
}
}
@Composable
private fun SecurityScoreBottomSheetV1(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheet<SecurityScoreBottomSheetContent>(
config = config,
addBottomInsets = false,
title = { TangemBottomSheetTitle(title = it.title) },
content = { content ->
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
Text(
text = content.description.resolveReference(),
style = TangemTheme.typography.body2.copy(
color = TangemTheme.colors.text.secondary,
),
)
SpacerH12()
content.providers.fastForEachIndexed { index, provider ->
DividerContainer(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = content.providers.lastIndex,
addDefaultPadding = false,
)
.background(TangemTheme.colors.background.action),
showDivider = index != content.providers.lastIndex,
) {
SecurityScoreProviderRow(
providerUM = provider,
onLinkClick = { content.onProviderLinkClick(provider) },
)
}
}
SpacerH16()
SpacerH(bottomBarHeight)
}
},
)
SecurityScoreBottomSheetV2(config)
}
@Composable
@ -167,74 +104,6 @@ private fun SecurityScoreBottomSheetV2(config: TangemBottomSheetConfig) {
)
}
@Composable
private fun SecurityScoreProviderRow(
providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM,
onLinkClick: () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing12)
.heightIn(min = TangemTheme.dimens.size68),
verticalAlignment = Alignment.CenterVertically,
) {
SubcomposeAsyncImage(
modifier = Modifier
.size(size = TangemTheme.dimens.size40)
.clip(TangemTheme.shapes.roundedCorners8),
model = ImageRequest.Builder(context = LocalContext.current)
.data(providerUM.iconUrl)
.crossfade(enable = true)
.allowHardware(false)
.build(),
loading = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
error = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
contentDescription = null,
)
Column(
modifier = Modifier.padding(start = TangemTheme.dimens.spacing12),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
) {
Text(
text = providerUM.name,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
providerUM.lastAuditDate?.let { auditDate ->
Text(
text = auditDate,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
SpacerWMax()
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
modifier = Modifier.clickable(
enabled = providerUM.urlData != null,
indication = ripple(bounded = false),
interactionSource = remember { MutableInteractionSource() },
onClick = onLinkClick,
),
) {
ScoreStarsBlock(
score = providerUM.score,
scoreTextStyle = TangemTheme.typography.body2,
horizontalSpacing = TangemTheme.dimens.spacing3,
)
UrlBlockV1(providerUM)
}
}
}
@Composable
private fun SecurityScoreProviderRowV2(
providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM,
@ -305,34 +174,6 @@ private fun SecurityScoreProviderRowV2(
}
}
@Composable
private fun UrlBlockV1(
providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM,
modifier: Modifier = Modifier,
) {
val urlData = providerUM.urlData
val rootHost = urlData?.rootHost
if (urlData != null && rootHost != null) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
) {
Text(
text = urlData.rootHost,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
Icon(
modifier = Modifier
.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_arrow_top_right_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
}
@Composable
private fun UrlBlockV2(
providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM,
@ -401,8 +242,8 @@ fun StarBlock(score: Float, modifier: Modifier = Modifier) {
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun SecurityScoreBottomSheetPreviewV1() {
TangemThemePreview {
private fun SecurityScoreBottomSheetPreviewV2() {
TangemThemePreviewRedesign {
SecurityScoreBottomSheet(
config = TangemBottomSheetConfig(
isShown = true,
@ -411,21 +252,4 @@ private fun SecurityScoreBottomSheetPreviewV1() {
),
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun SecurityScoreBottomSheetPreviewV2() {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
SecurityScoreBottomSheet(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = SecurityScorePreviewData.bottomSheetContent,
),
)
}
}
}

View file

@ -11,9 +11,7 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.items.DescriptionItem
import com.tangem.core.ui.components.items.DescriptionPlaceholder
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.feed.components.NewsSlider
@ -22,79 +20,16 @@ import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM
import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM.RelatedNews
@Suppress("CanBeNonNullable")
internal fun LazyListScope.tokenMarketDetailsBody(
isRedesignEnabled: Boolean,
state: MarketsTokenDetailsUM.Body,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
relatedNews: RelatedNews,
marketingBanner: @Composable (Modifier) -> Unit,
) {
if (isRedesignEnabled) {
tokenMarketDetailsBodyV2(
state = state,
relatedNews = relatedNews,
marketingBanner = marketingBanner,
)
} else {
tokenMarketDetailsBodyV1(
state = state,
portfolioBlock = portfolioBlock,
relatedNews = relatedNews,
)
}
}
@Suppress("CanBeNonNullable")
private fun LazyListScope.tokenMarketDetailsBodyV1(
state: MarketsTokenDetailsUM.Body,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
relatedNews: RelatedNews,
) {
when (state) {
MarketsTokenDetailsUM.Body.Loading -> {
item("description-loading") {
DescriptionPlaceholder(modifier = Modifier.blockPaddings())
}
if (portfolioBlock != null) {
item(key = "portfolio") {
portfolioBlock(Modifier.blockPaddings())
}
}
aboutCoinHeader()
loadingInfoBlocks(false)
}
is MarketsTokenDetailsUM.Body.Content -> {
if (state.description != null) {
description(state.description)
}
if (portfolioBlock != null) {
item(key = "portfolio") {
portfolioBlock(Modifier.blockPaddings())
}
}
if (relatedNews.articles.isNotEmpty()) {
relatedNews(relatedNews)
} else {
sectionStub(RelatedNews.SECTION_KEY)
}
aboutCoinHeader()
infoBlocksListV1(state = state.infoBlocks)
}
is MarketsTokenDetailsUM.Body.Error -> {
error(state)
}
MarketsTokenDetailsUM.Body.Nothing -> {
// Do nothing
}
}
tokenMarketDetailsBodyV2(
state = state,
relatedNews = relatedNews,
marketingBanner = marketingBanner,
)
}
private fun LazyListScope.marketingBannerItem(marketingBanner: @Composable (Modifier) -> Unit) {
@ -125,7 +60,7 @@ private fun LazyListScope.tokenMarketDetailsBodyV2(
DescriptionPlaceholder(modifier = Modifier.blockPaddings())
}
loadingInfoBlocks(true)
loadingInfoBlocks()
}
is MarketsTokenDetailsUM.Body.Content -> {
if (state.description != null) {
@ -164,36 +99,12 @@ private fun LazyListScope.error(state: MarketsTokenDetailsUM.Body.Error) {
}
}
private fun LazyListScope.aboutCoinHeader() {
item("aboutCoinHeader") {
Text(
modifier = Modifier.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing20,
),
text = stringResourceSafe(R.string.markets_about_coin_header),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h3,
)
}
}
private fun LazyListScope.description(description: MarketsTokenDetailsUM.Description) {
item("description") {
DescriptionItem(
modifier = Modifier
.conditionalCompose(
condition = LocalRedesignEnabled.current,
modifier = {
this
.padding(horizontal = TangemTheme.dimens2.x4)
.padding(bottom = 24.dp)
},
otherModifier = {
blockPaddings()
},
),
.padding(horizontal = TangemTheme.dimens2.x4)
.padding(bottom = 24.dp),
description = description.shortDescription,
hasFullDescription = description.fullDescription != null,
onReadMoreClick = description.onReadMoreClick,
@ -201,60 +112,6 @@ private fun LazyListScope.description(description: MarketsTokenDetailsUM.Descrip
}
}
internal fun LazyListScope.infoBlocksListV1(state: MarketsTokenDetailsUM.InformationBlocks) {
if (state.insights != null) {
item("insights") {
InsightsBlock(
modifier = Modifier.blockPaddings(),
state = state.insights,
)
}
}
if (state.securityScore != null) {
item("securityScore") {
SecurityScoreBlock(
modifier = Modifier.blockPaddings(),
state = state.securityScore,
)
}
}
if (state.metrics != null) {
item("metrics") {
MetricsBlock(
modifier = Modifier.blockPaddings(),
state = state.metrics,
)
}
}
if (state.pricePerformance != null) {
item("pricePerformance") {
PricePerformanceBlock(
modifier = Modifier.blockPaddings(),
state = state.pricePerformance,
)
}
}
item(key = "listedOn") {
ListedOnBlock(
state = state.listedOn,
modifier = Modifier.blockPaddings(),
)
}
if (state.links != null) {
item("links") {
LinksBlock(
modifier = Modifier.blockPaddings(),
state = state.links,
)
}
}
}
internal fun LazyListScope.infoBlocksListV2(state: MarketsTokenDetailsUM.InformationBlocks, relatedNews: RelatedNews) {
if (state.metrics != null) {
item("metrics") {
@ -306,40 +163,8 @@ internal fun LazyListScope.infoBlocksListV2(state: MarketsTokenDetailsUM.Informa
}
}
private fun LazyListScope.loadingInfoBlocks(isRedesignEnabled: Boolean) {
if (isRedesignEnabled) {
loadingInfoBlocksV2()
} else {
loadingInfoBlocksV1()
}
}
private fun LazyListScope.loadingInfoBlocksV1() {
item("insights-loading") {
InsightsBlockPlaceholder(
modifier = Modifier.blockPaddings(),
)
}
item("securityScore-loading") {
SecurityScoreBlockPlaceholder(modifier = Modifier.blockPaddings())
}
item("metrics-loading") {
MetricsBlockPlaceholder(modifier = Modifier.blockPaddings())
}
item("pricePerformance-loading") {
PricePerformanceBlockPlaceholder(modifier = Modifier.blockPaddings())
}
item(key = "listedOn-loading") {
ListedOnBlockPlaceholder(modifier = Modifier.blockPaddings())
}
item("links-loading") {
LinksBlockPlaceholder(modifier = Modifier.blockPaddings())
}
private fun LazyListScope.loadingInfoBlocks() {
loadingInfoBlocksV2()
}
private fun LazyListScope.loadingInfoBlocksV2() {
@ -364,41 +189,6 @@ private fun LazyListScope.loadingInfoBlocksV2() {
}
}
private fun LazyListScope.relatedNews(relatedNews: RelatedNews) {
item(RelatedNews.SECTION_KEY) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 32.dp, top = 20.dp)
.onFirstVisible(
minFractionVisible = 0.5f,
callback = relatedNews.onFirstVisible,
),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Text(
modifier = Modifier.padding(start = 16.dp),
text = stringResourceSafe(R.string.news_related_news),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
NewsSlider(
NewsSliderConfig(
callbacks = NewsSliderCallbacks(
onOpenAllNews = {}, // not applicable here
onSliderScroll = relatedNews.onScroll,
onSliderEndReached = {}, // not applicable here
onArticleClick = relatedNews.onArticledClicked,
),
content = relatedNews.articles,
shouldShowSeeAllNewsItem = false,
),
)
}
}
}
private fun LazyListScope.relatedNewsV2(relatedNews: RelatedNews) {
item(RelatedNews.SECTION_KEY) {
Column(
@ -436,15 +226,7 @@ private fun LazyListScope.relatedNewsV2(relatedNews: RelatedNews) {
@Composable
private fun Modifier.blockPaddings(): Modifier {
return if (LocalRedesignEnabled.current) {
this
.padding(horizontal = TangemTheme.dimens2.x4)
.padding(bottom = TangemTheme.dimens2.x2)
} else {
this.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing12,
)
}
return this
.padding(horizontal = TangemTheme.dimens2.x4)
.padding(bottom = TangemTheme.dimens2.x2)
}

View file

@ -2,17 +2,12 @@ package com.tangem.features.feed.ui.market.list
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
@ -22,17 +17,11 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.markets.preview.MarketChartListItemPreviewDataProvider
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
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.event.consumedEvent
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.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.models.currency.CryptoCurrency
@ -45,64 +34,6 @@ import com.tangem.features.feed.ui.market.list.components.MarketsListSortByBotto
import com.tangem.features.feed.ui.market.list.components.Options
import com.tangem.features.feed.ui.utils.FadeConstants.BASE_FADE_LEVEL
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
private const val DELAY_FOR_FOCUS_REQUEST = 500L
@Composable
internal fun TopBarWithSearch(
onBackClick: () -> Unit,
onSearchClick: () -> Unit,
marketsSearchBar: MarketsSearchBar,
bottomSheetState: BottomSheetState = BottomSheetState.EXPANDED,
) {
val background = LocalMainBottomSheetColor.current.value
val focusRequester = remember { FocusRequester() }
val shouldShowAppBar = !marketsSearchBar.shouldAlwaysShowSearchBar &&
!marketsSearchBar.searchBarUM.isActive &&
marketsSearchBar.searchBarUM.query.isEmpty()
AnimatedContent(
targetState = shouldShowAppBar,
label = "TopBarTransition",
) { showAppBar ->
if (showAppBar) {
AppBarWithBackButtonAndIcon(
onBackClick = onBackClick,
backButtonEnabled = bottomSheetState == BottomSheetState.EXPANDED,
endButtonEnabled = bottomSheetState == BottomSheetState.EXPANDED,
text = stringResourceSafe(R.string.markets_common_title),
iconRes = R.drawable.ic_search_24,
onIconClick = onSearchClick,
backgroundColor = background,
)
} else {
SearchBar(
modifier = Modifier
.drawBehind { drawRect(background) }
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp),
state = marketsSearchBar.searchBarUM,
colors = TangemSearchBarDefaults.defaultTextFieldColors.copy(
focusedContainerColor = TangemTheme.colors.field.focused,
unfocusedContainerColor = TangemTheme.colors.field.focused,
),
focusRequester = focusRequester,
)
val shouldRequestFocus =
(marketsSearchBar.shouldAlwaysShowSearchBar || marketsSearchBar.searchBarUM.isActive) &&
bottomSheetState == BottomSheetState.EXPANDED
if (shouldRequestFocus) {
LaunchedEffect(bottomSheetState) {
delay(DELAY_FOR_FOCUS_REQUEST)
focusRequester.requestFocus()
}
}
}
}
}
@Composable
internal fun MarketsList(contentPadding: PaddingValues, state: MarketsListUM, modifier: Modifier = Modifier) {
@ -121,77 +52,10 @@ internal fun MarketsList(contentPadding: PaddingValues, state: MarketsListUM, mo
@Suppress("LongMethod")
@Composable
private fun ColumnScope.Content(contentPadding: PaddingValues, state: MarketsListUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
ContentV2(
contentPadding = contentPadding,
state = state,
)
} else {
ContentV1(
contentPadding = contentPadding,
state = state,
modifier = modifier,
)
}
}
@Suppress("LongMethod")
@Composable
private fun ColumnScope.ContentV1(contentPadding: PaddingValues, state: MarketsListUM, modifier: Modifier = Modifier) {
val strokeColor = TangemTheme.colors.stroke.primary
val scrolledState = remember { mutableStateOf(false) }
Column(modifier.padding(horizontal = TangemTheme.dimens.size16)) {
SpacerH(contentPadding.calculateTopPadding())
AnimatedVisibility(
visible = scrolledState.value.not() &&
state.isInSearchMode &&
state.marketsSearchBar.searchBarUM.query.isNotEmpty(),
) {
Column {
SpacerH8()
Text(
text = stringResourceSafe(id = R.string.markets_search_result_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
SpacerH12()
}
}
Column {
AnimatedVisibility(!state.isInSearchMode && !state.marketsSearchBar.shouldAlwaysShowSearchBar) {
Options(
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
sortByTypeUM = state.selectedSortBy,
trendInterval = state.selectedInterval,
onIntervalClick = state.onIntervalClick,
onSortByClick = state.onSortByButtonClick,
sortMenuUM = state.sortByMenuUM,
)
}
}
}
val strokeWidth = TangemTheme.dimens.size0_5
Box(
Modifier
.fillMaxWidth()
.height(strokeWidth)
.drawBehind {
// draw horizontal line
if (scrolledState.value) {
drawLine(
color = strokeColor,
start = Offset(0f, size.height),
end = Offset(size.width, size.height),
strokeWidth = strokeWidth.toPx(),
)
}
},
)
ItemsList(
scrolledState = scrolledState,
isInSearchMode = state.isInSearchMode,
state = state.list,
private fun ColumnScope.Content(contentPadding: PaddingValues, state: MarketsListUM) {
ContentV2(
contentPadding = contentPadding,
state = state,
)
}
@ -229,10 +93,8 @@ private fun ColumnScope.ContentV2(contentPadding: PaddingValues, state: MarketsL
}
}
},
sortByTypeUM = state.selectedSortBy,
trendInterval = state.selectedInterval,
onIntervalClick = state.onIntervalClick,
onSortByClick = state.onSortByButtonClick,
sortMenuUM = state.sortByMenuUM,
)
}

View file

@ -23,10 +23,8 @@ import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.list.InfiniteListHandler
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.MarketsTestTags
import com.tangem.domain.models.currency.CryptoCurrency
@ -46,7 +44,6 @@ internal fun MarketsListLazyColumn(
modifier: Modifier = Modifier,
topContentPadding: Dp = 0.dp,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
val coroutineScope = rememberCoroutineScope()
@ -105,16 +102,11 @@ internal fun MarketsListLazyColumn(
key = { _, item -> item.getComposeKey() },
) { index, item ->
MarketsListItem(
modifier = Modifier.conditionalCompose(
condition = isRedesignEnabled,
modifier = {
roundedShapeItemDecoration(
currentIndex = index,
lastIndex = state.items.lastIndex,
backgroundColor = TangemTheme.colors2.surface.level3,
radius = TangemTheme.dimens2.x5,
)
},
modifier = Modifier.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = state.items.lastIndex,
backgroundColor = TangemTheme.colors2.surface.level3,
radius = TangemTheme.dimens2.x5,
),
model = item,
onClick = { state.onItemClick(item) },

View file

@ -1,16 +1,11 @@
package com.tangem.features.feed.ui.market.list.components
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.ds.button.PrimaryInverseTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
@ -18,97 +13,28 @@ import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.model.market.list.state.MarketsListUM
import com.tangem.features.feed.model.market.list.state.SortByMenuUM
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import kotlinx.collections.immutable.persistentListOf
import com.tangem.core.ui.ds.button.TangemButtonIconPosition as RedesignTangemButtonIconPosition
@Suppress("LongParameterList")
@Composable
internal fun Options(
sortMenuUM: SortByMenuUM,
sortByTypeUM: SortByTypeUM,
trendInterval: MarketsListUM.TrendInterval,
onSortByClick: () -> Unit,
onIntervalClick: (MarketsListUM.TrendInterval) -> Unit,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
OptionsV2(
sortMenuUM = sortMenuUM,
trendInterval = trendInterval,
onIntervalClick = onIntervalClick,
modifier = modifier,
)
} else {
OptionsV1(
sortByTypeUM = sortByTypeUM,
trendInterval = trendInterval,
onSortByClick = onSortByClick,
onIntervalClick = onIntervalClick,
modifier = modifier,
)
}
}
@Composable
private fun OptionsV1(
sortByTypeUM: SortByTypeUM,
trendInterval: MarketsListUM.TrendInterval,
onSortByClick: () -> Unit,
onIntervalClick: (MarketsListUM.TrendInterval) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.height(IntrinsicSize.Max)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
SecondarySmallButton(
config = SmallButtonConfig(
text = sortByTypeUM.text,
onClick = onSortByClick,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
),
)
SegmentedButtons(
config = persistentListOf(
MarketsListUM.TrendInterval.H24,
MarketsListUM.TrendInterval.D7,
MarketsListUM.TrendInterval.M1,
),
color = TangemTheme.colors.button.secondary,
initialSelectedItem = trendInterval,
onClick = onIntervalClick,
modifier = Modifier
.width(160.dp)
.fillMaxHeight(),
) {
Box(
Modifier
.fillMaxSize()
.align(Alignment.Center)
.padding(
vertical = TangemTheme.dimens.spacing4,
),
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = it.text.resolveReference(),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.primary1,
)
}
}
}
OptionsV2(
sortMenuUM = sortMenuUM,
trendInterval = trendInterval,
onIntervalClick = onIntervalClick,
modifier = modifier,
)
}
@Composable

View file

@ -13,7 +13,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.pager.PagerIndicator
import com.tangem.core.ui.ds.TangemPagerIndicator
import com.tangem.core.ui.ds.TangemPagerIndicatorColors
import com.tangem.core.ui.res.*
@ -62,7 +61,6 @@ internal fun NewsDetailsContent(state: NewsDetailsUM, contentPadding: PaddingVal
@Composable
private fun Content(contentPadding: PaddingValues, state: NewsDetailsUM, background: Color) {
val isRedesignEnabled = LocalRedesignEnabled.current
val pagerState = rememberPagerState(
initialPage = state.selectedArticleIndex,
pageCount = { state.articles.size },
@ -106,56 +104,22 @@ private fun Content(contentPadding: PaddingValues, state: NewsDetailsUM, backgro
)
}
if (state.articles.size > 1) {
if (isRedesignEnabled) {
TangemPagerIndicator(
pagerState = pagerState,
modifier = Modifier
.align(Alignment.BottomCenter)
.windowInsetsPadding(WindowInsets.navigationBars),
colors = TangemPagerIndicatorColors.copy(
overlay = TangemTheme.colors2.tabs.backgroundSecondary.copy(PAGER_BG_ALPHA),
),
hazeState = localHaze,
)
} else {
PagerIndicator(
pagerState = pagerState,
modifier = Modifier
.align(Alignment.BottomCenter)
.windowInsetsPadding(WindowInsets.navigationBars),
)
}
TangemPagerIndicator(
pagerState = pagerState,
modifier = Modifier
.align(Alignment.BottomCenter)
.windowInsetsPadding(WindowInsets.navigationBars),
colors = TangemPagerIndicatorColors.copy(
overlay = TangemTheme.colors2.tabs.backgroundSecondary.copy(PAGER_BG_ALPHA),
),
hazeState = localHaze,
)
}
}
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewNewsDetailsContent() {
TangemThemePreview {
val background = TangemTheme.colors.background.tertiary
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
) {
NewsDetailsContent(
state = NewsDetailsUM(
articlesStateUM = ArticlesStateUM.Content,
articles = MockArticlesFactory.createMockArticles(),
selectedArticleIndex = 0,
onShareClick = {},
onLikeClick = {},
onBackClick = {},
onArticleIndexChanged = {},
),
contentPadding = PaddingValues(),
)
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable

View file

@ -11,16 +11,13 @@ 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
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
@ -30,7 +27,6 @@ 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
import com.tangem.features.feed.ui.feed.components.articles.ArticleHeader
import com.tangem.features.feed.ui.news.details.state.ArticleUM
@ -47,150 +43,14 @@ internal fun ArticleDetail(
relatedTokensUM: RelatedTokensUM,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
ArticleDetailV2(
contentPadding = contentPadding,
article = article,
onLikeClick = onLikeClick,
relatedTokensUM = relatedTokensUM,
modifier = modifier,
hazeState = hazeState,
)
} else {
ArticleDetailV1(
article = article,
onLikeClick = onLikeClick,
relatedTokensUM = relatedTokensUM,
modifier = modifier,
)
}
}
@Suppress("LongMethod")
@Composable
private fun ArticleDetailV1(
article: ArticleUM,
onLikeClick: () -> Unit,
relatedTokensUM: RelatedTokensUM,
modifier: Modifier = Modifier,
) {
val hapticFeedback = LocalHapticFeedback.current
val density = LocalDensity.current
val background = LocalMainBottomSheetColor.current.value
val pagerHeight = 32.dp
val contentPadding = pagerHeight + 56.dp + with(density) {
WindowInsets.navigationBars.getBottom(this).div(this.density)
}.dp
Box(modifier = modifier) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
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,
modifier = Modifier
.padding(top = 32.dp)
.padding(horizontal = 16.dp),
)
}
Text(
text = article.content,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
modifier = Modifier
.padding(top = 16.dp)
.padding(horizontal = 16.dp),
)
SpacerH(24.dp)
SecondaryButtonIconStart(
modifier = Modifier.padding(horizontal = 16.dp),
iconResId = if (article.isLiked) {
R.drawable.ic_heart_filled_20
} else {
R.drawable.ic_heart_20
},
iconTint = Color.Unspecified,
text = stringResourceSafe(R.string.news_like),
size = TangemButtonSize.RoundedAction,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onLikeClick()
},
)
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.typography.h3,
color = TangemTheme.colors.text.primary1,
)
Text(
text = "${article.relatedArticles.size}",
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
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(),
)
}
}
}
}
}
BottomFade(
modifier = Modifier
.align(Alignment.BottomCenter),
backgroundColor = background,
)
}
ArticleDetailV2(
contentPadding = contentPadding,
article = article,
onLikeClick = onLikeClick,
relatedTokensUM = relatedTokensUM,
modifier = modifier,
hazeState = hazeState,
)
}
@Suppress("LongMethod")

View file

@ -10,94 +10,12 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
fun NewsDetailsPlaceholder(contentPadding: PaddingValues, background: Color, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
NewsDetailsPlaceholderV2(contentPadding, background, modifier)
} else {
NewsDetailsPlaceholderV1(background, modifier)
}
}
@Suppress("LongMethod")
@Composable
private fun NewsDetailsPlaceholderV1(background: Color, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.background(background)
.padding(16.dp),
) {
RectangleShimmer(modifier = Modifier.size(width = 112.dp, height = 20.dp))
SpacerH(8.dp)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(28.dp),
)
SpacerH(4.dp)
RectangleShimmer(modifier = Modifier.size(height = 28.dp, width = 208.dp))
SpacerH(20.dp)
RectangleShimmer(modifier = Modifier.size(height = 36.dp, width = 99.dp), radius = 12.dp)
SpacerH(32.dp)
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp),
)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 30.dp),
)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 30.dp),
)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 24.dp),
)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 70.dp),
)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 30.dp),
)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 96.dp),
)
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.padding(end = 100.dp),
)
}
}
NewsDetailsPlaceholderV2(contentPadding, background, modifier)
}
@Suppress("LongMethod")
@ -183,18 +101,6 @@ private fun NewsDetailsPlaceholderV2(contentPadding: PaddingValues, background:
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun NewsDetailsPlaceholderPreviewV1() {
TangemThemePreview {
NewsDetailsPlaceholder(
background = TangemTheme.colors.background.tertiary,
contentPadding = PaddingValues(),
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable

View file

@ -2,7 +2,6 @@ package com.tangem.features.feed.ui.news.details.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
@ -11,66 +10,22 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
internal fun QuickRecap(content: String, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
QuickRecapV2(content, modifier)
} else {
QuickRecapV1(content, modifier)
}
}
@Composable
private fun QuickRecapV1(content: String, modifier: Modifier = Modifier) {
Box(
modifier = modifier.height(IntrinsicSize.Min),
) {
VerticalDivider(
modifier = Modifier
.fillMaxHeight()
.padding(start = 8.dp),
thickness = 2.dp,
color = TangemTheme.colors.stroke.primary,
)
Column(modifier = Modifier.padding(start = 20.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.ic_quick_recap_16),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
modifier = Modifier.size(20.dp),
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResourceSafe(R.string.news_quick_recap),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.accent,
)
}
Spacer(modifier = Modifier.height(12.dp))
Text(
text = content,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
}
}
QuickRecapV2(content, modifier)
}
@Composable

View file

@ -24,87 +24,12 @@ import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.news.details.state.RelatedArticleUM
@Composable
internal fun RelatedNewsItem(relatedArticle: RelatedArticleUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
RelatedNewsItemV2(relatedArticle, modifier)
} else {
RelatedNewsItemV1(relatedArticle, modifier)
}
}
@Composable
private fun RelatedNewsItemV1(relatedArticle: RelatedArticleUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.sizeIn(maxWidth = 256.dp, minHeight = 132.dp)
.background(color = TangemTheme.colors.background.action, shape = RoundedCornerShape(12.dp))
.clickable(onClick = relatedArticle.onClick)
.padding(12.dp),
) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Column(modifier = Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 4.dp)) {
Icon(
painter = painterResource(id = R.drawable.ic_explore_16),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
modifier = Modifier.size(16.dp),
)
SpacerW(4.dp)
Text(
text = relatedArticle.media.name,
style = TangemTheme.typography.caption1,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.tertiary,
)
}
if (relatedArticle.title.isNotEmpty()) {
SpacerH(4.dp)
Text(
text = relatedArticle.title,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
}
}
if (relatedArticle.imageUrl != null) {
SubcomposeAsyncImage(
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(4.dp)),
contentScale = ContentScale.Crop,
model = ImageRequest.Builder(context = LocalContext.current)
.data(relatedArticle.imageUrl)
.crossfade(enable = false)
.allowHardware(true)
.memoryCachePolicy(CachePolicy.DISABLED)
.build(),
loading = {
RectangleShimmer(
modifier = Modifier.size(40.dp),
radius = 4.dp,
)
},
error = {},
contentDescription = relatedArticle.media.name,
)
}
}
SpacerHMax()
Text(
text = relatedArticle.publishedAt.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
RelatedNewsItemV2(relatedArticle, modifier)
}
@Suppress("LongMethod")

View file

@ -10,17 +10,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.common.ui.markets.MarketsListItem
import com.tangem.common.ui.markets.MarketsListItemPlaceholder
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.model.news.details.NewsDetailsModel.Companion.RELATED_TOKEN_MAX_COUNT
import com.tangem.features.feed.ui.news.details.state.RelatedTokensUM
@ -31,69 +27,11 @@ internal fun RelatedTokensBlock(
onItemClick: ((MarketsListItemUM) -> Unit)?,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
RelatedTokensBlockV2(
relatedTokensUM = relatedTokensUM,
onItemClick = onItemClick,
modifier = modifier,
)
} else {
RelatedTokensBlockV1(
relatedTokensUM = relatedTokensUM,
onItemClick = onItemClick,
modifier = modifier,
)
}
}
@Composable
internal fun RelatedTokensBlockV1(
relatedTokensUM: RelatedTokensUM,
onItemClick: ((MarketsListItemUM) -> Unit)?,
modifier: Modifier = Modifier,
) {
val isVisible = remember(relatedTokensUM) {
when (relatedTokensUM) {
is RelatedTokensUM.Content -> relatedTokensUM.items.isNotEmpty()
RelatedTokensUM.Loading -> true
RelatedTokensUM.LoadingError -> false
}
}
if (!isVisible) return
Column(modifier = modifier) {
SpacerH(40.dp)
Text(
text = stringResourceSafe(R.string.news_related_tokens),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
SpacerH(12.dp)
BlockCard(
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(modifier = Modifier.fillMaxWidth()) {
when (relatedTokensUM) {
is RelatedTokensUM.Content -> {
relatedTokensUM.items.fastForEach { marketsListItemUM ->
MarketsListItem(
model = marketsListItemUM,
onClick = { onItemClick?.invoke(marketsListItemUM) },
)
}
}
RelatedTokensUM.Loading -> {
repeat(RELATED_TOKEN_MAX_COUNT) {
MarketsListItemPlaceholder()
}
}
RelatedTokensUM.LoadingError -> Unit
}
}
}
}
RelatedTokensBlockV2(
relatedTokensUM = relatedTokensUM,
onItemClick = onItemClick,
modifier = modifier,
)
}
@Composable

View file

@ -13,16 +13,13 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.TopFade
import com.tangem.core.ui.components.chip.Chip
import com.tangem.core.ui.components.chip.entity.ChipUM
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.ds.tabs.TangemTab
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
@ -36,57 +33,11 @@ import kotlinx.collections.immutable.toImmutableSet
@Composable
internal fun NewsListContent(contentPadding: PaddingValues, state: NewsListUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
NewsListContentV2(
contentPadding = contentPadding,
state = state,
modifier = modifier,
)
} else {
NewsListContentV1(
contentPadding = contentPadding,
state = state,
modifier = modifier,
)
}
}
@Composable
internal fun NewsListContentV1(contentPadding: PaddingValues, state: NewsListUM, modifier: Modifier = Modifier) {
val background = LocalMainBottomSheetColor.current.value
val lazyListState = rememberLazyListState()
val chipsListState = rememberLazyListState()
ScrollChipsToSelected(state = state, chipsListState = chipsListState)
Column(
modifier = modifier
.fillMaxSize()
.background(background),
) {
SpacerH(contentPadding.calculateTopPadding())
LazyRow(
state = chipsListState,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(
items = state.filters,
key = { it.id },
) { filter ->
Chip(state = filter)
}
}
SpacerH(16.dp)
NewsListLazyColumn(
newsListState = state.newsListState,
listOfArticles = state.listOfArticles,
lazyListState = lazyListState,
onArticleClick = state.onArticleClick,
)
}
NewsListContentV2(
contentPadding = contentPadding,
state = state,
modifier = modifier,
)
}
@Composable

View file

@ -19,9 +19,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.components.list.InfiniteListHandler
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.DefaultLoadingArticle
@ -121,7 +119,6 @@ private fun Content(
modifier = Modifier
.heightIn(min = 152.dp)
.fillMaxWidth(),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
articleConfigUM = article,
onArticleClick = {
onArticleClick(article.id)

View file

@ -6,7 +6,6 @@ import com.google.common.truth.Truth.assertThat
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.model.MutableParamsContainer
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.earn.usecase.FetchTopEarnTokensUseCase
@ -49,7 +48,6 @@ internal class FeedComponentModelTest {
private val fetchTopEarnTokensUseCase: FetchTopEarnTokensUseCase = mockk(relaxed = true)
private val getTopEarnTokensUseCase: GetTopEarnTokensUseCase = mockk()
private val appRouter: AppRouter = mockk(relaxed = true)
private val designFeatureToggles: DesignFeatureToggles = mockk()
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory = mockk(relaxed = true)
private val getTopFiveMarketTokenUseCase: GetTopFiveMarketTokenUseCase = mockk(relaxed = true)
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase = mockk()
@ -89,7 +87,6 @@ internal class FeedComponentModelTest {
every { getSelectedAppCurrencyUseCase() } returns flowOf(Either.Right(AppCurrency.Default))
every { manageTrendingNewsUseCase.observeTrendingNews() } returns emptyFlow()
every { getTopEarnTokensUseCase() } returns emptyFlow()
every { designFeatureToggles.isRedesignEnabled } returns false
val paramsContainer = MutableParamsContainer(FeedParams(feedClickIntents = feedClickIntents))
@ -102,7 +99,6 @@ internal class FeedComponentModelTest {
fetchTopEarnTokensUseCase = fetchTopEarnTokensUseCase,
getTopEarnTokensUseCase = getTopEarnTokensUseCase,
appRouter = appRouter,
designFeatureToggles = designFeatureToggles,
addToPortfolioManagerFactory = addToPortfolioManagerFactory,
forYouFeatureToggles = forYouFeatureToggles,
getTopFiveMarketTokenUseCase = getTopFiveMarketTokenUseCase,