diff --git a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleInfo.kt b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleInfo.kt index f7cc656eae..a2834e2afb 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleInfo.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleInfo.kt @@ -1,7 +1,10 @@ package com.tangem.common.ui.news import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -9,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme @@ -49,6 +53,8 @@ internal fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = M Text( text = createdAt, style = TangemTheme.typography.subtitle2, + maxLines = 1, + overflow = TextOverflow.Ellipsis, color = TangemTheme.colors.text.secondary, ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsArticleDto.kt b/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsArticleDto.kt index 292b3d4559..ef9e77a8f0 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsArticleDto.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsArticleDto.kt @@ -27,15 +27,4 @@ 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, ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsDetailsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsDetailsResponse.kt index 230c3f42fb..c3f8338224 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsDetailsResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsDetailsResponse.kt @@ -17,4 +17,21 @@ data class NewsDetailsResponse( @Json(name = "shortContent") val shortContent: String, @Json(name = "content") val content: String, @Json(name = "originalArticles") val originalArticles: List, +) + +@JsonClass(generateAdapter = true) +data class NewsOriginalArticleDto( + @Json(name = "id") val id: Int, + @Json(name = "title") val title: String, + @Json(name = "source") val source: Source, + @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, +) + +@JsonClass(generateAdapter = true) +data class Source( + @Json(name = "id") val id: Int, + @Json(name = "name") val name: String, ) \ No newline at end of file diff --git a/data/news/src/main/java/com/tangem/data/news/repository/NewsMapper.kt b/data/news/src/main/java/com/tangem/data/news/repository/NewsMapper.kt index 6f34dd3ed4..7f63a42814 100644 --- a/data/news/src/main/java/com/tangem/data/news/repository/NewsMapper.kt +++ b/data/news/src/main/java/com/tangem/data/news/repository/NewsMapper.kt @@ -4,11 +4,7 @@ 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.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 +import com.tangem.domain.models.news.* internal fun NewsDetailsResponse.toDomainDetailedArticle(): DetailedArticle { return DetailedArticle( @@ -54,7 +50,10 @@ internal fun NewsOriginalArticleDto.toDomainOriginalArticle(): OriginalArticle { return OriginalArticle( id = id, title = title, - sourceName = sourceName, + source = Source( + id = source.id, + name = source.name, + ), locale = language, publishedAt = publishedAt, url = url, diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/news/OriginalArticle.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/news/OriginalArticle.kt index d087148b79..83c59e20c0 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/news/OriginalArticle.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/news/OriginalArticle.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.Serializable [REDACTED_AUTHOR] * @param id - unique identifier of the article * @param title - article title - * @param sourceName - name of original article source + * @param source - object of source name and identifier * @param locale - language of the article * @param publishedAt - date of article publishing * @param url - link to source of original article @@ -18,9 +18,15 @@ import kotlinx.serialization.Serializable data class OriginalArticle( val id: Int, val title: String, - val sourceName: String, + val source: Source, val locale: String, val publishedAt: String, val url: String, val imageUrl: String?, +) + +@Serializable +data class Source( + val id: Int, + val name: String, ) \ No newline at end of file diff --git a/domain/news/src/main/java/com/tangem/domain/news/usecase/ObserveNewsDetailsUseCase.kt b/domain/news/src/main/java/com/tangem/domain/news/usecase/ObserveNewsDetailsUseCase.kt index ea0dc371d9..373456dce2 100644 --- a/domain/news/src/main/java/com/tangem/domain/news/usecase/ObserveNewsDetailsUseCase.kt +++ b/domain/news/src/main/java/com/tangem/domain/news/usecase/ObserveNewsDetailsUseCase.kt @@ -1,5 +1,6 @@ package com.tangem.domain.news.usecase +import arrow.core.Either import com.tangem.domain.models.news.DetailedArticle import com.tangem.domain.news.repository.NewsRepository import kotlinx.coroutines.flow.Flow @@ -23,7 +24,7 @@ class ObserveNewsDetailsUseCase( /** * Prefetches the given article ids (can be called with current + next ids for pager preloading). */ - suspend fun prefetch(newsIds: Collection, language: String?) { + suspend fun prefetch(newsIds: Collection, language: String?): Either = Either.catch { repository.fetchDetailedArticles(newsIds, language) } } \ No newline at end of file diff --git a/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/components/FeedEntryRoute.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/components/FeedEntryRoute.kt index e6f0e3df45..7c9ffb7c0a 100644 --- a/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/components/FeedEntryRoute.kt +++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/components/FeedEntryRoute.kt @@ -24,4 +24,7 @@ sealed interface FeedEntryRoute { @Serializable data object MarketTokenList : FeedEntryRoute + + @Serializable + data class NewsDetail(val articleId: Int, val preselectedArticlesId: List) : FeedEntryRoute } \ 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 dead2e7970..75523f67d4 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 @@ -15,10 +15,13 @@ import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.navigation.inner.InnerRouter import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent +import com.tangem.core.ui.res.LocalMainBottomSheetColor +import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.TokenMarketParams import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent +import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent import com.tangem.features.feed.entry.components.FeedEntryComponent import com.tangem.features.feed.entry.components.FeedEntryRoute import com.tangem.features.feed.model.feed.FeedModelClickIntents @@ -55,6 +58,9 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor( source = "Market", ), onBackClicked = { onChildBack() }, + onArticleClick = { articleId, preselectedArticlesId -> + onArticleClick(articleId, preselectedArticlesId) + }, ), ), ) @@ -73,8 +79,16 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor( ) } - override fun onArticleClick(articleId: Int) { - innerRouter.push(FeedEntryChildFactory.Child.NewsDetails) + override fun onArticleClick(articleId: Int, preselectedArticlesId: List) { + innerRouter.push( + FeedEntryChildFactory.Child.NewsDetails( + params = DefaultNewsDetailsComponent.Params( + articleId = articleId, + onBackClicked = { onChildBack() }, + preselectedArticlesId = preselectedArticlesId, + ), + ), + ) } override fun onOpenAllNews() { @@ -121,20 +135,25 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor( @Composable override fun Content(modifier: Modifier) { - val bottomSheetState = remember { - derivedStateOf { BottomSheetState.EXPANDED } - } + val background = TangemTheme.colors.background.tertiary + CompositionLocalProvider( + LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, + ) { + val bottomSheetState = remember { + derivedStateOf { BottomSheetState.EXPANDED } + } - BackHandler { - router.pop() - } + BackHandler { + router.pop() + } - EntryContent( - bottomSheetState = bottomSheetState, - stackState = stack.subscribeAsState(), - onHeaderSizeChange = {}, - isOpenedInBottomSheet = false, - ) + EntryContent( + bottomSheetState = bottomSheetState, + stackState = stack.subscribeAsState(), + onHeaderSizeChange = {}, + isOpenedInBottomSheet = false, + ) + } } private fun onChildBack() { @@ -157,6 +176,9 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor( ) }, onBackClicked = { router.pop() }, + onArticleClick = { articleId, preselectedArticlesId -> + clickIntents.onArticleClick(articleId, preselectedArticlesId) + }, ), ) FeedEntryRoute.MarketTokenList -> FeedEntryChildFactory.Child.TokenList( @@ -167,6 +189,13 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor( shouldAlwaysShowSearchBar = false, ), ) + is FeedEntryRoute.NewsDetail -> FeedEntryChildFactory.Child.NewsDetails( + DefaultNewsDetailsComponent.Params( + articleId = entryRoute.articleId, + onBackClicked = { router.pop() }, + preselectedArticlesId = entryRoute.preselectedArticlesId, + ), + ) null -> FeedEntryChildFactory.Child.Feed } } 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 da730fb1a2..0a38fba062 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 @@ -43,7 +43,7 @@ internal class FeedEntryChildFactory @Inject constructor( @Serializable @Immutable - data object NewsDetails : Child + data class NewsDetails(val params: DefaultNewsDetailsComponent.Params) : Child } fun createChild( @@ -67,9 +67,10 @@ internal class FeedEntryChildFactory @Inject constructor( params = child.params, ) } - Child.NewsDetails -> { + is Child.NewsDetails -> { DefaultNewsDetailsComponent( appComponentContext = appComponentContext, + params = child.params, ) } Child.NewsList -> { diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt index ed0f3f488a..31352a5aef 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt @@ -15,7 +15,6 @@ import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.res.LocalMainBottomSheetColor -import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.TokenMarketParams @@ -87,13 +86,14 @@ internal class DefaultMarketsTokenDetailsComponent( @Composable override fun Title(bottomSheetState: State) { val state by model.state.collectAsStateWithLifecycle() + val background = LocalMainBottomSheetColor.current.value MarketsTokenDetailsTopBar( onBackClick = { params.onBackClicked() }, isBackButtonEnabled = bottomSheetState.value == BottomSheetState.EXPANDED, shouldShowPriceSubtitle = state.shouldShowPriceSubtitle, tokenName = state.tokenName, tokenPrice = state.priceText, - backgroundColor = TangemTheme.colors.background.tertiary, + backgroundColor = background, ) } @@ -131,6 +131,7 @@ internal class DefaultMarketsTokenDetailsComponent( val shouldShowPortfolio: Boolean, val analyticsParams: AnalyticsParams?, val onBackClicked: () -> Unit, + val onArticleClick: (articleId: Int, preselectedArticlesId: List) -> Unit, ) @Serializable diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokenActionsHandler.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokenActionsHandler.kt index d9abe7979e..0762d2602e 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokenActionsHandler.kt @@ -20,6 +20,8 @@ import com.tangem.domain.onramp.model.OnrampSource import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.TokenActionsState +import com.tangem.domain.tokens.model.details.NavigationAction +import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase import com.tangem.features.feed.components.market.details.portfolio.impl.loader.PortfolioData import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM import com.tangem.features.feed.impl.R @@ -41,6 +43,7 @@ internal class TokenActionsHandler @AssistedInject constructor( private val isDemoCardUseCase: IsDemoCardUseCase, private val messageSender: UiMessageSender, private val shareManager: ShareManager, + private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase, ) { private val disabledActionsInDemoMode = buildSet { @@ -186,22 +189,37 @@ internal class TokenActionsHandler @AssistedInject constructor( val (userWalletId, cryptoCurrencyStatus) = cryptoCurrencyData.let { currencyData -> currencyData.userWallet.walletId to currencyData.status } - if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true) { - router.push( - AppRoute.YieldSupplyActive( + val tokenEnterStatus = yieldSupplyEnterStatusUseCase(userWalletId, cryptoCurrencyStatus).getOrNull() + val isActiveYield = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true + + when { + tokenEnterStatus != null -> router.push( + AppRoute.CurrencyDetails( userWalletId = userWalletId, - cryptoCurrency = cryptoCurrencyStatus.currency, - apy = yieldSupplyApy, - ), - ) - } else { - router.push( - AppRoute.YieldSupplyPromo( - userWalletId = userWalletId, - cryptoCurrency = cryptoCurrencyStatus.currency, - apy = yieldSupplyApy, + currency = cryptoCurrencyStatus.currency, + navigationAction = NavigationAction.YieldSupply( + isActive = isActiveYield, + ), ), ) + isActiveYield -> { + router.push( + AppRoute.YieldSupplyActive( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + apy = yieldSupplyApy, + ), + ) + } + else -> { + router.push( + AppRoute.YieldSupplyPromo( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + apy = yieldSupplyApy, + ), + ) + } } } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/QuickActionUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/QuickActionUM.kt index d9ba4e54b8..c98c012df0 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/QuickActionUM.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/QuickActionUM.kt @@ -44,7 +44,7 @@ internal sealed class QuickActionUM( data class YieldMode( private val apy: String, ) : QuickActionUM( - title = resourceReference(R.string.yield_module_start_earning), + title = resourceReference(R.string.common_yield_mode), description = resourceReference(R.string.yield_module_main_screen_promo_banner_message, wrappedList(apy)), icon = R.drawable.ic_analytics_up_mini_24, ) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/TokenActionsBSContentUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/TokenActionsBSContentUM.kt index b02f6a8266..f35729eaa8 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/TokenActionsBSContentUM.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/TokenActionsBSContentUM.kt @@ -48,7 +48,7 @@ internal data class TokenActionsBSContentUM( iconRes = R.drawable.ic_staking_24, ), YieldMode( - text = resourceReference(R.string.yield_module_start_earning), + text = resourceReference(R.string.common_yield_mode), iconRes = R.drawable.ic_analytics_up_mini_24, ), ; diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/details/DefaultNewsDetailsComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/details/DefaultNewsDetailsComponent.kt index 49913d39ef..004f1db029 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/details/DefaultNewsDetailsComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/details/DefaultNewsDetailsComponent.kt @@ -2,18 +2,63 @@ package com.tangem.features.feed.components.news.details 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.res.LocalMainBottomSheetColor +import com.tangem.features.feed.model.news.details.NewsDetailsModel +import com.tangem.features.feed.ui.news.details.NewsDetailsContent +import kotlinx.serialization.Serializable internal class DefaultNewsDetailsComponent( appComponentContext: AppComponentContext, + private val params: Params, ) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext { - @Composable - override fun Title(bottomSheetState: State) {} + private val newsDetailsModel = getOrCreateModel(params = params) @Composable - override fun Content(bottomSheetState: State, modifier: Modifier) {} + override fun Title(bottomSheetState: State) { + val background = LocalMainBottomSheetColor.current.value + val state by newsDetailsModel.state.collectAsStateWithLifecycle() + TangemTopAppBar( + containerColor = background, + title = null, + startButton = TopAppBarButtonUM.Icon( + iconRes = R.drawable.ic_back_24, + onClicked = state.onBackClick, + isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED, + ), + endButton = TopAppBarButtonUM.Icon( + iconRes = R.drawable.ic_share_24, + onClicked = state.onShareClick, + isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED, + ), + ) + } + + @Composable + override fun Content(bottomSheetState: State, modifier: Modifier) { + val state by newsDetailsModel.state.collectAsStateWithLifecycle() + NewsDetailsContent( + state = state, + modifier = modifier, + ) + } + + @Serializable + data class Params( + val articleId: Int, + val onBackClicked: () -> Unit, + val preselectedArticlesId: List = emptyList(), + val tokenIds: List = emptyList(), + val categoryIds: List = emptyList(), + ) } \ 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 4d988a12de..31ca5d9dcf 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 @@ -5,6 +5,7 @@ import com.tangem.core.decompose.model.Model 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 dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -29,4 +30,9 @@ internal interface ModelModule { @IntoMap @ClassKey(MarketsTokenDetailsModel::class) fun provideMarketsTokenDetailsModel(model: MarketsTokenDetailsModel): Model + + @Binds + @IntoMap + @ClassKey(NewsDetailsModel::class) + fun provideNewsDetailsModel(model: NewsDetailsModel): Model } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt index 91d996e6ea..2fb7016135 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt @@ -31,6 +31,7 @@ import org.joda.time.DateTime import org.joda.time.DateTimeZone import javax.inject.Inject import kotlin.collections.all +import kotlin.collections.map @Stable @ModelScoped @@ -311,7 +312,17 @@ internal class FeedComponentModel @Inject constructor( params.feedClickIntents.onMarketOpenClick(sortBy) }, onArticleClick = { articleId -> - params.feedClickIntents.onArticleClick(articleId) + val trendingArticleId = state.value.trendingArticle?.id + params.feedClickIntents.onArticleClick( + articleId = articleId, + preselectedArticlesId = listOfNotNull(trendingArticleId) + + when (state.value.news.newsUMState) { + NewsUMState.CONTENT -> state.value.news.content.map { it.id } + NewsUMState.LOADING, + NewsUMState.ERROR, + -> emptyList() + }, + ) }, onOpenAllNews = { params.feedClickIntents.onOpenAllNews() diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedModelClickIntents.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedModelClickIntents.kt index 2cc84cf12f..66d705a427 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedModelClickIntents.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedModelClickIntents.kt @@ -10,6 +10,6 @@ import com.tangem.features.feed.model.market.list.state.SortByTypeUM internal interface FeedModelClickIntents { fun onMarketItemClick(token: TokenMarketParams, appCurrency: AppCurrency) fun onMarketOpenClick(sortBy: SortByTypeUM?) - fun onArticleClick(articleId: Int) + fun onArticleClick(articleId: Int, preselectedArticlesId: List = emptyList()) fun onOpenAllNews() } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt index 5f5943a741..3f6bce01b5 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt @@ -241,9 +241,7 @@ internal class MarketsTokenDetailsModel @Inject constructor( onShouldShowPriceSubtitleChange = ::onShouldShowPriceSubtitleChange, relatedNews = MarketsTokenDetailsUM.RelatedNews( articles = persistentListOf(), - onArticledClicked = { - // TODO in [REDACTED_JIRA] - }, + onArticledClicked = {}, ), ), ) @@ -313,9 +311,16 @@ internal class MarketsTokenDetailsModel @Inject constructor( ), ).onRight { articles -> state.update { marketsTokenDetailsUM -> + val relatedNews = relatedNewsConverter.convert(articles) marketsTokenDetailsUM.copy( relatedNews = marketsTokenDetailsUM.relatedNews.copy( - articles = relatedNewsConverter.convert(articles), + articles = relatedNews, + onArticledClicked = { articledId -> + params.onArticleClick( + /* articledId */ articledId, + /* preselectedIds */ relatedNews.map { it.id }, + ) + }, ), ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/RelatedNewsConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/RelatedNewsConverter.kt index 4a9b183de1..f3967a83fc 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/RelatedNewsConverter.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/RelatedNewsConverter.kt @@ -4,20 +4,14 @@ import com.tangem.common.ui.news.ArticleConfigUM import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.WrappedList -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.FormattedDate -import com.tangem.core.ui.utils.getFormattedDate import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.news.ShortArticle -import com.tangem.features.feed.impl.R -import com.tangem.utils.StringsSigns +import com.tangem.features.feed.ui.utils.mapFormattedDate import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentSet -import org.joda.time.DateTime class RelatedNewsConverter : Converter, ImmutableList> { @@ -51,34 +45,4 @@ class RelatedNewsConverter : Converter, ImmutableList
TextReference.Str(value = formattedDate.date) - is FormattedDate.HoursAgo -> TextReference.PluralRes( - id = R.plurals.news_published_hours_ago, - count = formattedDate.hours, - formatArgs = wrappedList(formattedDate.hours), - ) - is FormattedDate.MinutesAgo -> TextReference.PluralRes( - id = R.plurals.news_published_minutes_ago, - count = formattedDate.minutes, - formatArgs = wrappedList(formattedDate.minutes), - ) - is FormattedDate.Today -> TextReference.Combined( - refs = WrappedList( - data = listOf( - TextReference.Res(R.string.common_today), - TextReference.Str(StringsSigns.COMA_SIGN), - TextReference.Str(StringsSigns.WHITE_SPACE), - TextReference.Str(formattedDate.time), - ), - ), - ) - } - } } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/list/MarketsListModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/list/MarketsListModel.kt index 323fe1b119..6780d5a73f 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/list/MarketsListModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/list/MarketsListModel.kt @@ -122,7 +122,7 @@ internal class MarketsListModel @Inject constructor( flow4 = shouldShowYieldModeMarketPromoUseCase( appCurrency = currentAppCurrency.value, interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(), - ), + ).conflate(), ) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo -> MarketsItemsData( items = uiItems, @@ -138,7 +138,7 @@ internal class MarketsListModel @Inject constructor( flow3 = shouldShowYieldModeMarketPromoUseCase( appCurrency = currentAppCurrency.value, interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(), - ), + ).conflate(), ) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo -> MarketsItemsData( items = uiItems, diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt new file mode 100644 index 0000000000..7d8791f935 --- /dev/null +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt @@ -0,0 +1,93 @@ +package com.tangem.features.feed.model.news.details + +import androidx.compose.runtime.Stable +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.navigation.url.UrlOpener +import com.tangem.domain.news.usecase.ObserveNewsDetailsUseCase +import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent +import com.tangem.features.feed.model.news.details.converter.NewsDetailsConverter +import com.tangem.features.feed.ui.news.details.state.NewsDetailsUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.indexOfFirstOrNull +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import java.util.Locale +import javax.inject.Inject + +@Stable +@ModelScoped +@Suppress("LongParameterList") +internal class NewsDetailsModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val observeNewsDetailsUseCase: ObserveNewsDetailsUseCase, + private val urlOpener: UrlOpener, + paramsContainer: ParamsContainer, +) : Model() { + + private val params = paramsContainer.require() + + private val currentLanguage = Locale.getDefault().language + + private val converter = NewsDetailsConverter( + onSourceClick = { url -> + urlOpener.openUrl(url) + }, + ) + + private val _state = MutableStateFlow( + NewsDetailsUM( + articles = persistentListOf(), + selectedArticleIndex = 0, + onShareClick = { /* [REDACTED_TODO_COMMENT] */ }, + onLikeClick = { /* [REDACTED_TODO_COMMENT] */ }, + onBackClick = params.onBackClicked, + onArticleIndexChanged = { /* [REDACTED_TODO_COMMENT] */ }, + ), + ) + + val state: StateFlow = _state.asStateFlow() + + init { + if (params.preselectedArticlesId.isNotEmpty()) { + handlePreselectedArticles() + } else { + // TODO handle pagination [REDACTED_TASK_KEY] + } + } + + private fun handlePreselectedArticles() { + modelScope.launch { + observeNewsDetailsUseCase.prefetch( + newsIds = params.preselectedArticlesId, + language = currentLanguage, + ) + + observeNewsDetailsUseCase + .invoke() + .map { articlesMap -> + params.preselectedArticlesId.mapNotNull { articleId -> + articlesMap[articleId]?.let { detailedArticle -> + converter.convert(detailedArticle) + } + } + } + .map { articles -> + articles.toImmutableList() + } + .onEach { articles -> + val selectedIndex = articles.indexOfFirstOrNull { it.id == params.articleId } ?: 0 + _state.update { newsDetailsUM -> + newsDetailsUM.copy( + articles = articles, + selectedArticleIndex = selectedIndex, + ) + } + } + .launchIn(modelScope) + } + } +} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/converter/NewsDetailsConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/converter/NewsDetailsConverter.kt new file mode 100644 index 0000000000..6a0fa54bae --- /dev/null +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/converter/NewsDetailsConverter.kt @@ -0,0 +1,101 @@ +package com.tangem.features.feed.model.news.details.converter + +import androidx.compose.runtime.Stable +import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM +import com.tangem.core.ui.components.label.entity.LabelUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.utils.FormattedDate +import com.tangem.core.ui.utils.getFormattedDate +import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.news.DetailedArticle +import com.tangem.features.feed.impl.R +import com.tangem.features.feed.ui.news.details.state.ArticleUM +import com.tangem.features.feed.ui.news.details.state.Source +import com.tangem.features.feed.ui.news.details.state.SourceUM +import com.tangem.utils.StringsSigns +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import org.joda.time.DateTime + +@Stable +internal class NewsDetailsConverter( + private val onSourceClick: (String) -> Unit, +) : Converter { + + override fun convert(value: DetailedArticle): ArticleUM { + return ArticleUM( + id = value.id, + title = value.title, + createdAt = mapFormattedDate(value.createdAt), + score = value.score, + tags = buildTags(value), + shortContent = value.shortContent, + content = value.content, + sources = buildSources(value), + ) + } + + private fun buildTags(article: DetailedArticle): ImmutableList { + val categoryLabels = article.categories.map { category -> + LabelUM(text = TextReference.Str(category.name)) + } + val tokenLabels = article.relatedTokens.map { token -> + LabelUM( + text = TextReference.Str(token.symbol), + leadingContent = LabelLeadingContentUM.Token( + iconUrl = getTokenIconUrlFromDefaultHost( + tokenId = CryptoCurrency.RawID(token.id), + ), + ), + ) + } + return (categoryLabels + tokenLabels).toImmutableList() + } + + private fun buildSources(article: DetailedArticle): ImmutableList { + return article.originalArticles.map { originalArticle -> + SourceUM( + id = originalArticle.id, + title = originalArticle.title, + source = Source(id = originalArticle.source.id, name = originalArticle.source.name), + publishedAt = mapFormattedDate(originalArticle.publishedAt), + url = originalArticle.url, + onClick = { onSourceClick(originalArticle.url) }, + ) + }.toImmutableList() + } + + private fun mapFormattedDate(createdAt: String): TextReference { + val formattedDate = getFormattedDate( + createdAt = createdAt, + now = DateTime.now(), + ) + return when (formattedDate) { + is FormattedDate.FullDate -> TextReference.Str(value = formattedDate.date) + is FormattedDate.HoursAgo -> TextReference.PluralRes( + id = R.plurals.news_published_hours_ago, + count = formattedDate.hours, + formatArgs = wrappedList(formattedDate.hours), + ) + is FormattedDate.MinutesAgo -> TextReference.PluralRes( + id = R.plurals.news_published_minutes_ago, + count = formattedDate.minutes, + formatArgs = wrappedList(formattedDate.minutes), + ) + is FormattedDate.Today -> TextReference.Combined( + refs = WrappedList( + data = listOf( + TextReference.Res(R.string.common_today), + TextReference.Str(StringsSigns.COMA_SIGN), + TextReference.Str(StringsSigns.WHITE_SPACE), + TextReference.Str(formattedDate.time), + ), + ), + ) + } + } +} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt index d2822c4ba1..25a206dcb3 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt @@ -357,7 +357,7 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM, articleConfigUM = article, onArticleClick = { feedListCallbacks.onArticleClick(article.id) }, modifier = Modifier - .height(164.dp) + .heightIn(min = 164.dp) .width(216.dp), colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action), ) @@ -365,7 +365,9 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM, item { ShowMoreArticlesCard( - modifier = Modifier.size(width = 216.dp, height = 164.dp), + modifier = Modifier + .width(216.dp) + .heightIn(min = 164.dp), onClick = feedListCallbacks.onOpenAllNews, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/TrendingNewsStateFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/TrendingNewsStateFactory.kt index c2a568ad85..f5c7fe446b 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/TrendingNewsStateFactory.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/TrendingNewsStateFactory.kt @@ -4,22 +4,16 @@ import com.tangem.common.ui.news.ArticleConfigUM import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.WrappedList -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.FormattedDate -import com.tangem.core.ui.utils.getFormattedDate import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.news.ShortArticle import com.tangem.domain.models.news.TrendingNews -import com.tangem.features.feed.impl.R +import com.tangem.features.feed.ui.utils.mapFormattedDate import com.tangem.utils.Provider -import com.tangem.utils.StringsSigns import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentSet -import org.joda.time.DateTime internal class TrendingNewsStateFactory( private val currentStateProvider: Provider, @@ -111,34 +105,4 @@ internal class TrendingNewsStateFactory( } return (categoryLabels + tokenLabels).toPersistentSet() } - - private fun mapFormattedDate(createdAt: String): TextReference { - val formattedDate = getFormattedDate( - createdAt = createdAt, - now = DateTime.now(), - ) - return when (formattedDate) { - is FormattedDate.FullDate -> TextReference.Str(value = formattedDate.date) - is FormattedDate.HoursAgo -> TextReference.PluralRes( - id = R.plurals.news_published_hours_ago, - count = formattedDate.hours, - formatArgs = wrappedList(formattedDate.hours), - ) - is FormattedDate.MinutesAgo -> TextReference.PluralRes( - id = R.plurals.news_published_minutes_ago, - count = formattedDate.minutes, - formatArgs = wrappedList(formattedDate.minutes), - ) - is FormattedDate.Today -> TextReference.Combined( - refs = WrappedList( - data = listOf( - TextReference.Res(R.string.common_today), - TextReference.Str(StringsSigns.COMA_SIGN), - TextReference.Str(StringsSigns.WHITE_SPACE), - TextReference.Str(formattedDate.time), - ), - ), - ) - } - } } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/list/MarketsList.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/list/MarketsList.kt index 0268627666..f3b6f8abe0 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/list/MarketsList.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/list/MarketsList.kt @@ -142,14 +142,14 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif ) } - val marketsNotification = state.marketsNotificationUM AnimatedVisibility( - state.isInSearchMode.not() && + state.list !is ListUM.LoadingError && state.isInSearchMode.not() && state.selectedSortBy != SortByTypeUM.YieldSupply, ) { + val wrappedNotification = remember(this) { state.marketsNotificationUM } val showMore = stringResourceSafe(R.string.common_show_more) - when (marketsNotification) { + when (wrappedNotification) { is MarketsNotificationUM.YieldSupplyPromo -> { val description = stringResourceSafe( R.string.markets_yield_supply_banner_description, @@ -165,7 +165,7 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif } YieldSupplyInMarketsPromoNotification( - config = marketsNotification.config.copy( + config = wrappedNotification.config.copy( subtitle = clickableDescription, ), modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), diff --git a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/ui/NewsDetailsContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt similarity index 63% rename from features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/ui/NewsDetailsContent.kt rename to features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt index a2c5e93a36..61ef4bc6c9 100644 --- a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/ui/NewsDetailsContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt @@ -1,9 +1,13 @@ -package com.tangem.features.news.details.impl.ui +package com.tangem.features.feed.ui.news.details import android.content.res.Configuration import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape @@ -11,57 +15,57 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.common.ui.news.ArticleHeader import com.tangem.core.ui.R import com.tangem.core.ui.components.SecondaryButtonIconStart -import com.tangem.core.ui.components.appbar.TangemTopAppBar -import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.SpacerW import com.tangem.core.ui.components.buttons.common.TangemButtonSize import com.tangem.core.ui.components.pager.PagerIndicator -import com.tangem.core.ui.extensions.conditionalCompose +import com.tangem.core.ui.extensions.resolveReference +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.news.details.impl.MockArticlesFactory +import com.tangem.features.feed.ui.news.details.state.ArticleUM +import com.tangem.features.feed.ui.news.details.state.MockArticlesFactory +import com.tangem.features.feed.ui.news.details.state.NewsDetailsUM +import com.tangem.features.feed.ui.news.details.state.SourceUM -// TODO [REDACTED_TASK_KEY] make internal @Composable -fun NewsDetailsContent( - state: NewsDetailsUM, - onBackClick: () -> Unit, - modifier: Modifier = Modifier, - isBottomSheetMode: Boolean = false, -) { +internal fun NewsDetailsContent(state: NewsDetailsUM, modifier: Modifier = Modifier) { + val background = LocalMainBottomSheetColor.current.value val pagerState = rememberPagerState( initialPage = state.selectedArticleIndex, pageCount = { state.articles.size }, ) + LaunchedEffect(state.selectedArticleIndex) { + if (pagerState.currentPage != state.selectedArticleIndex) { + pagerState.scrollToPage(state.selectedArticleIndex) + } + } + + LaunchedEffect(pagerState.currentPage) { + if (pagerState.currentPage != state.selectedArticleIndex) { + state.onArticleIndexChanged(pagerState.currentPage) + } + } + Column( modifier = modifier .fillMaxSize() - .background(TangemTheme.colors.background.tertiary) - .conditionalCompose(isBottomSheetMode) { systemBarsPadding() }, + .background(background), ) { - TangemTopAppBar( - title = null, - startButton = TopAppBarButtonUM.Icon( - iconRes = R.drawable.ic_back_24, - onClicked = onBackClick, - ), - endButton = TopAppBarButtonUM.Icon( - iconRes = R.drawable.ic_share_24, - onClicked = state.onShareClick, - ), - ) - Box( - modifier = Modifier.fillMaxSize(), - ) { + Box(modifier = Modifier.fillMaxSize()) { if (state.articles.isNotEmpty()) { HorizontalPager( state = pagerState, @@ -73,20 +77,13 @@ fun NewsDetailsContent( onLikeClick = state.onLikeClick, ) } - if (state.articles.size > 1) { - Column( + PagerIndicator( + pagerState = pagerState, modifier = Modifier .align(Alignment.BottomCenter) - .fillMaxWidth(), - ) { - PagerIndicator( - pagerState = pagerState, - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(bottom = 16.dp), - ) - } + .windowInsetsPadding(WindowInsets.navigationBars), + ) } } } @@ -97,27 +94,27 @@ fun NewsDetailsContent( @Composable private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onLikeClick: () -> Unit) { val density = LocalDensity.current - val pagerHeight = 48.dp - val contentPadding = 56.dp LazyColumn( - modifier = modifier.padding(horizontal = 16.dp), - contentPadding = PaddingValues( - bottom = contentPadding + pagerHeight + WindowInsets.navigationBars.getBottom(density).dp, - ), + modifier = modifier, + contentPadding = PaddingValues(bottom = 56.dp + WindowInsets.navigationBars.getBottom(density).dp), ) { item { ArticleHeader( title = article.title, - createdAt = article.createdAt, + createdAt = article.createdAt.resolveReference(), score = article.score, tags = article.tags, - modifier = Modifier.padding(top = 16.dp), + modifier = Modifier + .padding(top = 16.dp) + .padding(horizontal = 16.dp), ) if (article.shortContent.isNotEmpty()) { QuickRecap( content = article.shortContent, - modifier = Modifier.padding(top = 32.dp), + modifier = Modifier + .padding(top = 32.dp) + .padding(horizontal = 16.dp), ) } @@ -125,14 +122,17 @@ private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onL text = article.content, style = TangemTheme.typography.body1, color = TangemTheme.colors.text.primary1, - modifier = Modifier.padding(top = 16.dp), + modifier = Modifier + .padding(top = 16.dp) + .padding(horizontal = 16.dp), ) - Spacer(modifier = Modifier.height(24.dp)) + SpacerH(24.dp) SecondaryButtonIconStart( + modifier = Modifier.padding(horizontal = 16.dp), iconResId = R.drawable.ic_heart_20, - text = "Like", // TODO [REDACTED_TASK_KEY] export to strings + text = stringResourceSafe(R.string.news_like), size = TangemButtonSize.RoundedAction, onClick = onLikeClick, ) @@ -140,14 +140,16 @@ private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onL // TODO [REDACTED_TASK_KEY] add related tokens block if (article.sources.isNotEmpty()) { - Spacer(modifier = Modifier.height(24.dp)) - Row { + SpacerH(24.dp) + Row( + modifier = Modifier.padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { Text( - text = "Sources", // TODO [REDACTED_TASK_KEY] export to strings + text = stringResourceSafe(R.string.news_sources), style = TangemTheme.typography.h3, color = TangemTheme.colors.text.primary1, ) - Spacer(modifier = Modifier.width(8.dp)) Text( text = "${article.sources.size}", style = TangemTheme.typography.h3, @@ -159,23 +161,19 @@ private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onL if (article.sources.isNotEmpty()) { item { - val sourcesPagerState = rememberPagerState( - pageCount = { article.sources.size }, - ) - Spacer(modifier = Modifier.height(12.dp)) - HorizontalPager( - state = sourcesPagerState, - modifier = Modifier - .fillMaxWidth(), - pageSpacing = 12.dp, - contentPadding = PaddingValues(horizontal = 0.dp), - ) { page -> - SourceItem( - source = article.sources[page], - modifier = Modifier.fillMaxWidth(), - ) + LazyRow( + modifier = Modifier.padding(vertical = 12.dp), + state = rememberLazyListState(), + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + items( + items = article.sources, + key = SourceUM::id, + ) { source -> + SourceItem(source = source) + } } - Spacer(modifier = Modifier.height(12.dp)) } } } @@ -206,7 +204,7 @@ private fun QuickRecap(content: String, modifier: Modifier = Modifier) { ) Spacer(modifier = Modifier.width(8.dp)) Text( - text = "Quick recap", // TODO [REDACTED_TASK_KEY] export to strings + text = stringResourceSafe(R.string.news_quick_recap), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.accent, ) @@ -225,10 +223,13 @@ private fun QuickRecap(content: String, modifier: Modifier = Modifier) { private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) { Column( modifier = modifier + .widthIn(max = 216.dp) + .heightIn(min = 132.dp) .background( - color = TangemTheme.colors.background.primary, + color = TangemTheme.colors.background.action, shape = RoundedCornerShape(12.dp), ) + .clickable(onClick = source.onClick) .padding(12.dp), ) { Row( @@ -239,25 +240,30 @@ private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) { painter = painterResource(id = R.drawable.ic_explore_16), contentDescription = null, tint = TangemTheme.colors.icon.informative, - modifier = Modifier.size(20.dp), + modifier = Modifier.size(16.dp), ) - Spacer(modifier = Modifier.width(4.dp)) + SpacerW(4.dp) Text( - text = source.sourceName, + text = source.source.name, style = TangemTheme.typography.caption1, + maxLines = 1, + overflow = TextOverflow.Ellipsis, color = TangemTheme.colors.text.tertiary, ) } if (source.title.isNotEmpty()) { + SpacerH(4.dp) Text( text = source.title, style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.primary1, + maxLines = 3, + overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(bottom = 12.dp), ) } Text( - text = source.publishedAt, + text = source.publishedAt.resolveReference(), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, ) @@ -273,10 +279,10 @@ private fun PreviewNewsDetailsContent() { state = NewsDetailsUM( articles = MockArticlesFactory.createMockArticles(), selectedArticleIndex = 0, - onShareClick = { }, - onLikeClick = { }, + onShareClick = {}, + onLikeClick = {}, + onBackClick = {}, ), - onBackClick = { }, ) } } \ No newline at end of file diff --git a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/MockArticlesFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/state/MockArticlesFactory.kt similarity index 80% rename from features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/MockArticlesFactory.kt rename to features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/state/MockArticlesFactory.kt index ac35bb6ee4..03b4990886 100644 --- a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/MockArticlesFactory.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/state/MockArticlesFactory.kt @@ -1,22 +1,19 @@ -package com.tangem.features.news.details.impl +package com.tangem.features.feed.ui.news.details.state import com.tangem.core.ui.components.label.entity.LabelSize import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.extensions.TextReference -import com.tangem.features.news.details.impl.ui.ArticleUM -import com.tangem.features.news.details.impl.ui.SourceUM import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList -// TODO [REDACTED_TASK_KEY] remove mock data @Suppress("MaximumLineLength", "LongMethod") -object MockArticlesFactory { +internal object MockArticlesFactory { fun createMockArticles(): ImmutableList = listOf( ArticleUM( id = 1, title = "SEC delays decisions on ETH-staking ETFs and spot XRP/SOL funds", - createdAt = "20 Jun, 21:45", + createdAt = TextReference.Str("20 Jun, 21:45"), score = 6.5f, tags = listOf( LabelUM(text = TextReference.Str("Regulation"), size = LabelSize.BIG), @@ -27,23 +24,31 @@ object MockArticlesFactory { SourceUM( id = 1, title = "Deeper liquidity could drive crypto market beyond \$6T", - sourceName = "cointelegraph", - publishedAt = "1h ago", + source = Source( + id = 11, + name = "Coin-telegraph", + ), + publishedAt = TextReference.Str("1h ago"), url = "https://cointelegraph.com", + onClick = {}, ), SourceUM( id = 2, title = "Top gainers and losers in crypto this week", - sourceName = "Investing", - publishedAt = "2h ago", + source = Source( + id = 10, + name = "Investing", + ), + publishedAt = TextReference.Str("2h ago"), url = "https://investing.com", + onClick = {}, ), ).toPersistentList(), ), ArticleUM( id = 2, title = "Bitcoin ETFs log 4th straight day of inflows (+\$550M)", - createdAt = "20 Jun, 20:15", + createdAt = TextReference.Str("20 Jun, 20:15"), score = 8.2f, tags = listOf( LabelUM(text = TextReference.Str("BTC"), size = LabelSize.BIG), @@ -55,16 +60,20 @@ object MockArticlesFactory { SourceUM( id = 3, title = "Bitcoin ETFs see massive inflows", - sourceName = "Bloomberg", - publishedAt = "3h ago", + source = Source( + id = 12, + name = "Bloomberg", + ), + publishedAt = TextReference.Str("3h ago"), url = "https://bloomberg.com", + onClick = {}, ), ).toPersistentList(), ), ArticleUM( id = 3, title = "Ethereum network upgrade scheduled for Q2 2025", - createdAt = "20 Jun, 18:30", + createdAt = TextReference.Str("20 Jun, 18:30"), score = 7.8f, tags = listOf( LabelUM(text = TextReference.Str("ETH"), size = LabelSize.BIG), @@ -76,16 +85,20 @@ object MockArticlesFactory { SourceUM( id = 4, title = "Ethereum core devs announce upgrade", - sourceName = "CoinDesk", - publishedAt = "5h ago", + source = Source( + id = 15, + name = "CoinDesk", + ), + publishedAt = TextReference.Str("5h ago"), url = "https://coindesk.com", + onClick = {}, ), ).toPersistentList(), ), ArticleUM( id = 4, title = "Solana surpasses Ethereum in daily transaction volume", - createdAt = "20 Jun, 16:00", + createdAt = TextReference.Str("20 Jun, 16:00"), score = 9.1f, tags = listOf( LabelUM(text = TextReference.Str("SOL"), size = LabelSize.BIG), @@ -97,16 +110,20 @@ object MockArticlesFactory { SourceUM( id = 5, title = "Solana transactions hit record", - sourceName = "The Block", - publishedAt = "7h ago", + source = Source( + id = 18, + name = "Times", + ), + publishedAt = TextReference.Str("7h ago"), url = "https://theblock.co", + onClick = {}, ), ).toPersistentList(), ), ArticleUM( id = 5, title = "DeFi protocol launches innovative yield farming strategy", - createdAt = "20 Jun, 14:20", + createdAt = TextReference.Str("20 Jun, 14:20"), score = 6.9f, tags = listOf( LabelUM(text = TextReference.Str("DeFi"), size = LabelSize.BIG), @@ -118,7 +135,7 @@ object MockArticlesFactory { ArticleUM( id = 6, title = "Crypto regulation bill advances in US Senate", - createdAt = "20 Jun, 12:45", + createdAt = TextReference.Str("20 Jun, 12:45"), score = 8.7f, tags = listOf( LabelUM(text = TextReference.Str("Regulation"), size = LabelSize.BIG), @@ -131,7 +148,7 @@ object MockArticlesFactory { ArticleUM( id = 7, title = "Major bank announces crypto custody services", - createdAt = "20 Jun, 10:30", + createdAt = TextReference.Str("20 Jun, 10:30"), score = 7.3f, tags = listOf( LabelUM(text = TextReference.Str("Adoption"), size = LabelSize.BIG), @@ -143,7 +160,7 @@ object MockArticlesFactory { ArticleUM( id = 8, title = "NFT marketplace reports 300% increase in trading volume", - createdAt = "20 Jun, 08:15", + createdAt = TextReference.Str("20 Jun, 08:15"), score = 5.8f, tags = listOf( LabelUM(text = TextReference.Str("NFT"), size = LabelSize.BIG), @@ -155,7 +172,7 @@ object MockArticlesFactory { ArticleUM( id = 9, title = "Layer 2 solution achieves 100,000 TPS milestone", - createdAt = "19 Jun, 22:00", + createdAt = TextReference.Str("19 Jun, 22:00"), score = 8.5f, tags = listOf( LabelUM(text = TextReference.Str("Technology"), size = LabelSize.BIG), @@ -168,7 +185,7 @@ object MockArticlesFactory { ArticleUM( id = 10, title = "Stablecoin market cap reaches new all-time high", - createdAt = "19 Jun, 19:30", + createdAt = TextReference.Str("19 Jun, 19:30"), score = 7.6f, tags = listOf( LabelUM(text = TextReference.Str("Stablecoins"), size = LabelSize.BIG), diff --git a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/ui/NewsDetailsUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/state/NewsDetailsUM.kt similarity index 52% rename from features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/ui/NewsDetailsUM.kt rename to features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/state/NewsDetailsUM.kt index 889fcc202a..a7524862e0 100644 --- a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/ui/NewsDetailsUM.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/state/NewsDetailsUM.kt @@ -1,21 +1,22 @@ -package com.tangem.features.news.details.impl.ui +package com.tangem.features.feed.ui.news.details.state import com.tangem.core.ui.components.label.entity.LabelUM +import com.tangem.core.ui.extensions.TextReference import kotlinx.collections.immutable.ImmutableList -// TODO [REDACTED_TASK_KEY] make internal -data class NewsDetailsUM( +internal data class NewsDetailsUM( val articles: ImmutableList, val selectedArticleIndex: Int, val onShareClick: () -> Unit, val onLikeClick: () -> Unit, + val onBackClick: () -> Unit, + val onArticleIndexChanged: (Int) -> Unit = {}, ) -// TODO [REDACTED_TASK_KEY] make internal -data class ArticleUM( +internal data class ArticleUM( val id: Int, val title: String, - val createdAt: String, + val createdAt: TextReference, val score: Float, val tags: ImmutableList, val shortContent: String, @@ -23,11 +24,16 @@ data class ArticleUM( val sources: ImmutableList, ) -// TODO [REDACTED_TASK_KEY] make internal -data class SourceUM( +internal data class SourceUM( val id: Int, val title: String, - val sourceName: String, - val publishedAt: String, + val source: Source, + val publishedAt: TextReference, val url: String, + val onClick: () -> Unit, +) + +internal data class Source( + val id: Int, + val name: String, ) \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/CreatedTimeFormatter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/CreatedTimeFormatter.kt new file mode 100644 index 0000000000..b1052994f0 --- /dev/null +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/CreatedTimeFormatter.kt @@ -0,0 +1,40 @@ +package com.tangem.features.feed.ui.utils + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.utils.FormattedDate +import com.tangem.core.ui.utils.getFormattedDate +import com.tangem.features.feed.impl.R +import com.tangem.utils.StringsSigns +import org.joda.time.DateTime + +internal fun mapFormattedDate(createdAt: String): TextReference { + val formattedDate = getFormattedDate( + createdAt = createdAt, + now = DateTime.now(), + ) + return when (formattedDate) { + is FormattedDate.FullDate -> TextReference.Str(value = formattedDate.date) + is FormattedDate.HoursAgo -> TextReference.PluralRes( + id = R.plurals.news_published_hours_ago, + count = formattedDate.hours, + formatArgs = wrappedList(formattedDate.hours), + ) + is FormattedDate.MinutesAgo -> TextReference.PluralRes( + id = R.plurals.news_published_minutes_ago, + count = formattedDate.minutes, + formatArgs = wrappedList(formattedDate.minutes), + ) + is FormattedDate.Today -> TextReference.Combined( + refs = WrappedList( + data = listOf( + TextReference.Res(R.string.common_today), + TextReference.Str(StringsSigns.COMA_SIGN), + TextReference.Str(StringsSigns.WHITE_SPACE), + TextReference.Str(formattedDate.time), + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/news/news-details/api/build.gradle.kts b/features/news/news-details/api/build.gradle.kts deleted file mode 100644 index a9c29a2049..0000000000 --- a/features/news/news-details/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.details.api" -} - -dependencies { - implementation(deps.compose.foundation) - - /* Project - Core */ - implementation(projects.core.decompose) - implementation(projects.core.ui) - - /* Compose */ - implementation(deps.compose.runtime) -} diff --git a/features/news/news-details/api/src/main/kotlin/com/tangem/features/news/details/api/NewsDetailsComponent.kt b/features/news/news-details/api/src/main/kotlin/com/tangem/features/news/details/api/NewsDetailsComponent.kt deleted file mode 100644 index 56d379faf0..0000000000 --- a/features/news/news-details/api/src/main/kotlin/com/tangem/features/news/details/api/NewsDetailsComponent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.features.news.details.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 NewsDetailsComponent : ComposableContentComponent { - - data class Params(val selectedArticleId: Int = 0) - - @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-details/impl/build.gradle.kts b/features/news/news-details/impl/build.gradle.kts deleted file mode 100644 index a9953868af..0000000000 --- a/features/news/news-details/impl/build.gradle.kts +++ /dev/null @@ -1,43 +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.details.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.newsDetails.api) - implementation(projects.domain.models) - - /** Other dependencies */ - implementation(deps.kotlin.immutable.collections) - implementation(deps.arrow.core) - implementation(deps.timber) - - /** DI */ - implementation(deps.hilt.android) - kapt(deps.hilt.kapt) -} diff --git a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/DefaultNewsDetailsComponent.kt b/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/DefaultNewsDetailsComponent.kt deleted file mode 100644 index 98c8989e54..0000000000 --- a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/DefaultNewsDetailsComponent.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.tangem.features.news.details.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.details.api.NewsDetailsComponent -import com.tangem.features.news.details.impl.ui.NewsDetailsContent -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject - -internal class DefaultNewsDetailsComponent @AssistedInject constructor( - @Assisted context: AppComponentContext, - @Assisted params: NewsDetailsComponent.Params, -) : NewsDetailsComponent, AppComponentContext by context { - - private val model: NewsDetailsModel = 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() - } - - NewsDetailsContent( - state = uiState, - onBackClick = ::navigateBack, - modifier = modifier, - isBottomSheetMode = true, - ) - } - - @Composable - override fun Content(modifier: Modifier) { - val uiState by model.uiState.collectAsStateWithLifecycle() - NewsDetailsContent( - state = uiState, - onBackClick = model::onBackClick, - modifier = modifier, - isBottomSheetMode = false, - ) - } - - private fun navigateBack() = router.pop() - - @AssistedFactory - interface Factory : NewsDetailsComponent.Factory { - override fun create( - context: AppComponentContext, - params: NewsDetailsComponent.Params, - ): DefaultNewsDetailsComponent - } -} \ No newline at end of file diff --git a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/NewsDetailsModel.kt b/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/NewsDetailsModel.kt deleted file mode 100644 index 3ff89dde9d..0000000000 --- a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/NewsDetailsModel.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.tangem.features.news.details.impl - -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.decompose.navigation.Router -import com.tangem.features.news.details.api.NewsDetailsComponent -import com.tangem.features.news.details.impl.ui.NewsDetailsUM -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.extensions.indexOfFirstOrNull -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import javax.inject.Inject - -@ModelScoped -internal class NewsDetailsModel @Inject constructor( - paramsContainer: ParamsContainer, - override val dispatchers: CoroutineDispatcherProvider, - private val router: Router, -) : Model() { - - private val mockedArticles = MockArticlesFactory.createMockArticles() - - private val params = paramsContainer.require() - - private val _uiState = MutableStateFlow( - NewsDetailsUM( - /* [REDACTED_TODO_COMMENT] */ - articles = mockedArticles, - selectedArticleIndex = mockedArticles.indexOfFirstOrNull { it.id == params.selectedArticleId } ?: 0, - onShareClick = { /* [REDACTED_TODO_COMMENT] */ }, - onLikeClick = { /* [REDACTED_TODO_COMMENT] */ }, - ), - ) - val uiState: StateFlow = _uiState.asStateFlow() - - fun onBackClick() { - router.pop() - } -} \ No newline at end of file diff --git a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/di/NewsDetailsModule.kt b/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/di/NewsDetailsModule.kt deleted file mode 100644 index f0fcac783e..0000000000 --- a/features/news/news-details/impl/src/main/kotlin/com/tangem/features/news/details/impl/di/NewsDetailsModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.features.news.details.impl.di - -import com.tangem.features.news.details.api.NewsDetailsComponent -import com.tangem.features.news.details.impl.DefaultNewsDetailsComponent -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 NewsDetailsModule { - - @Binds - @Singleton - fun bindNewsDetailsComponentFactory(factory: DefaultNewsDetailsComponent.Factory): NewsDetailsComponent.Factory -} \ No newline at end of file diff --git a/features/tester/impl/build.gradle.kts b/features/tester/impl/build.gradle.kts index 92a8671ee4..57fe70672c 100644 --- a/features/tester/impl/build.gradle.kts +++ b/features/tester/impl/build.gradle.kts @@ -59,10 +59,6 @@ dependencies { /** Feature Apis */ implementation(projects.features.tester.api) implementation(projects.features.pushNotifications.api) - implementation(projects.features.news.newsDetails.api) - implementation(projects.features.news.newsDetails.impl) - implementation(projects.features.news.newsList.api) - implementation(projects.features.news.newsList.impl) /* SDK */ implementation(tangemDeps.blockchain) diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt index 333c9fd9e6..e59702c4e2 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt @@ -14,9 +14,6 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.tangem.common.routing.AppRouter import com.tangem.core.navigation.finisher.AppFinisher import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeActivity import com.tangem.feature.tester.presentation.accounts.ui.AccountsScreen @@ -38,14 +35,7 @@ import com.tangem.feature.tester.presentation.providers.ui.BlockchainProvidersSc import com.tangem.feature.tester.presentation.providers.viewmodel.BlockchainProvidersViewModel import com.tangem.feature.tester.presentation.testpush.ui.TestPushScreen import com.tangem.feature.tester.presentation.testpush.viewmodel.TestPushViewModel -import com.tangem.feature.tester.presentation.news.ui.NewsScreen -import com.tangem.feature.tester.presentation.news.viewmodel.NewsViewModel -import com.tangem.features.news.details.impl.MockArticlesFactory -import com.tangem.features.news.details.impl.ui.ArticleUM -import com.tangem.features.news.details.impl.ui.NewsDetailsContent -import com.tangem.features.news.details.impl.ui.NewsDetailsUM import dagger.hilt.android.AndroidEntryPoint -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentSetOf import javax.inject.Inject @@ -92,7 +82,6 @@ internal class TesterActivity : ComposeActivity() { ButtonUM.TESTER_ACTIONS, ButtonUM.TEST_PUSHES, ButtonUM.ACCOUNTS, - ButtonUM.NEWS, ), onButtonClick = { buttonUM -> val route = when (buttonUM) { @@ -103,7 +92,6 @@ internal class TesterActivity : ComposeActivity() { ButtonUM.TESTER_ACTIONS -> TesterScreen.TESTER_ACTIONS ButtonUM.TEST_PUSHES -> TesterScreen.TEST_PUSHES ButtonUM.ACCOUNTS -> TesterScreen.ACCOUNTS - ButtonUM.NEWS -> TesterScreen.NEWS } innerTesterRouter.open(route) @@ -174,64 +162,6 @@ internal class TesterActivity : ComposeActivity() { AccountsScreen(state) } - - composable(route = TesterScreen.NEWS.name) { - val viewModel = hiltViewModel().apply { - setupNavigation(innerTesterRouter) - } - val state by viewModel.uiState.collectAsStateWithLifecycle() - - NewsScreen(state) - } - - composable(route = TesterScreen.NEWS_DETAILS.name) { - NewsDetailsContent( - state = NewsDetailsUM( - articles = MockArticlesFactory.createMockArticles(), - selectedArticleIndex = 0, - onLikeClick = { }, - onShareClick = { }, - ), - onBackClick = { innerTesterRouter.back() }, - ) - } - - composable(route = TesterScreen.NEWS_DETAILS_BOTTOM_SHEET.name) { - NewsDetailsBottomSheetTest( - onDismiss = { innerTesterRouter.back() }, - ) - } - } - } - - @Composable - private fun NewsDetailsBottomSheetTest(onDismiss: () -> Unit) { - data class NewsDetailsBottomSheetContent( - val articles: ImmutableList, - ) : TangemBottomSheetConfigContent - - val content = NewsDetailsBottomSheetContent( - articles = MockArticlesFactory.createMockArticles(), - ) - - TangemBottomSheet( - config = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = onDismiss, - content = content, - ), - containerColor = TangemTheme.colors.background.tertiary, - ) { sheetContent -> - NewsDetailsContent( - state = NewsDetailsUM( - articles = sheetContent.articles, - selectedArticleIndex = 0, - onLikeClick = { }, - onShareClick = { }, - ), - onBackClick = onDismiss, - isBottomSheetMode = true, - ) } } } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/state/TesterMenuUM.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/state/TesterMenuUM.kt index 00a019e9c4..fa30058309 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/state/TesterMenuUM.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/state/TesterMenuUM.kt @@ -25,6 +25,5 @@ data class TesterMenuUM( TESTER_ACTIONS(R.string.tester_actions), TEST_PUSHES(R.string.test_push), ACCOUNTS(R.string.accounts), - NEWS(R.string.news), } } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/navigation/TesterScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/navigation/TesterScreen.kt index 8d06310e39..e87d942c19 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/navigation/TesterScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/navigation/TesterScreen.kt @@ -14,7 +14,4 @@ internal enum class TesterScreen { BLOCKCHAIN_PROVIDERS, TEST_PUSHES, ACCOUNTS, - NEWS, - NEWS_DETAILS, - NEWS_DETAILS_BOTTOM_SHEET, } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/news/state/NewsUM.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/news/state/NewsUM.kt deleted file mode 100644 index 43c8c9efca..0000000000 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/news/state/NewsUM.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.feature.tester.presentation.news.state - -import androidx.annotation.StringRes -import com.tangem.feature.tester.impl.R -import kotlinx.collections.immutable.ImmutableSet - -data class NewsUM( - val onBackClick: () -> Unit, - val buttons: ImmutableSet, - val onButtonClick: (ButtonUM) -> Unit, -) { - - enum class ButtonUM(@StringRes val textResId: Int) { - NEWS_DETAILS(R.string.news_details), - NEWS_DETAILS_BOTTOM_SHEET(R.string.news_details_bottom_sheet), - } -} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/news/ui/NewsScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/news/ui/NewsScreen.kt deleted file mode 100644 index 2f0b9c690c..0000000000 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/news/ui/NewsScreen.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.feature.tester.presentation.news.ui - -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.appbar.AppBarWithBackButton -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.tester.impl.R -import com.tangem.feature.tester.presentation.news.state.NewsUM -import kotlinx.collections.immutable.ImmutableSet - -@OptIn(ExperimentalFoundationApi::class) -@Composable -internal fun NewsScreen(state: NewsUM, modifier: Modifier = Modifier) { - BackHandler(onBack = state.onBackClick) - - LazyColumn( - modifier = modifier - .fillMaxSize() - .background(TangemTheme.colors.background.primary), - ) { - stickyHeader { AppBar(onBackClick = state.onBackClick) } - - NewsButtons(buttons = state.buttons, onButtonClick = state.onButtonClick) - } -} - -@Composable -private fun AppBar(onBackClick: () -> Unit) { - AppBarWithBackButton( - onBackClick = onBackClick, - text = stringResourceSafe(id = R.string.news), - containerColor = TangemTheme.colors.background.primary, - ) -} - -@Suppress("FunctionNaming") -private fun LazyListScope.NewsButtons( - buttons: ImmutableSet, - onButtonClick: (NewsUM.ButtonUM) -> Unit, -) { - items(buttons.toList()) { button -> - PrimaryButton( - text = stringResourceSafe(button.textResId), - onClick = { onButtonClick(button) }, - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 8.dp) - .fillMaxWidth(), - ) - } -} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/news/viewmodel/NewsViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/news/viewmodel/NewsViewModel.kt deleted file mode 100644 index f39c639ff5..0000000000 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/news/viewmodel/NewsViewModel.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.feature.tester.presentation.news.viewmodel - -import androidx.lifecycle.ViewModel -import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter -import com.tangem.feature.tester.presentation.navigation.TesterScreen -import com.tangem.feature.tester.presentation.news.state.NewsUM -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.persistentSetOf -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -@HiltViewModel -internal class NewsViewModel @Inject constructor() : ViewModel() { - - private val _uiState = MutableStateFlow(createInitialState()) - val uiState: StateFlow = _uiState - - private var router: InnerTesterRouter? = null - - fun setupNavigation(router: InnerTesterRouter) { - this.router = router - } - - private fun createInitialState(): NewsUM { - return NewsUM( - onBackClick = ::onBackClick, - buttons = persistentSetOf( - NewsUM.ButtonUM.NEWS_DETAILS, - NewsUM.ButtonUM.NEWS_DETAILS_BOTTOM_SHEET, - ), - onButtonClick = ::onButtonClick, - ) - } - - private fun onBackClick() { - router?.back() - } - - private fun onButtonClick(button: NewsUM.ButtonUM) { - when (button) { - NewsUM.ButtonUM.NEWS_DETAILS -> router?.open(TesterScreen.NEWS_DETAILS) - NewsUM.ButtonUM.NEWS_DETAILS_BOTTOM_SHEET -> router?.open(TesterScreen.NEWS_DETAILS_BOTTOM_SHEET) - } - } -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 53bbc68e17..83aadc8341 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -302,12 +302,6 @@ include(":features:yield-supply:impl") include(":features:feed:api") include(":features:feed:impl") - -include(":features:news:news-details:api") -include(":features:news:news-details:impl") - -include(":features:news:news-list:api") -include(":features:news:news-list:impl") // endregion Feature modules // region Domain modules