Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-18 15:52:37 +03:00
parent d42cb2a79e
commit 00c5775f03
7 changed files with 70 additions and 29 deletions

View file

@ -28,6 +28,7 @@ import com.arkivanov.decompose.value.Value
import com.arkivanov.essenty.backhandler.BackHandler
import com.tangem.common.routing.AppRoute
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.components.haze.ProvideHaze
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
import com.tangem.core.ui.components.snackbar.TangemTopSnackbarHost
import com.tangem.core.ui.message.EventMessageEffect
@ -72,8 +73,10 @@ internal fun RootContent(
when (val instance = child.instance) {
is RoutingComponent.Child.Initial -> Unit
is RoutingComponent.Child.ComposableComponent -> {
ProvideHaze {
instance.component.Content(Modifier.fillMaxSize())
}
}
is RoutingComponent.Child.LegacyIntent -> {
// TODO: Remove and use it's own router: [REDACTED_JIRA]
LaunchedEffect(instance) {

View file

@ -4,10 +4,12 @@ import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.tween
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.layout
import com.arkivanov.decompose.extensions.compose.stack.animation.*
import com.tangem.common.routing.AppRoute
import kotlin.math.abs
object RoutingTransitionAnimationFactory {
@ -18,8 +20,10 @@ object RoutingTransitionAnimationFactory {
is AppRoute.Home,
-> fade(tween(400)).plus(scale(tween(400)))
is AppRoute.Wallet,
-> slideAndFade(directions = setOf(Direction.ENTER_BACK, Direction.EXIT_BACK))
.plus(
-> slideAndFade(
slideDirections = setOf(Direction.ENTER_BACK, Direction.EXIT_BACK),
fadeDirections = emptySet(),
).plus(
scaleWithDirection(
directions = setOf(Direction.ENTER_FRONT, Direction.EXIT_FRONT),
animationSpec = tween(400),
@ -52,28 +56,64 @@ object RoutingTransitionAnimationFactory {
)
}
/**
* @param slideDirections directions in which the horizontal slide is applied.
* `null` (default) means slide in all directions.
* @param fadeDirections directions in which the alpha fade is applied.
* `null` (default) means fade in all directions. Pass `emptySet()` to disable the fade
* entirely useful for screens that own a `hazeEffect` (e.g. WalletTopBar's progressive
* blur), where wrapping the screen in an animated `graphicsLayer { alpha = ... }` causes
* a visible blink over the blurred region.
*/
@Suppress("MagicNumber")
private fun slideAndFade(directions: Set<Direction>? = null): StackAnimator {
private fun slideAndFade(
slideDirections: Set<Direction>? = null,
fadeDirections: Set<Direction>? = null,
): StackAnimator {
val easing = CubicBezierEasing(a = 0.55f, b = 0.0f, c = 0.0f, d = 1f)
return stackAnimator(
val slide = stackAnimator(
animationSpec = tween(durationMillis = 400, easing = easing),
) { factor, direction, content ->
content(
if (directions == null || directions.contains(direction)) {
if (slideDirections == null || slideDirections.contains(direction)) {
Modifier.offsetXFactor(factor)
} else {
Modifier
},
)
}.plus(
fade(
}
val fade = directionalFade(
animationSpec = tween(
delayMillis = 50,
durationMillis = 300,
easing = easing,
),
),
directions = fadeDirections,
)
return slide.plus(fade)
}
/**
* Like `decompose.fade(...)` but only applies the alpha `graphicsLayer` when `direction`
* is in [directions]. `null` directions = always fade (matches stock `fade()` behavior).
* `emptySet()` directions = never fade (modifier passes through untouched).
*/
private fun directionalFade(
animationSpec: FiniteAnimationSpec<Float>,
directions: Set<Direction>?,
): StackAnimator = stackAnimator(animationSpec) { factor, direction, content ->
content(
if (directions == null || directions.contains(direction)) {
Modifier.graphicsLayer {
alpha = 1f - abs(factor)
compositingStrategy = CompositingStrategy.Offscreen
}
} else {
Modifier
},
)
}

View file

@ -14,7 +14,7 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
@OptIn(ExperimentalHazeMaterialsApi::class)
@Composable
internal fun ProvideHaze(content: @Composable () -> Unit) {
fun ProvideHaze(content: @Composable () -> Unit) {
val hazeState = rememberHazeState()
CompositionLocalProvider(
LocalHazeState provides hazeState,
@ -47,7 +47,7 @@ fun isHazeBlurEffectivelyEnabled(state: HazeState = LocalHazeState.current): Boo
@Composable
fun Modifier.hazeEffectTangem(
state: HazeState = LocalHazeState.current,
style: HazeStyle = HazeStyle.Unspecified,
style: HazeStyle = CupertinoMaterials.ultraThin(),
configure: HazeEffectScope.() -> Unit = {},
): Modifier {
val isGlobalBlurEnabled = isHazeBlurEffectivelyEnabled(state)

View file

@ -28,7 +28,7 @@ import com.tangem.core.ui.res.TangemThemePreviewRedesign
* @param modifier Modifier to be applied to the button.
*/
@Composable
fun SecondaryTangemButton(buttonUM: TangemButtonUM, modifier: Modifier = Modifier) {
fun SecondaryTangemButton(buttonUM: TangemButtonUM, modifier: Modifier = Modifier, withHazeEffect: Boolean = true) {
SecondaryTangemButton(
onClick = buttonUM.onClick,
modifier = modifier,
@ -40,6 +40,7 @@ fun SecondaryTangemButton(buttonUM: TangemButtonUM, modifier: Modifier = Modifie
size = buttonUM.size,
shape = buttonUM.shape,
onLongClick = buttonUM.onLongClick,
withHazeEffect = withHazeEffect,
)
}
@ -70,6 +71,7 @@ fun SecondaryTangemButton(
size: TangemButtonSize = TangemButtonSize.X15,
shape: TangemButtonShape = TangemButtonShape.Default,
onLongClick: (() -> Unit)? = null,
withHazeEffect: Boolean = true,
) {
val backgroundModifier = if (isEnabled) {
Modifier.background(TangemTheme.colors2.button.backgroundSecondary)
@ -86,7 +88,7 @@ fun SecondaryTangemButton(
onClick = onClick,
modifier = modifier
.clip(shape.toShape(size))
.hazeEffectTangem()
.then(if (withHazeEffect) Modifier.hazeEffectTangem() else Modifier)
.then(backgroundModifier),
text = text,
contentColor = contentColor,

View file

@ -2,14 +2,11 @@
package com.tangem.core.ui.res
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.haze.ProvideHaze
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.res.generated.TangemDimens3
import com.tangem.core.ui.res.generated.TangemTypography3
import com.tangem.core.ui.res.generated.darkColors3
@ -55,13 +52,11 @@ fun TangemThemeRedesign(content: @Composable () -> Unit) {
LocalTextSelectionColors provides TangemTextSelectionColors2,
) {
ProvideHaze {
Box(Modifier.hazeSourceTangem(zIndex = 1f)) {
content()
}
}
}
}
}
}
private val TangemTextSelectionColors2: TextSelectionColors

View file

@ -101,6 +101,7 @@ internal fun TokenMarketBlock(tokenMarketBlockUM: TokenMarketBlockUM, modifier:
tangemIconUM = TangemIconUM.Icon(iconRes = CoreR.drawable.ic_arrow_expand_24),
shape = TangemButtonShape.Rounded,
size = TangemButtonSize.X10,
withHazeEffect = false,
modifier = Modifier
.layoutId(TangemRowLayoutId.TAIL)
.padding(start = TangemTheme.dimens2.x10),

View file

@ -98,7 +98,7 @@ markdown = "0.7.2"
markdownComposeView = "0.5.4"
usedesk = "4.4.0"
sumsub = "1.38.0"
haze = "1.7.1"
haze = "1.7.2"
kotlinpoet = "1.18.1"
customerio = "4.6.3"
surveysparrow = "1.2.9"