Updated on 2026-08-14
This commit is contained in:
parent
fbc15cbb8c
commit
7b00754e24
12 changed files with 77 additions and 51 deletions
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ internal fun NewsSlider(newsSliderConfig: NewsSliderConfig) {
|
|||
.conditionalCompose(
|
||||
condition = isRedesignEnabled,
|
||||
modifier = {
|
||||
hazeSourceTangem(-1f)
|
||||
hazeSourceTangem(zIndex = -1f)
|
||||
},
|
||||
)
|
||||
.background(color = background),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@file:Suppress("MagicNumber", "LongMethod")
|
||||
|
||||
package com.tangem.feature.tester.presentation.storybook.page.background
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.background.northernlights.NorthernLightsBackground
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.tester.presentation.storybook.entity.NorthernLightsStory
|
||||
|
||||
@Composable
|
||||
|
|
@ -22,6 +24,7 @@ internal fun NorthernLightsStory(state: NorthernLightsStory, modifier: Modifier
|
|||
Box(modifier = modifier.fillMaxSize()) {
|
||||
NorthernLightsBackground(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
containerColor = TangemTheme.colors2.surface.level1,
|
||||
forceSimpleVersion = state.variant == NorthernLightsStory.Variant.Simple,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ internal class TokenActionsComponent @AssistedInject constructor(
|
|||
),
|
||||
) {
|
||||
Column {
|
||||
params.actions.forEach { action ->
|
||||
params.actions.fastForEach { action ->
|
||||
if (action.isEnabled) {
|
||||
val rowColors = if (action.isWarning) {
|
||||
getWarningRowColors()
|
||||
|
|
|
|||
|
|
@ -54,10 +54,7 @@ import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingTopBar
|
|||
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM
|
||||
|
|
@ -181,9 +178,16 @@ private fun WalletContent2(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.hazeSourceTangem(-1f),
|
||||
.hazeSourceTangem(zIndex = -1f),
|
||||
) {
|
||||
NorthernLightsBackground(Modifier.matchParentSize())
|
||||
NorthernLightsBackground(
|
||||
containerColor = if (LocalIsInDarkTheme.current) {
|
||||
TangemTheme.colors2.surface.level1
|
||||
} else {
|
||||
TangemTheme.colors2.surface.level2
|
||||
},
|
||||
modifier = Modifier.matchParentSize(),
|
||||
)
|
||||
|
||||
WalletPagerIndicator(
|
||||
pagerState = walletsPagerState,
|
||||
|
|
@ -201,7 +205,7 @@ private fun WalletContent2(
|
|||
state.wallets2[state.selectedWalletIndex]
|
||||
}
|
||||
|
||||
LaunchedEffect(walletsPagerState.currentPage) {
|
||||
LaunchedEffect(walletsPagerState.currentPage, currentWallet.walletsBalanceUM) {
|
||||
if (walletsPagerState.currentPage == currentWalletIndex) {
|
||||
walletBalance = (currentWallet.walletsBalanceUM as? WalletBalanceUM.Content)?.balanceInAppBar
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import com.tangem.feature.wallet.presentation.preview.WalletBalancePreview
|
|||
import com.tangem.feature.wallet.presentation.preview.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach
|
||||
import com.tangem.utils.StringsSigns
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val MIN_SCALE = 0.75f
|
||||
|
|
@ -135,7 +136,19 @@ private fun Balance(walletBalanceUM: WalletBalanceUM, isBalanceHidden: Boolean,
|
|||
),
|
||||
)
|
||||
}
|
||||
is WalletBalanceUM.Error,
|
||||
is WalletBalanceUM.Error -> {
|
||||
Text(
|
||||
text = StringsSigns.DASH_SIGN,
|
||||
style = TangemTheme.typography2.titleRegular44,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = TangemTheme.typography2.bodySemibold15.fontSize,
|
||||
maxFontSize = TangemTheme.typography2.titleRegular44.fontSize,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletBalanceUM.Loading,
|
||||
-> {
|
||||
TextShimmer(
|
||||
|
|
|
|||
|
|
@ -45,16 +45,14 @@ internal fun WalletListContent(
|
|||
overscrollEffect = rememberOverscrollEffect(),
|
||||
) {
|
||||
notifications(
|
||||
notifications = currentWallet.notifications.map { it.messageUM }
|
||||
.toPersistentList(),
|
||||
notifications = currentWallet.notifications.map { it.messageUM }.toPersistentList(),
|
||||
contentColor = containerColor,
|
||||
modifier = movableItemModifier,
|
||||
)
|
||||
notificationsCarousel(
|
||||
containerColor = containerColor,
|
||||
modifier = movableItemModifier,
|
||||
notifications = currentWallet.notifications.map { it.messageUM }
|
||||
.toPersistentList(),
|
||||
notifications = currentWallet.notificationsCarousel.map { it.messageUM }.toPersistentList(),
|
||||
)
|
||||
|
||||
tangemPay(
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ private fun LazyListScope.portfolioItem(
|
|||
)
|
||||
is TangemHeaderRowUM -> TangemHeaderRow(
|
||||
headerRowUM = tokenRowUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue