Updated on 2026-08-14
This commit is contained in:
parent
2396225311
commit
23ddf1a665
34 changed files with 1301 additions and 226 deletions
|
|
@ -1,76 +0,0 @@
|
|||
package com.tangem.common.ui.news
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
internal fun ArticleBadge(articleTagUM: ArticleTagUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = modifier
|
||||
.heightIn(min = 24.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.icon.informative.copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
when (articleTagUM) {
|
||||
is ArticleTagUM.Category -> Unit
|
||||
is ArticleTagUM.Token -> {
|
||||
CurrencyIcon(
|
||||
state = articleTagUM.iconState,
|
||||
shouldDisplayNetwork = false,
|
||||
iconSize = 16.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = articleTagUM.title.resolveReference(),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ArticleBadgePreview() {
|
||||
TangemThemePreview {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
ArticleBadge(
|
||||
articleTagUM = ArticleTagUM.Token(
|
||||
TextReference.Str("BTC"),
|
||||
iconState = CurrencyIconState.CoinIcon(
|
||||
url = "",
|
||||
fallbackResId = 0,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
ArticleBadge(
|
||||
articleTagUM = ArticleTagUM.Category(TextReference.Str("Regulation")),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.common.ui.news
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -12,24 +11,23 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.components.label.Label
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.utils.StringsSigns
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Composable
|
||||
fun ArticleCard(articleConfigUM: ArticleConfigUM, onArticleClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
|
|
@ -121,54 +119,13 @@ private fun DefaultArticle(articleConfigUM: ArticleConfigUM) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = Modifier) {
|
||||
val dotColor = TangemTheme.colors.text.secondary
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Image(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_start_circle_12),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = score.toString(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.size(4.dp)
|
||||
.drawWithCache {
|
||||
val radius = size.minDimension / 2f
|
||||
onDrawBehind {
|
||||
drawCircle(
|
||||
color = dotColor,
|
||||
radius = radius,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Text(
|
||||
text = createdAt,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun Tags(tags: ImmutableList<ArticleTagUM>, modifier: Modifier = Modifier) {
|
||||
private fun Tags(tags: ImmutableList<LabelUM>, modifier: Modifier = Modifier) {
|
||||
val expandIndicator = remember {
|
||||
ContextualFlowRowOverflow.expandIndicator {
|
||||
val remainingItems = tags.size - shownItemCount
|
||||
ArticleBadge(articleTagUM = ArticleTagUM.Category(TextReference.Str("${StringsSigns.PLUS}$remainingItems")))
|
||||
Label(state = LabelUM(TextReference.Str("${StringsSigns.PLUS}$remainingItems")))
|
||||
}
|
||||
}
|
||||
ContextualFlowRow(
|
||||
|
|
@ -179,7 +136,7 @@ private fun Tags(tags: ImmutableList<ArticleTagUM>, modifier: Modifier = Modifie
|
|||
maxLines = 1,
|
||||
overflow = expandIndicator,
|
||||
) { index ->
|
||||
ArticleBadge(articleTagUM = tags[index])
|
||||
Label(state = tags[index])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,14 +146,14 @@ private fun Tags(tags: ImmutableList<ArticleTagUM>, modifier: Modifier = Modifie
|
|||
private fun TagsPreview() {
|
||||
TangemThemePreview {
|
||||
Tags(
|
||||
tags = listOf(
|
||||
ArticleTagUM.Category(TextReference.Str("Hype")),
|
||||
ArticleTagUM.Category(TextReference.Str("BTC")),
|
||||
ArticleTagUM.Category(TextReference.Str("Supply")),
|
||||
ArticleTagUM.Category(TextReference.Str("Demand")),
|
||||
ArticleTagUM.Category(TextReference.Str("Best rate")),
|
||||
ArticleTagUM.Category(TextReference.Str("Breaking news")),
|
||||
).toPersistentList(),
|
||||
tags = persistentListOf(
|
||||
LabelUM(TextReference.Str("Hype")),
|
||||
LabelUM(TextReference.Str("BTC")),
|
||||
LabelUM(TextReference.Str("Supply")),
|
||||
LabelUM(TextReference.Str("Demand")),
|
||||
LabelUM(TextReference.Str("Best rate")),
|
||||
LabelUM(TextReference.Str("Breaking news")),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -206,12 +163,12 @@ private fun TagsPreview() {
|
|||
@Composable
|
||||
private fun ArticleCardsPreview() {
|
||||
val tags = listOf(
|
||||
ArticleTagUM.Category(TextReference.Str("Hype")),
|
||||
ArticleTagUM.Category(TextReference.Str("BTC")),
|
||||
ArticleTagUM.Category(TextReference.Str("Supply")),
|
||||
ArticleTagUM.Category(TextReference.Str("Demand")),
|
||||
ArticleTagUM.Category(TextReference.Str("Best rate")),
|
||||
ArticleTagUM.Category(TextReference.Str("Breaking news")),
|
||||
LabelUM(TextReference.Str("Hype")),
|
||||
LabelUM(TextReference.Str("BTC")),
|
||||
LabelUM(TextReference.Str("Supply")),
|
||||
LabelUM(TextReference.Str("Demand")),
|
||||
LabelUM(TextReference.Str("Best rate")),
|
||||
LabelUM(TextReference.Str("Breaking news")),
|
||||
).toImmutableSet()
|
||||
|
||||
val config = ArticleConfigUM(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.common.ui.news
|
||||
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
|
||||
data class ArticleConfigUM(
|
||||
|
|
@ -8,6 +9,6 @@ data class ArticleConfigUM(
|
|||
val score: Float,
|
||||
val createdAt: String,
|
||||
val isTrending: Boolean,
|
||||
val tags: ImmutableSet<ArticleTagUM>,
|
||||
val tags: ImmutableSet<LabelUM>,
|
||||
val isViewed: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.common.ui.news
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.label.Label
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun ArticleHeader(
|
||||
title: String,
|
||||
createdAt: String,
|
||||
score: Float,
|
||||
tags: ImmutableList<LabelUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
ArticleInfo(
|
||||
score = score,
|
||||
createdAt = createdAt,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
text = title,
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
if (tags.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
tags.forEach { tag ->
|
||||
Label(
|
||||
state = tag,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.common.ui.news
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = Modifier) {
|
||||
val dotColor = TangemTheme.colors.text.secondary
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Image(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_start_circle_12),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = score.toString(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.size(4.dp)
|
||||
.drawWithCache {
|
||||
val radius = size.minDimension / 2f
|
||||
onDrawBehind {
|
||||
drawCircle(
|
||||
color = dotColor,
|
||||
radius = radius,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Text(
|
||||
text = createdAt,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.common.ui.news
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
sealed interface ArticleTagUM {
|
||||
|
||||
val title: TextReference
|
||||
|
||||
data class Category(override val title: TextReference) : ArticleTagUM
|
||||
|
||||
data class Token(
|
||||
override val title: TextReference,
|
||||
val iconState: CurrencyIconState,
|
||||
) : ArticleTagUM
|
||||
}
|
||||
|
|
@ -220,6 +220,8 @@ fun SecondaryButtonIconEnd(
|
|||
modifier: Modifier = Modifier,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
shape: Shape = size.toShape(),
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
|
|
@ -230,6 +232,8 @@ fun SecondaryButtonIconEnd(
|
|||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
size = size,
|
||||
shape = shape,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -244,6 +248,8 @@ fun SecondaryButtonIconStart(
|
|||
modifier: Modifier = Modifier,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
shape: Shape = size.toShape(),
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
|
|
@ -254,6 +260,8 @@ fun SecondaryButtonIconStart(
|
|||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
size = size,
|
||||
shape = shape,
|
||||
)
|
||||
}
|
||||
// endregion SecondaryButton
|
||||
|
|
|
|||
|
|
@ -17,11 +17,15 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
|
||||
import com.tangem.core.ui.components.label.entity.LabelSize
|
||||
import com.tangem.core.ui.components.label.entity.LabelStyle
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -37,6 +41,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
*
|
||||
* @see <a href="https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=4480-1459&t=2QTpi1G7FeTexTFS-4">Figma</a>
|
||||
*/
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun Label(state: LabelUM, modifier: Modifier = Modifier) {
|
||||
val backgroundColor by animateColorAsState(
|
||||
|
|
@ -63,12 +68,28 @@ fun Label(state: LabelUM, modifier: Modifier = Modifier) {
|
|||
},
|
||||
)
|
||||
|
||||
AnimatedContent(targetState = state.text) { text ->
|
||||
val horizontalArrangementSize = remember {
|
||||
when (state.size) {
|
||||
LabelSize.REGULAR -> 4.dp
|
||||
LabelSize.BIG -> 8.dp
|
||||
}
|
||||
}
|
||||
|
||||
val paddings = remember {
|
||||
when (state.size) {
|
||||
LabelSize.REGULAR -> PaddingValues(horizontal = 8.dp, vertical = 4.dp)
|
||||
LabelSize.BIG -> PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedContent(
|
||||
modifier = modifier,
|
||||
targetState = state.text,
|
||||
) { text ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(horizontalArrangementSize),
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCorners8)
|
||||
.background(color = backgroundColor)
|
||||
.then(
|
||||
|
|
@ -82,8 +103,22 @@ fun Label(state: LabelUM, modifier: Modifier = Modifier) {
|
|||
Modifier
|
||||
},
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
.padding(paddings),
|
||||
) {
|
||||
state.leadingContent.let { leadingContentUM ->
|
||||
when (leadingContentUM) {
|
||||
is LabelLeadingContentUM.Token -> {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(16.dp),
|
||||
painter = painterResource(leadingContentUM.icon),
|
||||
tint = Color.Unspecified,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
LabelLeadingContentUM.None -> Unit
|
||||
}
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.weight(1.0f, fill = false),
|
||||
text = text.resolveReference(),
|
||||
|
|
@ -109,6 +144,8 @@ fun Label(state: LabelUM, modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
@ -118,47 +155,93 @@ private fun LabelPreview() {
|
|||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Regular Label"),
|
||||
style = LabelStyle.REGULAR,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str(
|
||||
"Regular long long long long long long long long long long long long Label",
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Regular Label"),
|
||||
style = LabelStyle.REGULAR,
|
||||
),
|
||||
style = LabelStyle.REGULAR,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
leadingContent = LabelLeadingContentUM.Token(
|
||||
icon = R.drawable.img_bsc_22,
|
||||
),
|
||||
text = TextReference.Str("Regular Label"),
|
||||
style = LabelStyle.REGULAR,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
),
|
||||
)
|
||||
}
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str(
|
||||
"Regular long long long long long long long long long long long long Label",
|
||||
),
|
||||
style = LabelStyle.REGULAR,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
}
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Regular Label"),
|
||||
style = LabelStyle.REGULAR,
|
||||
size = LabelSize.BIG,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Accent Label"),
|
||||
style = LabelStyle.ACCENT,
|
||||
size = LabelSize.BIG,
|
||||
icon = R.drawable.ic_information_24,
|
||||
),
|
||||
)
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("Warning Label"),
|
||||
style = LabelStyle.WARNING,
|
||||
size = LabelSize.BIG,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,29 @@
|
|||
package com.tangem.core.ui.components.label.entity
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
data class LabelUM(
|
||||
val text: TextReference,
|
||||
val style: LabelStyle,
|
||||
val style: LabelStyle = LabelStyle.REGULAR,
|
||||
val size: LabelSize = LabelSize.REGULAR,
|
||||
val leadingContent: LabelLeadingContentUM = LabelLeadingContentUM.None,
|
||||
@DrawableRes val icon: Int? = null,
|
||||
val onIconClick: (() -> Unit)? = null,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
sealed class LabelLeadingContentUM {
|
||||
data object None : LabelLeadingContentUM()
|
||||
data class Token(val icon: Int) : LabelLeadingContentUM()
|
||||
}
|
||||
|
||||
enum class LabelStyle {
|
||||
REGULAR, ACCENT, WARNING,
|
||||
}
|
||||
|
||||
enum class LabelSize {
|
||||
REGULAR, BIG,
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package com.tangem.core.ui.components.pager
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* Horizontal pager indicator
|
||||
*
|
||||
* @param pagerState state of pager
|
||||
* @param indicatorCount counter of visible indicator items
|
||||
*/
|
||||
@Composable
|
||||
fun PagerIndicator(pagerState: PagerState, modifier: Modifier = Modifier, indicatorCount: Int = 5) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val indicatorColor = TangemTheme.colors.control.key
|
||||
val overlayColor = TangemTheme.colors.overlay.secondary
|
||||
val indicatorSize = 8.dp
|
||||
val spacing = 4.dp
|
||||
|
||||
val totalWidth: Dp = indicatorSize * indicatorCount + spacing * (indicatorCount - 1)
|
||||
val widthInPx = LocalDensity.current.run { indicatorSize.toPx() }
|
||||
|
||||
val currentItem by remember {
|
||||
derivedStateOf {
|
||||
pagerState.currentPage
|
||||
}
|
||||
}
|
||||
|
||||
val itemCount = pagerState.pageCount
|
||||
|
||||
LaunchedEffect(key1 = currentItem) {
|
||||
val viewportSize = listState.layoutInfo.viewportSize
|
||||
listState.animateScrollToItem(
|
||||
currentItem,
|
||||
(widthInPx / 2 - viewportSize.width / 2).toInt(),
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = overlayColor,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.width(totalWidth),
|
||||
state = listState,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
userScrollEnabled = false,
|
||||
) {
|
||||
indicatorItems(
|
||||
itemCount = itemCount,
|
||||
currentItem = currentItem,
|
||||
indicatorShape = CircleShape,
|
||||
activeColor = indicatorColor,
|
||||
inActiveColor = indicatorColor.copy(alpha = 0.5f),
|
||||
indicatorSize = indicatorSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun LazyListScope.indicatorItems(
|
||||
itemCount: Int,
|
||||
currentItem: Int,
|
||||
indicatorShape: Shape,
|
||||
activeColor: Color,
|
||||
inActiveColor: Color,
|
||||
indicatorSize: Dp,
|
||||
) {
|
||||
items(itemCount) { index ->
|
||||
|
||||
val isSelected = index == currentItem
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(indicatorShape)
|
||||
.size(indicatorSize)
|
||||
.background(
|
||||
if (isSelected) activeColor else inActiveColor,
|
||||
indicatorShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PagerIndicatorPreviewFirstPage() {
|
||||
TangemThemePreview {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = 0,
|
||||
pageCount = { 10 },
|
||||
)
|
||||
PagerIndicator(pagerState = pagerState)
|
||||
}
|
||||
}
|
||||
}
|
||||
12
core/ui/src/main/res/drawable/ic_explore_16.xml
Normal file
12
core/ui/src/main/res/drawable/ic_explore_16.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M2.667,8C2.667,5.055 5.055,2.667 8,2.667C10.946,2.667 13.333,5.055 13.333,8C13.333,10.946 10.946,13.333 8,13.333C5.055,13.333 2.667,10.946 2.667,8ZM1.333,8C1.333,11.682 4.318,14.667 8,14.667C11.682,14.667 14.667,11.682 14.667,8C14.667,4.318 11.682,1.334 8,1.334C4.318,1.334 1.333,4.318 1.333,8Z"
|
||||
android:fillColor="#919191"/>
|
||||
<path
|
||||
android:pathData="M5.032,11.297C4.984,11.287 4.94,11.263 4.906,11.229C4.871,11.194 4.847,11.15 4.837,11.102C4.827,11.054 4.832,11.005 4.85,10.959L6.315,7.297C6.416,7.045 6.567,6.816 6.758,6.625C6.95,6.433 7.178,6.282 7.429,6.182L11.092,4.717C11.137,4.699 11.188,4.695 11.235,4.704C11.283,4.714 11.328,4.738 11.362,4.772C11.396,4.807 11.42,4.851 11.43,4.899C11.44,4.947 11.435,4.997 11.417,5.042L9.952,8.704C9.851,8.956 9.7,9.185 9.509,9.376C9.317,9.568 9.089,9.718 8.838,9.819L5.175,11.284C5.13,11.302 5.08,11.306 5.032,11.297ZM8.28,8.736C8.425,8.707 8.559,8.636 8.664,8.531C8.768,8.426 8.84,8.292 8.869,8.147C8.898,8.001 8.884,7.85 8.827,7.713C8.77,7.576 8.673,7.46 8.55,7.377C8.427,7.295 8.282,7.251 8.134,7.251C7.935,7.251 7.744,7.33 7.603,7.47C7.463,7.611 7.384,7.802 7.384,8.001C7.384,8.149 7.427,8.294 7.51,8.417C7.592,8.54 7.709,8.637 7.846,8.694C7.983,8.75 8.134,8.765 8.28,8.736Z"
|
||||
android:fillColor="#919191"/>
|
||||
</vector>
|
||||
15
core/ui/src/main/res/drawable/ic_heart_20.xml
Normal file
15
core/ui/src/main/res/drawable/ic_heart_20.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h20v20h-20z"/>
|
||||
<path
|
||||
android:pathData="M13.75,1.75C16.73,1.75 19.083,4.093 19.084,7.083C19.084,8.9 18.257,10.495 16.961,12.081C15.673,13.657 13.843,15.315 11.713,17.247L10.505,18.347L10,18.806L9.495,18.347L8.288,17.247C6.157,15.315 4.327,13.657 3.039,12.081C1.742,10.495 0.917,8.9 0.917,7.083C0.917,4.093 3.27,1.75 6.25,1.75C7.646,1.75 8.986,2.29 10,3.174C11.013,2.29 12.353,1.75 13.75,1.75Z"
|
||||
android:strokeWidth="1.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#1E1E1E"/>
|
||||
</group>
|
||||
</vector>
|
||||
15
core/ui/src/main/res/drawable/ic_quick_recap_16.xml
Normal file
15
core/ui/src/main/res/drawable/ic_quick_recap_16.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M1.873,2.461C2.205,2.461 2.473,2.73 2.473,3.061V8.464C2.473,9.9 3.638,11.064 5.073,11.064H11.855V10.146C11.855,9.831 12.203,9.639 12.469,9.809L14.858,11.326C15.105,11.483 15.105,11.844 14.858,12.002L12.469,13.519C12.203,13.688 11.855,13.497 11.855,13.181V12.264H5.073C2.975,12.264 1.273,10.562 1.273,8.464V3.061C1.273,2.73 1.542,2.461 1.873,2.461Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
<path
|
||||
android:pathData="M9.473,6.413C9.805,6.413 10.073,6.682 10.073,7.013C10.073,7.345 9.805,7.613 9.473,7.613H5.673C5.342,7.613 5.074,7.345 5.073,7.013C5.073,6.682 5.342,6.413 5.673,6.413H9.473Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
<path
|
||||
android:pathData="M13.273,2.696C13.605,2.696 13.873,2.964 13.873,3.296C13.873,3.627 13.605,3.896 13.273,3.896H5.673C5.342,3.896 5.074,3.627 5.073,3.296C5.073,2.964 5.342,2.696 5.673,2.696H13.273Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
</vector>
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedVisibility
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -137,11 +138,12 @@ private fun WalletBlock(
|
|||
vertical = 12.dp,
|
||||
),
|
||||
) {
|
||||
Row {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.padding(end = 8.dp),
|
||||
.weight(1f, fill = false),
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
|
|||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.common.ui.news.ArticleConfigUM
|
||||
import com.tangem.common.ui.news.ArticleTagUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.ui.feed.state.*
|
||||
import com.tangem.features.feed.ui.market.state.MarketsListItemUM
|
||||
import com.tangem.features.feed.ui.market.state.SortByTypeUM
|
||||
|
|
@ -159,21 +160,18 @@ internal object FeedListPreviewDataProvider {
|
|||
),
|
||||
)
|
||||
|
||||
private fun createArticleTags(): ImmutableSet<ArticleTagUM> {
|
||||
private fun createArticleTags(): ImmutableSet<LabelUM> {
|
||||
return persistentSetOf(
|
||||
ArticleTagUM.Token(
|
||||
title = TextReference.Str("BTC"),
|
||||
iconState = CurrencyIconState.CoinIcon(
|
||||
url = "",
|
||||
fallbackResId = 0,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
LabelUM(
|
||||
text = TextReference.Str("BTC"),
|
||||
leadingContent = LabelLeadingContentUM.Token(
|
||||
icon = R.drawable.img_btc_22,
|
||||
),
|
||||
),
|
||||
ArticleTagUM.Category(TextReference.Str("Regulation")),
|
||||
ArticleTagUM.Category(TextReference.Str("BTC")),
|
||||
ArticleTagUM.Category(TextReference.Str("Supply")),
|
||||
ArticleTagUM.Category(TextReference.Str("Demand")),
|
||||
LabelUM(TextReference.Str("Regulation")),
|
||||
LabelUM(TextReference.Str("BTC")),
|
||||
LabelUM(TextReference.Str("Supply")),
|
||||
LabelUM(TextReference.Str("Demand")),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.hotwallet.common.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -43,11 +44,12 @@ internal fun OptionBlock(
|
|||
}
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Row {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.padding(end = 4.dp),
|
||||
.weight(1f, fill = false),
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
|
|
|
|||
18
features/news/news-details/api/build.gradle.kts
Normal file
18
features/news/news-details/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.news.details.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.news.details.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface NewsDetailsComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(val selectedArticleId: Int = 0)
|
||||
|
||||
interface Factory : ComponentFactory<Params, NewsDetailsComponent>
|
||||
}
|
||||
43
features/news/news-details/impl/build.gradle.kts
Normal file
43
features/news/news-details/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
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)
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.news.details.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
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 Content(modifier: Modifier) {
|
||||
val uiState by model.uiState.collectAsState()
|
||||
NewsDetailsContent(
|
||||
state = uiState,
|
||||
onBackClick = model::onBackClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : NewsDetailsComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: NewsDetailsComponent.Params,
|
||||
): DefaultNewsDetailsComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
package com.tangem.features.news.details.impl
|
||||
|
||||
import com.tangem.core.ui.components.label.entity.LabelSize
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.news.details.impl.ui.ArticleUM
|
||||
import com.tangem.features.news.details.impl.ui.SourceUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
// TODO [REDACTED_TASK_KEY] remove mock data
|
||||
@Suppress("MaximumLineLength", "LongMethod")
|
||||
object MockArticlesFactory {
|
||||
fun createMockArticles(): ImmutableList<ArticleUM> = listOf(
|
||||
ArticleUM(
|
||||
id = 1,
|
||||
title = "SEC delays decisions on ETH-staking ETFs and spot XRP/SOL funds",
|
||||
createdAt = "20 Jun, 21:45",
|
||||
score = 6.5f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("Regulation"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "Bitwise has updated its Solana ETF, adding staking and setting a low management fee of 0.20%.",
|
||||
content = "Bitwise Asset Management has revised its filing to launch a Solana ETF, renaming it \"Bitwise Solana Staking ETF\" and setting an exceptionally low management fee of just 0.20%.\n\nThe update comes as the SEC prepares to review several Solana ETF applications.",
|
||||
sources = listOf(
|
||||
SourceUM(
|
||||
id = 1,
|
||||
title = "Deeper liquidity could drive crypto market beyond \$6T",
|
||||
sourceName = "cointelegraph",
|
||||
publishedAt = "1h ago",
|
||||
url = "https://cointelegraph.com",
|
||||
),
|
||||
SourceUM(
|
||||
id = 2,
|
||||
title = "Top gainers and losers in crypto this week",
|
||||
sourceName = "Investing",
|
||||
publishedAt = "2h ago",
|
||||
url = "https://investing.com",
|
||||
),
|
||||
).toPersistentList(),
|
||||
),
|
||||
ArticleUM(
|
||||
id = 2,
|
||||
title = "Bitcoin ETFs log 4th straight day of inflows (+\$550M)",
|
||||
createdAt = "20 Jun, 20:15",
|
||||
score = 8.2f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("BTC"), size = LabelSize.BIG),
|
||||
LabelUM(text = TextReference.Str("ETF"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "Bitcoin spot ETFs recorded fourth consecutive day of positive inflows.",
|
||||
content = "Bitcoin spot ETFs continue their impressive streak with \$550 million in net positive inflows.\n\nBlackRock's IBIT led with \$250M.",
|
||||
sources = listOf(
|
||||
SourceUM(
|
||||
id = 3,
|
||||
title = "Bitcoin ETFs see massive inflows",
|
||||
sourceName = "Bloomberg",
|
||||
publishedAt = "3h ago",
|
||||
url = "https://bloomberg.com",
|
||||
),
|
||||
).toPersistentList(),
|
||||
),
|
||||
ArticleUM(
|
||||
id = 3,
|
||||
title = "Ethereum network upgrade scheduled for Q2 2025",
|
||||
createdAt = "20 Jun, 18:30",
|
||||
score = 7.8f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("ETH"), size = LabelSize.BIG),
|
||||
LabelUM(text = TextReference.Str("Technology"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "Ethereum developers announced major network upgrade.",
|
||||
content = "The Ethereum Foundation announced a significant upgrade for Q2 2025.\n\nKey improvements include EVM enhancements.",
|
||||
sources = listOf(
|
||||
SourceUM(
|
||||
id = 4,
|
||||
title = "Ethereum core devs announce upgrade",
|
||||
sourceName = "CoinDesk",
|
||||
publishedAt = "5h ago",
|
||||
url = "https://coindesk.com",
|
||||
),
|
||||
).toPersistentList(),
|
||||
),
|
||||
ArticleUM(
|
||||
id = 4,
|
||||
title = "Solana surpasses Ethereum in daily transaction volume",
|
||||
createdAt = "20 Jun, 16:00",
|
||||
score = 9.1f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("SOL"), size = LabelSize.BIG),
|
||||
LabelUM(text = TextReference.Str("Market"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "Solana achieved new milestone processing more daily transactions than Ethereum.",
|
||||
content = "Solana processed over 50 million transactions in a single day.\n\nDriven by DeFi and NFT activity.",
|
||||
sources = listOf(
|
||||
SourceUM(
|
||||
id = 5,
|
||||
title = "Solana transactions hit record",
|
||||
sourceName = "The Block",
|
||||
publishedAt = "7h ago",
|
||||
url = "https://theblock.co",
|
||||
),
|
||||
).toPersistentList(),
|
||||
),
|
||||
ArticleUM(
|
||||
id = 5,
|
||||
title = "DeFi protocol launches innovative yield farming strategy",
|
||||
createdAt = "20 Jun, 14:20",
|
||||
score = 6.9f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("DeFi"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "New DeFi protocol introduced innovative yield farming approach.",
|
||||
content = "A newly launched protocol unveiled innovative yield farming mechanism.\n\nAPY rates range from 15% to 30%.",
|
||||
sources = persistentListOf(),
|
||||
),
|
||||
ArticleUM(
|
||||
id = 6,
|
||||
title = "Crypto regulation bill advances in US Senate",
|
||||
createdAt = "20 Jun, 12:45",
|
||||
score = 8.7f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("Regulation"), size = LabelSize.BIG),
|
||||
LabelUM(text = TextReference.Str("USA"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "Comprehensive cryptocurrency regulation bill passed Senate Banking Committee.",
|
||||
content = "The US Senate Banking Committee advanced landmark crypto regulation bill.\n\nKey provisions include asset definitions.",
|
||||
sources = persistentListOf(),
|
||||
),
|
||||
ArticleUM(
|
||||
id = 7,
|
||||
title = "Major bank announces crypto custody services",
|
||||
createdAt = "20 Jun, 10:30",
|
||||
score = 7.3f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("Adoption"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "World's largest bank announced cryptocurrency custody services.",
|
||||
content = "Major financial institution announced comprehensive crypto custody services.\n\nSupporting Bitcoin and Ethereum initially.",
|
||||
sources = persistentListOf(),
|
||||
),
|
||||
ArticleUM(
|
||||
id = 8,
|
||||
title = "NFT marketplace reports 300% increase in trading volume",
|
||||
createdAt = "20 Jun, 08:15",
|
||||
score = 5.8f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("NFT"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "Leading NFT marketplace experienced dramatic surge in trading activity.",
|
||||
content = "Prominent NFT marketplace reported 300% increase in trading volume.\n\nNew features include lower fees.",
|
||||
sources = persistentListOf(),
|
||||
),
|
||||
ArticleUM(
|
||||
id = 9,
|
||||
title = "Layer 2 solution achieves 100,000 TPS milestone",
|
||||
createdAt = "19 Jun, 22:00",
|
||||
score = 8.5f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("Technology"), size = LabelSize.BIG),
|
||||
LabelUM(text = TextReference.Str("L2"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "New Layer 2 scaling solution achieved 100,000 TPS in testing.",
|
||||
content = "Layer 2 solution processed 100,000 transactions per second.\n\nUsing zero-knowledge proof technology.",
|
||||
sources = persistentListOf(),
|
||||
),
|
||||
ArticleUM(
|
||||
id = 10,
|
||||
title = "Stablecoin market cap reaches new all-time high",
|
||||
createdAt = "19 Jun, 19:30",
|
||||
score = 7.6f,
|
||||
tags = listOf(
|
||||
LabelUM(text = TextReference.Str("Stablecoins"), size = LabelSize.BIG),
|
||||
LabelUM(text = TextReference.Str("Market"), size = LabelSize.BIG),
|
||||
).toPersistentList(),
|
||||
shortContent = "Total stablecoin market capitalization surpassed \$180 billion.",
|
||||
content = "Stablecoin market cap reached \$180 billion all-time high.\n\nDriven by DeFi activity and institutional adoption.",
|
||||
sources = persistentListOf(),
|
||||
),
|
||||
).toPersistentList()
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
package com.tangem.features.news.details.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.news.ArticleHeader
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.pager.PagerIndicator
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.news.details.impl.MockArticlesFactory
|
||||
|
||||
// TODO [REDACTED_TASK_KEY] make internal
|
||||
@Composable
|
||||
fun NewsDetailsContent(state: NewsDetailsUM, onBackClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = state.selectedArticleIndex,
|
||||
pageCount = { state.articles.size },
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
Column {
|
||||
TangemTopAppBar(
|
||||
title = null,
|
||||
startButton = TopAppBarButtonUM.Icon(
|
||||
iconRes = R.drawable.ic_back_24,
|
||||
onClicked = onBackClick,
|
||||
),
|
||||
endButton = TopAppBarButtonUM.Icon(
|
||||
iconRes = R.drawable.ic_share_24,
|
||||
onClicked = state.onShareClick,
|
||||
),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
if (state.articles.isNotEmpty()) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) { page ->
|
||||
ArticleDetail(
|
||||
article = state.articles[page],
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onLikeClick = state.onLikeClick,
|
||||
)
|
||||
}
|
||||
|
||||
if (state.articles.size > 1) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
PagerIndicator(
|
||||
pagerState = pagerState,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onLikeClick: () -> Unit) {
|
||||
val density = LocalDensity.current
|
||||
val pagerHeight = 48.dp
|
||||
val contentPadding = 56.dp
|
||||
LazyColumn(
|
||||
modifier = modifier.padding(horizontal = 16.dp),
|
||||
contentPadding = PaddingValues(
|
||||
bottom = contentPadding + pagerHeight + WindowInsets.navigationBars.getBottom(density).dp,
|
||||
),
|
||||
) {
|
||||
item {
|
||||
ArticleHeader(
|
||||
title = article.title,
|
||||
createdAt = article.createdAt,
|
||||
score = article.score,
|
||||
tags = article.tags,
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
)
|
||||
|
||||
if (article.shortContent.isNotEmpty()) {
|
||||
QuickRecap(
|
||||
content = article.shortContent,
|
||||
modifier = Modifier.padding(top = 32.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = article.content,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
SecondaryButtonIconStart(
|
||||
iconResId = R.drawable.ic_heart_20,
|
||||
text = "Like", // TODO [REDACTED_TASK_KEY] export to strings
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
onClick = onLikeClick,
|
||||
)
|
||||
|
||||
// TODO [REDACTED_TASK_KEY] add related tokens block
|
||||
|
||||
if (article.sources.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Row {
|
||||
Text(
|
||||
text = "Sources", // TODO [REDACTED_TASK_KEY] export to strings
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "${article.sources.size}",
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (article.sources.isNotEmpty()) {
|
||||
item {
|
||||
val sourcesPagerState = rememberPagerState(
|
||||
pageCount = { article.sources.size },
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
HorizontalPager(
|
||||
state = sourcesPagerState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
pageSpacing = 12.dp,
|
||||
contentPadding = PaddingValues(horizontal = 0.dp),
|
||||
) { page ->
|
||||
SourceItem(
|
||||
source = article.sources[page],
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun QuickRecap(content: String, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.height(IntrinsicSize.Min),
|
||||
) {
|
||||
VerticalDivider(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
thickness = 2.dp,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_quick_recap_16),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Quick recap", // TODO [REDACTED_TASK_KEY] export to strings
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
text = content,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.primary,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(bottom = 4.dp),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_explore_16),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = source.sourceName,
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
if (source.title.isNotEmpty()) {
|
||||
Text(
|
||||
text = source.title,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = source.publishedAt,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewNewsDetailsContent() {
|
||||
TangemThemePreview {
|
||||
NewsDetailsContent(
|
||||
state = NewsDetailsUM(
|
||||
articles = MockArticlesFactory.createMockArticles(),
|
||||
selectedArticleIndex = 0,
|
||||
onShareClick = { },
|
||||
onLikeClick = { },
|
||||
),
|
||||
onBackClick = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.features.news.details.impl.ui
|
||||
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
// TODO [REDACTED_TASK_KEY] make internal
|
||||
data class NewsDetailsUM(
|
||||
val articles: ImmutableList<ArticleUM>,
|
||||
val selectedArticleIndex: Int,
|
||||
val onShareClick: () -> Unit,
|
||||
val onLikeClick: () -> Unit,
|
||||
)
|
||||
|
||||
// TODO [REDACTED_TASK_KEY] make internal
|
||||
data class ArticleUM(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val createdAt: String,
|
||||
val score: Float,
|
||||
val tags: ImmutableList<LabelUM>,
|
||||
val shortContent: String,
|
||||
val content: String,
|
||||
val sources: ImmutableList<SourceUM>,
|
||||
)
|
||||
|
||||
// TODO [REDACTED_TASK_KEY] make internal
|
||||
data class SourceUM(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val sourceName: String,
|
||||
val publishedAt: String,
|
||||
val url: String,
|
||||
)
|
||||
|
|
@ -59,6 +59,8 @@ dependencies {
|
|||
/** Feature Apis */
|
||||
implementation(projects.features.tester.api)
|
||||
implementation(projects.features.pushNotifications.api)
|
||||
implementation(projects.features.news.newsDetails.api)
|
||||
implementation(projects.features.news.newsDetails.impl)
|
||||
|
||||
/* SDK */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ import com.tangem.feature.tester.presentation.providers.ui.BlockchainProvidersSc
|
|||
import com.tangem.feature.tester.presentation.providers.viewmodel.BlockchainProvidersViewModel
|
||||
import com.tangem.feature.tester.presentation.testpush.ui.TestPushScreen
|
||||
import com.tangem.feature.tester.presentation.testpush.viewmodel.TestPushViewModel
|
||||
import com.tangem.feature.tester.presentation.news.ui.NewsScreen
|
||||
import com.tangem.feature.tester.presentation.news.viewmodel.NewsViewModel
|
||||
import com.tangem.features.news.details.impl.MockArticlesFactory
|
||||
import com.tangem.features.news.details.impl.ui.NewsDetailsContent
|
||||
import com.tangem.features.news.details.impl.ui.NewsDetailsUM
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import javax.inject.Inject
|
||||
|
|
@ -82,9 +87,10 @@ internal class TesterActivity : ComposeActivity() {
|
|||
ButtonUM.TESTER_ACTIONS,
|
||||
ButtonUM.TEST_PUSHES,
|
||||
ButtonUM.ACCOUNTS,
|
||||
ButtonUM.NEWS,
|
||||
),
|
||||
onButtonClick = {
|
||||
val route = when (it) {
|
||||
onButtonClick = { buttonUM ->
|
||||
val route = when (buttonUM) {
|
||||
ButtonUM.FEATURE_TOGGLES -> TesterScreen.FEATURE_TOGGLES
|
||||
ButtonUM.EXCLUDED_BLOCKCHAINS -> TesterScreen.EXCLUDED_BLOCKCHAINS
|
||||
ButtonUM.ENVIRONMENT_TOGGLES -> TesterScreen.ENVIRONMENTS_TOGGLES
|
||||
|
|
@ -92,6 +98,7 @@ internal class TesterActivity : ComposeActivity() {
|
|||
ButtonUM.TESTER_ACTIONS -> TesterScreen.TESTER_ACTIONS
|
||||
ButtonUM.TEST_PUSHES -> TesterScreen.TEST_PUSHES
|
||||
ButtonUM.ACCOUNTS -> TesterScreen.ACCOUNTS
|
||||
ButtonUM.NEWS -> TesterScreen.NEWS
|
||||
}
|
||||
|
||||
innerTesterRouter.open(route)
|
||||
|
|
@ -162,6 +169,27 @@ internal class TesterActivity : ComposeActivity() {
|
|||
|
||||
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() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,5 +25,6 @@ data class TesterMenuUM(
|
|||
TESTER_ACTIONS(R.string.tester_actions),
|
||||
TEST_PUSHES(R.string.test_push),
|
||||
ACCOUNTS(R.string.accounts),
|
||||
NEWS(R.string.news),
|
||||
}
|
||||
}
|
||||
|
|
@ -14,4 +14,6 @@ internal enum class TesterScreen {
|
|||
BLOCKCHAIN_PROVIDERS,
|
||||
TEST_PUSHES,
|
||||
ACCOUNTS,
|
||||
NEWS,
|
||||
NEWS_DETAILS,
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
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,
|
||||
),
|
||||
onButtonClick = ::onButtonClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onBackClick() {
|
||||
router?.back()
|
||||
}
|
||||
|
||||
private fun onButtonClick(button: NewsUM.ButtonUM) {
|
||||
when (button) {
|
||||
NewsUM.ButtonUM.NEWS_DETAILS -> router?.open(TesterScreen.NEWS_DETAILS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,4 +18,6 @@
|
|||
<string name="share_logs" translatable="false">Share logs</string>
|
||||
<string name="test_push" translatable="false">Test push</string>
|
||||
<string name="accounts" translatable="false">Accounts</string>
|
||||
<string name="news" translatable="false">News</string>
|
||||
<string name="news_details" translatable="false">News details</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -300,6 +300,9 @@ include(":features:yield-supply:impl")
|
|||
|
||||
include(":features:feed:api")
|
||||
include(":features:feed:impl")
|
||||
|
||||
include(":features:news:news-details:api")
|
||||
include(":features:news:news-details:impl")
|
||||
// endregion Feature modules
|
||||
|
||||
// region Domain modules
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue