Updated on 2026-08-14
This commit is contained in:
commit
effe637f9f
9 changed files with 192 additions and 54 deletions
|
|
@ -29,7 +29,7 @@ import com.tangem.core.ui.components.RectangleShimmer
|
|||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||
import com.tangem.core.ui.components.flicker
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -133,10 +133,11 @@ private fun NameAndInfo(
|
|||
)
|
||||
} else {
|
||||
Text(
|
||||
modifier = Modifier.flicker(isFlickering),
|
||||
text = balanceValue,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2.applyBladeBrush(
|
||||
isEnabled = isFlickering,
|
||||
textColor = TangemTheme.colors.text.tertiary,
|
||||
),
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
fun Modifier.flicker(
|
||||
isFlickering: Boolean,
|
||||
targetTextAlpha: Float = 0.4f,
|
||||
animationDurationMillis: Int = 1200,
|
||||
animationDurationMillis: Int = 1500,
|
||||
): Modifier = composed {
|
||||
var alphaChange by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.core.ui.components.text
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.LocalBladeAnimation
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlin.math.sqrt
|
||||
|
||||
data class BladeAnimation(
|
||||
val offset: Float,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun rememberBladeAnimation(): BladeAnimation {
|
||||
val infiniteTransition = rememberInfiniteTransition()
|
||||
val offset by infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 1f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(durationMillis = 1500, easing = LinearEasing),
|
||||
),
|
||||
)
|
||||
|
||||
return BladeAnimation(offset)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun TextStyle.applyBladeBrush(isEnabled: Boolean, textColor: Color): TextStyle {
|
||||
val offset = LocalBladeAnimation.current.offset
|
||||
|
||||
return if (isEnabled) {
|
||||
val brush = remember(offset, textColor) {
|
||||
object : ShaderBrush() {
|
||||
override fun createShader(size: Size): Shader {
|
||||
val center = Offset(size.width / 2f, size.height / 2f)
|
||||
val diagonal = sqrt(size.width * size.width + size.height * size.height)
|
||||
val direction = Offset(x = 1f, y = 0.5f)
|
||||
val halfDist = diagonal / 2f
|
||||
val baseStart = center - direction * halfDist
|
||||
val baseEnd = center + direction * halfDist
|
||||
val shift = direction * offset * diagonal
|
||||
|
||||
return LinearGradientShader(
|
||||
colors = listOf(textColor.copy(alpha = 0.2f), textColor),
|
||||
from = baseStart + shift,
|
||||
to = baseEnd + shift,
|
||||
colorStops = listOf(0.0f, 0.25f),
|
||||
tileMode = TileMode.Mirror,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.copy(brush = brush)
|
||||
} else {
|
||||
this.copy(
|
||||
color = textColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
Text(
|
||||
"Blade animation text",
|
||||
style = TangemTheme.typography.body1.applyBladeBrush(true, Color.Black),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.audits.AuditLabel
|
||||
import com.tangem.core.ui.components.flicker
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
|
|
@ -53,12 +53,14 @@ internal fun TokenCryptoAmount(
|
|||
@Composable
|
||||
private fun CryptoAmountText(amount: String, modifier: Modifier = Modifier, isFlickering: Boolean = false) {
|
||||
Text(
|
||||
modifier = modifier.flicker(isFlickering),
|
||||
modifier = modifier,
|
||||
text = amount,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.caption2,
|
||||
style = TangemTheme.typography.caption2.applyBladeBrush(
|
||||
isEnabled = isFlickering,
|
||||
textColor = TangemTheme.colors.text.tertiary,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.flicker
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState as TokenFiatAmountState
|
||||
|
|
@ -97,12 +97,14 @@ private fun FiatAmountText(
|
|||
isFlickering: Boolean = false,
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier.flicker(isFlickering),
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
color = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.body2,
|
||||
style = TangemTheme.typography.body2.applyBladeBrush(
|
||||
isEnabled = isFlickering,
|
||||
textColor = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.core.ui.components.SpacerW4
|
|||
import com.tangem.core.ui.components.SpacerW6
|
||||
import com.tangem.core.ui.components.flicker
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -42,10 +43,15 @@ internal fun TokenPrice(state: TokenPriceState?, modifier: Modifier = Modifier)
|
|||
)
|
||||
}
|
||||
is TokenPriceState.TextContent -> {
|
||||
PriceText(text = state.value.resolveReference(), modifier = modifier, isAvailable = state.isAvailable)
|
||||
PriceText(
|
||||
text = state.value.resolveReference(),
|
||||
isFlickering = false,
|
||||
modifier = modifier,
|
||||
isAvailable = state.isAvailable,
|
||||
)
|
||||
}
|
||||
is TokenPriceState.Unknown -> {
|
||||
PriceText(text = DASH_SIGN, modifier = modifier)
|
||||
PriceText(text = DASH_SIGN, isFlickering = false, modifier = modifier)
|
||||
}
|
||||
is TokenPriceState.Loading -> {
|
||||
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
|
|
@ -65,37 +71,62 @@ private fun PriceBlock(
|
|||
type: PriceChangeType? = null,
|
||||
priceChangePercent: String? = null,
|
||||
) {
|
||||
Row(modifier = modifier.flicker(isFlickering), verticalAlignment = Alignment.CenterVertically) {
|
||||
PriceText(text = price, modifier = Modifier.weight(weight = 1f, fill = false))
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
PriceText(
|
||||
modifier = Modifier.weight(weight = 1f, fill = false),
|
||||
text = price,
|
||||
isFlickering = isFlickering,
|
||||
)
|
||||
|
||||
SpacerW6()
|
||||
|
||||
if (type != null) {
|
||||
PriceChangeIcon(type = type)
|
||||
PriceChangeIcon(
|
||||
modifier = Modifier.flicker(isFlickering),
|
||||
type = type,
|
||||
)
|
||||
SpacerW4()
|
||||
}
|
||||
|
||||
PriceChangeText(type = type, text = priceChangePercent)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PriceText(text: String, modifier: Modifier = Modifier, isAvailable: Boolean = true) {
|
||||
AnimatedContent(targetState = text, label = "Update the price text", modifier = modifier) { animatedText ->
|
||||
Text(
|
||||
text = animatedText,
|
||||
color = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.caption2,
|
||||
PriceChangeText(
|
||||
type = type,
|
||||
isFlickering = isFlickering,
|
||||
text = priceChangePercent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PriceChangeIcon(type: PriceChangeType) {
|
||||
AnimatedContent(targetState = type, label = "Update the price change's arrow") { animatedType ->
|
||||
private fun PriceText(
|
||||
text: String,
|
||||
isFlickering: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
isAvailable: Boolean = true,
|
||||
) {
|
||||
AnimatedContent(targetState = text, label = "Update the price text", modifier = modifier) { animatedText ->
|
||||
Text(
|
||||
text = animatedText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.caption2.applyBladeBrush(
|
||||
isEnabled = isFlickering,
|
||||
textColor = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PriceChangeIcon(type: PriceChangeType, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(
|
||||
targetState = type,
|
||||
label = "Update the price change's arrow",
|
||||
) { animatedType ->
|
||||
Icon(
|
||||
modifier = modifier,
|
||||
painter = painterResource(
|
||||
id = when (animatedType) {
|
||||
PriceChangeType.UP -> R.drawable.ic_arrow_up_8
|
||||
|
|
@ -114,19 +145,26 @@ private fun PriceChangeIcon(type: PriceChangeType) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun PriceChangeText(type: PriceChangeType?, text: String?, modifier: Modifier = Modifier) {
|
||||
private fun PriceChangeText(
|
||||
type: PriceChangeType?,
|
||||
text: String?,
|
||||
isFlickering: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedContent(targetState = text, modifier = modifier, label = "Update the price change's text") { animatedText ->
|
||||
Text(
|
||||
text = animatedText ?: DASH_SIGN,
|
||||
color = when (type) {
|
||||
PriceChangeType.UP -> TangemTheme.colors.text.accent
|
||||
PriceChangeType.DOWN -> TangemTheme.colors.text.warning
|
||||
PriceChangeType.NEUTRAL -> TangemTheme.colors.text.disabled
|
||||
null -> TangemTheme.colors.text.tertiary
|
||||
},
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.caption2,
|
||||
style = TangemTheme.typography.caption2.applyBladeBrush(
|
||||
isEnabled = isFlickering,
|
||||
textColor = when (type) {
|
||||
PriceChangeType.UP -> TangemTheme.colors.text.accent
|
||||
PriceChangeType.DOWN -> TangemTheme.colors.text.warning
|
||||
PriceChangeType.NEUTRAL -> TangemTheme.colors.text.disabled
|
||||
null -> TangemTheme.colors.text.tertiary
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import androidx.compose.ui.platform.LocalView
|
|||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.TangemShimmer
|
||||
import com.tangem.core.ui.components.text.BladeAnimation
|
||||
import com.tangem.core.ui.components.text.rememberBladeAnimation
|
||||
import com.tangem.core.ui.haptic.DefaultHapticManager
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
|
|
@ -98,6 +100,7 @@ fun TangemTheme(
|
|||
LocalSnackbarHostState provides snackbarHostState,
|
||||
LocalEventMessageHandler provides eventMessageHandler,
|
||||
LocalWindowSize provides windowSize,
|
||||
LocalBladeAnimation provides rememberBladeAnimation(),
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalTangemShimmer provides TangemShimmer,
|
||||
|
|
@ -305,6 +308,10 @@ val LocalMainBottomSheetColor = staticCompositionLocalOf<MutableState<Color>> {
|
|||
error("No MainBottomSheetColor provided")
|
||||
}
|
||||
|
||||
val LocalBladeAnimation = staticCompositionLocalOf<BladeAnimation> {
|
||||
error("No MainBottomSheetColor provided")
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the dark theme should be used based on the given [AppThemeMode].
|
||||
*
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import androidx.constraintlayout.compose.ConstraintLayout
|
|||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.buttons.HorizontalActionChips
|
||||
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
|
||||
import com.tangem.core.ui.components.flicker
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
|
|
@ -115,10 +115,12 @@ private fun FiatBalance(
|
|||
),
|
||||
)
|
||||
is TokenDetailsBalanceBlockState.Content -> Text(
|
||||
modifier = modifier.flicker(state.isBalanceFlickering),
|
||||
modifier = modifier,
|
||||
text = state.displayFiatBalance.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.h2.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
)
|
||||
is TokenDetailsBalanceBlockState.Error -> Text(
|
||||
modifier = modifier,
|
||||
|
|
@ -143,10 +145,12 @@ private fun CryptoBalance(
|
|||
),
|
||||
)
|
||||
is TokenDetailsBalanceBlockState.Content -> Text(
|
||||
modifier = modifier.flicker(state.isBalanceFlickering),
|
||||
modifier = modifier,
|
||||
text = state.displayCryptoBalance.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors.text.tertiary,
|
||||
),
|
||||
)
|
||||
is TokenDetailsBalanceBlockState.Error -> Text(
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import androidx.constraintlayout.compose.Dimension
|
|||
import com.tangem.core.ui.components.FontSizeRange
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.ResizableText
|
||||
import com.tangem.core.ui.components.flicker
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
|
|
@ -277,15 +277,16 @@ private fun Balance(state: WalletCardState, isBalanceHidden: Boolean, modifier:
|
|||
when (state) {
|
||||
is WalletCardState.Content -> {
|
||||
ResizableText(
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size32)
|
||||
.flicker(isFlickering = state.isBalanceFlickering),
|
||||
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
|
||||
text = balance,
|
||||
fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.h2,
|
||||
style = TangemTheme.typography.h2
|
||||
.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletCardState.Error -> NonContentBalanceText(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue