Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-30 08:52:15 +01:00
parent 028b21b112
commit 348d750054
17 changed files with 698 additions and 300 deletions

View file

@ -13,20 +13,13 @@ import com.tangem.domain.news.model.NewsListBatchFlow
import com.tangem.domain.news.model.NewsListBatchingContext
import com.tangem.domain.news.model.NewsListConfig
import com.tangem.domain.news.repository.NewsRepository
import com.tangem.pagination.BatchFetchResult
import com.tangem.pagination.BatchListSource
import com.tangem.pagination.*
import com.tangem.pagination.exception.EndOfPaginationException
import com.tangem.pagination.fetcher.BatchFetcher
import com.tangem.pagination.toBatchFlow
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import timber.log.Timber
/**
@ -42,12 +35,14 @@ internal class DefaultNewsRepository(
) : NewsRepository {
override fun getNewsListBatchFlow(context: NewsListBatchingContext, batchSize: Int): NewsListBatchFlow {
return BatchListSource(
val newsBatchFlow = BatchListSource(
fetchDispatcher = dispatchers.io,
context = context,
generateNewKey = { keys -> keys.lastOrNull()?.inc() ?: INITIAL_BATCH_KEY },
batchFetcher = createBatchFetcher(batchSize),
).toBatchFlow()
return updateViewedStatusForNewsBatch(newsBatchFlow, context.coroutineScope)
}
override suspend fun getNews(config: NewsListConfig, limit: Int): List<ShortArticle> {
@ -115,11 +110,13 @@ internal class DefaultNewsRepository(
}
override suspend fun getCategories(): List<ArticleCategory> {
return newsApi.getCategories().getOrThrow().items.map { dto ->
ArticleCategory(
id = dto.id,
name = dto.name,
)
return withContext(dispatchers.io) {
newsApi.getCategories().getOrThrow().items.map { dto ->
ArticleCategory(
id = dto.id,
name = dto.name,
)
}
}
}
@ -127,6 +124,38 @@ internal class DefaultNewsRepository(
newsViewedStore.updateViewed(articleIds, viewed)
}
private fun updateViewedStatusForNewsBatch(
newsBatchFlow: NewsListBatchFlow,
scope: CoroutineScope,
): NewsListBatchFlow {
return object : NewsListBatchFlow {
override val state: StateFlow<BatchListState<Int, List<ShortArticle>>> =
combine(
newsBatchFlow.state,
newsViewedStore.getAll(),
) { batchListState, viewedFlags ->
val updatedBatches = batchListState.data.map { batch ->
val updatedArticles = batch.data.map { article ->
val isViewed = viewedFlags[article.id] == true
article.copy(viewed = isViewed)
}
Batch(key = batch.key, data = updatedArticles)
}
BatchListState(
data = updatedBatches,
status = batchListState.status,
)
}.stateIn(
scope = scope,
started = SharingStarted.Eagerly,
initialValue = BatchListState(emptyList(), newsBatchFlow.state.value.status),
)
override val updateResults: SharedFlow<Pair<Nothing, BatchUpdateResult<Int, List<ShortArticle>>>> =
newsBatchFlow.updateResults
}
}
private suspend fun fetchDetailedArticlesInternal(newsIds: Collection<Int>, language: String?) =
withContext(dispatchers.io) {
if (newsIds.isEmpty()) return@withContext
@ -141,7 +170,7 @@ internal class DefaultNewsRepository(
if (idsToFetch.isEmpty()) return@withContext
val fetchedArticles = coroutineScope {
val fetchedArticles = supervisorScope {
idsToFetch.map { newsId ->
async {
newsApi.getNewsDetails(newsId = newsId, language = language)
@ -160,13 +189,12 @@ internal class DefaultNewsRepository(
private suspend fun fetchAndStoreTrendingNews(limit: Int, language: String?) {
return withContext(dispatchers.io) {
val apiResponse = newsApi.getTrendingNews(limit = limit, language = language)
when (val result = apiResponse) {
when (val apiResponse = newsApi.getTrendingNews(limit = limit, language = language)) {
is ApiResponse.Error -> {
Timber.e(
result.cause.cause,
apiResponse.cause.cause,
"Trending news fetch failed cause: ${
when (val error = result.cause) {
when (val error = apiResponse.cause) {
is ApiResponseError.HttpException -> error.code
is ApiResponseError.NetworkException -> "NetworkException"
is ApiResponseError.TimeoutException -> "TimeoutException"
@ -179,14 +207,14 @@ internal class DefaultNewsRepository(
key = TRENDING_NEWS_KEY,
value = TrendingNews.Error(
NewsError.Unknown(
message = result.cause.message,
message = apiResponse.cause.message,
code = null,
),
),
)
}
is ApiResponse.Success<NewsTrendingResponse> -> {
val freshArticles = result.data.items.map { it.toDomainShortArticle() }
val freshArticles = apiResponse.data.items.map { it.toDomainShortArticle() }
val articles = freshArticles.take(limit)
trendingNewsStore.store(TRENDING_NEWS_KEY, TrendingNews.Data(articles))
TrendingNews.Data(articles)
@ -271,7 +299,7 @@ internal class DefaultNewsRepository(
page = page,
limit = limit,
language = params.language,
snapshot = snapshotOverride,
snapshot = snapshotOverride?.takeIf { it.isNotEmpty() },
tokenIds = params.tokenIds.takeIf { it.isNotEmpty() },
categoryIds = params.categoryIds.takeIf { it.isNotEmpty() },
).getOrThrow()