Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-12 20:18:53 +05:00
parent 07f94d3a95
commit a8f4cce51e
7 changed files with 62 additions and 27 deletions

View file

@ -8,6 +8,7 @@ import androidx.compose.runtime.Immutable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
@ -74,7 +75,7 @@ fun TangemIcon(tangemIconUM: TangemIconUM, modifier: Modifier = Modifier) {
tint = tangemIconUM.tintReference(),
)
is TangemIconUM.Image -> Image(
imageVector = ImageVector.vectorResource(tangemIconUM.imageRes),
painter = painterResource(tangemIconUM.imageRes),
contentDescription = null,
modifier = modifier,
)

View file

@ -66,7 +66,7 @@ fun TangemMessage(
Alignment.Top
},
)
.size(TangemTheme.dimens2.x7),
.size(messageUM.iconSize),
)
}
},

View file

@ -6,10 +6,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
@ -25,6 +22,7 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.haze.hazeForegroundEffectTangem
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.LocalMessageEffectAnimation
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.utils.toPx
@ -33,6 +31,10 @@ import dev.chrisbanes.haze.HazeTint
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
data class MessageEffectAnimation(
val offsetState: State<Float>,
)
/**
* Different visual effects for [Tangem message component].
*/
@ -222,7 +224,6 @@ enum class TangemMessageEffect(val isAnimatable: Boolean) {
}
}
// todo redesign replace with proper color when design is ready
@Suppress("MagicNumber")
fun fallbackColor(isInDarkTheme: Boolean): Color {
return when (this) {
@ -242,12 +243,16 @@ internal fun Modifier.messageEffectBackground(
contentColor: Color,
): Modifier {
val isInDarkTheme = LocalIsInDarkTheme.current
val borderGradientColors = remember { messageEffect.getBorderGradient(isInDarkTheme) }
val gradientColors = remember { messageEffect.getColorGradient(isInDarkTheme) }
val gradientTint = remember { messageEffect.getGradientTint(isInDarkTheme) }
val borderGradientColors = remember(messageEffect, isInDarkTheme) { messageEffect.getBorderGradient(isInDarkTheme) }
val gradientColors = remember(messageEffect, isInDarkTheme) { messageEffect.getColorGradient(isInDarkTheme) }
val gradientTint = remember(messageEffect, isInDarkTheme) { messageEffect.getGradientTint(isInDarkTheme) }
val angle by rememberAnimationAngle(messageEffect.isAnimatable)
val brush = remember { Brush.sweepGradient(messageEffect.getColorGradient(isInDarkTheme)) }
val angle by if (messageEffect.isAnimatable) {
LocalMessageEffectAnimation.current.offsetState
} else {
remember { mutableFloatStateOf(0f) }
}
val brush = remember(gradientColors) { Brush.sweepGradient(gradientColors) }
val padding = 1.dp.toPx()
return this
@ -278,12 +283,14 @@ internal fun Modifier.messageEffectBackground(
}
.conditionalCompose(gradientColors.isNotEmpty()) {
drawWithContent {
rotate(angle) {
drawCircle(
brush = brush,
radius = size.width,
blendMode = BlendMode.SrcIn,
)
if (messageEffect.isAnimatable) {
rotate(angle) {
drawCircle(
brush = brush,
radius = size.width,
blendMode = BlendMode.SrcIn,
)
}
}
drawRect(
color = contentColor,
@ -296,9 +303,9 @@ internal fun Modifier.messageEffectBackground(
}
@Composable
private fun rememberAnimationAngle(isAnimatable: Boolean) = if (isAnimatable) {
internal fun rememberMessageEffectAnimationAngle(): MessageEffectAnimation {
val infiniteTransition = rememberInfiniteTransition()
infiniteTransition.animateFloat(
val offsetState = infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
@ -306,8 +313,10 @@ private fun rememberAnimationAngle(isAnimatable: Boolean) = if (isAnimatable) {
repeatMode = RepeatMode.Restart,
),
)
} else {
remember { mutableFloatStateOf(0f) }
return remember(offsetState) {
MessageEffectAnimation(offsetState)
}
}
// region Preview

View file

@ -1,6 +1,8 @@
package com.tangem.core.ui.ds.message
import androidx.annotation.DrawableRes
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
@ -10,12 +12,16 @@ import kotlinx.collections.immutable.persistentListOf
/**
* Data model representing the properties of a Tangem message.
*
* @param id Unique identifier for the message.
* @param title TextReference for the message title.
* @param subtitle TextReference for the message subtitle.
* @param messageEffect TangemMessageEffect defining the visual effect of the message.
* @param iconUM Optional TangemIconUM representing the icon to be displayed in the message.
* @param iconSize Dp value defining the size of the icon (default is 28.dp).
* @param isCentered Boolean indicating whether the icon is centered.
* @param buttonsUM ImmutableList of TangemMessageButtonUM representing the buttons in the message.
* @param onCloseClick Lambda to be invoked when the close button is clicked (optional
* @param onClick Lambda to be invoked when the message is clicked (optional).
* @param onCloseClick Lambda to be invoked when the close button is clicked (optional).
*/
data class TangemMessageUM(
val id: String,
@ -23,6 +29,7 @@ data class TangemMessageUM(
val subtitle: TextReference,
val messageEffect: TangemMessageEffect = TangemMessageEffect.None,
val iconUM: TangemIconUM? = null,
val iconSize: Dp = 28.dp,
val isCentered: Boolean = false,
val buttonsUM: ImmutableList<TangemMessageButtonUM> = persistentListOf(),
val onClick: (() -> Unit)? = null,

View file

@ -23,6 +23,8 @@ import com.tangem.core.ui.components.powersaving.rememberPowerSavingState
import com.tangem.core.ui.components.snackbar.TangemTopSnackbarHostState
import com.tangem.core.ui.components.text.BladeAnimation
import com.tangem.core.ui.components.text.rememberBladeAnimation
import com.tangem.core.ui.ds.message.MessageEffectAnimation
import com.tangem.core.ui.ds.message.rememberMessageEffectAnimationAngle
import com.tangem.core.ui.haptic.DefaultHapticManager
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.haptic.VibratorHapticManager
@ -128,6 +130,7 @@ fun TangemTheme(
LocalBladeAnimation provides rememberBladeAnimation(),
LocalSystemBarsIconsController provides systemBarsIconsController,
LocalPowerSavingState provides rememberPowerSavingState(),
LocalMessageEffectAnimation provides rememberMessageEffectAnimationAngle(),
) {
CompositionLocalProvider(
LocalTangemShimmer provides TangemShimmer,
@ -433,6 +436,10 @@ val LocalPowerSavingState = compositionLocalOf<PowerSavingState> {
error("No PowerSavingState provided")
}
val LocalMessageEffectAnimation = compositionLocalOf<MessageEffectAnimation> {
error("No MessageEffectAnimation provided")
}
/**
* Determines whether the dark theme should be used based on the given [AppThemeMode].
*

View file

@ -1,4 +1,5 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.message
import androidx.compose.foundation.background
@ -16,7 +17,10 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.message.*
import com.tangem.core.ui.ds.message.TangemMessage
import com.tangem.core.ui.ds.message.TangemMessageButtonUM
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.core.ui.ds.message.TangemMessageUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemMessageStory

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.annotation.DrawableRes
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.message.TangemMessageButtonUM
@ -196,7 +197,6 @@ internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val t
onClick = onConfirmClick,
),
),
),
type = WalletNotificationType.Critical,
)
@ -379,8 +379,14 @@ internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val t
title = resourceReference(R.string.notification_one_plus_one_title),
subtitle = resourceReference(R.string.notification_one_plus_one_text),
messageEffect = TangemMessageEffect.Magic,
onCloseClick = onCloseClick,
iconUM = TangemIconUM.Image(R.drawable.img_one_plus_one_promo),
iconSize = 54.dp,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.common_later),
type = TangemButtonType.PrimaryInverse,
onClick = onCloseClick,
),
TangemMessageButtonUM(
text = resourceReference(R.string.notification_one_plus_one_button),
type = TangemButtonType.Primary,
@ -452,7 +458,8 @@ internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val t
id = "PushNotificationsNotification",
title = resourceReference(R.string.user_push_notification_banner_title),
subtitle = resourceReference(R.string.user_push_notification_banner_subtitle),
onCloseClick = onCloseClick,
iconUM = TangemIconUM.Image(R.drawable.img_push_reminder),
iconSize = 54.dp,
messageEffect = TangemMessageEffect.Magic,
buttonsUM = persistentListOf(
TangemMessageButtonUM(