Updated on 2026-08-14
This commit is contained in:
parent
636ae3ee50
commit
a71b7ed4ba
43 changed files with 590 additions and 681 deletions
|
|
@ -1,7 +1,10 @@
|
||||||
package com.tangem.common.ui.news
|
package com.tangem.common.ui.news
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
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.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -9,6 +12,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawWithCache
|
import androidx.compose.ui.draw.drawWithCache
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
@ -49,6 +53,8 @@ internal fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = M
|
||||||
Text(
|
Text(
|
||||||
text = createdAt,
|
text = createdAt,
|
||||||
style = TangemTheme.typography.subtitle2,
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = TangemTheme.colors.text.secondary,
|
color = TangemTheme.colors.text.secondary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,3 @@ data class NewsRelatedTokenDto(
|
||||||
@Json(name = "symbol") val symbol: String,
|
@Json(name = "symbol") val symbol: String,
|
||||||
@Json(name = "name") val name: 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,
|
|
||||||
)
|
|
||||||
|
|
@ -18,3 +18,20 @@ data class NewsDetailsResponse(
|
||||||
@Json(name = "content") val content: String,
|
@Json(name = "content") val content: String,
|
||||||
@Json(name = "originalArticles") val originalArticles: List<NewsOriginalArticleDto>,
|
@Json(name = "originalArticles") val originalArticles: List<NewsOriginalArticleDto>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@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,
|
||||||
|
)
|
||||||
|
|
@ -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.NewsDetailsResponse
|
||||||
import com.tangem.datasource.api.news.models.response.NewsOriginalArticleDto
|
import com.tangem.datasource.api.news.models.response.NewsOriginalArticleDto
|
||||||
import com.tangem.datasource.api.news.models.response.NewsRelatedTokenDto
|
import com.tangem.datasource.api.news.models.response.NewsRelatedTokenDto
|
||||||
import com.tangem.domain.models.news.ArticleCategory
|
import com.tangem.domain.models.news.*
|
||||||
import com.tangem.domain.models.news.DetailedArticle
|
|
||||||
import com.tangem.domain.models.news.OriginalArticle
|
|
||||||
import com.tangem.domain.models.news.RelatedToken
|
|
||||||
import com.tangem.domain.models.news.ShortArticle
|
|
||||||
|
|
||||||
internal fun NewsDetailsResponse.toDomainDetailedArticle(): DetailedArticle {
|
internal fun NewsDetailsResponse.toDomainDetailedArticle(): DetailedArticle {
|
||||||
return DetailedArticle(
|
return DetailedArticle(
|
||||||
|
|
@ -54,7 +50,10 @@ internal fun NewsOriginalArticleDto.toDomainOriginalArticle(): OriginalArticle {
|
||||||
return OriginalArticle(
|
return OriginalArticle(
|
||||||
id = id,
|
id = id,
|
||||||
title = title,
|
title = title,
|
||||||
sourceName = sourceName,
|
source = Source(
|
||||||
|
id = source.id,
|
||||||
|
name = source.name,
|
||||||
|
),
|
||||||
locale = language,
|
locale = language,
|
||||||
publishedAt = publishedAt,
|
publishedAt = publishedAt,
|
||||||
url = url,
|
url = url,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import kotlinx.serialization.Serializable
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
* @param id - unique identifier of the article
|
* @param id - unique identifier of the article
|
||||||
* @param title - article title
|
* @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 locale - language of the article
|
||||||
* @param publishedAt - date of article publishing
|
* @param publishedAt - date of article publishing
|
||||||
* @param url - link to source of original article
|
* @param url - link to source of original article
|
||||||
|
|
@ -18,9 +18,15 @@ import kotlinx.serialization.Serializable
|
||||||
data class OriginalArticle(
|
data class OriginalArticle(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val title: String,
|
val title: String,
|
||||||
val sourceName: String,
|
val source: Source,
|
||||||
val locale: String,
|
val locale: String,
|
||||||
val publishedAt: String,
|
val publishedAt: String,
|
||||||
val url: String,
|
val url: String,
|
||||||
val imageUrl: String?,
|
val imageUrl: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Source(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.domain.news.usecase
|
package com.tangem.domain.news.usecase
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
import com.tangem.domain.models.news.DetailedArticle
|
import com.tangem.domain.models.news.DetailedArticle
|
||||||
import com.tangem.domain.news.repository.NewsRepository
|
import com.tangem.domain.news.repository.NewsRepository
|
||||||
import kotlinx.coroutines.flow.Flow
|
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).
|
* Prefetches the given article ids (can be called with current + next ids for pager preloading).
|
||||||
*/
|
*/
|
||||||
suspend fun prefetch(newsIds: Collection<Int>, language: String?) {
|
suspend fun prefetch(newsIds: Collection<Int>, language: String?): Either<Throwable, Unit> = Either.catch {
|
||||||
repository.fetchDetailedArticles(newsIds, language)
|
repository.fetchDetailedArticles(newsIds, language)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -24,4 +24,7 @@ sealed interface FeedEntryRoute {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object MarketTokenList : FeedEntryRoute
|
data object MarketTokenList : FeedEntryRoute
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NewsDetail(val articleId: Int, val preselectedArticlesId: List<Int>) : FeedEntryRoute
|
||||||
}
|
}
|
||||||
|
|
@ -15,10 +15,13 @@ import com.tangem.core.decompose.context.childByContext
|
||||||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
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.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.markets.TokenMarketParams
|
import com.tangem.domain.markets.TokenMarketParams
|
||||||
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
|
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
|
||||||
import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent
|
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.FeedEntryComponent
|
||||||
import com.tangem.features.feed.entry.components.FeedEntryRoute
|
import com.tangem.features.feed.entry.components.FeedEntryRoute
|
||||||
import com.tangem.features.feed.model.feed.FeedModelClickIntents
|
import com.tangem.features.feed.model.feed.FeedModelClickIntents
|
||||||
|
|
@ -55,6 +58,9 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
||||||
source = "Market",
|
source = "Market",
|
||||||
),
|
),
|
||||||
onBackClicked = { onChildBack() },
|
onBackClicked = { onChildBack() },
|
||||||
|
onArticleClick = { articleId, preselectedArticlesId ->
|
||||||
|
onArticleClick(articleId, preselectedArticlesId)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -73,8 +79,16 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onArticleClick(articleId: Int) {
|
override fun onArticleClick(articleId: Int, preselectedArticlesId: List<Int>) {
|
||||||
innerRouter.push(FeedEntryChildFactory.Child.NewsDetails)
|
innerRouter.push(
|
||||||
|
FeedEntryChildFactory.Child.NewsDetails(
|
||||||
|
params = DefaultNewsDetailsComponent.Params(
|
||||||
|
articleId = articleId,
|
||||||
|
onBackClicked = { onChildBack() },
|
||||||
|
preselectedArticlesId = preselectedArticlesId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenAllNews() {
|
override fun onOpenAllNews() {
|
||||||
|
|
@ -121,20 +135,25 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
val bottomSheetState = remember {
|
val background = TangemTheme.colors.background.tertiary
|
||||||
derivedStateOf { BottomSheetState.EXPANDED }
|
CompositionLocalProvider(
|
||||||
}
|
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
|
||||||
|
) {
|
||||||
|
val bottomSheetState = remember {
|
||||||
|
derivedStateOf { BottomSheetState.EXPANDED }
|
||||||
|
}
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
router.pop()
|
router.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
EntryContent(
|
EntryContent(
|
||||||
bottomSheetState = bottomSheetState,
|
bottomSheetState = bottomSheetState,
|
||||||
stackState = stack.subscribeAsState(),
|
stackState = stack.subscribeAsState(),
|
||||||
onHeaderSizeChange = {},
|
onHeaderSizeChange = {},
|
||||||
isOpenedInBottomSheet = false,
|
isOpenedInBottomSheet = false,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onChildBack() {
|
private fun onChildBack() {
|
||||||
|
|
@ -157,6 +176,9 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onBackClicked = { router.pop() },
|
onBackClicked = { router.pop() },
|
||||||
|
onArticleClick = { articleId, preselectedArticlesId ->
|
||||||
|
clickIntents.onArticleClick(articleId, preselectedArticlesId)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
FeedEntryRoute.MarketTokenList -> FeedEntryChildFactory.Child.TokenList(
|
FeedEntryRoute.MarketTokenList -> FeedEntryChildFactory.Child.TokenList(
|
||||||
|
|
@ -167,6 +189,13 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
||||||
shouldAlwaysShowSearchBar = false,
|
shouldAlwaysShowSearchBar = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
is FeedEntryRoute.NewsDetail -> FeedEntryChildFactory.Child.NewsDetails(
|
||||||
|
DefaultNewsDetailsComponent.Params(
|
||||||
|
articleId = entryRoute.articleId,
|
||||||
|
onBackClicked = { router.pop() },
|
||||||
|
preselectedArticlesId = entryRoute.preselectedArticlesId,
|
||||||
|
),
|
||||||
|
)
|
||||||
null -> FeedEntryChildFactory.Child.Feed
|
null -> FeedEntryChildFactory.Child.Feed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@Immutable
|
@Immutable
|
||||||
data object NewsDetails : Child
|
data class NewsDetails(val params: DefaultNewsDetailsComponent.Params) : Child
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createChild(
|
fun createChild(
|
||||||
|
|
@ -67,9 +67,10 @@ internal class FeedEntryChildFactory @Inject constructor(
|
||||||
params = child.params,
|
params = child.params,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Child.NewsDetails -> {
|
is Child.NewsDetails -> {
|
||||||
DefaultNewsDetailsComponent(
|
DefaultNewsDetailsComponent(
|
||||||
appComponentContext = appComponentContext,
|
appComponentContext = appComponentContext,
|
||||||
|
params = child.params,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Child.NewsList -> {
|
Child.NewsList -> {
|
||||||
|
|
|
||||||
|
|
@ -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.components.bottomsheets.state.BottomSheetState
|
||||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
||||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
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.account.featuretoggle.AccountsFeatureToggles
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.markets.TokenMarketParams
|
import com.tangem.domain.markets.TokenMarketParams
|
||||||
|
|
@ -87,13 +86,14 @@ internal class DefaultMarketsTokenDetailsComponent(
|
||||||
@Composable
|
@Composable
|
||||||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
||||||
val state by model.state.collectAsStateWithLifecycle()
|
val state by model.state.collectAsStateWithLifecycle()
|
||||||
|
val background = LocalMainBottomSheetColor.current.value
|
||||||
MarketsTokenDetailsTopBar(
|
MarketsTokenDetailsTopBar(
|
||||||
onBackClick = { params.onBackClicked() },
|
onBackClick = { params.onBackClicked() },
|
||||||
isBackButtonEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
|
isBackButtonEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
|
||||||
shouldShowPriceSubtitle = state.shouldShowPriceSubtitle,
|
shouldShowPriceSubtitle = state.shouldShowPriceSubtitle,
|
||||||
tokenName = state.tokenName,
|
tokenName = state.tokenName,
|
||||||
tokenPrice = state.priceText,
|
tokenPrice = state.priceText,
|
||||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
backgroundColor = background,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,6 +131,7 @@ internal class DefaultMarketsTokenDetailsComponent(
|
||||||
val shouldShowPortfolio: Boolean,
|
val shouldShowPortfolio: Boolean,
|
||||||
val analyticsParams: AnalyticsParams?,
|
val analyticsParams: AnalyticsParams?,
|
||||||
val onBackClicked: () -> Unit,
|
val onBackClicked: () -> Unit,
|
||||||
|
val onArticleClick: (articleId: Int, preselectedArticlesId: List<Int>) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import com.tangem.domain.onramp.model.OnrampSource
|
||||||
import com.tangem.domain.redux.ReduxStateHolder
|
import com.tangem.domain.redux.ReduxStateHolder
|
||||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||||
import com.tangem.domain.tokens.model.TokenActionsState
|
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.loader.PortfolioData
|
||||||
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM
|
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM
|
||||||
import com.tangem.features.feed.impl.R
|
import com.tangem.features.feed.impl.R
|
||||||
|
|
@ -41,6 +43,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||||
private val messageSender: UiMessageSender,
|
private val messageSender: UiMessageSender,
|
||||||
private val shareManager: ShareManager,
|
private val shareManager: ShareManager,
|
||||||
|
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val disabledActionsInDemoMode = buildSet {
|
private val disabledActionsInDemoMode = buildSet {
|
||||||
|
|
@ -186,22 +189,37 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
val (userWalletId, cryptoCurrencyStatus) = cryptoCurrencyData.let { currencyData ->
|
val (userWalletId, cryptoCurrencyStatus) = cryptoCurrencyData.let { currencyData ->
|
||||||
currencyData.userWallet.walletId to currencyData.status
|
currencyData.userWallet.walletId to currencyData.status
|
||||||
}
|
}
|
||||||
if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true) {
|
val tokenEnterStatus = yieldSupplyEnterStatusUseCase(userWalletId, cryptoCurrencyStatus).getOrNull()
|
||||||
router.push(
|
val isActiveYield = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true
|
||||||
AppRoute.YieldSupplyActive(
|
|
||||||
|
when {
|
||||||
|
tokenEnterStatus != null -> router.push(
|
||||||
|
AppRoute.CurrencyDetails(
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
currency = cryptoCurrencyStatus.currency,
|
||||||
apy = yieldSupplyApy,
|
navigationAction = NavigationAction.YieldSupply(
|
||||||
),
|
isActive = isActiveYield,
|
||||||
)
|
),
|
||||||
} else {
|
|
||||||
router.push(
|
|
||||||
AppRoute.YieldSupplyPromo(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
|
||||||
apy = yieldSupplyApy,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
isActiveYield -> {
|
||||||
|
router.push(
|
||||||
|
AppRoute.YieldSupplyActive(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||||
|
apy = yieldSupplyApy,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
router.push(
|
||||||
|
AppRoute.YieldSupplyPromo(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||||
|
apy = yieldSupplyApy,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ internal sealed class QuickActionUM(
|
||||||
data class YieldMode(
|
data class YieldMode(
|
||||||
private val apy: String,
|
private val apy: String,
|
||||||
) : QuickActionUM(
|
) : 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)),
|
description = resourceReference(R.string.yield_module_main_screen_promo_banner_message, wrappedList(apy)),
|
||||||
icon = R.drawable.ic_analytics_up_mini_24,
|
icon = R.drawable.ic_analytics_up_mini_24,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ internal data class TokenActionsBSContentUM(
|
||||||
iconRes = R.drawable.ic_staking_24,
|
iconRes = R.drawable.ic_staking_24,
|
||||||
),
|
),
|
||||||
YieldMode(
|
YieldMode(
|
||||||
text = resourceReference(R.string.yield_module_start_earning),
|
text = resourceReference(R.string.common_yield_mode),
|
||||||
iconRes = R.drawable.ic_analytics_up_mini_24,
|
iconRes = R.drawable.ic_analytics_up_mini_24,
|
||||||
),
|
),
|
||||||
;
|
;
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,63 @@ package com.tangem.features.feed.components.news.details
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
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.components.bottomsheets.state.BottomSheetState
|
||||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
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(
|
internal class DefaultNewsDetailsComponent(
|
||||||
appComponentContext: AppComponentContext,
|
appComponentContext: AppComponentContext,
|
||||||
|
private val params: Params,
|
||||||
) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext {
|
) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext {
|
||||||
|
|
||||||
@Composable
|
private val newsDetailsModel = getOrCreateModel<NewsDetailsModel, Params>(params = params)
|
||||||
override fun Title(bottomSheetState: State<BottomSheetState>) {}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(bottomSheetState: State<BottomSheetState>, modifier: Modifier) {}
|
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
||||||
|
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<BottomSheetState>, 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<Int> = emptyList(),
|
||||||
|
val tokenIds: List<String> = emptyList(),
|
||||||
|
val categoryIds: List<Int> = emptyList(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -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.feed.FeedComponentModel
|
||||||
import com.tangem.features.feed.model.market.details.MarketsTokenDetailsModel
|
import com.tangem.features.feed.model.market.details.MarketsTokenDetailsModel
|
||||||
import com.tangem.features.feed.model.market.list.MarketsListModel
|
import com.tangem.features.feed.model.market.list.MarketsListModel
|
||||||
|
import com.tangem.features.feed.model.news.details.NewsDetailsModel
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -29,4 +30,9 @@ internal interface ModelModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ClassKey(MarketsTokenDetailsModel::class)
|
@ClassKey(MarketsTokenDetailsModel::class)
|
||||||
fun provideMarketsTokenDetailsModel(model: MarketsTokenDetailsModel): Model
|
fun provideMarketsTokenDetailsModel(model: MarketsTokenDetailsModel): Model
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(NewsDetailsModel::class)
|
||||||
|
fun provideNewsDetailsModel(model: NewsDetailsModel): Model
|
||||||
}
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ import org.joda.time.DateTime
|
||||||
import org.joda.time.DateTimeZone
|
import org.joda.time.DateTimeZone
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.collections.all
|
import kotlin.collections.all
|
||||||
|
import kotlin.collections.map
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
|
|
@ -311,7 +312,17 @@ internal class FeedComponentModel @Inject constructor(
|
||||||
params.feedClickIntents.onMarketOpenClick(sortBy)
|
params.feedClickIntents.onMarketOpenClick(sortBy)
|
||||||
},
|
},
|
||||||
onArticleClick = { articleId ->
|
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 = {
|
onOpenAllNews = {
|
||||||
params.feedClickIntents.onOpenAllNews()
|
params.feedClickIntents.onOpenAllNews()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@ import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||||
internal interface FeedModelClickIntents {
|
internal interface FeedModelClickIntents {
|
||||||
fun onMarketItemClick(token: TokenMarketParams, appCurrency: AppCurrency)
|
fun onMarketItemClick(token: TokenMarketParams, appCurrency: AppCurrency)
|
||||||
fun onMarketOpenClick(sortBy: SortByTypeUM?)
|
fun onMarketOpenClick(sortBy: SortByTypeUM?)
|
||||||
fun onArticleClick(articleId: Int)
|
fun onArticleClick(articleId: Int, preselectedArticlesId: List<Int> = emptyList())
|
||||||
fun onOpenAllNews()
|
fun onOpenAllNews()
|
||||||
}
|
}
|
||||||
|
|
@ -241,9 +241,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
||||||
onShouldShowPriceSubtitleChange = ::onShouldShowPriceSubtitleChange,
|
onShouldShowPriceSubtitleChange = ::onShouldShowPriceSubtitleChange,
|
||||||
relatedNews = MarketsTokenDetailsUM.RelatedNews(
|
relatedNews = MarketsTokenDetailsUM.RelatedNews(
|
||||||
articles = persistentListOf(),
|
articles = persistentListOf(),
|
||||||
onArticledClicked = {
|
onArticledClicked = {},
|
||||||
// TODO in [REDACTED_JIRA]
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -313,9 +311,16 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
||||||
),
|
),
|
||||||
).onRight { articles ->
|
).onRight { articles ->
|
||||||
state.update { marketsTokenDetailsUM ->
|
state.update { marketsTokenDetailsUM ->
|
||||||
|
val relatedNews = relatedNewsConverter.convert(articles)
|
||||||
marketsTokenDetailsUM.copy(
|
marketsTokenDetailsUM.copy(
|
||||||
relatedNews = marketsTokenDetailsUM.relatedNews.copy(
|
relatedNews = marketsTokenDetailsUM.relatedNews.copy(
|
||||||
articles = relatedNewsConverter.convert(articles),
|
articles = relatedNews,
|
||||||
|
onArticledClicked = { articledId ->
|
||||||
|
params.onArticleClick(
|
||||||
|
/* articledId */ articledId,
|
||||||
|
/* preselectedIds */ relatedNews.map { it.id },
|
||||||
|
)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.LabelLeadingContentUM
|
||||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
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.data.common.currency.getTokenIconUrlFromDefaultHost
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.news.ShortArticle
|
import com.tangem.domain.models.news.ShortArticle
|
||||||
import com.tangem.features.feed.impl.R
|
import com.tangem.features.feed.ui.utils.mapFormattedDate
|
||||||
import com.tangem.utils.StringsSigns
|
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlinx.collections.immutable.toPersistentSet
|
import kotlinx.collections.immutable.toPersistentSet
|
||||||
import org.joda.time.DateTime
|
|
||||||
|
|
||||||
class RelatedNewsConverter : Converter<List<ShortArticle>, ImmutableList<ArticleConfigUM>> {
|
class RelatedNewsConverter : Converter<List<ShortArticle>, ImmutableList<ArticleConfigUM>> {
|
||||||
|
|
||||||
|
|
@ -51,34 +45,4 @@ class RelatedNewsConverter : Converter<List<ShortArticle>, ImmutableList<Article
|
||||||
}
|
}
|
||||||
return (categoryLabels + tokenLabels).toPersistentSet()
|
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +122,7 @@ internal class MarketsListModel @Inject constructor(
|
||||||
flow4 = shouldShowYieldModeMarketPromoUseCase(
|
flow4 = shouldShowYieldModeMarketPromoUseCase(
|
||||||
appCurrency = currentAppCurrency.value,
|
appCurrency = currentAppCurrency.value,
|
||||||
interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
|
interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
|
||||||
),
|
).conflate(),
|
||||||
) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo ->
|
) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo ->
|
||||||
MarketsItemsData(
|
MarketsItemsData(
|
||||||
items = uiItems,
|
items = uiItems,
|
||||||
|
|
@ -138,7 +138,7 @@ internal class MarketsListModel @Inject constructor(
|
||||||
flow3 = shouldShowYieldModeMarketPromoUseCase(
|
flow3 = shouldShowYieldModeMarketPromoUseCase(
|
||||||
appCurrency = currentAppCurrency.value,
|
appCurrency = currentAppCurrency.value,
|
||||||
interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
|
interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
|
||||||
),
|
).conflate(),
|
||||||
) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo ->
|
) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo ->
|
||||||
MarketsItemsData(
|
MarketsItemsData(
|
||||||
items = uiItems,
|
items = uiItems,
|
||||||
|
|
|
||||||
|
|
@ -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<DefaultNewsDetailsComponent.Params>()
|
||||||
|
|
||||||
|
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<NewsDetailsUM> = _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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<DetailedArticle, ArticleUM> {
|
||||||
|
|
||||||
|
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<LabelUM> {
|
||||||
|
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<SourceUM> {
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -357,7 +357,7 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM,
|
||||||
articleConfigUM = article,
|
articleConfigUM = article,
|
||||||
onArticleClick = { feedListCallbacks.onArticleClick(article.id) },
|
onArticleClick = { feedListCallbacks.onArticleClick(article.id) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(164.dp)
|
.heightIn(min = 164.dp)
|
||||||
.width(216.dp),
|
.width(216.dp),
|
||||||
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
|
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
|
||||||
)
|
)
|
||||||
|
|
@ -365,7 +365,9 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM,
|
||||||
|
|
||||||
item {
|
item {
|
||||||
ShowMoreArticlesCard(
|
ShowMoreArticlesCard(
|
||||||
modifier = Modifier.size(width = 216.dp, height = 164.dp),
|
modifier = Modifier
|
||||||
|
.width(216.dp)
|
||||||
|
.heightIn(min = 164.dp),
|
||||||
onClick = feedListCallbacks.onOpenAllNews,
|
onClick = feedListCallbacks.onOpenAllNews,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.LabelLeadingContentUM
|
||||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
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.data.common.currency.getTokenIconUrlFromDefaultHost
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.news.ShortArticle
|
import com.tangem.domain.models.news.ShortArticle
|
||||||
import com.tangem.domain.models.news.TrendingNews
|
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.Provider
|
||||||
import com.tangem.utils.StringsSigns
|
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlinx.collections.immutable.toPersistentSet
|
import kotlinx.collections.immutable.toPersistentSet
|
||||||
import org.joda.time.DateTime
|
|
||||||
|
|
||||||
internal class TrendingNewsStateFactory(
|
internal class TrendingNewsStateFactory(
|
||||||
private val currentStateProvider: Provider<FeedListUM>,
|
private val currentStateProvider: Provider<FeedListUM>,
|
||||||
|
|
@ -111,34 +105,4 @@ internal class TrendingNewsStateFactory(
|
||||||
}
|
}
|
||||||
return (categoryLabels + tokenLabels).toPersistentSet()
|
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -142,14 +142,14 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val marketsNotification = state.marketsNotificationUM
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
state.isInSearchMode.not() &&
|
state.list !is ListUM.LoadingError && state.isInSearchMode.not() &&
|
||||||
state.selectedSortBy != SortByTypeUM.YieldSupply,
|
state.selectedSortBy != SortByTypeUM.YieldSupply,
|
||||||
) {
|
) {
|
||||||
|
val wrappedNotification = remember(this) { state.marketsNotificationUM }
|
||||||
val showMore = stringResourceSafe(R.string.common_show_more)
|
val showMore = stringResourceSafe(R.string.common_show_more)
|
||||||
|
|
||||||
when (marketsNotification) {
|
when (wrappedNotification) {
|
||||||
is MarketsNotificationUM.YieldSupplyPromo -> {
|
is MarketsNotificationUM.YieldSupplyPromo -> {
|
||||||
val description = stringResourceSafe(
|
val description = stringResourceSafe(
|
||||||
R.string.markets_yield_supply_banner_description,
|
R.string.markets_yield_supply_banner_description,
|
||||||
|
|
@ -165,7 +165,7 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif
|
||||||
}
|
}
|
||||||
|
|
||||||
YieldSupplyInMarketsPromoNotification(
|
YieldSupplyInMarketsPromoNotification(
|
||||||
config = marketsNotification.config.copy(
|
config = wrappedNotification.config.copy(
|
||||||
subtitle = clickableDescription,
|
subtitle = clickableDescription,
|
||||||
),
|
),
|
||||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||||
|
|
|
||||||
|
|
@ -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 android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
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.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
|
@ -11,57 +15,57 @@ import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.VerticalDivider
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.common.ui.news.ArticleHeader
|
import com.tangem.common.ui.news.ArticleHeader
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
||||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
import com.tangem.core.ui.components.SpacerH
|
||||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
import com.tangem.core.ui.components.SpacerW
|
||||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||||
import com.tangem.core.ui.components.pager.PagerIndicator
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
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
|
@Composable
|
||||||
fun NewsDetailsContent(
|
internal fun NewsDetailsContent(state: NewsDetailsUM, modifier: Modifier = Modifier) {
|
||||||
state: NewsDetailsUM,
|
val background = LocalMainBottomSheetColor.current.value
|
||||||
onBackClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
isBottomSheetMode: Boolean = false,
|
|
||||||
) {
|
|
||||||
val pagerState = rememberPagerState(
|
val pagerState = rememberPagerState(
|
||||||
initialPage = state.selectedArticleIndex,
|
initialPage = state.selectedArticleIndex,
|
||||||
pageCount = { state.articles.size },
|
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(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(TangemTheme.colors.background.tertiary)
|
.background(background),
|
||||||
.conditionalCompose(isBottomSheetMode) { systemBarsPadding() },
|
|
||||||
) {
|
) {
|
||||||
TangemTopAppBar(
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
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(),
|
|
||||||
) {
|
|
||||||
if (state.articles.isNotEmpty()) {
|
if (state.articles.isNotEmpty()) {
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
|
|
@ -73,20 +77,13 @@ fun NewsDetailsContent(
|
||||||
onLikeClick = state.onLikeClick,
|
onLikeClick = state.onLikeClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.articles.size > 1) {
|
if (state.articles.size > 1) {
|
||||||
Column(
|
PagerIndicator(
|
||||||
|
pagerState = pagerState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.fillMaxWidth(),
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
) {
|
)
|
||||||
PagerIndicator(
|
|
||||||
pagerState = pagerState,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.padding(bottom = 16.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,27 +94,27 @@ fun NewsDetailsContent(
|
||||||
@Composable
|
@Composable
|
||||||
private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onLikeClick: () -> Unit) {
|
private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onLikeClick: () -> Unit) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val pagerHeight = 48.dp
|
|
||||||
val contentPadding = 56.dp
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = modifier.padding(horizontal = 16.dp),
|
modifier = modifier,
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(bottom = 56.dp + WindowInsets.navigationBars.getBottom(density).dp),
|
||||||
bottom = contentPadding + pagerHeight + WindowInsets.navigationBars.getBottom(density).dp,
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
ArticleHeader(
|
ArticleHeader(
|
||||||
title = article.title,
|
title = article.title,
|
||||||
createdAt = article.createdAt,
|
createdAt = article.createdAt.resolveReference(),
|
||||||
score = article.score,
|
score = article.score,
|
||||||
tags = article.tags,
|
tags = article.tags,
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
modifier = Modifier
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (article.shortContent.isNotEmpty()) {
|
if (article.shortContent.isNotEmpty()) {
|
||||||
QuickRecap(
|
QuickRecap(
|
||||||
content = article.shortContent,
|
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,
|
text = article.content,
|
||||||
style = TangemTheme.typography.body1,
|
style = TangemTheme.typography.body1,
|
||||||
color = TangemTheme.colors.text.primary1,
|
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(
|
SecondaryButtonIconStart(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
iconResId = R.drawable.ic_heart_20,
|
iconResId = R.drawable.ic_heart_20,
|
||||||
text = "Like", // TODO [REDACTED_TASK_KEY] export to strings
|
text = stringResourceSafe(R.string.news_like),
|
||||||
size = TangemButtonSize.RoundedAction,
|
size = TangemButtonSize.RoundedAction,
|
||||||
onClick = onLikeClick,
|
onClick = onLikeClick,
|
||||||
)
|
)
|
||||||
|
|
@ -140,14 +140,16 @@ private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onL
|
||||||
// TODO [REDACTED_TASK_KEY] add related tokens block
|
// TODO [REDACTED_TASK_KEY] add related tokens block
|
||||||
|
|
||||||
if (article.sources.isNotEmpty()) {
|
if (article.sources.isNotEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
SpacerH(24.dp)
|
||||||
Row {
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Sources", // TODO [REDACTED_TASK_KEY] export to strings
|
text = stringResourceSafe(R.string.news_sources),
|
||||||
style = TangemTheme.typography.h3,
|
style = TangemTheme.typography.h3,
|
||||||
color = TangemTheme.colors.text.primary1,
|
color = TangemTheme.colors.text.primary1,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = "${article.sources.size}",
|
text = "${article.sources.size}",
|
||||||
style = TangemTheme.typography.h3,
|
style = TangemTheme.typography.h3,
|
||||||
|
|
@ -159,23 +161,19 @@ private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onL
|
||||||
|
|
||||||
if (article.sources.isNotEmpty()) {
|
if (article.sources.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
val sourcesPagerState = rememberPagerState(
|
LazyRow(
|
||||||
pageCount = { article.sources.size },
|
modifier = Modifier.padding(vertical = 12.dp),
|
||||||
)
|
state = rememberLazyListState(),
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||||
HorizontalPager(
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
state = sourcesPagerState,
|
) {
|
||||||
modifier = Modifier
|
items(
|
||||||
.fillMaxWidth(),
|
items = article.sources,
|
||||||
pageSpacing = 12.dp,
|
key = SourceUM::id,
|
||||||
contentPadding = PaddingValues(horizontal = 0.dp),
|
) { source ->
|
||||||
) { page ->
|
SourceItem(source = source)
|
||||||
SourceItem(
|
}
|
||||||
source = article.sources[page],
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +204,7 @@ private fun QuickRecap(content: String, modifier: Modifier = Modifier) {
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Quick recap", // TODO [REDACTED_TASK_KEY] export to strings
|
text = stringResourceSafe(R.string.news_quick_recap),
|
||||||
style = TangemTheme.typography.subtitle2,
|
style = TangemTheme.typography.subtitle2,
|
||||||
color = TangemTheme.colors.text.accent,
|
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) {
|
private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.widthIn(max = 216.dp)
|
||||||
|
.heightIn(min = 132.dp)
|
||||||
.background(
|
.background(
|
||||||
color = TangemTheme.colors.background.primary,
|
color = TangemTheme.colors.background.action,
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
)
|
)
|
||||||
|
.clickable(onClick = source.onClick)
|
||||||
.padding(12.dp),
|
.padding(12.dp),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -239,25 +240,30 @@ private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) {
|
||||||
painter = painterResource(id = R.drawable.ic_explore_16),
|
painter = painterResource(id = R.drawable.ic_explore_16),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = TangemTheme.colors.icon.informative,
|
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(
|
||||||
text = source.sourceName,
|
text = source.source.name,
|
||||||
style = TangemTheme.typography.caption1,
|
style = TangemTheme.typography.caption1,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = TangemTheme.colors.text.tertiary,
|
color = TangemTheme.colors.text.tertiary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (source.title.isNotEmpty()) {
|
if (source.title.isNotEmpty()) {
|
||||||
|
SpacerH(4.dp)
|
||||||
Text(
|
Text(
|
||||||
text = source.title,
|
text = source.title,
|
||||||
style = TangemTheme.typography.subtitle2,
|
style = TangemTheme.typography.subtitle2,
|
||||||
color = TangemTheme.colors.text.primary1,
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.padding(bottom = 12.dp),
|
modifier = Modifier.padding(bottom = 12.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = source.publishedAt,
|
text = source.publishedAt.resolveReference(),
|
||||||
style = TangemTheme.typography.caption2,
|
style = TangemTheme.typography.caption2,
|
||||||
color = TangemTheme.colors.text.tertiary,
|
color = TangemTheme.colors.text.tertiary,
|
||||||
)
|
)
|
||||||
|
|
@ -273,10 +279,10 @@ private fun PreviewNewsDetailsContent() {
|
||||||
state = NewsDetailsUM(
|
state = NewsDetailsUM(
|
||||||
articles = MockArticlesFactory.createMockArticles(),
|
articles = MockArticlesFactory.createMockArticles(),
|
||||||
selectedArticleIndex = 0,
|
selectedArticleIndex = 0,
|
||||||
onShareClick = { },
|
onShareClick = {},
|
||||||
onLikeClick = { },
|
onLikeClick = {},
|
||||||
|
onBackClick = {},
|
||||||
),
|
),
|
||||||
onBackClick = { },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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.LabelSize
|
||||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
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.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
// TODO [REDACTED_TASK_KEY] remove mock data
|
|
||||||
@Suppress("MaximumLineLength", "LongMethod")
|
@Suppress("MaximumLineLength", "LongMethod")
|
||||||
object MockArticlesFactory {
|
internal object MockArticlesFactory {
|
||||||
fun createMockArticles(): ImmutableList<ArticleUM> = listOf(
|
fun createMockArticles(): ImmutableList<ArticleUM> = listOf(
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 1,
|
id = 1,
|
||||||
title = "SEC delays decisions on ETH-staking ETFs and spot XRP/SOL funds",
|
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,
|
score = 6.5f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("Regulation"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("Regulation"), size = LabelSize.BIG),
|
||||||
|
|
@ -27,23 +24,31 @@ object MockArticlesFactory {
|
||||||
SourceUM(
|
SourceUM(
|
||||||
id = 1,
|
id = 1,
|
||||||
title = "Deeper liquidity could drive crypto market beyond \$6T",
|
title = "Deeper liquidity could drive crypto market beyond \$6T",
|
||||||
sourceName = "cointelegraph",
|
source = Source(
|
||||||
publishedAt = "1h ago",
|
id = 11,
|
||||||
|
name = "Coin-telegraph",
|
||||||
|
),
|
||||||
|
publishedAt = TextReference.Str("1h ago"),
|
||||||
url = "https://cointelegraph.com",
|
url = "https://cointelegraph.com",
|
||||||
|
onClick = {},
|
||||||
),
|
),
|
||||||
SourceUM(
|
SourceUM(
|
||||||
id = 2,
|
id = 2,
|
||||||
title = "Top gainers and losers in crypto this week",
|
title = "Top gainers and losers in crypto this week",
|
||||||
sourceName = "Investing",
|
source = Source(
|
||||||
publishedAt = "2h ago",
|
id = 10,
|
||||||
|
name = "Investing",
|
||||||
|
),
|
||||||
|
publishedAt = TextReference.Str("2h ago"),
|
||||||
url = "https://investing.com",
|
url = "https://investing.com",
|
||||||
|
onClick = {},
|
||||||
),
|
),
|
||||||
).toPersistentList(),
|
).toPersistentList(),
|
||||||
),
|
),
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 2,
|
id = 2,
|
||||||
title = "Bitcoin ETFs log 4th straight day of inflows (+\$550M)",
|
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,
|
score = 8.2f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("BTC"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("BTC"), size = LabelSize.BIG),
|
||||||
|
|
@ -55,16 +60,20 @@ object MockArticlesFactory {
|
||||||
SourceUM(
|
SourceUM(
|
||||||
id = 3,
|
id = 3,
|
||||||
title = "Bitcoin ETFs see massive inflows",
|
title = "Bitcoin ETFs see massive inflows",
|
||||||
sourceName = "Bloomberg",
|
source = Source(
|
||||||
publishedAt = "3h ago",
|
id = 12,
|
||||||
|
name = "Bloomberg",
|
||||||
|
),
|
||||||
|
publishedAt = TextReference.Str("3h ago"),
|
||||||
url = "https://bloomberg.com",
|
url = "https://bloomberg.com",
|
||||||
|
onClick = {},
|
||||||
),
|
),
|
||||||
).toPersistentList(),
|
).toPersistentList(),
|
||||||
),
|
),
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 3,
|
id = 3,
|
||||||
title = "Ethereum network upgrade scheduled for Q2 2025",
|
title = "Ethereum network upgrade scheduled for Q2 2025",
|
||||||
createdAt = "20 Jun, 18:30",
|
createdAt = TextReference.Str("20 Jun, 18:30"),
|
||||||
score = 7.8f,
|
score = 7.8f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("ETH"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("ETH"), size = LabelSize.BIG),
|
||||||
|
|
@ -76,16 +85,20 @@ object MockArticlesFactory {
|
||||||
SourceUM(
|
SourceUM(
|
||||||
id = 4,
|
id = 4,
|
||||||
title = "Ethereum core devs announce upgrade",
|
title = "Ethereum core devs announce upgrade",
|
||||||
sourceName = "CoinDesk",
|
source = Source(
|
||||||
publishedAt = "5h ago",
|
id = 15,
|
||||||
|
name = "CoinDesk",
|
||||||
|
),
|
||||||
|
publishedAt = TextReference.Str("5h ago"),
|
||||||
url = "https://coindesk.com",
|
url = "https://coindesk.com",
|
||||||
|
onClick = {},
|
||||||
),
|
),
|
||||||
).toPersistentList(),
|
).toPersistentList(),
|
||||||
),
|
),
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 4,
|
id = 4,
|
||||||
title = "Solana surpasses Ethereum in daily transaction volume",
|
title = "Solana surpasses Ethereum in daily transaction volume",
|
||||||
createdAt = "20 Jun, 16:00",
|
createdAt = TextReference.Str("20 Jun, 16:00"),
|
||||||
score = 9.1f,
|
score = 9.1f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("SOL"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("SOL"), size = LabelSize.BIG),
|
||||||
|
|
@ -97,16 +110,20 @@ object MockArticlesFactory {
|
||||||
SourceUM(
|
SourceUM(
|
||||||
id = 5,
|
id = 5,
|
||||||
title = "Solana transactions hit record",
|
title = "Solana transactions hit record",
|
||||||
sourceName = "The Block",
|
source = Source(
|
||||||
publishedAt = "7h ago",
|
id = 18,
|
||||||
|
name = "Times",
|
||||||
|
),
|
||||||
|
publishedAt = TextReference.Str("7h ago"),
|
||||||
url = "https://theblock.co",
|
url = "https://theblock.co",
|
||||||
|
onClick = {},
|
||||||
),
|
),
|
||||||
).toPersistentList(),
|
).toPersistentList(),
|
||||||
),
|
),
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 5,
|
id = 5,
|
||||||
title = "DeFi protocol launches innovative yield farming strategy",
|
title = "DeFi protocol launches innovative yield farming strategy",
|
||||||
createdAt = "20 Jun, 14:20",
|
createdAt = TextReference.Str("20 Jun, 14:20"),
|
||||||
score = 6.9f,
|
score = 6.9f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("DeFi"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("DeFi"), size = LabelSize.BIG),
|
||||||
|
|
@ -118,7 +135,7 @@ object MockArticlesFactory {
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 6,
|
id = 6,
|
||||||
title = "Crypto regulation bill advances in US Senate",
|
title = "Crypto regulation bill advances in US Senate",
|
||||||
createdAt = "20 Jun, 12:45",
|
createdAt = TextReference.Str("20 Jun, 12:45"),
|
||||||
score = 8.7f,
|
score = 8.7f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("Regulation"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("Regulation"), size = LabelSize.BIG),
|
||||||
|
|
@ -131,7 +148,7 @@ object MockArticlesFactory {
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 7,
|
id = 7,
|
||||||
title = "Major bank announces crypto custody services",
|
title = "Major bank announces crypto custody services",
|
||||||
createdAt = "20 Jun, 10:30",
|
createdAt = TextReference.Str("20 Jun, 10:30"),
|
||||||
score = 7.3f,
|
score = 7.3f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("Adoption"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("Adoption"), size = LabelSize.BIG),
|
||||||
|
|
@ -143,7 +160,7 @@ object MockArticlesFactory {
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 8,
|
id = 8,
|
||||||
title = "NFT marketplace reports 300% increase in trading volume",
|
title = "NFT marketplace reports 300% increase in trading volume",
|
||||||
createdAt = "20 Jun, 08:15",
|
createdAt = TextReference.Str("20 Jun, 08:15"),
|
||||||
score = 5.8f,
|
score = 5.8f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("NFT"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("NFT"), size = LabelSize.BIG),
|
||||||
|
|
@ -155,7 +172,7 @@ object MockArticlesFactory {
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 9,
|
id = 9,
|
||||||
title = "Layer 2 solution achieves 100,000 TPS milestone",
|
title = "Layer 2 solution achieves 100,000 TPS milestone",
|
||||||
createdAt = "19 Jun, 22:00",
|
createdAt = TextReference.Str("19 Jun, 22:00"),
|
||||||
score = 8.5f,
|
score = 8.5f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("Technology"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("Technology"), size = LabelSize.BIG),
|
||||||
|
|
@ -168,7 +185,7 @@ object MockArticlesFactory {
|
||||||
ArticleUM(
|
ArticleUM(
|
||||||
id = 10,
|
id = 10,
|
||||||
title = "Stablecoin market cap reaches new all-time high",
|
title = "Stablecoin market cap reaches new all-time high",
|
||||||
createdAt = "19 Jun, 19:30",
|
createdAt = TextReference.Str("19 Jun, 19:30"),
|
||||||
score = 7.6f,
|
score = 7.6f,
|
||||||
tags = listOf(
|
tags = listOf(
|
||||||
LabelUM(text = TextReference.Str("Stablecoins"), size = LabelSize.BIG),
|
LabelUM(text = TextReference.Str("Stablecoins"), size = LabelSize.BIG),
|
||||||
|
|
@ -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.components.label.entity.LabelUM
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
// TODO [REDACTED_TASK_KEY] make internal
|
internal data class NewsDetailsUM(
|
||||||
data class NewsDetailsUM(
|
|
||||||
val articles: ImmutableList<ArticleUM>,
|
val articles: ImmutableList<ArticleUM>,
|
||||||
val selectedArticleIndex: Int,
|
val selectedArticleIndex: Int,
|
||||||
val onShareClick: () -> Unit,
|
val onShareClick: () -> Unit,
|
||||||
val onLikeClick: () -> Unit,
|
val onLikeClick: () -> Unit,
|
||||||
|
val onBackClick: () -> Unit,
|
||||||
|
val onArticleIndexChanged: (Int) -> Unit = {},
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO [REDACTED_TASK_KEY] make internal
|
internal data class ArticleUM(
|
||||||
data class ArticleUM(
|
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val title: String,
|
val title: String,
|
||||||
val createdAt: String,
|
val createdAt: TextReference,
|
||||||
val score: Float,
|
val score: Float,
|
||||||
val tags: ImmutableList<LabelUM>,
|
val tags: ImmutableList<LabelUM>,
|
||||||
val shortContent: String,
|
val shortContent: String,
|
||||||
|
|
@ -23,11 +24,16 @@ data class ArticleUM(
|
||||||
val sources: ImmutableList<SourceUM>,
|
val sources: ImmutableList<SourceUM>,
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO [REDACTED_TASK_KEY] make internal
|
internal data class SourceUM(
|
||||||
data class SourceUM(
|
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val title: String,
|
val title: String,
|
||||||
val sourceName: String,
|
val source: Source,
|
||||||
val publishedAt: String,
|
val publishedAt: TextReference,
|
||||||
val url: String,
|
val url: String,
|
||||||
|
val onClick: () -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal data class Source(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
)
|
)
|
||||||
|
|
@ -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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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<BottomSheetState>,
|
|
||||||
onHeaderSizeChange: (Dp) -> Unit,
|
|
||||||
modifier: Modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
interface Factory : ComponentFactory<Params, NewsDetailsComponent>
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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<BottomSheetState>,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<NewsDetailsComponent.Params>()
|
|
||||||
|
|
||||||
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<NewsDetailsUM> = _uiState.asStateFlow()
|
|
||||||
|
|
||||||
fun onBackClick() {
|
|
||||||
router.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -59,10 +59,6 @@ dependencies {
|
||||||
/** Feature Apis */
|
/** Feature Apis */
|
||||||
implementation(projects.features.tester.api)
|
implementation(projects.features.tester.api)
|
||||||
implementation(projects.features.pushNotifications.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 */
|
/* SDK */
|
||||||
implementation(tangemDeps.blockchain)
|
implementation(tangemDeps.blockchain)
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
import com.tangem.core.navigation.finisher.AppFinisher
|
import com.tangem.core.navigation.finisher.AppFinisher
|
||||||
import com.tangem.core.ui.UiDependencies
|
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.res.TangemTheme
|
||||||
import com.tangem.core.ui.screen.ComposeActivity
|
import com.tangem.core.ui.screen.ComposeActivity
|
||||||
import com.tangem.feature.tester.presentation.accounts.ui.AccountsScreen
|
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.providers.viewmodel.BlockchainProvidersViewModel
|
||||||
import com.tangem.feature.tester.presentation.testpush.ui.TestPushScreen
|
import com.tangem.feature.tester.presentation.testpush.ui.TestPushScreen
|
||||||
import com.tangem.feature.tester.presentation.testpush.viewmodel.TestPushViewModel
|
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 dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -92,7 +82,6 @@ internal class TesterActivity : ComposeActivity() {
|
||||||
ButtonUM.TESTER_ACTIONS,
|
ButtonUM.TESTER_ACTIONS,
|
||||||
ButtonUM.TEST_PUSHES,
|
ButtonUM.TEST_PUSHES,
|
||||||
ButtonUM.ACCOUNTS,
|
ButtonUM.ACCOUNTS,
|
||||||
ButtonUM.NEWS,
|
|
||||||
),
|
),
|
||||||
onButtonClick = { buttonUM ->
|
onButtonClick = { buttonUM ->
|
||||||
val route = when (buttonUM) {
|
val route = when (buttonUM) {
|
||||||
|
|
@ -103,7 +92,6 @@ internal class TesterActivity : ComposeActivity() {
|
||||||
ButtonUM.TESTER_ACTIONS -> TesterScreen.TESTER_ACTIONS
|
ButtonUM.TESTER_ACTIONS -> TesterScreen.TESTER_ACTIONS
|
||||||
ButtonUM.TEST_PUSHES -> TesterScreen.TEST_PUSHES
|
ButtonUM.TEST_PUSHES -> TesterScreen.TEST_PUSHES
|
||||||
ButtonUM.ACCOUNTS -> TesterScreen.ACCOUNTS
|
ButtonUM.ACCOUNTS -> TesterScreen.ACCOUNTS
|
||||||
ButtonUM.NEWS -> TesterScreen.NEWS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
innerTesterRouter.open(route)
|
innerTesterRouter.open(route)
|
||||||
|
|
@ -174,64 +162,6 @@ internal class TesterActivity : ComposeActivity() {
|
||||||
|
|
||||||
AccountsScreen(state)
|
AccountsScreen(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(route = TesterScreen.NEWS.name) {
|
|
||||||
val viewModel = hiltViewModel<NewsViewModel>().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<ArticleUM>,
|
|
||||||
) : TangemBottomSheetConfigContent
|
|
||||||
|
|
||||||
val content = NewsDetailsBottomSheetContent(
|
|
||||||
articles = MockArticlesFactory.createMockArticles(),
|
|
||||||
)
|
|
||||||
|
|
||||||
TangemBottomSheet<NewsDetailsBottomSheetContent>(
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +25,5 @@ data class TesterMenuUM(
|
||||||
TESTER_ACTIONS(R.string.tester_actions),
|
TESTER_ACTIONS(R.string.tester_actions),
|
||||||
TEST_PUSHES(R.string.test_push),
|
TEST_PUSHES(R.string.test_push),
|
||||||
ACCOUNTS(R.string.accounts),
|
ACCOUNTS(R.string.accounts),
|
||||||
NEWS(R.string.news),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,4 @@ internal enum class TesterScreen {
|
||||||
BLOCKCHAIN_PROVIDERS,
|
BLOCKCHAIN_PROVIDERS,
|
||||||
TEST_PUSHES,
|
TEST_PUSHES,
|
||||||
ACCOUNTS,
|
ACCOUNTS,
|
||||||
NEWS,
|
|
||||||
NEWS_DETAILS,
|
|
||||||
NEWS_DETAILS_BOTTOM_SHEET,
|
|
||||||
}
|
}
|
||||||
|
|
@ -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<ButtonUM>,
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<NewsUM.ButtonUM>,
|
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<NewsUM> = _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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -302,12 +302,6 @@ include(":features:yield-supply:impl")
|
||||||
|
|
||||||
include(":features:feed:api")
|
include(":features:feed:api")
|
||||||
include(":features:feed:impl")
|
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
|
// endregion Feature modules
|
||||||
|
|
||||||
// region Domain modules
|
// region Domain modules
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue