Updated on 2026-08-14
This commit is contained in:
parent
96154e6983
commit
18201957b1
22 changed files with 460 additions and 18 deletions
|
|
@ -163,6 +163,7 @@ dependencies {
|
|||
implementation(projects.domain.yieldSupply)
|
||||
implementation(projects.domain.blockaid)
|
||||
implementation(projects.domain.hotWallet)
|
||||
implementation(projects.domain.news)
|
||||
|
||||
implementation(projects.common)
|
||||
implementation(projects.common.routing)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase
|
||||
import com.tangem.domain.news.usecase.GetNewsListBatchFlowUseCase
|
||||
import com.tangem.domain.news.usecase.ObserveNewsDetailsUseCase
|
||||
import com.tangem.domain.news.usecase.ManageTrendingNewsUseCase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object NewsDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNewsCategoriesUseCase(repository: NewsRepository): GetNewsCategoriesUseCase {
|
||||
return GetNewsCategoriesUseCase(repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideObserveNewsDetailsUseCase(repository: NewsRepository): ObserveNewsDetailsUseCase {
|
||||
return ObserveNewsDetailsUseCase(repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideObserveTrendingNewsUseCase(repository: NewsRepository): ManageTrendingNewsUseCase {
|
||||
return ManageTrendingNewsUseCase(repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNewsListBatchFlowUseCase(repository: NewsRepository): GetNewsListBatchFlowUseCase {
|
||||
return GetNewsListBatchFlowUseCase(repository)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.news.details.DefaultNewsDetailsStore
|
||||
import com.tangem.datasource.local.news.details.NewsDetailsStore
|
||||
import com.tangem.datasource.local.news.trending.DefaultTrendingNewsStore
|
||||
import com.tangem.datasource.local.news.trending.TrendingNewsStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object NewsStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNewsDetailsStore(): NewsDetailsStore {
|
||||
return DefaultNewsDetailsStore(store = RuntimeSharedStore())
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTrendingNewsStore(): TrendingNewsStore {
|
||||
return DefaultTrendingNewsStore(store = RuntimeSharedStore())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
package com.tangem.datasource.local.datastore.core
|
||||
|
||||
@Deprecated(
|
||||
message = "Use RuntimeSharedStore instead",
|
||||
replaceWith = ReplaceWith("RuntimeSharedStore"),
|
||||
)
|
||||
internal interface StringKeyDataStore<Value : Any> : DataStore<String, Value>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.datasource.local.news.details
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultNewsDetailsStore(
|
||||
private val store: RuntimeSharedStore<Map<Int, DetailedArticle>>,
|
||||
) : NewsDetailsStore {
|
||||
|
||||
override fun getAll(): Flow<List<DetailedArticle>> {
|
||||
return store.get().map { it.values.toList() }
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(id: Int): DetailedArticle? {
|
||||
return store.getSyncOrNull()?.get(id)
|
||||
}
|
||||
|
||||
override suspend fun store(id: Int, article: DetailedArticle) {
|
||||
store.update(emptyMap()) { current ->
|
||||
current + (id to article)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun store(articles: Map<Int, DetailedArticle>) {
|
||||
if (articles.isEmpty()) return
|
||||
|
||||
store.update(emptyMap()) { current ->
|
||||
current + articles
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
store.store(emptyMap())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.datasource.local.news.details
|
||||
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NewsDetailsStore {
|
||||
|
||||
fun getAll(): Flow<List<DetailedArticle>>
|
||||
|
||||
suspend fun getSyncOrNull(id: Int): DetailedArticle?
|
||||
|
||||
suspend fun store(id: Int, article: DetailedArticle)
|
||||
|
||||
suspend fun store(articles: Map<Int, DetailedArticle>)
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.datasource.local.news.trending
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private typealias TrendingCache = Map<String, List<ShortArticle>>
|
||||
|
||||
internal class DefaultTrendingNewsStore(
|
||||
private val store: RuntimeSharedStore<TrendingCache>,
|
||||
) : TrendingNewsStore {
|
||||
|
||||
override fun get(key: String): Flow<List<ShortArticle>> {
|
||||
return store.get().map { it[key].orEmpty() }
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(key: String): List<ShortArticle>? {
|
||||
return store.getSyncOrNull()?.get(key)
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, value: List<ShortArticle>) {
|
||||
store.update(emptyMap()) { current ->
|
||||
current + (key to value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
store.store(emptyMap())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.local.news.trending
|
||||
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TrendingNewsStore {
|
||||
|
||||
fun get(key: String): Flow<List<ShortArticle>>
|
||||
|
||||
suspend fun getSyncOrNull(key: String): List<ShortArticle>?
|
||||
|
||||
suspend fun store(key: String, value: List<ShortArticle>)
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.data.news.di
|
|||
|
||||
import com.tangem.data.news.repository.DefaultNewsRepository
|
||||
import com.tangem.datasource.api.news.NewsApi
|
||||
import com.tangem.datasource.local.news.details.NewsDetailsStore
|
||||
import com.tangem.datasource.local.news.trending.TrendingNewsStore
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -16,10 +18,17 @@ internal object NewsDataModule {
|
|||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideNewsRepository(newsApi: NewsApi, dispatchers: CoroutineDispatcherProvider): NewsRepository {
|
||||
fun provideNewsRepository(
|
||||
newsApi: NewsApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
newsDetailsStore: NewsDetailsStore,
|
||||
trendingNewsStore: TrendingNewsStore,
|
||||
): NewsRepository {
|
||||
return DefaultNewsRepository(
|
||||
newsApi = newsApi,
|
||||
dispatchers = dispatchers,
|
||||
newsDetailsStore = newsDetailsStore,
|
||||
trendingNewsStore = trendingNewsStore,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,14 @@ package com.tangem.data.news.repository
|
|||
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.news.NewsApi
|
||||
import com.tangem.datasource.local.news.details.NewsDetailsStore
|
||||
import com.tangem.datasource.local.news.trending.TrendingNewsStore
|
||||
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.models.ArticleCategory
|
||||
import com.tangem.domain.news.models.DetailedArticle
|
||||
import com.tangem.domain.news.models.ShortArticle
|
||||
import com.tangem.domain.models.news.ArticleCategory
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import com.tangem.pagination.BatchFetchResult
|
||||
import com.tangem.pagination.BatchListSource
|
||||
|
|
@ -17,6 +19,13 @@ import com.tangem.pagination.toBatchFlow
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.collections.orEmpty
|
||||
|
||||
/**
|
||||
* Implementation of [NewsRepository].
|
||||
|
|
@ -25,6 +34,8 @@ import javax.inject.Inject
|
|||
internal class DefaultNewsRepository @Inject constructor(
|
||||
private val newsApi: NewsApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val newsDetailsStore: NewsDetailsStore,
|
||||
private val trendingNewsStore: TrendingNewsStore,
|
||||
) : NewsRepository {
|
||||
|
||||
override fun getNewsListBatchFlow(context: NewsListBatchingContext, batchSize: Int): NewsListBatchFlow {
|
||||
|
|
@ -37,13 +48,54 @@ internal class DefaultNewsRepository @Inject constructor(
|
|||
}
|
||||
|
||||
override suspend fun getDetailedArticle(newsId: Int, language: String?): DetailedArticle {
|
||||
val response = newsApi.getNewsDetails(newsId = newsId, language = language).getOrThrow()
|
||||
return response.toDomainDetailedArticle()
|
||||
val cached = newsDetailsStore.getSyncOrNull(newsId)
|
||||
if (cached != null) return cached
|
||||
|
||||
fetchDetailedArticlesInternal(newsIds = listOf(newsId), language = language)
|
||||
|
||||
return requireNotNull(newsDetailsStore.getSyncOrNull(newsId)) {
|
||||
"Unable to load detailed article with id=$newsId"
|
||||
}
|
||||
}
|
||||
|
||||
override fun observeDetailedArticles(): Flow<Map<Int, DetailedArticle>> {
|
||||
return newsDetailsStore.getAll().map { articles ->
|
||||
articles.associateBy(DetailedArticle::id)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchDetailedArticles(newsIds: Collection<Int>, language: String?) {
|
||||
fetchDetailedArticlesInternal(newsIds = newsIds, language = language)
|
||||
}
|
||||
|
||||
override suspend fun getTrendingNews(limit: Int, language: String?): List<ShortArticle> {
|
||||
val response = newsApi.getTrendingNews(limit = limit, language = language).getOrThrow()
|
||||
return response.items.map { it.toDomainShortArticle() }
|
||||
return fetchAndStoreTrendingNews(limit = limit, language = language)
|
||||
}
|
||||
|
||||
override fun observeTrendingNews(): Flow<List<ShortArticle>> {
|
||||
return trendingNewsStore.get(TRENDING_NEWS_KEY)
|
||||
}
|
||||
|
||||
override suspend fun refreshTrendingNews(limit: Int, language: String?) {
|
||||
fetchAndStoreTrendingNews(limit = limit, language = language)
|
||||
}
|
||||
|
||||
override suspend fun updateTrendingNewsViewed(articleIds: Collection<Int>, viewed: Boolean) {
|
||||
if (articleIds.isEmpty()) return
|
||||
|
||||
val current = trendingNewsStore.getSyncOrNull(TRENDING_NEWS_KEY).orEmpty()
|
||||
if (current.isEmpty()) return
|
||||
|
||||
val ids = articleIds.toSet()
|
||||
val updated = current.map { article ->
|
||||
if (article.id in ids) {
|
||||
article.copy(viewed = viewed)
|
||||
} else {
|
||||
article
|
||||
}
|
||||
}
|
||||
|
||||
trendingNewsStore.store(TRENDING_NEWS_KEY, updated)
|
||||
}
|
||||
|
||||
override suspend fun getCategories(): List<ArticleCategory> {
|
||||
|
|
@ -55,6 +107,61 @@ internal class DefaultNewsRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchDetailedArticlesInternal(newsIds: Collection<Int>, language: String?) =
|
||||
withContext(dispatchers.io) {
|
||||
if (newsIds.isEmpty()) return@withContext
|
||||
|
||||
val uniqueIds = newsIds.distinct()
|
||||
val idsToFetch = buildList {
|
||||
for (id in uniqueIds) {
|
||||
val cached = newsDetailsStore.getSyncOrNull(id)
|
||||
if (cached == null) add(id)
|
||||
}
|
||||
}
|
||||
|
||||
if (idsToFetch.isEmpty()) return@withContext
|
||||
|
||||
val fetchedArticles = coroutineScope {
|
||||
idsToFetch.map { newsId ->
|
||||
async {
|
||||
newsApi.getNewsDetails(newsId = newsId, language = language)
|
||||
.getOrThrow()
|
||||
.toDomainDetailedArticle()
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
|
||||
if (fetchedArticles.isNotEmpty()) {
|
||||
newsDetailsStore.store(
|
||||
articles = fetchedArticles.associateBy(DetailedArticle::id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchAndStoreTrendingNews(limit: Int, language: String?): List<ShortArticle> {
|
||||
return withContext(dispatchers.io) {
|
||||
val response = newsApi.getTrendingNews(limit = limit, language = language).getOrThrow()
|
||||
val freshArticles = response.items.map { it.toDomainShortArticle() }
|
||||
val currentArticles = trendingNewsStore.getSyncOrNull(TRENDING_NEWS_KEY).orEmpty()
|
||||
val merged = mergeTrendingArticles(current = currentArticles, fresh = freshArticles).take(limit)
|
||||
|
||||
trendingNewsStore.store(TRENDING_NEWS_KEY, merged)
|
||||
|
||||
merged
|
||||
}
|
||||
}
|
||||
|
||||
private fun mergeTrendingArticles(current: List<ShortArticle>, fresh: List<ShortArticle>): List<ShortArticle> {
|
||||
if (current.isEmpty()) return fresh
|
||||
|
||||
val currentById = current.associateBy(ShortArticle::id)
|
||||
|
||||
return fresh.map { article ->
|
||||
val stored = currentById[article.id] ?: return@map article
|
||||
article.copy(viewed = stored.viewed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBatchFetcher(batchSize: Int): BatchFetcher<NewsListConfig, List<ShortArticle>> {
|
||||
return NewsBatchFetcher(
|
||||
newsApi = newsApi,
|
||||
|
|
@ -167,5 +274,6 @@ internal class DefaultNewsRepository @Inject constructor(
|
|||
private companion object {
|
||||
private const val INITIAL_BATCH_KEY = 0
|
||||
private const val FIRST_PAGE = 1
|
||||
private const val TRENDING_NEWS_KEY = "trending_news"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,11 @@ 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.NewsRelatedTokenDto
|
||||
import com.tangem.domain.news.models.*
|
||||
import com.tangem.domain.models.news.ArticleCategory
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import com.tangem.domain.models.news.OriginalArticle
|
||||
import com.tangem.domain.models.news.RelatedToken
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
|
||||
internal fun NewsDetailsResponse.toDomainDetailedArticle(): DetailedArticle {
|
||||
return DetailedArticle(
|
||||
|
|
@ -34,6 +38,7 @@ internal fun NewsArticleDto.toDomainShortArticle(): ShortArticle {
|
|||
isTrending = isTrending,
|
||||
title = title,
|
||||
newsUrl = newsUrl,
|
||||
viewed = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.news.models
|
||||
package com.tangem.domain.models.news
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.news.models
|
||||
package com.tangem.domain.models.news
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.news.models
|
||||
package com.tangem.domain.models.news
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.news.models
|
||||
package com.tangem.domain.models.news
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.news.models
|
||||
package com.tangem.domain.models.news
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -15,6 +15,7 @@ import kotlinx.serialization.Serializable
|
|||
* @param relatedTokens - tokens, which were mentioned in article
|
||||
* @param title - article title
|
||||
* @param newsUrl - link for article to share
|
||||
* @param viewed - is article is viewed (store only runtime)
|
||||
*/
|
||||
@Serializable
|
||||
data class ShortArticle(
|
||||
|
|
@ -27,4 +28,5 @@ data class ShortArticle(
|
|||
val isTrending: Boolean,
|
||||
val title: String,
|
||||
val newsUrl: String,
|
||||
val viewed: Boolean,
|
||||
)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.domain.news.model
|
||||
|
||||
import com.tangem.domain.news.models.ShortArticle
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.pagination.BatchFlow
|
||||
import com.tangem.pagination.BatchingContext
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ package com.tangem.domain.news.repository
|
|||
|
||||
import com.tangem.domain.news.model.NewsListBatchFlow
|
||||
import com.tangem.domain.news.model.NewsListBatchingContext
|
||||
import com.tangem.domain.news.models.ArticleCategory
|
||||
import com.tangem.domain.news.models.DetailedArticle
|
||||
import com.tangem.domain.news.models.ShortArticle
|
||||
import com.tangem.domain.models.news.ArticleCategory
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -26,6 +27,16 @@ interface NewsRepository {
|
|||
*/
|
||||
suspend fun getDetailedArticle(newsId: Int, language: String?): DetailedArticle
|
||||
|
||||
/**
|
||||
* Observes cached detailed articles.
|
||||
*/
|
||||
fun observeDetailedArticles(): Flow<Map<Int, DetailedArticle>>
|
||||
|
||||
/**
|
||||
* Fetches and caches detailed articles for provided ids in parallel.
|
||||
*/
|
||||
suspend fun fetchDetailedArticles(newsIds: Collection<Int>, language: String?)
|
||||
|
||||
/**
|
||||
* Returns list of trending news by limit and with correct locale.
|
||||
*
|
||||
|
|
@ -34,6 +45,21 @@ interface NewsRepository {
|
|||
*/
|
||||
suspend fun getTrendingNews(limit: Int, language: String?): List<ShortArticle>
|
||||
|
||||
/**
|
||||
* Observes trending news with runtime viewed flag support.
|
||||
*/
|
||||
fun observeTrendingNews(): Flow<List<ShortArticle>>
|
||||
|
||||
/**
|
||||
* Refreshes trending news list and updates cache without overriding viewed status.
|
||||
*/
|
||||
suspend fun refreshTrendingNews(limit: Int, language: String?)
|
||||
|
||||
/**
|
||||
* Updates viewed flag for provided trending articles.
|
||||
*/
|
||||
suspend fun updateTrendingNewsViewed(articleIds: Collection<Int>, viewed: Boolean)
|
||||
|
||||
/**
|
||||
* Returns available categories.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import com.tangem.domain.models.news.ArticleCategory
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
|
||||
/**
|
||||
* Returns the list of available news categories.
|
||||
*
|
||||
* Use this from presentation layer to refresh filters or render category chips.
|
||||
*/
|
||||
class GetNewsCategoriesUseCase(
|
||||
private val repository: NewsRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Fetches categories from the repository.
|
||||
*/
|
||||
suspend operator fun invoke(): List<ArticleCategory> {
|
||||
return repository.getCategories()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import com.tangem.domain.news.model.NewsListBatchFlow
|
||||
import com.tangem.domain.news.model.NewsListBatchingContext
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
|
||||
/**
|
||||
* Provides a pagination-aware flow of short articles.
|
||||
*
|
||||
* Mirrors behaviour of other BatchFlow use cases (e.g., manage tokens) and can be used with pagers.
|
||||
*/
|
||||
class GetNewsListBatchFlowUseCase(
|
||||
private val repository: NewsRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Creates a [NewsListBatchFlow] for the given batching context and page size.
|
||||
*/
|
||||
operator fun invoke(context: NewsListBatchingContext, batchSize: Int = DEFAULT_BATCH_SIZE): NewsListBatchFlow {
|
||||
return repository.getNewsListBatchFlow(context, batchSize)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val DEFAULT_BATCH_SIZE = 20
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Exposes trending news as a cached flow and provides helpers to refresh or mark items as viewed.
|
||||
*
|
||||
* Keep a single instance per process (provided via DI) and collect the flow to render trending rows.
|
||||
*/
|
||||
class ManageTrendingNewsUseCase(private val repository: NewsRepository) {
|
||||
|
||||
/**
|
||||
* Observes the current cached list of trending articles (max 10 items).
|
||||
*/
|
||||
operator fun invoke(): Flow<List<ShortArticle>> {
|
||||
return repository.observeTrendingNews()
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces refresh from backend while preserving local `viewed` flags.
|
||||
*/
|
||||
suspend fun refresh(limit: Int, language: String?) {
|
||||
repository.refreshTrendingNews(limit, language)
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a single article as viewed/unviewed.
|
||||
*/
|
||||
suspend fun markAsViewed(articleId: Int, viewed: Boolean = true) {
|
||||
repository.updateTrendingNewsViewed(listOf(articleId), viewed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks multiple articles at once (useful for bulk updates).
|
||||
*/
|
||||
suspend fun markAsViewed(articleIds: Collection<Int>, viewed: Boolean = true) {
|
||||
repository.updateTrendingNewsViewed(articleIds, viewed)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Provides a hot stream with cached detailed articles and supports proactive prefetching.
|
||||
*
|
||||
* Subscribe once per screen to receive updates for any article that was fetched through [prefetch].
|
||||
*/
|
||||
class ObserveNewsDetailsUseCase(
|
||||
private val repository: NewsRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Observes all cached detailed articles mapped by id.
|
||||
*/
|
||||
operator fun invoke(): Flow<Map<Int, DetailedArticle>> {
|
||||
return repository.observeDetailedArticles()
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetches the given article ids (can be called with current + next ids for pager preloading).
|
||||
*/
|
||||
suspend fun prefetch(newsIds: Collection<Int>, language: String?) {
|
||||
repository.fetchDetailedArticles(newsIds, language)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue