Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-24 14:22:13 +08:00
parent bced8fd66c
commit 6386cb1ea4
7 changed files with 319 additions and 213 deletions

View file

@ -1,202 +0,0 @@
package com.tangem.core.ui.components
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
/**
* Closable notification with custom icon
* Child of parent component
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1045-807&t=6CVvYDJe0sB7wBKE-0">Figma component</a>
*
* Use to show banner with custom icon and possibility to close
* i.e. Feedback notification
*
* @param title notification title
* @param icon drawable res on icon
* @param iconColor icon color
* @param onClick callback on click
* @param onCloseClick callback on close icon click
*/
@Composable
fun ClosableNotification(
title: String,
@DrawableRes icon: Int,
iconColor: Color,
onClick: (() -> Unit),
onCloseClick: (() -> Unit),
) {
NotificationCardTemplate(onClick) {
Icon(
modifier = Modifier
.size(TangemTheme.dimens.size20)
.align(Alignment.CenterStart),
painter = painterResource(id = icon),
tint = iconColor,
contentDescription = null,
)
Text(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing28)
.align(Alignment.CenterStart),
text = title,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
)
Icon(
modifier = Modifier
.size(TangemTheme.dimens.size20)
.align(Alignment.CenterEnd)
.clickable(onClick = onCloseClick),
painter = painterResource(id = R.drawable.ic_close_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
/**
* Notification component from Design system
* There are few states for this component, but only one parent, see link below
*
* Use this for Notification with title, subtitle, clickable or not
*
* @param title notification title
* @param subtitle notification subtitle
* @param onClick click on notification, if its null then no chevron icon
*
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1045-807&t=6CVvYDJe0sB7wBKE-0">Figma component</a>
*/
@Composable
fun WarningNotification(title: String, subtitle: String?, onClick: (() -> Unit)?) {
NotificationCardTemplate(onClick) {
Image(
modifier = Modifier
.size(TangemTheme.dimens.size20)
.align(Alignment.CenterStart),
painter = painterResource(id = R.drawable.img_attention_20),
contentDescription = null,
)
Column(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing28)
.align(Alignment.CenterStart),
) {
Text(
text = title,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
)
if (!subtitle.isNullOrEmpty()) {
SpacerH2()
Text(
text = subtitle,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption,
)
}
}
if (onClick != null) {
Icon(
modifier = Modifier
.size(TangemTheme.dimens.size20)
.align(Alignment.CenterEnd),
painter = painterResource(id = R.drawable.ic_chevron_right_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun NotificationCardTemplate(onClick: (() -> Unit)? = null, content: @Composable BoxScope.() -> Unit) {
Surface(
color = TangemTheme.colors.button.secondary,
shape = RoundedCornerShape(TangemTheme.dimens.radius18),
onClick = onClick ?: {},
enabled = onClick != null,
) {
Box(
Modifier
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing8,
)
.wrapContentSize(),
) {
content()
}
}
}
// region Preview
@Composable
private fun WarningNotificationPreview() {
Column(modifier = Modifier.fillMaxWidth()) {
WarningNotification(
title = "Your wallet hasnt been backed up",
subtitle = "Lorem ipsum dolor sit amet, consectetur " +
"adipiscing elit, sed do eiusmod tempor incididunt ut labore et...",
onClick = {},
)
SpacerH32()
WarningNotification(
title = "Your wallet hasnt been backed up",
subtitle = null,
onClick = {},
)
SpacerH32()
WarningNotification(
title = "Your wallet hasnt been backed up",
subtitle = "Lorem ipsum dolor sit amet, consectetur " +
"adipiscing elit, sed do eiusmod tempor incididunt ut labore et...",
onClick = null,
)
SpacerH32()
WarningNotification(
title = "Your wallet hasnt been backed up",
subtitle = null,
onClick = null,
)
SpacerH32()
ClosableNotification(
title = "Like tangem app?",
icon = R.drawable.ic_star_24,
iconColor = TangemTheme.colors.icon.attention,
onClick = {},
onCloseClick = {},
)
}
}
@Preview(showBackground = true)
@Composable
private fun Preview_WarningNotification_InLightTheme() {
TangemTheme(isDark = false) {
WarningNotificationPreview()
}
}
@Preview(showBackground = true)
@Composable
private fun Preview_WarningNotification_InDarkTheme() {
TangemTheme(isDark = true) {
WarningNotificationPreview()
}
}
// endregion Preview

View file

@ -0,0 +1,171 @@
package com.tangem.core.ui.components.notifications
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH2
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
/**
* Notification component from Design system.
* Use this for Notification with title, subtitle, clickable or not.
*
* @param state component state
* @param modifier modifier
*
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1045-807&t=6CVvYDJe0sB7wBKE-0"
* >Figma component</a>
*/
@Composable
fun Notification(state: NotificationState, modifier: Modifier = Modifier) {
Surface(
onClick = if (state is NotificationState.Action) {
state.onClick
} else {
{}
},
modifier = modifier,
enabled = when (state) {
is NotificationState.Simple -> false
is NotificationState.Action -> true
},
shape = RoundedCornerShape(TangemTheme.dimens.radius18),
color = TangemTheme.colors.button.secondary,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing12, vertical = TangemTheme.dimens.spacing8),
) {
NotificationIcon(
iconResId = state.iconResId,
iconTint = state.tint,
modifier = Modifier
.size(size = TangemTheme.dimens.size20)
.align(alignment = Alignment.CenterStart),
)
NotificationInfoBlock(
title = state.title,
subtitle = state.subtitle,
modifier = Modifier.align(alignment = Alignment.CenterStart),
)
if (state is NotificationState.Action) {
Icon(
modifier = Modifier
.size(size = TangemTheme.dimens.size20)
.align(alignment = Alignment.CenterEnd),
painter = painterResource(id = R.drawable.ic_chevron_right_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
}
}
@Composable
private fun NotificationIcon(@DrawableRes iconResId: Int, iconTint: Color?, modifier: Modifier = Modifier) {
if (iconTint != null) {
Icon(
painter = painterResource(id = iconResId),
contentDescription = null,
modifier = modifier,
tint = iconTint,
)
} else {
Image(
painter = painterResource(id = iconResId),
contentDescription = null,
modifier = modifier,
)
}
}
@Composable
private fun NotificationInfoBlock(title: String, subtitle: String?, modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing30)) {
Text(
text = title,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.body2,
)
if (!subtitle.isNullOrEmpty()) {
SpacerH2()
Text(
text = subtitle,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption,
)
}
}
}
@Preview
@Composable
private fun Preview_WarningNotification_Light(
@PreviewParameter(NotificationStateProvider::class)
state: NotificationState,
) {
TangemTheme(isDark = false) {
Notification(state)
}
}
@Preview
@Composable
private fun Preview_WarningNotification_Dark(
@PreviewParameter(NotificationStateProvider::class)
state: NotificationState,
) {
TangemTheme(isDark = true) {
Notification(state)
}
}
private class NotificationStateProvider : CollectionPreviewParameterProvider<NotificationState>(
collection = listOf(
NotificationState.Simple(
title = "Your wallet hasnt been backed up",
subtitle = "Lorem ipsum dolor sit amet, consectetur " +
"adipiscing elit, sed do eiusmod tempor incididunt ut labore et...",
iconResId = R.drawable.img_attention_20,
),
NotificationState.Simple(
title = "Your wallet hasnt been backed up",
subtitle = null,
iconResId = R.drawable.ic_alert_circle_24,
tint = TangemColorPalette.Amaranth,
),
NotificationState.Action(
title = "Your wallet hasnt been backed up",
subtitle = "Lorem ipsum dolor sit amet, consectetur " +
"adipiscing elit, sed do eiusmod tempor incididunt ut labore et...",
iconResId = R.drawable.img_attention_20,
onClick = {},
),
NotificationState.Action(
title = "Your wallet hasnt been backed up",
subtitle = null,
iconResId = R.drawable.ic_alert_circle_24,
tint = TangemColorPalette.Amaranth,
onClick = {},
),
),
)

View file

@ -0,0 +1,54 @@
package com.tangem.core.ui.components.notifications
import androidx.annotation.DrawableRes
import androidx.compose.ui.graphics.Color
/**
* Notification component state
*
* @property title title
* @property subtitle subtitle
* @property iconResId icon resource id
* @property tint icon tint
*
[REDACTED_AUTHOR]
*/
sealed class NotificationState(
open val title: String,
open val subtitle: String? = null,
@DrawableRes open val iconResId: Int,
open val tint: Color? = null,
) {
/**
* Simple notification state. Non clickable.
*
* @property title title
* @property subtitle subtitle
* @property iconResId icon resource id
* @property tint icon tint
*/
data class Simple(
override val title: String,
override val subtitle: String? = null,
@DrawableRes override val iconResId: Int,
override val tint: Color? = null,
) : NotificationState(title, subtitle, iconResId, tint)
/**
* Clickable notification state
*
* @property title title
* @property subtitle subtitle
* @property iconResId icon resource id
* @property tint icon tint
* @param onClick lambda be invoked when notification component is clicked
*/
data class Action(
override val title: String,
override val subtitle: String? = null,
@DrawableRes override val iconResId: Int,
override val tint: Color? = null,
val onClick: () -> Unit,
) : NotificationState(title, subtitle, iconResId, tint)
}

View file

@ -76,6 +76,7 @@ data class TangemDimens internal constructor(
val size200: Dp = 200.dp,
// endregion Size
// region Spacing
val spacing0: Dp = 0.dp,
val spacing0_5: Dp = 0.5.dp,
val spacing2: Dp = 2.dp,
val spacing4: Dp = 4.dp,
@ -91,6 +92,7 @@ data class TangemDimens internal constructor(
val spacing24: Dp = 24.dp,
val spacing26: Dp = 26.dp,
val spacing28: Dp = 28.dp,
val spacing30: Dp = 30.dp,
val spacing32: Dp = 32.dp,
val spacing34: Dp = 34.dp,
val spacing36: Dp = 34.dp,

View file

@ -7,10 +7,7 @@ import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenO
import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig
import com.tangem.feature.wallet.presentation.wallet.state.*
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import java.util.UUID
@ -220,6 +217,12 @@ internal object WalletPreviewData {
),
),
),
notifications = persistentListOf(
WalletNotification.UnreachableNetworks,
WalletNotification.LikeTangemApp(onClick = {}),
WalletNotification.NeedToBackup(onClick = {}),
WalletNotification.ScanCard(onClick = {}),
),
onOrganizeTokensClick = {},
)
@ -252,5 +255,11 @@ internal object WalletPreviewData {
),
),
),
notifications = persistentListOf(
WalletNotification.UnreachableNetworks,
WalletNotification.LikeTangemApp(onClick = {}),
WalletNotification.NeedToBackup(onClick = {}),
WalletNotification.ScanCard(onClick = {}),
),
)
}

View file

@ -0,0 +1,65 @@
package com.tangem.feature.wallet.presentation.wallet.state
import com.tangem.core.ui.components.notifications.NotificationState
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.feature.wallet.impl.R
/**
* Wallet notification component state
*
* @property state state
*
[REDACTED_AUTHOR]
*/
sealed class WalletNotification(open val state: NotificationState) {
/**
* "Backup the card" notification
*
* @property onClick lambda be invoked when notification is clicked
*/
data class NeedToBackup(val onClick: () -> Unit) : WalletNotification(
state = NotificationState.Action(
title = "Backup your card",
iconResId = R.drawable.ic_alert_circle_24,
onClick = onClick,
tint = TangemColorPalette.Amaranth,
),
)
/** "Unreachable networks" notification */
object UnreachableNetworks : WalletNotification(
state = NotificationState.Simple(
title = "Some networks are unreachable",
iconResId = R.drawable.img_attention_20,
tint = null,
),
)
/**
* "Like Tangem App" notification
*
* @property onClick lambda be invoked when notification is clicked
*/
data class LikeTangemApp(val onClick: () -> Unit) : WalletNotification(
state = NotificationState.Action(
title = "Like Tangem App?",
iconResId = R.drawable.ic_star_24,
onClick = onClick,
tint = TangemColorPalette.Tangerine,
),
)
/**
* "Scan the card" notification
*
* @property onClick lambda be invoked when notification is clicked
*/
data class ScanCard(val onClick: () -> Unit) : WalletNotification(
state = NotificationState.Action(
title = "Scan your card to continue",
iconResId = R.drawable.ic_tangem_24,
onClick = onClick,
),
)
}

View file

@ -10,6 +10,7 @@ import kotlinx.collections.immutable.ImmutableList
* @property selectedWallet selected wallet
* @property wallets list of wallets states
* @property contentItems content items
* @property notifications notifications
*
[REDACTED_AUTHOR]
*/
@ -19,16 +20,19 @@ internal sealed class WalletStateHolder(
open val selectedWallet: WalletCardState,
open val wallets: ImmutableList<WalletCardState>,
open val contentItems: ImmutableList<WalletContentItemState>,
open val notifications: ImmutableList<WalletNotification>,
) {
/**
* Multi currency wallet content state
*
* @property onBackClick lambda be invoked when back button is clicked
* @property topBarConfig top bar config
* @property selectedWallet selected wallet
* @property wallets list of wallets states
* @property contentItems content items
* @property onBackClick lambda be invoked when back button is clicked
* @property topBarConfig top bar config
* @property selectedWallet selected wallet
* @property wallets list of wallets states
* @property contentItems content items
* @property notifications notifications
* @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked
*/
data class MultiCurrencyContent(
override val onBackClick: () -> Unit,
@ -36,8 +40,9 @@ internal sealed class WalletStateHolder(
override val selectedWallet: WalletCardState,
override val wallets: ImmutableList<WalletCardState>,
override val contentItems: ImmutableList<WalletContentItemState.MultiCurrencyItem>,
override val notifications: ImmutableList<WalletNotification>,
val onOrganizeTokensClick: () -> Unit,
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems)
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems, notifications)
/**
* Single currency wallet content state
@ -47,6 +52,7 @@ internal sealed class WalletStateHolder(
* @property selectedWallet selected wallet
* @property wallets list of wallets states
* @property contentItems content items
* @property notifications notifications
*/
data class SingleCurrencyContent(
override val onBackClick: () -> Unit,
@ -54,5 +60,6 @@ internal sealed class WalletStateHolder(
override val selectedWallet: WalletCardState,
override val wallets: ImmutableList<WalletCardState>,
override val contentItems: ImmutableList<WalletContentItemState.SingleCurrencyItem>,
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems)
override val notifications: ImmutableList<WalletNotification>,
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems, notifications)
}