Updated on 2026-08-14
This commit is contained in:
parent
9105ffbd75
commit
dcb8b474cf
20 changed files with 550 additions and 8 deletions
|
|
@ -54,5 +54,9 @@
|
|||
{
|
||||
"name": "ACCOUNTS_FEATURE_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "FEED_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ sealed class ApiConfig {
|
|||
BlockAid,
|
||||
YieldSupply,
|
||||
MoonPay,
|
||||
News,
|
||||
}
|
||||
|
||||
private fun initializeId(): ID {
|
||||
|
|
@ -41,6 +42,7 @@ sealed class ApiConfig {
|
|||
is BlockAid -> ID.BlockAid
|
||||
is YieldSupply -> ID.YieldSupply
|
||||
is MoonPay -> ID.MoonPay
|
||||
is News -> ID.News
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.utils.Provider
|
||||
|
||||
/**
|
||||
* News [ApiConfig]
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class News(
|
||||
private val authProvider: AuthProvider,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
|
||||
|
||||
override val environmentConfigs: List<ApiEnvironmentConfig> = listOf(
|
||||
createProdEnvironment(),
|
||||
createDevEnvironment(),
|
||||
)
|
||||
|
||||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE,
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
INTERNAL_BUILD_TYPE,
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = PROD_BASE_URL,
|
||||
headers = createHeaders(ApiEnvironment.PROD),
|
||||
)
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = DEV_BASE_URL,
|
||||
headers = createHeaders(ApiEnvironment.DEV),
|
||||
)
|
||||
|
||||
private fun createHeaders(environment: ApiEnvironment) = buildMap {
|
||||
putAll(
|
||||
RequestHeader.TangemApiKeyHeader(
|
||||
authProvider = authProvider,
|
||||
apiEnvironment = Provider { environment },
|
||||
).values,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
private const val PROD_BASE_URL = "https://api.tangem.org/"
|
||||
private const val DEV_BASE_URL = "[REDACTED_ENV_URL]"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.datasource.api.news
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.news.models.response.NewsCategoriesResponse
|
||||
import com.tangem.datasource.api.news.models.response.NewsDetailsResponse
|
||||
import com.tangem.datasource.api.news.models.response.NewsListResponse
|
||||
import com.tangem.datasource.api.news.models.response.NewsTrendingResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface NewsApi {
|
||||
|
||||
@GET(NEWS_PATH)
|
||||
suspend fun getNews(
|
||||
@Query("page") page: Int? = null,
|
||||
@Query("limit") limit: Int? = null,
|
||||
@Query("lang") language: String? = null,
|
||||
@Query("asOf") snapshot: String? = null,
|
||||
@Query("tokenIds") tokenIds: List<String>? = null,
|
||||
@Query("categoryIds") categoryIds: List<Int>? = null,
|
||||
): ApiResponse<NewsListResponse>
|
||||
|
||||
@GET("$NEWS_PATH/{newsId}")
|
||||
suspend fun getNewsDetails(
|
||||
@Path("newsId") newsId: Int,
|
||||
@Query("lang") language: String? = null,
|
||||
): ApiResponse<NewsDetailsResponse>
|
||||
|
||||
@GET("$NEWS_PATH/trending")
|
||||
suspend fun getTrendingNews(
|
||||
@Query("limit") limit: Int? = null,
|
||||
@Query("lang") language: String? = null,
|
||||
): ApiResponse<NewsTrendingResponse>
|
||||
|
||||
@GET("$NEWS_PATH/categories")
|
||||
suspend fun getCategories(): ApiResponse<NewsCategoriesResponse>
|
||||
|
||||
private companion object {
|
||||
|
||||
private const val NEWS_PATH = "api/v1/news"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsArticleDto(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "createdAt") val createdAt: String,
|
||||
@Json(name = "score") val score: Double,
|
||||
@Json(name = "language") val language: String,
|
||||
@Json(name = "isTrending") val isTrending: Boolean,
|
||||
@Json(name = "categories") val categories: List<NewsCategoryDto>,
|
||||
@Json(name = "relatedTokens") val relatedTokens: List<NewsRelatedTokenDto>,
|
||||
@Json(name = "title") val title: String,
|
||||
@Json(name = "newsUrl") val newsUrl: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsCategoryDto(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "name") val name: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsRelatedTokenDto(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "symbol") val symbol: String,
|
||||
@Json(name = "name") val name: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsOriginalArticleDto(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "title") val title: String,
|
||||
@Json(name = "sourceName") val sourceName: String,
|
||||
@Json(name = "language") val language: String,
|
||||
@Json(name = "publishedAt") val publishedAt: String,
|
||||
@Json(name = "url") val url: String,
|
||||
@Json(name = "imageUrl") val imageUrl: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsCategoriesResponse(
|
||||
@Json(name = "items") val items: List<NewsCategoryDto>,
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsDetailsResponse(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "createdAt") val createdAt: String,
|
||||
@Json(name = "score") val score: Double,
|
||||
@Json(name = "language") val language: String,
|
||||
@Json(name = "isTrending") val isTrending: Boolean,
|
||||
@Json(name = "categories") val categories: List<NewsCategoryDto>,
|
||||
@Json(name = "relatedTokens") val relatedTokens: List<NewsRelatedTokenDto>,
|
||||
@Json(name = "title") val title: String,
|
||||
@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>,
|
||||
)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsListResponse(
|
||||
@Json(name = "meta") val meta: NewsListMetaDto,
|
||||
@Json(name = "items") val items: List<NewsArticleDto>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsListMetaDto(
|
||||
@Json(name = "page") val page: Int,
|
||||
@Json(name = "limit") val limit: Int,
|
||||
@Json(name = "total") val total: Long,
|
||||
@Json(name = "hasNext") val hasNext: Boolean,
|
||||
@Json(name = "asOf") val asOf: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsTrendingResponse(
|
||||
@Json(name = "meta") val meta: NewsTrendingMetaDto,
|
||||
@Json(name = "items") val items: List<NewsArticleDto>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsTrendingMetaDto(
|
||||
@Json(name = "limit") val limit: Int,
|
||||
)
|
||||
|
|
@ -58,6 +58,10 @@ internal object ApiConfigsModule {
|
|||
appInfoProvider = appInfoProvider,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideNewsConfig(authProvider: AuthProvider): ApiConfig = News(authProvider = authProvider)
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideYieldSupplyConfig(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
|
|||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.moonpay.MoonPayApi
|
||||
import com.tangem.datasource.api.news.NewsApi
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
|
|
@ -151,4 +152,13 @@ internal object NetworkModule {
|
|||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNewsApi(retrofitApiBuilder: RetrofitApiBuilder): NewsApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.News,
|
||||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +73,7 @@ class ApiConfigTest {
|
|||
ApiConfig.ID.BlockAid -> BlockAid(configStorage = mockk())
|
||||
ApiConfig.ID.MoonPay -> MoonPay()
|
||||
ApiConfig.ID.P2PEthPool -> P2PEthPool(p2pAuthProvider = mockk())
|
||||
ApiConfig.ID.News -> News(authProvider = appAuthProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ internal class ProdApiConfigsManagerTest {
|
|||
ApiConfig.ID.BlockAid -> BlockAid(configStorage = environmentConfigStorage)
|
||||
ApiConfig.ID.MoonPay -> MoonPay()
|
||||
ApiConfig.ID.P2PEthPool -> P2PEthPool(p2pAuthProvider = p2pEthPoolAuthProvider)
|
||||
ApiConfig.ID.News -> News(authProvider = appAuthProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -128,6 +129,7 @@ internal class ProdApiConfigsManagerTest {
|
|||
ApiConfig.ID.BlockAid -> createBlockAidSdkModel()
|
||||
ApiConfig.ID.MoonPay -> createMoonPayModel()
|
||||
ApiConfig.ID.P2PEthPool -> createP2PModel()
|
||||
ApiConfig.ID.News -> createNewsModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -295,6 +297,29 @@ internal class ProdApiConfigsManagerTest {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createNewsModel(): TestModel {
|
||||
val (environment, baseUrl) = when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE,
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV to "[REDACTED_ENV_URL]"
|
||||
INTERNAL_BUILD_TYPE,
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD to "https://tangem.com/"
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.News,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = environment,
|
||||
baseUrl = baseUrl,
|
||||
headers = mapOf(
|
||||
"api-key" to ProviderSuspend { TANGEM_API_KEY },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.checkHeaderValueOrEmpty(): String {
|
||||
for (i in this.indices) {
|
||||
val c = this[i]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.data.news.di
|
||||
|
||||
import com.tangem.data.news.repository.DefaultNewsRepository
|
||||
import com.tangem.datasource.api.news.NewsApi
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -14,7 +16,10 @@ internal object NewsDataModule {
|
|||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesQuotesRepository(): NewsRepository {
|
||||
return DefaultNewsRepository()
|
||||
fun provideNewsRepository(newsApi: NewsApi, dispatchers: CoroutineDispatcherProvider): NewsRepository {
|
||||
return DefaultNewsRepository(
|
||||
newsApi = newsApi,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,170 @@
|
|||
package com.tangem.data.news.repository
|
||||
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.news.NewsApi
|
||||
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.news.repository.NewsRepository
|
||||
import com.tangem.pagination.BatchFetchResult
|
||||
import com.tangem.pagination.BatchListSource
|
||||
import com.tangem.pagination.exception.EndOfPaginationException
|
||||
import com.tangem.pagination.fetcher.BatchFetcher
|
||||
import com.tangem.pagination.toBatchFlow
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Default implementation of [NewsRepository]
|
||||
*
|
||||
* Implementation of [NewsRepository].
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultNewsRepository : NewsRepository
|
||||
internal class DefaultNewsRepository @Inject constructor(
|
||||
private val newsApi: NewsApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : NewsRepository {
|
||||
|
||||
override fun getNewsListBatchFlow(context: NewsListBatchingContext, batchSize: Int): NewsListBatchFlow {
|
||||
return BatchListSource(
|
||||
fetchDispatcher = dispatchers.io,
|
||||
context = context,
|
||||
generateNewKey = { keys -> keys.lastOrNull()?.inc() ?: INITIAL_BATCH_KEY },
|
||||
batchFetcher = createBatchFetcher(batchSize),
|
||||
).toBatchFlow()
|
||||
}
|
||||
|
||||
override suspend fun getDetailedArticle(newsId: Int, language: String?): DetailedArticle {
|
||||
val response = newsApi.getNewsDetails(newsId = newsId, language = language).getOrThrow()
|
||||
return response.toDomainDetailedArticle()
|
||||
}
|
||||
|
||||
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() }
|
||||
}
|
||||
|
||||
override suspend fun getCategories(): List<ArticleCategory> {
|
||||
return newsApi.getCategories().getOrThrow().items.map { dto ->
|
||||
ArticleCategory(
|
||||
id = dto.id,
|
||||
name = dto.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBatchFetcher(batchSize: Int): BatchFetcher<NewsListConfig, List<ShortArticle>> {
|
||||
return NewsBatchFetcher(
|
||||
newsApi = newsApi,
|
||||
batchSize = batchSize,
|
||||
)
|
||||
}
|
||||
|
||||
private class NewsBatchFetcher(
|
||||
private val newsApi: NewsApi,
|
||||
private val batchSize: Int,
|
||||
) : BatchFetcher<NewsListConfig, List<ShortArticle>> {
|
||||
|
||||
private var state: NewsPaginationState? = null
|
||||
|
||||
override suspend fun fetchFirst(requestParams: NewsListConfig): BatchFetchResult<List<ShortArticle>> {
|
||||
return runCatching {
|
||||
loadPage(
|
||||
page = FIRST_PAGE,
|
||||
params = requestParams,
|
||||
limit = batchSize,
|
||||
snapshotOverride = requestParams.snapshot,
|
||||
)
|
||||
}.fold(
|
||||
onSuccess = { result ->
|
||||
state = result.state
|
||||
result.batchResult
|
||||
},
|
||||
onFailure = { throwable -> BatchFetchResult.Error(throwable) },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun fetchNext(
|
||||
overrideRequestParams: NewsListConfig?,
|
||||
lastResult: BatchFetchResult<List<ShortArticle>>,
|
||||
): BatchFetchResult<List<ShortArticle>> {
|
||||
val currentState = state ?: return BatchFetchResult.Error(
|
||||
IllegalStateException("fetchFirst must be called"),
|
||||
)
|
||||
|
||||
if (lastResult is BatchFetchResult.Success && lastResult.last && overrideRequestParams == null) {
|
||||
return BatchFetchResult.Error(EndOfPaginationException())
|
||||
}
|
||||
|
||||
val params = overrideRequestParams ?: currentState.params
|
||||
val shouldReset = overrideRequestParams != null && overrideRequestParams != currentState.params
|
||||
val pageToLoad = if (shouldReset) FIRST_PAGE else currentState.nextPage
|
||||
val snapshot = if (shouldReset) overrideRequestParams.snapshot else currentState.snapshot
|
||||
|
||||
return runCatching {
|
||||
loadPage(
|
||||
page = pageToLoad,
|
||||
params = params,
|
||||
limit = batchSize,
|
||||
snapshotOverride = snapshot,
|
||||
)
|
||||
}.fold(
|
||||
onSuccess = { result ->
|
||||
state = result.state
|
||||
result.batchResult
|
||||
},
|
||||
onFailure = { throwable -> BatchFetchResult.Error(throwable) },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadPage(
|
||||
page: Int,
|
||||
params: NewsListConfig,
|
||||
limit: Int,
|
||||
snapshotOverride: String?,
|
||||
): PageLoadResult {
|
||||
val response = newsApi.getNews(
|
||||
page = page,
|
||||
limit = limit,
|
||||
language = params.language,
|
||||
snapshot = snapshotOverride,
|
||||
tokenIds = params.tokenIds.takeIf { it.isNotEmpty() },
|
||||
categoryIds = params.categoryIds.takeIf { it.isNotEmpty() },
|
||||
).getOrThrow()
|
||||
|
||||
val items = response.items.map { it.toDomainShortArticle() }
|
||||
|
||||
val batchResult = BatchFetchResult.Success(
|
||||
data = items,
|
||||
empty = items.isEmpty(),
|
||||
last = response.meta.hasNext.not(),
|
||||
)
|
||||
|
||||
return PageLoadResult(
|
||||
batchResult = batchResult,
|
||||
state = NewsPaginationState(
|
||||
nextPage = response.meta.page + 1,
|
||||
snapshot = response.meta.asOf,
|
||||
params = params.copy(snapshot = response.meta.asOf),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class PageLoadResult(
|
||||
val batchResult: BatchFetchResult.Success<List<ShortArticle>>,
|
||||
val state: NewsPaginationState,
|
||||
)
|
||||
|
||||
private data class NewsPaginationState(
|
||||
val nextPage: Int,
|
||||
val snapshot: String?,
|
||||
val params: NewsListConfig,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
private const val INITIAL_BATCH_KEY = 0
|
||||
private const val FIRST_PAGE = 1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
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.NewsRelatedTokenDto
|
||||
import com.tangem.domain.news.models.*
|
||||
|
||||
internal fun NewsDetailsResponse.toDomainDetailedArticle(): DetailedArticle {
|
||||
return DetailedArticle(
|
||||
id = id,
|
||||
createdAt = createdAt,
|
||||
score = score.toFloat(),
|
||||
locale = language,
|
||||
isTrending = isTrending,
|
||||
categories = categories.map { ArticleCategory(id = it.id, name = it.name) },
|
||||
relatedTokens = relatedTokens.map { it.toDomainRelatedToken() },
|
||||
title = title,
|
||||
newsUrl = newsUrl,
|
||||
shortContent = shortContent,
|
||||
content = content,
|
||||
originalArticles = originalArticles.map { it.toDomainOriginalArticle() },
|
||||
)
|
||||
}
|
||||
|
||||
internal fun NewsArticleDto.toDomainShortArticle(): ShortArticle {
|
||||
return ShortArticle(
|
||||
id = id,
|
||||
createdAt = createdAt,
|
||||
score = score.toFloat(),
|
||||
locale = language,
|
||||
categories = categories.map { ArticleCategory(id = it.id, name = it.name) },
|
||||
relatedTokens = relatedTokens.map { it.toDomainRelatedToken() },
|
||||
isTrending = isTrending,
|
||||
title = title,
|
||||
newsUrl = newsUrl,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun NewsRelatedTokenDto.toDomainRelatedToken(): RelatedToken {
|
||||
return RelatedToken(
|
||||
id = id,
|
||||
symbol = symbol,
|
||||
name = name,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun NewsOriginalArticleDto.toDomainOriginalArticle(): OriginalArticle {
|
||||
return OriginalArticle(
|
||||
id = id,
|
||||
title = title,
|
||||
sourceName = sourceName,
|
||||
locale = language,
|
||||
publishedAt = publishedAt,
|
||||
url = url,
|
||||
imageUrl = imageUrl,
|
||||
)
|
||||
}
|
||||
|
|
@ -7,5 +7,6 @@ plugins {
|
|||
dependencies {
|
||||
api(projects.domain.core)
|
||||
api(projects.domain.models)
|
||||
api(projects.core.pagination)
|
||||
implementation(deps.kotlin.serialization)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.domain.news.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents config for request of news list.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*
|
||||
* @param language device locale (ex: en, ru).
|
||||
* @param snapshot id snapshot (`meta.asOf`) to stabilize responses.
|
||||
* @param tokenIds filter by tokens.
|
||||
* @param categoryIds filter by category.
|
||||
*/
|
||||
@Serializable
|
||||
data class NewsListConfig(
|
||||
val language: String,
|
||||
val snapshot: String,
|
||||
val tokenIds: List<String> = emptyList(),
|
||||
val categoryIds: List<Int> = emptyList(),
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.domain.news.model
|
||||
|
||||
import com.tangem.domain.news.models.ShortArticle
|
||||
import com.tangem.pagination.BatchFlow
|
||||
import com.tangem.pagination.BatchingContext
|
||||
|
||||
typealias NewsListBatchingContext = BatchingContext<Int, NewsListConfig, Nothing>
|
||||
|
||||
typealias NewsListBatchFlow = BatchFlow<Int, List<ShortArticle>, Nothing>
|
||||
|
|
@ -1,8 +1,41 @@
|
|||
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
|
||||
|
||||
/**
|
||||
* News repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface NewsRepository
|
||||
interface NewsRepository {
|
||||
|
||||
/**
|
||||
* Returns flow of batch with pagination handling.
|
||||
*
|
||||
* @param context pagination context
|
||||
* @param batchSize page capacity
|
||||
*/
|
||||
fun getNewsListBatchFlow(context: NewsListBatchingContext, batchSize: Int): NewsListBatchFlow
|
||||
|
||||
/**
|
||||
* Returns detailed article by id with locale configuration.
|
||||
* @param newsId news identification
|
||||
* @param language current locale
|
||||
*/
|
||||
suspend fun getDetailedArticle(newsId: Int, language: String?): DetailedArticle
|
||||
|
||||
/**
|
||||
* Returns list of trending news by limit and with correct locale.
|
||||
*
|
||||
* @param limit
|
||||
* @param language current device locale
|
||||
*/
|
||||
suspend fun getTrendingNews(limit: Int, language: String?): List<ShortArticle>
|
||||
|
||||
/**
|
||||
* Returns available categories.
|
||||
*/
|
||||
suspend fun getCategories(): List<ArticleCategory>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue