Updated on 2026-08-14
This commit is contained in:
parent
6dc6e28205
commit
3b2a867959
15 changed files with 213 additions and 47 deletions
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.core.analytics.models
|
||||
|
||||
const val IS_NOT_HTTP_ERROR = "Is not http error"
|
||||
|
||||
sealed class AnalyticsParam {
|
||||
|
||||
sealed class CardBalanceState(val value: String) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.markets.analytics
|
|||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.IS_NOT_HTTP_ERROR
|
||||
|
||||
sealed interface MarketsDataAnalyticsEvent {
|
||||
|
||||
|
|
@ -82,8 +83,4 @@ sealed interface MarketsDataAnalyticsEvent {
|
|||
Custom("Custom"),
|
||||
Unknown("Unknown"),
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val IS_NOT_HTTP_ERROR = "Is not http error"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
package com.tangem.data.news.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.flatten
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.fold
|
||||
|
|
@ -90,7 +94,7 @@ internal class DefaultNewsRepository(
|
|||
.combine(newsLikedStore.getAll()) { articles, likedFlags ->
|
||||
articles.map { article ->
|
||||
article.copy(
|
||||
isLiked = isNewsLiked(article.id),
|
||||
isLiked = likedFlags[article.id] == true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -99,9 +103,10 @@ internal class DefaultNewsRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchDetailedArticles(newsIds: Collection<Int>, language: String?) {
|
||||
fetchDetailedArticlesInternal(newsIds = newsIds, language = language)
|
||||
}
|
||||
override suspend fun fetchDetailedArticles(
|
||||
newsIds: Collection<Int>,
|
||||
language: String?,
|
||||
): Either<Map<Int, Throwable>, Unit> = fetchDetailedArticlesInternal(newsIds = newsIds, language = language)
|
||||
|
||||
override suspend fun fetchTrendingNews(limit: Int, language: String?) {
|
||||
fetchAndStoreTrendingNews(limit = limit, language = language)
|
||||
|
|
@ -145,10 +150,9 @@ internal class DefaultNewsRepository(
|
|||
return newsLikedStore.getSync()[articleId] == true
|
||||
}
|
||||
|
||||
override suspend fun toggleNewsLiked(articleId: Int): Boolean = withContext(dispatchers.io) {
|
||||
override suspend fun toggleNewsLiked(articleId: Int) = withContext(dispatchers.io) {
|
||||
val isNewsLikedValue = !isNewsLiked(articleId)
|
||||
newsLikedStore.updateLiked(listOf(articleId), isNewsLikedValue)
|
||||
isNewsLikedValue
|
||||
}
|
||||
|
||||
private fun updateViewedStatusForNewsBatch(
|
||||
|
|
@ -162,9 +166,12 @@ internal class DefaultNewsRepository(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchDetailedArticlesInternal(newsIds: Collection<Int>, language: String?) =
|
||||
private suspend fun fetchDetailedArticlesInternal(
|
||||
newsIds: Collection<Int>,
|
||||
language: String?,
|
||||
): Either<Map<Int, Throwable>, Unit> = Either.catch {
|
||||
withContext(dispatchers.io) {
|
||||
if (newsIds.isEmpty()) return@withContext
|
||||
if (newsIds.isEmpty()) return@withContext Unit.right()
|
||||
|
||||
val uniqueIds = newsIds.distinct()
|
||||
val idsToFetch = buildList {
|
||||
|
|
@ -174,26 +181,42 @@ internal class DefaultNewsRepository(
|
|||
}
|
||||
}
|
||||
|
||||
if (idsToFetch.isEmpty()) return@withContext
|
||||
if (idsToFetch.isEmpty()) return@withContext Unit.right()
|
||||
|
||||
val fetchedArticles = supervisorScope {
|
||||
idsToFetch.map { newsId ->
|
||||
async {
|
||||
newsApi.getNewsDetails(newsId = newsId, language = language)
|
||||
.getOrThrow()
|
||||
.toDomainDetailedArticle(
|
||||
isLiked = isNewsLiked(newsId),
|
||||
)
|
||||
Either.catch {
|
||||
newsApi.getNewsDetails(newsId = newsId, language = language)
|
||||
.getOrThrow()
|
||||
.toDomainDetailedArticle(
|
||||
isLiked = isNewsLiked(newsId),
|
||||
)
|
||||
}.mapLeft {
|
||||
newsId to it
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
|
||||
if (fetchedArticles.isNotEmpty()) {
|
||||
newsDetailsStore.store(
|
||||
articles = fetchedArticles.associateBy(DetailedArticle::id),
|
||||
)
|
||||
}
|
||||
fetchedArticles
|
||||
.filterIsInstance<Either.Right<DetailedArticle>>()
|
||||
.map { it.value }
|
||||
.let { articles ->
|
||||
if (articles.isNotEmpty()) {
|
||||
newsDetailsStore.store(
|
||||
articles = articles.associateBy(DetailedArticle::id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val errors = fetchedArticles
|
||||
.filterIsInstance<Either.Left<Pair<Int, Throwable>>>()
|
||||
.associate { it.value.first to it.value.second }
|
||||
|
||||
if (errors.isEmpty()) Unit.right() else errors.left()
|
||||
}
|
||||
}.mapLeft { t -> mapOf(GLOBAL_ERROR_ID to t) }.flatten()
|
||||
|
||||
private suspend fun fetchAndStoreTrendingNews(limit: Int, language: String?) {
|
||||
return withContext(dispatchers.io) {
|
||||
|
|
@ -378,5 +401,6 @@ internal class DefaultNewsRepository(
|
|||
private const val INITIAL_BATCH_KEY = 0
|
||||
private const val FIRST_PAGE = 1
|
||||
private const val TRENDING_NEWS_KEY = "trending_news"
|
||||
private const val GLOBAL_ERROR_ID = -1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.news.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.news.ArticleCategory
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
|
|
@ -37,7 +38,7 @@ interface NewsRepository {
|
|||
/**
|
||||
* Fetches and caches detailed articles for provided ids in parallel.
|
||||
*/
|
||||
suspend fun fetchDetailedArticles(newsIds: Collection<Int>, language: String?)
|
||||
suspend fun fetchDetailedArticles(newsIds: Collection<Int>, language: String?): Either<Map<Int, Throwable>, Unit>
|
||||
|
||||
/**
|
||||
* Fetch list of trending news by limit and with correct locale and store it in runtime data store.
|
||||
|
|
@ -64,7 +65,6 @@ interface NewsRepository {
|
|||
|
||||
/**
|
||||
* Toggle news liked state
|
||||
* @return an actual liked state of the article
|
||||
*/
|
||||
suspend fun toggleNewsLiked(articleId: Int): Boolean
|
||||
suspend fun toggleNewsLiked(articleId: Int)
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ class ObserveNewsDetailsUseCase(
|
|||
/**
|
||||
* Prefetches the given article ids (can be called with current + next ids for pager preloading).
|
||||
*/
|
||||
suspend fun prefetch(newsIds: Collection<Int>, language: String?): Either<Throwable, Unit> = Either.catch {
|
||||
suspend fun prefetch(newsIds: Collection<Int>, language: String?): Either<Map<Int, Throwable>, Unit> =
|
||||
repository.fetchDetailedArticles(newsIds, language)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
|
||||
class ToggleArticleLikedUseCase(private val repository: NewsRepository) {
|
||||
|
|
@ -7,7 +8,7 @@ class ToggleArticleLikedUseCase(private val repository: NewsRepository) {
|
|||
/**
|
||||
* Toggle an article liked state.
|
||||
*/
|
||||
suspend fun toggleLiked(articleId: Int): Boolean {
|
||||
return repository.toggleNewsLiked(articleId)
|
||||
suspend fun toggleLiked(articleId: Int) = Either.catch {
|
||||
repository.toggleNewsLiked(articleId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.features.feed.components.market.details.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE
|
||||
import com.tangem.core.analytics.models.IS_NOT_HTTP_ERROR
|
||||
import com.tangem.core.analytics.models.OneTimePerSessionEvent
|
||||
|
||||
internal sealed class MarketTokenAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent(category = "CoinPage", event = event, params = params) {
|
||||
|
||||
data class TokenNewsViewed(
|
||||
private val token: String,
|
||||
) : MarketTokenAnalyticsEvent(
|
||||
event = "Token News Viewed",
|
||||
params = mapOf(
|
||||
AnalyticsParam.TOKEN_PARAM to token,
|
||||
),
|
||||
), OneTimePerSessionEvent {
|
||||
override val oneTimeEventId: String = event + token
|
||||
}
|
||||
|
||||
data class TokenNewsCarouselScrolled(
|
||||
private val token: String,
|
||||
) : MarketTokenAnalyticsEvent(
|
||||
event = "Token News Carousel Scrolled",
|
||||
params = mapOf(
|
||||
AnalyticsParam.TOKEN_PARAM to token,
|
||||
),
|
||||
), OneTimePerSessionEvent {
|
||||
override val oneTimeEventId: String = event + token
|
||||
}
|
||||
|
||||
data class TokenNewsLoadError(
|
||||
private val code: Int?,
|
||||
private val message: String,
|
||||
) : MarketTokenAnalyticsEvent(
|
||||
event = "Token News Load Error",
|
||||
params = mapOf(
|
||||
ERROR_CODE to (code ?: IS_NOT_HTTP_ERROR).toString(),
|
||||
ERROR_MESSAGE to message,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsParam
|
|||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.core.analytics.models.IS_NOT_HTTP_ERROR
|
||||
import com.tangem.core.analytics.models.OneTimePerSessionEvent
|
||||
|
||||
internal sealed class FeedAnalyticsEvent(
|
||||
|
|
@ -89,8 +90,4 @@ internal sealed class FeedAnalyticsEvent(
|
|||
)
|
||||
|
||||
class TokenSearchedClicked : FeedAnalyticsEvent(event = "Token Searched Clicked")
|
||||
|
||||
private companion object {
|
||||
const val IS_NOT_HTTP_ERROR = "Is not http error"
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.format.bigdecimal.price
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains
|
||||
|
|
@ -36,6 +37,7 @@ import com.tangem.domain.settings.usercountry.models.UserCountry
|
|||
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
|
||||
import com.tangem.features.feed.components.market.details.analytics.MarketTokenAnalyticsEvent
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.model.market.details.analytics.MarketDetailsAnalyticsEvent
|
||||
import com.tangem.features.feed.model.market.details.converter.DescriptionConverter
|
||||
|
|
@ -242,6 +244,8 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
relatedNews = MarketsTokenDetailsUM.RelatedNews(
|
||||
articles = persistentListOf(),
|
||||
onArticledClicked = {},
|
||||
onFirstVisible = {},
|
||||
onScroll = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -322,10 +326,36 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
/* preselectedIds */ relatedNews.map { it.id },
|
||||
)
|
||||
},
|
||||
onFirstVisible = {
|
||||
analyticsEventHandler.send(
|
||||
MarketTokenAnalyticsEvent.TokenNewsViewed(
|
||||
token = params.token.symbol,
|
||||
),
|
||||
)
|
||||
},
|
||||
onScroll = {
|
||||
analyticsEventHandler.send(
|
||||
MarketTokenAnalyticsEvent.TokenNewsCarouselScrolled(
|
||||
token = params.token.symbol,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}.onLeft { throwable ->
|
||||
val (code, message) = if (throwable is ApiResponseError.HttpException) {
|
||||
throwable.code.numericCode to throwable.message.orEmpty()
|
||||
} else {
|
||||
null to ""
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
MarketTokenAnalyticsEvent.TokenNewsLoadError(
|
||||
code = code,
|
||||
message = message,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ package com.tangem.features.feed.model.news.details
|
|||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
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.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.GetTokenMarketInfoUseCase
|
||||
|
|
@ -33,6 +35,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -129,7 +132,9 @@ internal class NewsDetailsModel @Inject constructor(
|
|||
|
||||
private fun onLikeClick(articleId: Int) {
|
||||
modelScope.launch {
|
||||
toggleArticleLikedUseCase.toggleLiked(articleId)
|
||||
toggleArticleLikedUseCase
|
||||
.toggleLiked(articleId)
|
||||
.onLeft { Timber.e(it) }
|
||||
analyticsEventHandler.send(NewsDetailsAnalyticsEvent.NewsLikeClicked(articleId))
|
||||
}
|
||||
}
|
||||
|
|
@ -202,7 +207,35 @@ internal class NewsDetailsModel @Inject constructor(
|
|||
observeNewsDetailsUseCase.prefetch(
|
||||
newsIds = params.preselectedArticlesId,
|
||||
language = currentLanguage,
|
||||
).mapLeft {
|
||||
).onLeft { errors ->
|
||||
errors.onEach { (newsId, throwable) ->
|
||||
when {
|
||||
// an article is opened from deeplink and is not found
|
||||
params.screenSource == AnalyticsParam.ScreensSources.NewsLink.value &&
|
||||
newsId == params.articleId &&
|
||||
throwable is ApiResponseError.HttpException &&
|
||||
throwable.code == ApiResponseError.HttpException.Code.NOT_FOUND
|
||||
-> {
|
||||
analyticsEventHandler.send(
|
||||
NewsDetailsAnalyticsEvent.NewsLinkMismatch(
|
||||
newsId = params.articleId,
|
||||
),
|
||||
)
|
||||
}
|
||||
// global request executing error
|
||||
newsId < 0 -> {
|
||||
Timber.e(throwable)
|
||||
}
|
||||
else -> {
|
||||
analyticsEventHandler.send(
|
||||
NewsDetailsAnalyticsEvent.NewsArticleLoadError(
|
||||
newsId = newsId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.mapLeft {
|
||||
stateFactory.createErrorState()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,4 +41,22 @@ internal sealed class NewsDetailsAnalyticsEvent(
|
|||
), OneTimePerSessionEvent {
|
||||
override val oneTimeEventId: String = event + newsId
|
||||
}
|
||||
|
||||
data class NewsArticleLoadError(
|
||||
private val newsId: Int,
|
||||
) : NewsDetailsAnalyticsEvent(
|
||||
event = "News Article Load Error",
|
||||
params = mapOf(
|
||||
"News Id" to newsId.toString(),
|
||||
),
|
||||
)
|
||||
|
||||
data class NewsLinkMismatch(
|
||||
private val newsId: Int,
|
||||
) : NewsDetailsAnalyticsEvent(
|
||||
event = "News Link Mismatch",
|
||||
params = mapOf(
|
||||
"News Id" to newsId.toString(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.feed.model.news.list.analytics
|
|||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE
|
||||
import com.tangem.core.analytics.models.IS_NOT_HTTP_ERROR
|
||||
|
||||
internal sealed class NewsListAnalyticsEvent(
|
||||
event: String,
|
||||
|
|
@ -28,8 +29,4 @@ internal sealed class NewsListAnalyticsEvent(
|
|||
"Selected Categories" to categoryId.toString(),
|
||||
),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val IS_NOT_HTTP_ERROR = "Is not http error"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.feed.ui.market.detailed.components
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -11,9 +11,9 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.remember
|
||||
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.common.ui.news.ArticleCard
|
||||
import com.tangem.common.ui.news.ArticleConfigUM
|
||||
import com.tangem.core.ui.components.UnableToLoadData
|
||||
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||
import com.tangem.core.ui.components.items.DescriptionItem
|
||||
|
|
@ -24,6 +24,8 @@ import com.tangem.features.feed.impl.R
|
|||
import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM
|
||||
import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM.RelatedNews
|
||||
|
||||
private const val FOURTH_ITEM_INDEX = 3
|
||||
|
||||
@Suppress("CanBeNonNullable") // TODO will be removed after [REDACTED_JIRA]
|
||||
internal fun LazyListScope.tokenMarketDetailsBody(
|
||||
state: MarketsTokenDetailsUM.Body,
|
||||
|
|
@ -215,7 +217,11 @@ private fun LazyListScope.relatedNews(relatedNews: RelatedNews) {
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 32.dp, top = 20.dp),
|
||||
.padding(bottom = 32.dp, top = 20.dp)
|
||||
.onFirstVisible(
|
||||
minFractionVisible = 0.5f,
|
||||
callback = relatedNews.onFirstVisible,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text(
|
||||
|
|
@ -231,14 +237,23 @@ private fun LazyListScope.relatedNews(relatedNews: RelatedNews) {
|
|||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
state = listState,
|
||||
) {
|
||||
items(
|
||||
itemsIndexed(
|
||||
items = relatedNews.articles,
|
||||
key = ArticleConfigUM::id,
|
||||
) { article ->
|
||||
key = { index, article -> article.id },
|
||||
) { index, article ->
|
||||
val articleModifier = if (index == FOURTH_ITEM_INDEX) {
|
||||
Modifier.onFirstVisible(
|
||||
minFractionVisible = 0.5f,
|
||||
callback = relatedNews.onScroll,
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
||||
ArticleCard(
|
||||
articleConfigUM = article,
|
||||
onArticleClick = { relatedNews.onArticledClicked(article.id) },
|
||||
modifier = Modifier
|
||||
modifier = articleModifier
|
||||
.height(164.dp)
|
||||
.width(216.dp),
|
||||
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ internal object MarketsTokenDetailsPreview {
|
|||
relatedNews = MarketsTokenDetailsUM.RelatedNews(
|
||||
articles = persistentListOf(),
|
||||
onArticledClicked = {},
|
||||
onFirstVisible = {},
|
||||
onScroll = {},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -136,6 +138,8 @@ internal object MarketsTokenDetailsPreview {
|
|||
relatedNews = MarketsTokenDetailsUM.RelatedNews(
|
||||
articles = persistentListOf(),
|
||||
onArticledClicked = {},
|
||||
onFirstVisible = {},
|
||||
onScroll = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -76,5 +76,7 @@ internal data class MarketsTokenDetailsUM(
|
|||
data class RelatedNews(
|
||||
val articles: ImmutableList<ArticleConfigUM>,
|
||||
val onArticledClicked: (id: Int) -> Unit,
|
||||
val onFirstVisible: () -> Unit,
|
||||
val onScroll: () -> Unit,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue