Updated on 2026-08-14
This commit is contained in:
parent
fc0dd403c6
commit
b08892762c
14 changed files with 109 additions and 127 deletions
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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>,
|
||||
)
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue