Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-03 17:39:04 +05:00
parent fbc15cbb8c
commit 7b00754e24
12 changed files with 77 additions and 51 deletions

View file

@ -4,12 +4,7 @@ package com.tangem.core.ui.components.background.northernlights
import android.os.Build
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.StartOffset
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@ -21,7 +16,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.components.background.shaderBackground
import com.tangem.core.ui.res.LocalPowerSavingState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.shader.NorthernLightsMeshGradientShader
/**
@ -29,10 +23,14 @@ import com.tangem.core.ui.shader.NorthernLightsMeshGradientShader
* Uses a RuntimeShader on Android 13+ and falls back to a simpler implementation on older versions and in power saving mode.
*/
@Composable
fun NorthernLightsBackground(modifier: Modifier = Modifier, forceSimpleVersion: Boolean = false) {
fun NorthernLightsBackground(
containerColor: Color,
modifier: Modifier = Modifier,
forceSimpleVersion: Boolean = false,
) {
val isPowerSavingMode by LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsState()
if (!forceSimpleVersion && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !isPowerSavingMode) {
NorthernLightsBackgroundWithShader(modifier)
NorthernLightsBackgroundWithShader(containerColor, modifier)
} else {
MovingColorfulBlubsBackground(modifier)
}
@ -40,9 +38,8 @@ fun NorthernLightsBackground(modifier: Modifier = Modifier, forceSimpleVersion:
@Suppress("LongMethod")
@Composable
private fun NorthernLightsBackgroundWithShader(modifier: Modifier = Modifier) {
private fun NorthernLightsBackgroundWithShader(containerColor: Color, modifier: Modifier = Modifier) {
val transition = rememberInfiniteTransition(label = "FluidMeshGradientV2")
val backgroundColor = TangemTheme.colors2.surface.level1
// Each track cycles through 4 states (matching the screenshot frames):
// deep/dark → saturated+bright → light/pastel → vibrant/vivid → back
@ -128,7 +125,7 @@ private fun NorthernLightsBackgroundWithShader(modifier: Modifier = Modifier) {
Color(0xFF1444AA),
Color(0xFF4422BB),
Color(0xFF331199),
backgroundColor,
containerColor,
),
speed = 0.5f,
scale = 4f,
@ -139,12 +136,12 @@ private fun NorthernLightsBackgroundWithShader(modifier: Modifier = Modifier) {
colorsArray[1] = color2
colorsArray[2] = color3
colorsArray[3] = color4
colorsArray[4] = backgroundColor
colorsArray[4] = containerColor
shader.updateColors(colorsArray)
Box(
modifier = modifier
.background(backgroundColor)
.background(containerColor)
.fillMaxSize()
.shaderBackground(shader),
)

View file

@ -32,15 +32,15 @@ internal fun ProvideHaze(content: @Composable () -> Unit) {
*/
@Composable
fun Modifier.hazeEffectTangem(
state: HazeState = LocalHazeState.current,
style: HazeStyle = HazeStyle.Unspecified,
configure: HazeEffectScope.() -> Unit = {},
): Modifier {
val powerSavingEnabled = LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsState()
val hazeState = LocalHazeState.current
val isGlobalBlurEnabled = hazeState.blurEnabled && !powerSavingEnabled.value
val isGlobalBlurEnabled = state.blurEnabled && !powerSavingEnabled.value
val rootBackground by LocalRootBackgroundColor.current
return hazeEffect(hazeState, style) {
return hazeEffect(state, style) {
fallbackTint = HazeTint(rootBackground)
if (isGlobalBlurEnabled) {
configure()
@ -78,5 +78,5 @@ fun Modifier.hazeForegroundEffectTangem(
* Applies a haze source to the [Modifier] using the current global haze state.
*/
@Composable
fun Modifier.hazeSourceTangem(zIndex: Float = 0f, key: Any? = null) =
this.hazeSource(LocalHazeState.current, zIndex, key)
fun Modifier.hazeSourceTangem(state: HazeState = LocalHazeState.current, zIndex: Float = 0f, key: Any? = null) =
this.hazeSource(state, zIndex, key)

View file

@ -106,7 +106,10 @@ enum class TangemMessageEffect(val isAnimatable: Boolean) {
Color(0x1AFFFFFF),
)
} else {
persistentListOf()
persistentListOf(
Color(0xFFE1E1E1),
Color(0xFFE1E1E1),
)
}
}
}
@ -192,7 +195,10 @@ enum class TangemMessageEffect(val isAnimatable: Boolean) {
Color(0x17E44848),
)
None -> if (isInDarkTheme) {
persistentListOf()
persistentListOf(
Color(0x1AFFFFFF),
Color(0x1AFFFFFF),
)
} else {
persistentListOf(
Color(0x0d000000),
@ -238,9 +244,10 @@ internal fun Modifier.messageEffectBackground(
val isInDarkTheme = LocalIsInDarkTheme.current
val borderGradientColors = remember { messageEffect.getBorderGradient(isInDarkTheme) }
val gradientColors = remember { messageEffect.getColorGradient(isInDarkTheme) }
val gradientTint = remember { messageEffect.getGradientTint(isInDarkTheme) }
val angle by rememberAnimationAngle(messageEffect.isAnimatable)
val brush = Brush.sweepGradient(messageEffect.getColorGradient(isInDarkTheme))
val brush = remember { Brush.sweepGradient(messageEffect.getColorGradient(isInDarkTheme)) }
val padding = 1.dp.toPx()
return this
@ -254,14 +261,14 @@ internal fun Modifier.messageEffectBackground(
border(
width = 1.dp,
brush = Brush.sweepGradient(
colors = messageEffect.getBorderGradient(isInDarkTheme),
colors = borderGradientColors,
center = Offset.Infinite,
),
shape = RoundedCornerShape(radius),
)
}
.hazeForegroundEffectTangem(
style = HazeStyle(tints = messageEffect.getGradientTint(isInDarkTheme)),
style = HazeStyle(tints = gradientTint),
isBlurEnabled = true,
) {
fallbackTint = HazeTint(

View file

@ -25,10 +25,7 @@ import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenElementsTestTags
@ -40,12 +37,13 @@ import com.tangem.core.ui.test.TokenElementsTestTags
* @param modifier Modifier for the composable
*/
@Composable
fun TangemHeaderRow(headerRowUM: TangemHeaderRowUM, modifier: Modifier = Modifier) {
fun TangemHeaderRow(headerRowUM: TangemHeaderRowUM, modifier: Modifier = Modifier, isBalanceHidden: Boolean = false) {
TangemHeaderRow(
headTangemIconUM = headerRowUM.startIconUM,
footerTangemIconRes = headerRowUM.endIconRes,
title = headerRowUM.title,
subtitle = headerRowUM.subtitle,
isBalanceHidden = isBalanceHidden,
modifier = modifier,
)
}
@ -63,6 +61,7 @@ fun TangemHeaderRow(headerRowUM: TangemHeaderRowUM, modifier: Modifier = Modifie
@Composable
fun TangemHeaderRow(
modifier: Modifier = Modifier,
isBalanceHidden: Boolean = false,
subtitle: TextReference? = null,
onItemClick: (() -> Unit)? = null,
@DrawableRes footerTangemIconRes: Int? = null,
@ -92,7 +91,7 @@ fun TangemHeaderRow(
) {
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
Text(
text = wrappedSubtitle.resolveAnnotatedReference(),
text = wrappedSubtitle.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
@ -131,6 +130,7 @@ fun TangemHeaderRow(
fun TangemHeaderRow(
title: TextReference,
modifier: Modifier = Modifier,
isBalanceHidden: Boolean = false,
subtitle: TextReference? = null,
headTangemIconUM: TangemIconUM? = null,
@DrawableRes footerTangemIconRes: Int? = null,
@ -172,7 +172,7 @@ fun TangemHeaderRow(
) {
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
Text(
text = wrappedSubtitle.resolveAnnotatedReference(),
text = wrappedSubtitle.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,

View file

@ -24,6 +24,12 @@ internal fun RowScope.TokenRowPriceChangeContent(
isFlickering: Boolean,
isAvailable: Boolean = true,
) {
val color = when (priceChangeState.type) {
PriceChangeType.UP -> TangemTheme.colors2.graphic.status.accent
PriceChangeType.DOWN -> TangemTheme.colors2.graphic.status.warning
PriceChangeType.NEUTRAL -> TangemTheme.colors2.graphic.neutral.tertiary
}
AnimatedContent(
targetState = priceChangeState.type,
label = "Update the price change's arrow",
@ -39,11 +45,7 @@ internal fun RowScope.TokenRowPriceChangeContent(
},
),
),
tint = when (animatedType) {
PriceChangeType.UP -> TangemTheme.colors2.graphic.status.accent
PriceChangeType.DOWN -> TangemTheme.colors2.graphic.status.warning
PriceChangeType.NEUTRAL -> TangemTheme.colors2.graphic.neutral.secondary
},
tint = color,
contentDescription = null,
modifier = Modifier.size(TangemTheme.dimens2.x3),
)
@ -61,7 +63,7 @@ internal fun RowScope.TokenRowPriceChangeContent(
style = TangemTheme.typography2.captionSemibold12.applyBladeBrush(
isEnabled = isFlickering,
textColor = if (isAvailable) {
TangemTheme.colors2.text.neutral.secondary
color
} else {
TangemTheme.colors2.text.status.disabled
},