diff --git a/domain/news/src/main/java/com/tangem/domain/news/usecase/GetNewsCategoriesUseCase.kt b/domain/news/src/main/java/com/tangem/domain/news/usecase/GetNewsCategoriesUseCase.kt index 35e1f8f0bb..aa8b308d94 100644 --- a/domain/news/src/main/java/com/tangem/domain/news/usecase/GetNewsCategoriesUseCase.kt +++ b/domain/news/src/main/java/com/tangem/domain/news/usecase/GetNewsCategoriesUseCase.kt @@ -1,5 +1,6 @@ package com.tangem.domain.news.usecase +import arrow.core.Either import com.tangem.domain.models.news.ArticleCategory import com.tangem.domain.news.repository.NewsRepository @@ -15,7 +16,7 @@ class GetNewsCategoriesUseCase( /** * Fetches categories from the repository. */ - suspend operator fun invoke(): List { - return repository.getCategories() + suspend operator fun invoke(): Either> = Either.catch { + repository.getCategories() } } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/DefaultFeedEntryComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/DefaultFeedEntryComponent.kt index 75523f67d4..28f4ccb71c 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/DefaultFeedEntryComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/DefaultFeedEntryComponent.kt @@ -111,6 +111,7 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor( router = innerRouter, ), feedEntryClickIntents = clickIntents, + onBackClicked = { onChildBack() }, ) }, ) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt index 0a38fba062..cfc874b8b1 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt @@ -50,6 +50,7 @@ internal class FeedEntryChildFactory @Inject constructor( child: Child, appComponentContext: AppComponentContext, feedEntryClickIntents: FeedEntryClickIntents, + onBackClicked: () -> Unit, ): ComposableModularBottomSheetContentComponent { return when (child) { is Child.TokenDetails -> { @@ -76,6 +77,10 @@ internal class FeedEntryChildFactory @Inject constructor( Child.NewsList -> { DefaultNewsListComponent( appComponentContext = appComponentContext, + params = DefaultNewsListComponent.Params( + onArticleClicked = { feedEntryClickIntents.onArticleClick(articleId = it) }, + onBackClick = onBackClicked, + ), ) } Child.Feed -> { diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt index 992bd9120c..2cec3b93c7 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt @@ -2,20 +2,56 @@ package com.tangem.features.feed.components.news.list import androidx.compose.runtime.Composable import androidx.compose.runtime.State +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.R +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.LocalMainBottomSheetColor +import com.tangem.features.feed.model.news.list.NewsListModel +import com.tangem.features.feed.ui.news.list.NewsListContent +import kotlinx.serialization.Serializable internal class DefaultNewsListComponent( appComponentContext: AppComponentContext, + private val params: Params, ) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext { + private val newsListModel = getOrCreateModel(params = params) + @Composable override fun Title(bottomSheetState: State) { + val background = LocalMainBottomSheetColor.current.value + val state by newsListModel.state.collectAsStateWithLifecycle() + TangemTopAppBar( + containerColor = background, + title = stringResourceSafe(R.string.common_news), + startButton = TopAppBarButtonUM.Icon( + iconRes = R.drawable.ic_back_24, + onClicked = state.onBackClick, + isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED, + ), + ) } @Composable override fun Content(bottomSheetState: State, modifier: Modifier) { + val state by newsListModel.state.collectAsStateWithLifecycle() + NewsListContent( + state = state, + modifier = modifier, + ) } + + @Serializable + data class Params( + val onArticleClicked: (Int) -> Unit, + val onBackClick: () -> Unit, + ) } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/di/ModelModule.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/di/ModelModule.kt index 31ca5d9dcf..209d30fe44 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/di/ModelModule.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/di/ModelModule.kt @@ -6,6 +6,7 @@ import com.tangem.features.feed.model.feed.FeedComponentModel import com.tangem.features.feed.model.market.details.MarketsTokenDetailsModel import com.tangem.features.feed.model.market.list.MarketsListModel import com.tangem.features.feed.model.news.details.NewsDetailsModel +import com.tangem.features.feed.model.news.list.NewsListModel import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -35,4 +36,9 @@ internal interface ModelModule { @IntoMap @ClassKey(NewsDetailsModel::class) fun provideNewsDetailsModel(model: NewsDetailsModel): Model + + @Binds + @IntoMap + @ClassKey(NewsListModel::class) + fun provideNewsListModel(model: NewsListModel): Model } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt new file mode 100644 index 0000000000..ccaf7b8c33 --- /dev/null +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt @@ -0,0 +1,82 @@ +package com.tangem.features.feed.model.news.list + +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.ui.components.chip.entity.ChipUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase +import com.tangem.features.feed.components.news.list.DefaultNewsListComponent +import com.tangem.features.feed.ui.news.list.state.NewsListUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ModelScoped +internal class NewsListModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val getNewsCategoriesUseCase: GetNewsCategoriesUseCase, + paramsContainer: ParamsContainer, +) : Model() { + + private val params = paramsContainer.require() + + private val _state = MutableStateFlow( + NewsListUM( + selectedCategoryId = 0, + filters = persistentListOf(), + articles = persistentListOf(), + onArticleClick = params.onArticleClicked, + onBackClick = params.onBackClick, + ), + ) + val state = _state.asStateFlow() + + init { + modelScope.launch(dispatchers.default) { + val filterChips = getNewsCategoriesUseCase + .invoke() + .fold( + ifLeft = { + persistentListOf() + }, + ifRight = { categories -> + categories.map { articleCategory -> + ChipUM( + id = articleCategory.id, + text = TextReference.Str(articleCategory.name), + isSelected = false, + onClick = { + onCategoryClick(articleCategory.id) + }, + ) + }.toImmutableList() + }, + ) + _state.update { currentState -> + currentState.copy(filters = filterChips) + } + } + } + + private fun onCategoryClick(categoryId: Int) { + _state.update { currentState -> + currentState.copy( + selectedCategoryId = categoryId, + filters = updateFilterChips(categoryId), + ) + } + } + + private fun updateFilterChips(categoryId: Int): ImmutableList { + return state.value.filters.map { chip -> + chip.copy(isSelected = chip.id == categoryId) + }.toImmutableList() + } +} \ No newline at end of file diff --git a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/ui/NewsListContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt similarity index 87% rename from features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/ui/NewsListContent.kt rename to features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt index ea8a333901..d9b5b00948 100644 --- a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/ui/NewsListContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt @@ -1,4 +1,4 @@ -package com.tangem.features.news.list.impl.ui +package com.tangem.features.feed.ui.news.list import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -11,32 +11,27 @@ import androidx.compose.ui.tooling.preview.Preview 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.R import com.tangem.core.ui.components.SpacerH -import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.block.TangemBlockCardColors import com.tangem.core.ui.components.chip.Chip import com.tangem.core.ui.components.chip.entity.ChipUM import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.feed.ui.news.list.state.NewsListUM import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableSet @Composable -internal fun NewsListContent(state: NewsListUM, onBackClick: () -> Unit, modifier: Modifier = Modifier) { +internal fun NewsListContent(state: NewsListUM, modifier: Modifier = Modifier) { + val background = LocalMainBottomSheetColor.current.value Column( modifier = modifier .fillMaxSize() - .background(color = TangemTheme.colors.background.tertiary), + .background(background), ) { - AppBarWithBackButton( - text = stringResourceSafe(R.string.common_news), - onBackClick = onBackClick, - ) - LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -48,9 +43,8 @@ internal fun NewsListContent(state: NewsListUM, onBackClick: () -> Unit, modifie Chip(state = filter) } } - LazyColumn( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxWidth(), contentPadding = PaddingValues(16.dp), ) { items( @@ -145,8 +139,8 @@ private fun NewsListContentPreview() { filters = filters, articles = articles, onArticleClick = {}, + onBackClick = {}, ), - onBackClick = {}, ) } } \ No newline at end of file diff --git a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/ui/NewsListUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/state/NewsListUM.kt similarity index 82% rename from features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/ui/NewsListUM.kt rename to features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/state/NewsListUM.kt index d3e853c285..c1f6174111 100644 --- a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/ui/NewsListUM.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/state/NewsListUM.kt @@ -1,4 +1,4 @@ -package com.tangem.features.news.list.impl.ui +package com.tangem.features.feed.ui.news.list.state import androidx.compose.runtime.Immutable import com.tangem.common.ui.news.ArticleConfigUM @@ -11,4 +11,5 @@ data class NewsListUM( val filters: ImmutableList, val articles: ImmutableList, val onArticleClick: (Int) -> Unit, + val onBackClick: () -> Unit, ) \ No newline at end of file diff --git a/features/news/news-list/api/.gitignore b/features/news/news-list/api/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/features/news/news-list/api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/features/news/news-list/api/build.gradle.kts b/features/news/news-list/api/build.gradle.kts deleted file mode 100644 index 2de4c641e7..0000000000 --- a/features/news/news-list/api/build.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - alias(deps.plugins.android.library) - alias(deps.plugins.kotlin.android) - id("configuration") -} - -android { - namespace = "com.tangem.features.news.list.api" -} - -dependencies { - implementation(deps.compose.foundation) - - /* Project - Core */ - implementation(projects.core.decompose) - implementation(projects.core.ui) - - /* Compose */ - implementation(deps.compose.runtime) -} \ No newline at end of file diff --git a/features/news/news-list/api/src/main/kotlin/com/tangem/features/news/list/api/NewsListComponent.kt b/features/news/news-list/api/src/main/kotlin/com/tangem/features/news/list/api/NewsListComponent.kt deleted file mode 100644 index 225a75a329..0000000000 --- a/features/news/news-list/api/src/main/kotlin/com/tangem/features/news/list/api/NewsListComponent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.features.news.list.api - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState -import com.tangem.core.ui.decompose.ComposableContentComponent - -interface NewsListComponent : ComposableContentComponent { - - data class Params(val selectedFilter: String? = null) - - @Composable - fun BottomSheetContent( - bottomSheetState: State, - onHeaderSizeChange: (Dp) -> Unit, - modifier: Modifier, - ) - - interface Factory : ComponentFactory -} \ No newline at end of file diff --git a/features/news/news-list/impl/.gitignore b/features/news/news-list/impl/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/features/news/news-list/impl/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/features/news/news-list/impl/build.gradle.kts b/features/news/news-list/impl/build.gradle.kts deleted file mode 100644 index baf05ca033..0000000000 --- a/features/news/news-list/impl/build.gradle.kts +++ /dev/null @@ -1,46 +0,0 @@ -plugins { - alias(deps.plugins.android.library) - alias(deps.plugins.kotlin.android) - alias(deps.plugins.kotlin.kapt) - alias(deps.plugins.hilt.android) - id("configuration") -} - -android { - namespace = "com.tangem.features.news.list.impl" -} - -dependencies { - /* AndroidX */ - implementation(deps.lifecycle.compose) - implementation(deps.androidx.activity.compose) - - /** Compose */ - implementation(deps.compose.foundation) - implementation(deps.compose.ui) - implementation(deps.compose.ui.tooling) - implementation(deps.compose.material3) - - /** Core modules */ - implementation(projects.core.ui) - implementation(projects.core.utils) - implementation(projects.core.decompose) - implementation(projects.common.ui) - implementation(projects.common.routing) - - /** Feature modules */ - implementation(projects.features.news.newsList.api) - - /** Domain modules */ - implementation(projects.domain.models) - implementation(projects.domain.news) - - /** Other dependencies */ - implementation(deps.kotlin.immutable.collections) - implementation(deps.arrow.core) - implementation(deps.timber) - - /** DI */ - implementation(deps.hilt.android) - kapt(deps.hilt.kapt) -} \ No newline at end of file diff --git a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/DefaultNewsListComponent.kt b/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/DefaultNewsListComponent.kt deleted file mode 100644 index 1ed37bdc4e..0000000000 --- a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/DefaultNewsListComponent.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.features.news.list.impl - -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState -import com.tangem.features.news.list.api.NewsListComponent -import com.tangem.features.news.list.impl.ui.NewsListContent -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject - -internal class DefaultNewsListComponent @AssistedInject constructor( - @Assisted context: AppComponentContext, - @Assisted params: NewsListComponent.Params, -) : NewsListComponent, AppComponentContext by context { - - private val model: NewsListModel = getOrCreateModel(params) - - @Composable - override fun BottomSheetContent( - bottomSheetState: State, - onHeaderSizeChange: (Dp) -> Unit, - modifier: Modifier, - ) { - val uiState by model.uiState.collectAsStateWithLifecycle() - val bsState by bottomSheetState - - BackHandler(enabled = bsState == BottomSheetState.EXPANDED) { - navigateBack() - } - - NewsListContent( - state = uiState, - onBackClick = ::navigateBack, - modifier = modifier, - ) - } - - @Composable - override fun Content(modifier: Modifier) { - val uiState by model.uiState.collectAsStateWithLifecycle() - NewsListContent( - state = uiState, - onBackClick = model::onBackClick, - modifier = modifier, - ) - } - - private fun navigateBack() = router.pop() - - @AssistedFactory - interface Factory : NewsListComponent.Factory { - override fun create(context: AppComponentContext, params: NewsListComponent.Params): DefaultNewsListComponent - } -} \ No newline at end of file diff --git a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/NewsListModel.kt b/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/NewsListModel.kt deleted file mode 100644 index 18c010aaac..0000000000 --- a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/NewsListModel.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.tangem.features.news.list.impl - -import com.tangem.core.decompose.di.ModelScoped -import com.tangem.core.decompose.model.Model -import com.tangem.core.decompose.navigation.Router -import com.tangem.core.ui.components.chip.entity.ChipUM -import com.tangem.core.ui.extensions.TextReference -import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase -import com.tangem.features.news.list.impl.ui.NewsListUM -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import javax.inject.Inject - -@ModelScoped -internal class NewsListModel @Inject constructor( - override val dispatchers: CoroutineDispatcherProvider, - private val getNewsCategoriesUseCase: GetNewsCategoriesUseCase, - private val router: Router, -) : Model() { - - val uiState: StateFlow - field = MutableStateFlow( - NewsListUM( - selectedCategoryId = 0, - filters = persistentListOf(), - articles = persistentListOf(), - onArticleClick = ::onArticleClick, - ), - ) - - init { - modelScope.launch(dispatchers.default) { - val filterChips = getNewsCategoriesUseCase - .invoke() - .map { articleCategory -> - ChipUM( - id = articleCategory.id, - text = TextReference.Str(articleCategory.name), - isSelected = false, - onClick = { - onCategoryClick(articleCategory.id) - }, - ) - } - .toImmutableList() - uiState.update { currentState -> - currentState.copy(filters = filterChips) - } - } - } - - fun onBackClick() { - router.pop() - } - - private fun onArticleClick(articleId: Int) { - // TODO [REDACTED_TASK_KEY] - articleId - } - - private fun onCategoryClick(categoryId: Int) { - uiState.update { currentState -> - currentState.copy( - selectedCategoryId = categoryId, - filters = updateFilterChips(categoryId), - ) - } - } - - private fun updateFilterChips(categoryId: Int): ImmutableList { - return uiState.value.filters.map { chip -> - chip.copy(isSelected = chip.id == categoryId) - }.toImmutableList() - } -} \ No newline at end of file diff --git a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/di/NewsListModule.kt b/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/di/NewsListModule.kt deleted file mode 100644 index b3d323d053..0000000000 --- a/features/news/news-list/impl/src/main/kotlin/com/tangem/features/news/list/impl/di/NewsListModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.features.news.list.impl.di - -import com.tangem.features.news.list.api.NewsListComponent -import com.tangem.features.news.list.impl.DefaultNewsListComponent -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface NewsListModule { - - @Binds - @Singleton - fun bindNewsListComponentFactory(factory: DefaultNewsListComponent.Factory): NewsListComponent.Factory -} \ No newline at end of file