Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-26 08:43:23 +01:00
parent fc0dd403c6
commit b08892762c
14 changed files with 109 additions and 127 deletions

View file

@ -16,14 +16,14 @@ data class NewsDetailsResponse(
@Json(name = "newsUrl") val newsUrl: String,
@Json(name = "shortContent") val shortContent: String,
@Json(name = "content") val content: String,
@Json(name = "originalArticles") val originalArticles: List<NewsOriginalArticleDto>,
@Json(name = "relatedArticles") val relatedArticles: List<NewsRelatedArticleDto>,
)
@JsonClass(generateAdapter = true)
data class NewsOriginalArticleDto(
data class NewsRelatedArticleDto(
@Json(name = "id") val id: Int,
@Json(name = "title") val title: String,
@Json(name = "source") val source: Source,
@Json(name = "media") val media: Media,
@Json(name = "language") val language: String,
@Json(name = "publishedAt") val publishedAt: String,
@Json(name = "url") val url: String,
@ -31,7 +31,7 @@ data class NewsOriginalArticleDto(
)
@JsonClass(generateAdapter = true)
data class Source(
data class Media(
@Json(name = "id") val id: Int,
@Json(name = "name") val name: String,
)

View file

@ -2,7 +2,7 @@ package com.tangem.data.news.repository
import com.tangem.datasource.api.news.models.response.NewsArticleDto
import com.tangem.datasource.api.news.models.response.NewsDetailsResponse
import com.tangem.datasource.api.news.models.response.NewsOriginalArticleDto
import com.tangem.datasource.api.news.models.response.NewsRelatedArticleDto
import com.tangem.datasource.api.news.models.response.NewsRelatedTokenDto
import com.tangem.domain.models.news.*
@ -19,7 +19,7 @@ internal fun NewsDetailsResponse.toDomainDetailedArticle(isLiked: Boolean): Deta
newsUrl = newsUrl,
shortContent = shortContent,
content = content,
originalArticles = originalArticles.map { it.toDomainOriginalArticle() },
relatedArticles = relatedArticles.map { it.toDomainRelatedArticle() },
isLiked = isLiked,
)
}
@ -47,13 +47,13 @@ internal fun NewsRelatedTokenDto.toDomainRelatedToken(): RelatedToken {
)
}
internal fun NewsOriginalArticleDto.toDomainOriginalArticle(): OriginalArticle {
return OriginalArticle(
internal fun NewsRelatedArticleDto.toDomainRelatedArticle(): RelatedArticle {
return RelatedArticle(
id = id,
title = title,
source = Source(
id = source.id,
name = source.name,
media = Media(
id = media.id,
name = media.name,
),
locale = language,
publishedAt = publishedAt,

View file

@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable
* @param newsUrl - link for article to share
* @param shortContent - short description
* @param content - main text of the article
* @param originalArticles - original articles, which were base to build detailed article.
* @param relatedArticles - articles, which were base to build detailed article.
* @param isLiked - flag indicating whether the news is liked or not
*/
@Serializable
@ -33,6 +33,6 @@ data class DetailedArticle(
val newsUrl: String,
val shortContent: String,
val content: String,
val originalArticles: List<OriginalArticle>,
val relatedArticles: List<RelatedArticle>,
val isLiked: Boolean,
)

View file

@ -3,22 +3,22 @@ package com.tangem.domain.models.news
import kotlinx.serialization.Serializable
/**
* Represents an original article, which was base to build detailed article.
* Represents an related article, which was base to build detailed article.
*
[REDACTED_AUTHOR]
* @param id - unique identifier of the article
* @param title - article title
* @param source - object of source name and identifier
* @param media - object of media name and identifier
* @param locale - language of the article
* @param publishedAt - date of article publishing
* @param url - link to source of original article
* @param imageUrl - image from source of original article
*/
@Serializable
data class OriginalArticle(
data class RelatedArticle(
val id: Int,
val title: String,
val source: Source,
val media: Media,
val locale: String,
val publishedAt: String,
val url: String,
@ -26,7 +26,7 @@ data class OriginalArticle(
)
@Serializable
data class Source(
data class Media(
val id: Int,
val name: String,
)

View file

@ -179,7 +179,6 @@ internal class AddToPortfolioModel @Inject constructor(
callbackDelegate.onChangePortfolioClick.receiveAsFlow()
.onEach {
middleNavigationJob?.cancel()
setSelectedAccountToSelectorController()
middleNavigationJob = changePortfolioNavigationFlow(data).launchIn(this)
logAccountSelector(isAccountMode)
navigation.pushNew(AddToPortfolioRoutes.PortfolioSelector)
@ -229,24 +228,15 @@ internal class AddToPortfolioModel @Inject constructor(
}
}
private fun setSelectedAccountToSelectorController() {
modelScope.launch(dispatchers.default) {
val selectedAccount = selectedPortfolio
.first()
.account
.account
.account
.accountId
portfolioSelectorController.selectAccount(selectedAccount)
}
}
private fun changePortfolioNavigationFlow(data: AvailableToAddData): Flow<Unit> {
private fun changePortfolioNavigationFlow(data: AvailableToAddData): Flow<Unit> = flow {
val selectedPortfolioValue = selectedPortfolio.first()
val selectedAccount = selectedPortfolioValue.account.account.account.accountId
portfolioSelectorController.selectAccount(selectedAccount)
val changedPortfolio = setupPortfolioFlow(data)
.drop(1)
.onEach { portfolio -> navigation.pushNew(routeToNetworkSelector(portfolio)) }
val changedNetwork = setupNetworkFlow(changedPortfolio)
return combine(
combine(
flow = changedPortfolio,
flow2 = changedNetwork,
transform = { newPortfolio, newNetwork ->
@ -254,7 +244,7 @@ internal class AddToPortfolioModel @Inject constructor(
selectedNetwork.tryEmit(newNetwork)
navigation.popToFirst()
},
)
).collect { emit(it) }
}
private fun setupTokenActionsFlow(

View file

@ -22,11 +22,11 @@ internal class PortfolioAnalyticsEvent(
)
fun popupToChooseAccount() = PortfolioAnalyticsEvent(
event = "Popup to choose account",
event = "Choose Account Opened",
)
fun addToNotMainAccount() = PortfolioAnalyticsEvent(
event = "Button - Add (token not to main Account)",
event = "Button - Add To Account",
)
fun addToPortfolioWalletChanged() = PortfolioAnalyticsEvent(event = "Wallet Selected")

View file

@ -78,16 +78,7 @@ internal sealed class FeedAnalyticsEvent(
),
)
data class AllWidgetsLoadError(
private val code: Int?,
private val message: String,
) : FeedAnalyticsEvent(
event = "All Widgets Load Error",
params = mapOf(
ERROR_CODE to (code ?: IS_NOT_HTTP_ERROR).toString(),
ERROR_MESSAGE to message,
),
)
class AllWidgetsLoadError : FeedAnalyticsEvent(event = "All Widgets Load Error")
class TokenSearchedClicked : FeedAnalyticsEvent(event = "Token Searched Clicked")
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.feed.model.feed.state.transformers
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.domain.models.news.TrendingNews
import com.tangem.features.feed.model.feed.analytics.FeedAnalyticsEvent
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
@ -38,7 +37,7 @@ internal class UpdateGlobalFeedStateTransformer(
isNewsError && areAllChartsLoading -> GlobalFeedState.Loading
isNewsError && areAllChartsError -> {
if (previousGlobalState !is GlobalFeedState.Error) {
sendErrorAnalytics(errorStatesByOrder)
sendErrorAnalytics()
}
GlobalFeedState.Error(
onRetryClicked = onRetryClicked,
@ -58,19 +57,9 @@ internal class UpdateGlobalFeedStateTransformer(
)
}
private fun sendErrorAnalytics(errorStatesByOrder: Map<SortByTypeUM, Throwable?>) {
val firstError: Throwable? = errorStatesByOrder.values.firstNotNullOfOrNull { it }
firstError?.let { error ->
val (code, message) = when (error) {
is ApiResponseError.HttpException -> error.code.numericCode to error.message
else -> null to ""
}
analyticsEventHandler.send(
FeedAnalyticsEvent.AllWidgetsLoadError(
code = code,
message = message.orEmpty(),
),
)
}
private fun sendErrorAnalytics() {
analyticsEventHandler.send(
FeedAnalyticsEvent.AllWidgetsLoadError(),
)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.feed.model.market.list
import androidx.compose.runtime.Stable
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -16,6 +17,10 @@ import com.tangem.domain.markets.TokenMarketListConfig
import com.tangem.domain.markets.toSerializableParam
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.UserCountryError
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent
import com.tangem.features.feed.model.market.list.analytics.MarketsListAnalyticsEvent
import com.tangem.features.feed.model.market.list.state.ListUM
@ -48,6 +53,7 @@ internal class MarketsListModel @Inject constructor(
paramsContainer: ParamsContainer,
private val promoRepository: PromoRepository,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getUserCountryUseCase: GetUserCountryUseCase,
) : Model() {
private val updateQuotesJob = JobHolder()
@ -124,12 +130,14 @@ internal class MarketsListModel @Inject constructor(
appCurrency = currentAppCurrency.value,
interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
).conflate(),
) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo ->
flow5 = getUserCountryUseCase.invoke(),
) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo, userCountry ->
MarketsItemsData(
items = uiItems,
isInErrorState = isInInitialLoadingErrorState,
isSearchNotFound = isSearchNotFoundState,
shouldShowYieldModePromo = isYieldModePromo,
userCountry = userCountry,
)
}
} else {
@ -140,17 +148,20 @@ internal class MarketsListModel @Inject constructor(
appCurrency = currentAppCurrency.value,
interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
).conflate(),
) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo ->
flow4 = getUserCountryUseCase.invoke(),
) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo, userCountry ->
MarketsItemsData(
items = uiItems,
isInErrorState = isInInitialLoadingErrorState,
isSearchNotFound = false,
shouldShowYieldModePromo = shouldShowYieldModePromo,
userCountry = userCountry,
)
}
}
}.collect { marketsItemsData ->
val shouldShowYieldModePromo = marketsItemsData.shouldShowYieldModePromo
val isApplyFCARestrictions = marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions()
val shouldShowYieldModePromo = marketsItemsData.shouldShowYieldModePromo && !isApplyFCARestrictions
if (marketsListUMStateManager.state.value.marketsNotificationUM == null && shouldShowYieldModePromo) {
analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoShown())
}
@ -332,5 +343,6 @@ internal class MarketsListModel @Inject constructor(
val isInErrorState: Boolean,
val isSearchNotFound: Boolean,
val shouldShowYieldModePromo: Boolean,
val userCountry: Either<UserCountryError, UserCountry>,
)
}

View file

@ -14,7 +14,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.GetTokenMarketInfoUseCase
import com.tangem.domain.markets.GetTokenPriceChartUseCase
import com.tangem.domain.models.news.OriginalArticle
import com.tangem.domain.models.news.RelatedArticle
import com.tangem.domain.news.usecase.GetNewsListBatchFlowUseCase
import com.tangem.domain.news.usecase.MarkArticleAsViewedUseCase
import com.tangem.domain.news.usecase.ObserveNewsDetailsUseCase
@ -60,7 +60,7 @@ internal class NewsDetailsModel @Inject constructor(
private val params = paramsContainer.require<DefaultNewsDetailsComponent.Params>()
private val currentLanguage = Locale.getDefault().language
private val newsDetailsConverter = NewsDetailsConverter(onSourceClick = ::onOriginalArticleClick)
private val newsDetailsConverter = NewsDetailsConverter(onRelatedArticleClick = ::onRelatedArticleClick)
private val paginationManager: NewsDetailsPaginationManager? = params.paginationConfig?.let { config ->
NewsDetailsPaginationManager(
@ -139,14 +139,14 @@ internal class NewsDetailsModel @Inject constructor(
}
}
private fun onOriginalArticleClick(originalArticle: OriginalArticle) {
private fun onRelatedArticleClick(relatedArticle: RelatedArticle) {
analyticsEventHandler.send(
NewsDetailsAnalyticsEvent.RelatedNewsClicked(
newsId = params.articleId,
relatedNewsId = originalArticle.id,
relatedNewsId = relatedArticle.id,
),
)
urlOpener.openUrl(originalArticle.url)
urlOpener.openUrl(relatedArticle.url)
}
private fun onArticleIndexChanged(newIndex: Int) {

View file

@ -11,11 +11,11 @@ import com.tangem.core.ui.utils.getFormattedDate
import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.news.DetailedArticle
import com.tangem.domain.models.news.OriginalArticle
import com.tangem.domain.models.news.RelatedArticle
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.news.details.state.ArticleUM
import com.tangem.features.feed.ui.news.details.state.Source
import com.tangem.features.feed.ui.news.details.state.SourceUM
import com.tangem.features.feed.ui.news.details.state.Media
import com.tangem.features.feed.ui.news.details.state.RelatedArticleUM
import com.tangem.utils.StringsSigns
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
@ -24,7 +24,7 @@ import org.joda.time.DateTime
@Stable
internal class NewsDetailsConverter(
private val onSourceClick: (OriginalArticle) -> Unit,
private val onRelatedArticleClick: (RelatedArticle) -> Unit,
) : Converter<DetailedArticle, ArticleUM> {
override fun convert(value: DetailedArticle): ArticleUM {
@ -36,7 +36,7 @@ internal class NewsDetailsConverter(
tags = buildTags(value),
shortContent = value.shortContent,
content = value.content,
sources = buildSources(value),
relatedArticles = buildRelatedArticles(value),
newsUrl = value.newsUrl,
relatedTokens = value.relatedTokens.toImmutableList(),
isLiked = value.isLiked,
@ -60,16 +60,16 @@ internal class NewsDetailsConverter(
return (categoryLabels + tokenLabels).toImmutableList()
}
private fun buildSources(article: DetailedArticle): ImmutableList<SourceUM> {
return article.originalArticles.map { originalArticle ->
SourceUM(
id = originalArticle.id,
title = originalArticle.title,
source = Source(id = originalArticle.source.id, name = originalArticle.source.name),
publishedAt = mapFormattedDate(originalArticle.publishedAt),
url = originalArticle.url,
onClick = { onSourceClick(originalArticle) },
imageUrl = originalArticle.imageUrl,
private fun buildRelatedArticles(article: DetailedArticle): ImmutableList<RelatedArticleUM> {
return article.relatedArticles.map { relatedArticle ->
RelatedArticleUM(
id = relatedArticle.id,
title = relatedArticle.title,
media = Media(id = relatedArticle.media.id, name = relatedArticle.media.name),
publishedAt = mapFormattedDate(relatedArticle.publishedAt),
url = relatedArticle.url,
onClick = { onRelatedArticleClick(relatedArticle) },
imageUrl = relatedArticle.imageUrl,
)
}.toImmutableList()
}

View file

@ -211,7 +211,7 @@ private fun ArticleDetail(
modifier = Modifier.padding(horizontal = 16.dp),
)
if (article.sources.isNotEmpty()) {
if (article.relatedArticles.isNotEmpty()) {
SpacerH(24.dp)
Row(
modifier = Modifier.padding(horizontal = 16.dp),
@ -223,7 +223,7 @@ private fun ArticleDetail(
color = TangemTheme.colors.text.primary1,
)
Text(
text = "${article.sources.size}",
text = "${article.relatedArticles.size}",
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.tertiary,
)
@ -231,8 +231,8 @@ private fun ArticleDetail(
}
}
if (article.sources.isNotEmpty()) {
item("sources") {
if (article.relatedArticles.isNotEmpty()) {
item("relatedArticles") {
LazyRow(
modifier = Modifier.padding(vertical = 12.dp),
state = rememberLazyListState(),
@ -240,11 +240,11 @@ private fun ArticleDetail(
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
items(
items = article.sources,
key = SourceUM::id,
) { source ->
SourceItem(
source = source,
items = article.relatedArticles,
key = RelatedArticleUM::id,
) { article ->
RelatedNewsItem(
relatedArticle = article,
modifier = Modifier.fillParentMaxHeight(),
)
}
@ -301,12 +301,12 @@ private fun QuickRecap(content: String, modifier: Modifier = Modifier) {
}
@Composable
private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) {
private fun RelatedNewsItem(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 = source.onClick)
.clickable(onClick = relatedArticle.onClick)
.padding(12.dp),
) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
@ -320,17 +320,17 @@ private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) {
)
SpacerW(4.dp)
Text(
text = source.source.name,
text = relatedArticle.media.name,
style = TangemTheme.typography.caption1,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.tertiary,
)
}
if (source.title.isNotEmpty()) {
if (relatedArticle.title.isNotEmpty()) {
SpacerH(4.dp)
Text(
text = source.title,
text = relatedArticle.title,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
maxLines = 3,
@ -338,14 +338,14 @@ private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) {
)
}
}
if (source.imageUrl != null) {
if (relatedArticle.imageUrl != null) {
SubcomposeAsyncImage(
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(4.dp)),
contentScale = ContentScale.Crop,
model = ImageRequest.Builder(context = LocalContext.current)
.data(source.imageUrl)
.data(relatedArticle.imageUrl)
.crossfade(enable = false)
.allowHardware(true)
.memoryCachePolicy(CachePolicy.DISABLED)
@ -357,13 +357,13 @@ private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) {
)
},
error = {},
contentDescription = source.source.name,
contentDescription = relatedArticle.media.name,
)
}
}
SpacerHMax()
Text(
text = source.publishedAt.resolveReference(),
text = relatedArticle.publishedAt.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)

View file

@ -20,11 +20,11 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "Bitwise has updated its Solana ETF, adding staking and setting a low management fee of 0.20%.",
content = "Bitwise Asset Management has revised its filing to launch a Solana ETF, renaming it \"Bitwise Solana Staking ETF\" and setting an exceptionally low management fee of just 0.20%.\n\nThe update comes as the SEC prepares to review several Solana ETF applications.",
sources = listOf(
SourceUM(
relatedArticles = listOf(
RelatedArticleUM(
id = 1,
title = "Deeper liquidity could drive crypto market beyond \$6T",
source = Source(
media = Media(
id = 11,
name = "Coin-telegraph",
),
@ -33,10 +33,10 @@ internal object MockArticlesFactory {
onClick = {},
imageUrl = null,
),
SourceUM(
RelatedArticleUM(
id = 2,
title = "Top gainers and losers in crypto this week",
source = Source(
media = Media(
id = 10,
name = "Investing",
),
@ -61,11 +61,11 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "Bitcoin spot ETFs recorded fourth consecutive day of positive inflows.",
content = "Bitcoin spot ETFs continue their impressive streak with \$550 million in net positive inflows.\n\nBlackRock's IBIT led with \$250M.",
sources = listOf(
SourceUM(
relatedArticles = listOf(
RelatedArticleUM(
id = 3,
title = "Bitcoin ETFs see massive inflows",
source = Source(
media = Media(
id = 12,
name = "Bloomberg",
),
@ -90,11 +90,11 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "Ethereum developers announced major network upgrade.",
content = "The Ethereum Foundation announced a significant upgrade for Q2 2025.\n\nKey improvements include EVM enhancements.",
sources = listOf(
SourceUM(
relatedArticles = listOf(
RelatedArticleUM(
id = 4,
title = "Ethereum core devs announce upgrade",
source = Source(
media = Media(
id = 15,
name = "CoinDesk",
),
@ -119,11 +119,11 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "Solana achieved new milestone processing more daily transactions than Ethereum.",
content = "Solana processed over 50 million transactions in a single day.\n\nDriven by DeFi and NFT activity.",
sources = listOf(
SourceUM(
relatedArticles = listOf(
RelatedArticleUM(
id = 5,
title = "Solana transactions hit record",
source = Source(
media = Media(
id = 18,
name = "Times",
),
@ -147,7 +147,7 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "New DeFi protocol introduced innovative yield farming approach.",
content = "A newly launched protocol unveiled innovative yield farming mechanism.\n\nAPY rates range from 15% to 30%.",
sources = persistentListOf(),
relatedArticles = persistentListOf(),
newsUrl = "",
relatedTokens = persistentListOf(),
isLiked = false,
@ -163,7 +163,7 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "Comprehensive cryptocurrency regulation bill passed Senate Banking Committee.",
content = "The US Senate Banking Committee advanced landmark crypto regulation bill.\n\nKey provisions include asset definitions.",
sources = persistentListOf(),
relatedArticles = persistentListOf(),
newsUrl = "",
relatedTokens = persistentListOf(),
isLiked = false,
@ -178,7 +178,7 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "World's largest bank announced cryptocurrency custody services.",
content = "Major financial institution announced comprehensive crypto custody services.\n\nSupporting Bitcoin and Ethereum initially.",
sources = persistentListOf(),
relatedArticles = persistentListOf(),
newsUrl = "",
relatedTokens = persistentListOf(),
isLiked = false,
@ -193,7 +193,7 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "Leading NFT marketplace experienced dramatic surge in trading activity.",
content = "Prominent NFT marketplace reported 300% increase in trading volume.\n\nNew features include lower fees.",
sources = persistentListOf(),
relatedArticles = persistentListOf(),
newsUrl = "",
relatedTokens = persistentListOf(),
isLiked = false,
@ -209,7 +209,7 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "New Layer 2 scaling solution achieved 100,000 TPS in testing.",
content = "Layer 2 solution processed 100,000 transactions per second.\n\nUsing zero-knowledge proof technology.",
sources = persistentListOf(),
relatedArticles = persistentListOf(),
newsUrl = "",
relatedTokens = persistentListOf(),
isLiked = false,
@ -225,7 +225,7 @@ internal object MockArticlesFactory {
).toPersistentList(),
shortContent = "Total stablecoin market capitalization surpassed \$180 billion.",
content = "Stablecoin market cap reached \$180 billion all-time high.\n\nDriven by DeFi activity and institutional adoption.",
sources = persistentListOf(),
relatedArticles = persistentListOf(),
newsUrl = "",
relatedTokens = persistentListOf(),
isLiked = false,

View file

@ -33,16 +33,16 @@ internal data class ArticleUM(
val tags: ImmutableList<LabelUM>,
val shortContent: String,
val content: String,
val sources: ImmutableList<SourceUM>,
val relatedArticles: ImmutableList<RelatedArticleUM>,
val newsUrl: String,
val relatedTokens: ImmutableList<RelatedToken>,
val isLiked: Boolean,
)
internal data class SourceUM(
internal data class RelatedArticleUM(
val id: Int,
val title: String,
val source: Source,
val media: Media,
val publishedAt: TextReference,
val url: String,
val onClick: () -> Unit,
@ -62,7 +62,7 @@ internal sealed interface RelatedTokensUM {
data object LoadingError : RelatedTokensUM
}
internal data class Source(
internal data class Media(
val id: Int,
val name: String,
)