Updated on 2026-08-14
This commit is contained in:
parent
866701cdd6
commit
fef3d9fa5e
5 changed files with 90 additions and 20 deletions
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.feed.model.news.list
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -8,6 +9,7 @@ import com.tangem.domain.news.model.NewsListConfig
|
|||
import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase
|
||||
import com.tangem.domain.news.usecase.GetNewsListBatchFlowUseCase
|
||||
import com.tangem.features.feed.components.news.list.DefaultNewsListComponent
|
||||
import com.tangem.features.feed.model.news.list.analytics.NewsListAnalyticsEvent
|
||||
import com.tangem.features.feed.model.news.list.statemanager.NewsListStateManager
|
||||
import com.tangem.features.feed.model.news.list.loader.NewsCategoriesLoader
|
||||
import com.tangem.features.feed.model.news.list.statemanager.NewsListBatchFlowManager
|
||||
|
|
@ -31,6 +33,7 @@ internal class NewsListModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getNewsCategoriesUseCase: GetNewsCategoriesUseCase,
|
||||
private val getNewsListBatchFlowUseCase: GetNewsListBatchFlowUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -58,6 +61,10 @@ internal class NewsListModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private val newsListStateManager by lazy(LazyThreadSafetyMode.NONE) {
|
||||
NewsListStateManager(analyticsEventHandler)
|
||||
}
|
||||
|
||||
private val _state = MutableStateFlow(
|
||||
NewsListUM(
|
||||
selectedCategoryId = DEFAULT_ALL_NEWS_CATEGORIES_ID,
|
||||
|
|
@ -95,12 +102,12 @@ internal class NewsListModel @Inject constructor(
|
|||
modelScope.launch(dispatchers.default) {
|
||||
combine(
|
||||
batchFlowManager.uiItems,
|
||||
batchFlowManager.isInInitialLoadingErrorState,
|
||||
batchFlowManager.initialLoadingError,
|
||||
batchFlowManager.paginationStatus,
|
||||
) { articles, isError, paginationStatus ->
|
||||
NewsListStateManager.calculateState(
|
||||
) { articles, loadingError, paginationStatus ->
|
||||
newsListStateManager.calculateState(
|
||||
articles = articles,
|
||||
isError = isError,
|
||||
error = loadingError,
|
||||
paginationStatus = paginationStatus,
|
||||
onRetryClick = {
|
||||
loadCategories()
|
||||
|
|
@ -125,6 +132,9 @@ internal class NewsListModel @Inject constructor(
|
|||
} else {
|
||||
categoryId
|
||||
}
|
||||
if (newCategoryId != DEFAULT_ALL_NEWS_CATEGORIES_ID) {
|
||||
analyticsEventHandler.send(NewsListAnalyticsEvent.NewsCategoriesClick(newCategoryId))
|
||||
}
|
||||
selectedCategoryId.value = newCategoryId
|
||||
_state.update { currentState ->
|
||||
currentState.copy(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
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
|
||||
|
||||
internal sealed class NewsListAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent(category = "Markets", event = event, params = params) {
|
||||
|
||||
data class NewsListLoadError(
|
||||
private val code: Int?,
|
||||
private val message: String,
|
||||
) : NewsListAnalyticsEvent(
|
||||
event = "News List Load Error",
|
||||
params = mapOf(
|
||||
ERROR_CODE to (code ?: IS_NOT_HTTP_ERROR).toString(),
|
||||
ERROR_MESSAGE to message,
|
||||
),
|
||||
)
|
||||
|
||||
data class NewsCategoriesClick(
|
||||
private val categoryId: Int,
|
||||
) : NewsListAnalyticsEvent(
|
||||
event = "News Categories Selected",
|
||||
params = mapOf(
|
||||
"Selected Categories" to categoryId.toString(),
|
||||
),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val IS_NOT_HTTP_ERROR = "Is not http error"
|
||||
}
|
||||
}
|
||||
|
|
@ -69,13 +69,20 @@ internal open class NewsListBatchFlowManager(
|
|||
initialValue = persistentListOf(),
|
||||
)
|
||||
|
||||
val isInInitialLoadingErrorState = batchFlow.state
|
||||
.map { it.status is PaginationStatus.InitialLoadingError }
|
||||
val initialLoadingError: StateFlow<Throwable?> = batchFlow.state
|
||||
.map { state ->
|
||||
val status = state.status
|
||||
if (status is PaginationStatus.InitialLoadingError) {
|
||||
status.throwable
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.stateIn(
|
||||
scope = modelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = false,
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
val paginationStatus: StateFlow<PaginationStatus<List<ShortArticle>>> = batchFlow.state
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
package com.tangem.features.feed.model.news.list.statemanager
|
||||
|
||||
import com.tangem.common.ui.news.ArticleConfigUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.features.feed.model.news.list.analytics.NewsListAnalyticsEvent
|
||||
import com.tangem.features.feed.ui.news.list.state.NewsListState
|
||||
import com.tangem.pagination.PaginationStatus
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* A pure function for calculating the state of the news list.
|
||||
* Extracted from NewsListModel to simplify testing and improve readability.
|
||||
*/
|
||||
internal object NewsListStateManager {
|
||||
internal class NewsListStateManager(
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Calculates the state of the news list based on the current data.
|
||||
*
|
||||
* @param articles the current list of articles
|
||||
* @param isError flag indicating an initial loading error
|
||||
* @param error flag indicating an initial loading error
|
||||
* @param paginationStatus the current pagination status
|
||||
* @param onRetryClick handler for the retry loading action
|
||||
* @param onLoadMore handler for loading the next page
|
||||
|
|
@ -24,13 +25,16 @@ internal object NewsListStateManager {
|
|||
*/
|
||||
fun calculateState(
|
||||
articles: ImmutableList<ArticleConfigUM>,
|
||||
isError: Boolean,
|
||||
error: Throwable?,
|
||||
paginationStatus: PaginationStatus<*>,
|
||||
onRetryClick: () -> Unit,
|
||||
onLoadMore: () -> Unit,
|
||||
): Pair<NewsListState, ImmutableList<ArticleConfigUM>> {
|
||||
if (error != null) {
|
||||
sendErrorAnalytics(error)
|
||||
}
|
||||
return when {
|
||||
isError -> NewsListState.LoadingError(onRetryClick) to persistentListOf()
|
||||
error != null -> NewsListState.LoadingError(onRetryClick) to persistentListOf()
|
||||
paginationStatus is PaginationStatus.InitialLoading && articles.isEmpty() -> {
|
||||
NewsListState.Loading to persistentListOf()
|
||||
}
|
||||
|
|
@ -38,4 +42,17 @@ internal object NewsListStateManager {
|
|||
else -> NewsListState.Content(loadMore = onLoadMore) to articles
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendErrorAnalytics(throwable: Throwable) {
|
||||
val (code, message) = when (throwable) {
|
||||
is ApiResponseError.HttpException -> throwable.code.numericCode to throwable.message
|
||||
else -> null to ""
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
NewsListAnalyticsEvent.NewsListLoadError(
|
||||
code = code,
|
||||
message = message.orEmpty(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||
import androidx.compose.foundation.layout.*
|
||||
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.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -354,12 +355,12 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM,
|
|||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
state = listState,
|
||||
) {
|
||||
items(
|
||||
itemsIndexed(
|
||||
items = news.content,
|
||||
key = ArticleConfigUM::id,
|
||||
contentType = { "article" },
|
||||
) { article ->
|
||||
val articleModifier = if (news.content.indexOf(article) == FOURTH_ITEM_INDEX) {
|
||||
key = { _, article -> article.id },
|
||||
contentType = { _, _ -> "article" },
|
||||
) { index, article ->
|
||||
val articleModifier = if (index == FOURTH_ITEM_INDEX) {
|
||||
Modifier.onFirstVisible(
|
||||
minFractionVisible = 0.5f,
|
||||
callback = feedListCallbacks.onSliderScroll,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue