Updated on 2026-08-14
This commit is contained in:
parent
13d5d24cd4
commit
e018eee87b
8 changed files with 289 additions and 204 deletions
|
|
@ -4,13 +4,7 @@ import android.content.res.Configuration
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -36,20 +30,12 @@ import com.tangem.common.ui.earn.EarnBlockUM.Type
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.ds.button.TangemButton
|
||||
import com.tangem.core.ui.ds.button.TangemButtonShape
|
||||
import com.tangem.core.ui.ds.button.TangemButtonSize
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.ds.button.*
|
||||
import com.tangem.core.ui.ds.image.TangemIcon
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.TangemRowContainer
|
||||
import com.tangem.core.ui.ds.row.TangemRowLayoutId
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.res.R as CoreResR
|
||||
|
|
@ -74,7 +60,7 @@ fun EarnBlock(state: EarnBlockUM, modifier: Modifier = Modifier) {
|
|||
|
||||
@Composable
|
||||
private fun EarnBlockLoading(modifier: Modifier = Modifier) {
|
||||
val shape = RoundedCornerShape(TangemTheme.dimens2.x4)
|
||||
val shape = RoundedCornerShape(TangemTheme.dimens2.x5)
|
||||
TangemRowContainer(
|
||||
modifier = modifier
|
||||
.clip(shape)
|
||||
|
|
@ -91,13 +77,13 @@ private fun EarnBlockLoading(modifier: Modifier = Modifier) {
|
|||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.START_TOP)
|
||||
.size(width = TangemTheme.dimens2.x16, height = TangemTheme.dimens2.x5),
|
||||
.size(width = ShimmerSubtitleWidth, height = TangemTheme.dimens2.x4),
|
||||
radius = TangemTheme.dimens2.x2,
|
||||
)
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.START_BOTTOM)
|
||||
.size(width = ShimmerSubtitleWidth, height = TangemTheme.dimens2.x4),
|
||||
.size(width = TangemTheme.dimens2.x16, height = TangemTheme.dimens2.x5),
|
||||
radius = TangemTheme.dimens2.x2,
|
||||
)
|
||||
},
|
||||
|
|
@ -106,7 +92,7 @@ private fun EarnBlockLoading(modifier: Modifier = Modifier) {
|
|||
|
||||
@Composable
|
||||
private fun EarnBlockContent(state: EarnBlockUM.Content, modifier: Modifier = Modifier) {
|
||||
val shape = RoundedCornerShape(TangemTheme.dimens2.x4)
|
||||
val shape = RoundedCornerShape(TangemTheme.dimens2.x5)
|
||||
|
||||
val clickModifier = state.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier
|
||||
|
||||
|
|
@ -114,7 +100,7 @@ private fun EarnBlockContent(state: EarnBlockUM.Content, modifier: Modifier = Mo
|
|||
modifier = modifier
|
||||
.clip(shape)
|
||||
.then(clickModifier.backgroundModifier(state.type, state.backgroundUM, shape)),
|
||||
contentPadding = PaddingValues(all = TangemTheme.dimens2.x3),
|
||||
contentPadding = PaddingValues(all = TangemTheme.dimens2.x4),
|
||||
content = {
|
||||
EarnBlockIcon(
|
||||
type = state.type,
|
||||
|
|
@ -361,7 +347,7 @@ private val EarnBlockUM.TitleUM.Style.textStyle: TextStyle
|
|||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = when (this) {
|
||||
EarnBlockUM.TitleUM.Style.Large -> TangemTheme.typography2.bodySemibold16
|
||||
EarnBlockUM.TitleUM.Style.Large -> TangemTheme.typography2.bodyMedium16
|
||||
EarnBlockUM.TitleUM.Style.Small -> TangemTheme.typography2.captionMedium12
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +355,7 @@ private val EarnBlockUM.SubtitleUM.Style.textStyle: TextStyle
|
|||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = when (this) {
|
||||
EarnBlockUM.SubtitleUM.Style.Large -> TangemTheme.typography2.bodySemibold16
|
||||
EarnBlockUM.SubtitleUM.Style.Large -> TangemTheme.typography2.bodyMedium16
|
||||
EarnBlockUM.SubtitleUM.Style.Small -> TangemTheme.typography2.captionMedium12
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -410,12 +396,12 @@ private class EarnBlockStakingPreviewProvider : CollectionPreviewParameterProvid
|
|||
iconUM = EarnBlockUM.IconUM.Plain(iconRes = R.drawable.ic_staking_disable_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.common_stake),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Disabled,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(CoreResR.string.staking_notification_network_error_text),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
),
|
||||
trailingUM = null,
|
||||
|
|
@ -426,12 +412,12 @@ private class EarnBlockStakingPreviewProvider : CollectionPreviewParameterProvid
|
|||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_staking_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.common_staking),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = stringReference("Average APR 5.24%"),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
|
|
@ -445,12 +431,12 @@ private class EarnBlockStakingPreviewProvider : CollectionPreviewParameterProvid
|
|||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_staking_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.staking_enabled),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = stringReference("$ 12.34 rewards"),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Balance(
|
||||
|
|
@ -475,14 +461,14 @@ private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterPr
|
|||
id = CoreResR.string.yield_module_token_details_earn_notification_subtitle,
|
||||
formatArgs = wrappedList("5.24"),
|
||||
),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(
|
||||
CoreResR.string.yield_module_token_details_earn_notification_description,
|
||||
),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
|
|
@ -497,7 +483,7 @@ private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterPr
|
|||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_yield_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.yield_module_transaction_enter),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
|
|
@ -505,7 +491,7 @@ private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterPr
|
|||
id = CoreResR.string.yield_module_average_apy,
|
||||
formatArgs = wrappedList("5.24"),
|
||||
),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
|
|
@ -521,7 +507,7 @@ private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterPr
|
|||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_yield_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.common_yield_mode),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
iconUM = EarnBlockUM.TitleUM.IconUM(tone = EarnBlockUM.TitleUM.IconTone.Warning),
|
||||
),
|
||||
|
|
@ -530,7 +516,7 @@ private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterPr
|
|||
id = CoreResR.string.yield_module_average_apy,
|
||||
formatArgs = wrappedList("5.24"),
|
||||
),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
|
|
@ -546,7 +532,7 @@ private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterPr
|
|||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_yield_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.common_yield_mode),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
iconUM = EarnBlockUM.TitleUM.IconUM(tone = EarnBlockUM.TitleUM.IconTone.Info),
|
||||
),
|
||||
|
|
@ -555,7 +541,7 @@ private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterPr
|
|||
id = CoreResR.string.yield_module_average_apy,
|
||||
formatArgs = wrappedList("5.24"),
|
||||
),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
|
|
@ -571,12 +557,12 @@ private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterPr
|
|||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_yield_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.common_yield_mode),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(CoreResR.string.common_enabling),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||
loader = EarnBlockUM.SubtitleUM.Loader(tone = EarnBlockUM.SubtitleUM.LoaderTone.Positive),
|
||||
),
|
||||
|
|
@ -589,12 +575,12 @@ private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterPr
|
|||
iconUM = EarnBlockUM.IconUM.Plain(iconRes = R.drawable.ic_yield_disabling_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.common_yield_mode),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(CoreResR.string.common_disabling),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
loader = EarnBlockUM.SubtitleUM.Loader(tone = EarnBlockUM.SubtitleUM.LoaderTone.Muted),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -93,6 +93,39 @@ fun Modifier.topFade(
|
|||
solidStop = solidStop,
|
||||
)
|
||||
|
||||
/**
|
||||
* Draws a vertical gradient fade over the top edge with custom [colorStops].
|
||||
*
|
||||
* Stops are defined relative to [height] (0f = top, 1f = bottom of the fade region).
|
||||
*
|
||||
* Note: the gradient is drawn over the entire content area, so the last color stop's color
|
||||
* extends below the fade region down to the bottom. Pass [Color.Transparent] as the last stop
|
||||
* (or a color matching the underlying content) to avoid covering the area outside the fade.
|
||||
*/
|
||||
@Composable
|
||||
fun Modifier.topFade(height: Dp, vararg colorStops: Pair<Float, Color>): Modifier = composed {
|
||||
drawWithContent {
|
||||
drawContent()
|
||||
|
||||
if (this.size.height <= 0f) return@drawWithContent
|
||||
val fraction = (height.toPx() / this.size.height).coerceIn(0f, 1f)
|
||||
if (fraction <= 0f) return@drawWithContent
|
||||
|
||||
val (start, end) = this.size.getFadeOffsets(FadePosition.TOP)
|
||||
|
||||
drawRect(
|
||||
brush = Brush.linearGradient(
|
||||
colorStops = colorStops
|
||||
.map { (stop, color) -> stop.coerceIn(0f, 1f) * fraction to color }
|
||||
.toTypedArray(),
|
||||
start = start,
|
||||
end = end,
|
||||
),
|
||||
size = this.size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class FadePosition {
|
||||
TOP, BOTTOM, LEFT, RIGHT
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -28,9 +29,10 @@ import androidx.compose.ui.unit.sp
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
enum class AccountIconSize {
|
||||
Default, Large, Medium, Small, ExtraSmall, RedesignedDefault
|
||||
Default, Large, Medium, Small, ExtraSmall, RedesignedDefault, RedesignExtraSmall
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -129,6 +131,7 @@ fun AccountCharIcon(char: Char, color: Color, size: AccountIconSize, modifier: M
|
|||
AccountIconSize.Small -> TangemTheme.typography.subtitle2
|
||||
AccountIconSize.ExtraSmall -> TangemTheme.typography.caption1
|
||||
AccountIconSize.RedesignedDefault -> TangemTheme.typography2.headingSemibold28
|
||||
AccountIconSize.RedesignExtraSmall -> TangemTheme.typography2.captionMedium11
|
||||
}
|
||||
|
||||
val textSize by animateFloatAsState(
|
||||
|
|
@ -148,6 +151,7 @@ fun AccountCharIcon(char: Char, color: Color, size: AccountIconSize, modifier: M
|
|||
text = char.uppercase(),
|
||||
style = textStyle.copy(fontSize = textSize.sp),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -161,6 +165,7 @@ private fun AccountIconSize.iconSizeInDp(): Dp = when (this) {
|
|||
AccountIconSize.Small -> 12.dp
|
||||
AccountIconSize.ExtraSmall -> 8.dp
|
||||
AccountIconSize.RedesignedDefault -> 20.dp
|
||||
AccountIconSize.RedesignExtraSmall -> 8.dp
|
||||
}
|
||||
|
||||
fun AccountIconSize.toBoxSize(): Dp = when (this) {
|
||||
|
|
@ -170,6 +175,7 @@ fun AccountIconSize.toBoxSize(): Dp = when (this) {
|
|||
AccountIconSize.Small -> 20.dp
|
||||
AccountIconSize.ExtraSmall -> 14.dp
|
||||
AccountIconSize.RedesignedDefault -> 40.dp
|
||||
AccountIconSize.RedesignExtraSmall -> 16.dp
|
||||
}
|
||||
|
||||
private fun AccountIconSize.boxShapeSizeInDp(): Dp = when (this) {
|
||||
|
|
@ -179,6 +185,7 @@ private fun AccountIconSize.boxShapeSizeInDp(): Dp = when (this) {
|
|||
AccountIconSize.Small -> 6.dp
|
||||
AccountIconSize.ExtraSmall -> 4.dp
|
||||
AccountIconSize.RedesignedDefault -> 12.dp
|
||||
AccountIconSize.RedesignExtraSmall -> 6.dp
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
|
|
@ -194,6 +201,19 @@ private fun Preview() {
|
|||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewRedesigned() {
|
||||
TangemThemePreviewRedesign {
|
||||
Row(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
Sample()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Sample() {
|
||||
var sizeState by remember { mutableStateOf(AccountIconSize.ExtraSmall) }
|
||||
|
|
@ -206,8 +226,9 @@ private fun Sample() {
|
|||
AccountIconSize.Large -> AccountIconSize.Medium
|
||||
AccountIconSize.Medium -> AccountIconSize.Small
|
||||
AccountIconSize.Small -> AccountIconSize.ExtraSmall
|
||||
AccountIconSize.ExtraSmall -> AccountIconSize.Default
|
||||
AccountIconSize.RedesignedDefault -> AccountIconSize.Large
|
||||
AccountIconSize.ExtraSmall -> AccountIconSize.RedesignedDefault
|
||||
AccountIconSize.RedesignedDefault -> AccountIconSize.RedesignExtraSmall
|
||||
AccountIconSize.RedesignExtraSmall -> AccountIconSize.Default
|
||||
}
|
||||
}) { Text("Change") }
|
||||
|
||||
|
|
@ -225,5 +246,6 @@ private fun Sample() {
|
|||
AccountCharIcon(char = 'M', color = Color.Magenta, size = AccountIconSize.Medium)
|
||||
AccountCharIcon(char = 'S', color = Color.DarkGray, size = AccountIconSize.Small)
|
||||
AccountCharIcon(char = 'E', color = Color.Green, size = AccountIconSize.ExtraSmall)
|
||||
AccountCharIcon(char = 'D', color = Color.LightGray, size = AccountIconSize.RedesignExtraSmall)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,12 +8,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.defaultAmount
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.formatStyled
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -61,12 +56,12 @@ internal class UpdateStakingNotificationTransformer(
|
|||
iconUM = EarnBlockUM.IconUM.Plain(iconRes = CoreUiR.drawable.ic_staking_disable_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.common_staking),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Disabled,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = resourceReference(CoreResR.string.staking_notification_network_error_text),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
),
|
||||
trailingUM = null,
|
||||
|
|
@ -122,12 +117,12 @@ internal class UpdateStakingNotificationTransformer(
|
|||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = CoreUiR.drawable.ic_staking_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(id = CoreResR.string.common_staking),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||
text = stakeAvailableSubtitle(availability.option.displayRewardInfo),
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
),
|
||||
trailingUM = EarnBlockUM.TrailingUM.Button(
|
||||
|
|
@ -170,7 +165,7 @@ internal class UpdateStakingNotificationTransformer(
|
|||
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = CoreUiR.drawable.ic_staking_40),
|
||||
titleUM = EarnBlockUM.TitleUM(
|
||||
text = resourceReference(CoreResR.string.staking_enabled),
|
||||
style = EarnBlockUM.TitleUM.Style.Large,
|
||||
style = EarnBlockUM.TitleUM.Style.Small,
|
||||
tone = EarnBlockUM.TitleUM.Tone.Primary,
|
||||
),
|
||||
subtitleUM = getRewardSubtitle(status, rewardFiatAmount),
|
||||
|
|
@ -257,7 +252,7 @@ internal class UpdateStakingNotificationTransformer(
|
|||
|
||||
return EarnBlockUM.SubtitleUM.Text(
|
||||
text = text,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||
style = EarnBlockUM.SubtitleUM.Style.Large,
|
||||
tone = if (isAccent) EarnBlockUM.SubtitleUM.Tone.Accent else EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,12 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import com.tangem.common.ui.earn.EarnBlock
|
||||
import com.tangem.common.ui.notifications.notifications
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -31,31 +16,30 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.common.ui.account.AccountIconUM
|
||||
import com.tangem.common.ui.earn.EarnBlock
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
|
||||
import com.tangem.common.ui.notifications.notifications
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshSlidingContainer
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.topFade
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.themedColor
|
||||
import com.tangem.core.ui.res.LocalHazeState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.AddFundsUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceTypeUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TransferUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM.TitleState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.ZeroBalanceActionsUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlock
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.ZeroBalanceActionsBlock
|
||||
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
|
||||
|
|
@ -64,6 +48,7 @@ import com.tangem.features.txhistory.component.TxHistoryComponent
|
|||
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
|
||||
import com.tangem.features.txhistory.entity.TxHistoryUM
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyComponent
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -72,6 +57,9 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
private val TopBarHeight: Dp = 64.dp
|
||||
private val MarketBlockHorizontalPadding: Dp = 14.dp
|
||||
|
||||
private const val TOP_FADE_MID_STOP = 0.8f
|
||||
private const val TOP_FADE_MID_ALPHA = 0.8f
|
||||
|
||||
@Composable
|
||||
internal fun TokenDetailsScreen(
|
||||
tokenDetailsUM: TokenDetailsUM,
|
||||
|
|
@ -84,20 +72,17 @@ internal fun TokenDetailsScreen(
|
|||
val expressState by expressTransactionsComponent.state.collectAsStateWithLifecycle()
|
||||
val statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() }
|
||||
val topBarTotalHeight = TopBarHeight + statusBarHeight
|
||||
val hazeState = rememberHazeState()
|
||||
|
||||
val rootBackground = TangemTheme.colors2.surface.level2
|
||||
var marketBlockHeight by remember { mutableStateOf(0.dp) }
|
||||
val effectiveBottomPadding = marketBlockHeight + TangemTheme.dimens2.x4
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(rootBackground),
|
||||
) {
|
||||
CompositionLocalProvider(LocalHazeState provides hazeState) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.hazeSourceTangem(zIndex = -2f),
|
||||
.background(rootBackground),
|
||||
) {
|
||||
TangemPullToRefreshSlidingContainer(
|
||||
config = tokenDetailsUM.pullToRefreshConfig,
|
||||
|
|
@ -115,27 +100,17 @@ internal fun TokenDetailsScreen(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
||||
TokenDetailsTopBar(topAppBarUM = tokenDetailsUM.topAppBarUM)
|
||||
|
||||
if (tokenMarketBlockComponent != null) {
|
||||
TokenDetailsMarketBlockOverlay(
|
||||
component = tokenMarketBlockComponent,
|
||||
onHeightChange = { marketBlockHeight = it },
|
||||
)
|
||||
}
|
||||
expressState.bottomSheetSlot?.content(null)
|
||||
}
|
||||
|
||||
TokenDetailsTopBarOverlay(topAppBarUM = tokenDetailsUM.topAppBarUM)
|
||||
|
||||
if (tokenMarketBlockComponent != null) {
|
||||
TokenDetailsMarketBlockOverlay(
|
||||
component = tokenMarketBlockComponent,
|
||||
onHeightChange = { marketBlockHeight = it },
|
||||
)
|
||||
}
|
||||
|
||||
expressState.bottomSheetSlot?.content(null)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenDetailsTopBarOverlay(topAppBarUM: TokenDetailsTopAppBarUM) {
|
||||
Box(
|
||||
modifier = Modifier.background(TangemTheme.colors2.surface.level2),
|
||||
) {
|
||||
TokenDetailsTopBar(topAppBarUM = topAppBarUM)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +164,14 @@ private fun TokenDetailsBody(
|
|||
.padding(start = TangemTheme.dimens2.x4, end = TangemTheme.dimens2.x4, top = TangemTheme.dimens2.x4)
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
modifier = modifier
|
||||
.hazeSourceTangem(state = LocalHazeState.current)
|
||||
.topFade(
|
||||
height = topContentPadding,
|
||||
0f to rootBackground,
|
||||
TOP_FADE_MID_STOP to rootBackground.copy(alpha = TOP_FADE_MID_ALPHA),
|
||||
1f to Color.Transparent,
|
||||
),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(top = topContentPadding, bottom = bottomContentPadding),
|
||||
) {
|
||||
|
|
@ -201,14 +183,6 @@ private fun TokenDetailsBody(
|
|||
)
|
||||
}
|
||||
val balance = tokenDetailsUM.balanceBlockUM
|
||||
if (balance is TokenDetailsBalanceBlockUM.Content && balance.isBalanceZero) {
|
||||
item(key = "zero_balance_actions") {
|
||||
ZeroBalanceActionsBlock(
|
||||
state = tokenDetailsUM.zeroBalanceActionsUM,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
notifications(
|
||||
notifications = tokenDetailsUM.notifications,
|
||||
contentColor = rootBackground,
|
||||
|
|
@ -218,12 +192,20 @@ private fun TokenDetailsBody(
|
|||
item(key = "staking_block") {
|
||||
EarnBlock(
|
||||
state = earnBlock,
|
||||
modifier = itemModifier,
|
||||
modifier = itemModifier.padding(vertical = TangemTheme.dimens2.x3),
|
||||
)
|
||||
}
|
||||
}
|
||||
item(key = "yield_supply_block") {
|
||||
yieldSupplyComponent.Content(modifier = itemModifier.padding(vertical = TangemTheme.dimens2.x2))
|
||||
yieldSupplyComponent.Content(modifier = itemModifier.padding(vertical = TangemTheme.dimens2.x3))
|
||||
}
|
||||
if (balance is TokenDetailsBalanceBlockUM.Content && balance.isBalanceZero) {
|
||||
item(key = "zero_balance_actions") {
|
||||
ZeroBalanceActionsBlock(
|
||||
state = tokenDetailsUM.zeroBalanceActionsUM,
|
||||
modifier = itemModifier.padding(vertical = TangemTheme.dimens2.x2),
|
||||
)
|
||||
}
|
||||
}
|
||||
with(expressTransactionsComponent) {
|
||||
expressTransactionsContent(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
|
|
@ -11,6 +13,7 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.*
|
||||
|
|
@ -30,6 +33,8 @@ import com.tangem.core.ui.components.account.AccountIconSize
|
|||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownItem
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
|
||||
import com.tangem.core.ui.components.haze.ProvideHaze
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||
import com.tangem.core.ui.ds.image.TangemDeviceIcon
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
|
|
@ -44,19 +49,22 @@ import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
|||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM.TitleState
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.*
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
|
||||
@Composable
|
||||
internal fun TokenDetailsTopBar(topAppBarUM: TokenDetailsTopAppBarUM, modifier: Modifier = Modifier) {
|
||||
val actionModifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.hazeEffectTangem { blurRadius = ACTION_BLUR_RADIUS }
|
||||
TangemTopBar(
|
||||
modifier = modifier.statusBarsPadding(),
|
||||
startContent = {
|
||||
TangemTopBarActionContent(
|
||||
modifier = actionModifier,
|
||||
actionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_arrow_back_28,
|
||||
onClick = topAppBarUM.onBackClick,
|
||||
ghostModeProgress = 1f,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
@ -65,10 +73,10 @@ internal fun TokenDetailsTopBar(topAppBarUM: TokenDetailsTopAppBarUM, modifier:
|
|||
var isDropdownMenuShown by rememberSaveable { mutableStateOf(false) }
|
||||
Box {
|
||||
TangemTopBarActionContent(
|
||||
modifier = actionModifier,
|
||||
actionUM = TangemTopBarActionUM(
|
||||
iconRes = CoreUiR.drawable.ic_more_default_24,
|
||||
onClick = { isDropdownMenuShown = true },
|
||||
ghostModeProgress = 1f,
|
||||
),
|
||||
)
|
||||
TangemDropdownMenu(
|
||||
|
|
@ -100,8 +108,8 @@ internal fun TokenDetailsTopBar(topAppBarUM: TokenDetailsTopAppBarUM, modifier:
|
|||
TokenDetailsTitle(titleState = topAppBarUM.titleState)
|
||||
Text(
|
||||
text = topAppBarUM.subtitle.resolveAnnotatedReference(),
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -138,11 +146,7 @@ private fun TokenDetailsTitle(titleState: TitleState) {
|
|||
AdaptiveTokenWithSecondaryRow(
|
||||
tokenName = titleState.tokenName,
|
||||
secondaryName = titleState.walletName,
|
||||
template = stringResourceSafe(
|
||||
id = CoreUiR.string.token_details_toolbar_title_token_in_wallet,
|
||||
titleState.tokenName,
|
||||
titleState.walletName,
|
||||
),
|
||||
template = formattedTitleTemplate(CoreUiR.string.token_details_toolbar_title_token_in_wallet),
|
||||
appearance = appearance,
|
||||
icon = {
|
||||
TangemDeviceIcon(
|
||||
|
|
@ -153,21 +157,16 @@ private fun TokenDetailsTitle(titleState: TitleState) {
|
|||
)
|
||||
}
|
||||
is TitleState.WithAccount -> {
|
||||
val accountNameStr = titleState.accountName.resolveAnnotatedReference().toString()
|
||||
AdaptiveTokenWithSecondaryRow(
|
||||
tokenName = titleState.tokenName,
|
||||
secondaryName = accountNameStr,
|
||||
template = stringResourceSafe(
|
||||
id = CoreUiR.string.token_details_toolbar_title_token_in_account,
|
||||
titleState.tokenName,
|
||||
accountNameStr,
|
||||
),
|
||||
secondaryName = titleState.accountName.resolveAnnotatedReference().toString(),
|
||||
template = formattedTitleTemplate(CoreUiR.string.token_details_toolbar_title_token_in_account),
|
||||
appearance = appearance,
|
||||
icon = {
|
||||
AccountIcon(
|
||||
name = titleState.accountName,
|
||||
icon = titleState.accountIconUM,
|
||||
size = AccountIconSize.ExtraSmall,
|
||||
size = AccountIconSize.RedesignExtraSmall,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -178,9 +177,12 @@ private fun TokenDetailsTitle(titleState: TitleState) {
|
|||
/**
|
||||
* Adaptive title for [TitleState.WithAccount] / [TitleState.WithWallet].
|
||||
*
|
||||
* Phrase template carries the [IMAGE_PLACEHOLDER] marker — translator decides where
|
||||
* the icon sits (e.g. "Tether in [⭐] Portfolio" or "Tether in My Wallet [⭐]").
|
||||
* RTL is handled by BiDi inside the single [Text].
|
||||
* Raw template carries [TOKEN_MARKER] / [SECONDARY_MARKER] for the names and
|
||||
* [IMAGE_PLACEHOLDER] for the icon — translator decides where each sits
|
||||
* (e.g. "Tether in [⭐] Portfolio" or "Tether in My Wallet [⭐]"). Anything outside
|
||||
* the markers (connecting words, punctuation) is painted as tertiary text — no
|
||||
* locale-specific substring matching required. RTL is handled by BiDi inside the
|
||||
* single [Text].
|
||||
*
|
||||
* Width-driven cascade:
|
||||
* 1–2. Full phrase as single [Text] with inline icon; [TextOverflow.Ellipsis]
|
||||
|
|
@ -201,7 +203,7 @@ private fun AdaptiveTokenWithSecondaryRow(
|
|||
appearance: TitleAppearance,
|
||||
icon: @Composable () -> Unit,
|
||||
) {
|
||||
val (beforeIcon, afterIcon) = remember(template) { splitTemplate(template) }
|
||||
val segments = remember(template) { parseTemplate(template) }
|
||||
val inlineContent = rememberIconInlineContent(appearance.iconSize, icon)
|
||||
|
||||
BoxWithConstraints(
|
||||
|
|
@ -209,16 +211,16 @@ private fun AdaptiveTokenWithSecondaryRow(
|
|||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val isFullTextShown = rememberShouldShowFullText(
|
||||
beforeIcon = beforeIcon,
|
||||
afterIcon = afterIcon,
|
||||
secondaryName = secondaryName,
|
||||
segments = segments,
|
||||
tokenName = tokenName,
|
||||
appearance = appearance,
|
||||
maxWidthPx = constraints.maxWidth,
|
||||
)
|
||||
if (isFullTextShown) {
|
||||
FullPhraseTitle(
|
||||
beforeIcon = beforeIcon,
|
||||
afterIcon = afterIcon,
|
||||
segments = segments,
|
||||
tokenName = tokenName,
|
||||
secondaryName = secondaryName,
|
||||
style = appearance.style,
|
||||
inlineContent = inlineContent,
|
||||
)
|
||||
|
|
@ -232,17 +234,25 @@ private fun AdaptiveTokenWithSecondaryRow(
|
|||
}
|
||||
}
|
||||
|
||||
private fun splitTemplate(template: String): Pair<String, String> {
|
||||
val parts = template.split(IMAGE_PLACEHOLDER, limit = 2)
|
||||
return if (parts.size == 2) parts[0] to parts[1] else template to ""
|
||||
}
|
||||
/**
|
||||
* Returns the title template with its `%1$s` / `%2$s` placeholders replaced by [TOKEN_MARKER] /
|
||||
* [SECONDARY_MARKER] sentinels (and `%%image%%` collapsed to [IMAGE_PLACEHOLDER]).
|
||||
*
|
||||
* Feeding the markers in as format args lets [String.format] resolve all escaping for us, so
|
||||
* [parseTemplate] sees a clean string and the real names stay un-substituted until render time.
|
||||
*/
|
||||
@Composable
|
||||
private fun formattedTitleTemplate(@StringRes id: Int): String = stringResourceSafe(id, TOKEN_MARKER, SECONDARY_MARKER)
|
||||
|
||||
@Composable
|
||||
private fun rememberIconInlineContent(iconSize: Dp, icon: @Composable () -> Unit): Map<String, InlineTextContent> {
|
||||
private fun rememberIconInlineContent(
|
||||
iconSize: Dp,
|
||||
icon: @Composable () -> Unit,
|
||||
): ImmutableMap<String, InlineTextContent> {
|
||||
val iconSizeSp = with(LocalDensity.current) { iconSize.toSp() }
|
||||
val currentIcon by rememberUpdatedState(icon)
|
||||
return remember(iconSizeSp) {
|
||||
mapOf(
|
||||
persistentMapOf(
|
||||
ICON_INLINE_ID to InlineTextContent(
|
||||
placeholder = Placeholder(
|
||||
width = iconSizeSp,
|
||||
|
|
@ -261,23 +271,29 @@ private fun rememberIconInlineContent(iconSize: Dp, icon: @Composable () -> Unit
|
|||
|
||||
@Composable
|
||||
private fun rememberShouldShowFullText(
|
||||
beforeIcon: String,
|
||||
afterIcon: String,
|
||||
secondaryName: String,
|
||||
segments: ImmutableList<TitleSegment>,
|
||||
tokenName: String,
|
||||
appearance: TitleAppearance,
|
||||
maxWidthPx: Int,
|
||||
): Boolean {
|
||||
val measurer = rememberTextMeasurer()
|
||||
val density = LocalDensity.current
|
||||
return remember(beforeIcon, afterIcon, secondaryName, maxWidthPx, appearance, density) {
|
||||
return remember(segments, tokenName, maxWidthPx, appearance, density) {
|
||||
if (maxWidthPx <= 0) return@remember true
|
||||
val fullTextWidthPx = measurer
|
||||
.measure(text = beforeIcon + afterIcon, style = appearance.style, softWrap = false)
|
||||
.size.width
|
||||
val secondaryWidthPx = measurer
|
||||
.measure(text = secondaryName, style = appearance.style, softWrap = false)
|
||||
.size.width
|
||||
val staticWidthPx = (fullTextWidthPx - secondaryWidthPx).coerceAtLeast(0)
|
||||
val staticText = buildString {
|
||||
for (segment in segments) {
|
||||
when (segment) {
|
||||
TitleSegment.Token -> append(tokenName)
|
||||
is TitleSegment.Plain -> append(segment.text)
|
||||
TitleSegment.Secondary, TitleSegment.Image -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
val staticWidthPx = if (staticText.isEmpty()) {
|
||||
0
|
||||
} else {
|
||||
measurer.measure(text = staticText, style = appearance.style, softWrap = false).size.width
|
||||
}
|
||||
val iconReservePx = with(density) {
|
||||
(appearance.iconSize + appearance.spacing * 2).toPx()
|
||||
}.toInt()
|
||||
|
|
@ -288,16 +304,25 @@ private fun rememberShouldShowFullText(
|
|||
|
||||
@Composable
|
||||
private fun FullPhraseTitle(
|
||||
beforeIcon: String,
|
||||
afterIcon: String,
|
||||
segments: ImmutableList<TitleSegment>,
|
||||
tokenName: String,
|
||||
secondaryName: String,
|
||||
style: TextStyle,
|
||||
inlineContent: Map<String, InlineTextContent>,
|
||||
inlineContent: ImmutableMap<String, InlineTextContent>,
|
||||
) {
|
||||
val fullText = remember(beforeIcon, afterIcon) {
|
||||
val tertiaryColor = TangemTheme.colors2.text.neutral.tertiary
|
||||
val fullText = remember(segments, tokenName, secondaryName, tertiaryColor) {
|
||||
buildAnnotatedString {
|
||||
append(beforeIcon)
|
||||
appendInlineContent(ICON_INLINE_ID, IMAGE_PLACEHOLDER)
|
||||
append(afterIcon)
|
||||
for (segment in segments) {
|
||||
when (segment) {
|
||||
TitleSegment.Token -> append(tokenName)
|
||||
TitleSegment.Secondary -> append(secondaryName)
|
||||
TitleSegment.Image -> appendInlineContent(ICON_INLINE_ID, IMAGE_PLACEHOLDER)
|
||||
is TitleSegment.Plain -> withStyle(SpanStyle(color = tertiaryColor)) {
|
||||
append(segment.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(
|
||||
|
|
@ -311,6 +336,47 @@ private fun FullPhraseTitle(
|
|||
)
|
||||
}
|
||||
|
||||
private sealed interface TitleSegment {
|
||||
data object Token : TitleSegment
|
||||
data object Secondary : TitleSegment
|
||||
data object Image : TitleSegment
|
||||
data class Plain(val text: String) : TitleSegment
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the [String.format]ed template (see [formattedTitleTemplate]) into ordered segments.
|
||||
*
|
||||
* Markers appear in their resolved form: [TOKEN_MARKER] / [SECONDARY_MARKER] for the names and
|
||||
* [IMAGE_PLACEHOLDER] for the icon (escaping was already collapsed by [String.format]). Anything
|
||||
* else is plain connecting text painted as tertiary.
|
||||
*/
|
||||
private fun parseTemplate(template: String): ImmutableList<TitleSegment> {
|
||||
val segments = mutableListOf<TitleSegment>()
|
||||
val plain = StringBuilder()
|
||||
|
||||
fun flushPlain() {
|
||||
if (plain.isNotEmpty()) {
|
||||
segments += TitleSegment.Plain(plain.toString())
|
||||
plain.clear()
|
||||
}
|
||||
}
|
||||
|
||||
var i = 0
|
||||
while (i < template.length) {
|
||||
val marker = MARKERS.firstOrNull { (text, _) -> template.startsWith(text, i) }
|
||||
if (marker != null) {
|
||||
flushPlain()
|
||||
segments += marker.second
|
||||
i += marker.first.length
|
||||
} else {
|
||||
plain.append(template[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
flushPlain()
|
||||
return segments.toImmutableList()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FallbackTokenWithIconTitle(tokenName: String, appearance: TitleAppearance, icon: @Composable () -> Unit) {
|
||||
Row(
|
||||
|
|
@ -342,7 +408,17 @@ private data class TitleAppearance(
|
|||
|
||||
private const val ICON_INLINE_ID = "account_icon"
|
||||
private const val IMAGE_PLACEHOLDER = "%image%"
|
||||
private const val TOKEN_MARKER = "%1\$s"
|
||||
private const val SECONDARY_MARKER = "%2\$s"
|
||||
|
||||
// Order matters: longer/overlapping markers must be tried first by [parseTemplate].
|
||||
private val MARKERS: List<Pair<String, TitleSegment>> = listOf(
|
||||
IMAGE_PLACEHOLDER to TitleSegment.Image,
|
||||
TOKEN_MARKER to TitleSegment.Token,
|
||||
SECONDARY_MARKER to TitleSegment.Secondary,
|
||||
)
|
||||
|
||||
private val ACTION_BLUR_RADIUS = 8.dp
|
||||
private val MIN_TITLE_FONT_SIZE = 12.sp
|
||||
private val MAX_TITLE_FONT_SIZE = 16.sp
|
||||
private val MIN_SECONDARY_NAME_WIDTH = 48.dp
|
||||
|
|
@ -355,20 +431,22 @@ private fun TokenDetailsTopBar_Preview(
|
|||
@PreviewParameter(TokenDetailsTopBarPreviewProvider::class) titleState: TitleState,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TokenDetailsTopBar(
|
||||
topAppBarUM = TokenDetailsTopAppBarUM(
|
||||
titleState = titleState,
|
||||
subtitle = stringReference("ERC-20 in Ethereum network"),
|
||||
onBackClick = {},
|
||||
menuItems = persistentListOf(
|
||||
TangemDropdownMenuItem(
|
||||
title = stringReference("Hide Token"),
|
||||
textColor = themedColor { TangemTheme.colors.text.warning },
|
||||
onClick = {},
|
||||
ProvideHaze {
|
||||
TokenDetailsTopBar(
|
||||
topAppBarUM = TokenDetailsTopAppBarUM(
|
||||
titleState = titleState,
|
||||
subtitle = stringReference("ERC-20 in Ethereum network"),
|
||||
onBackClick = {},
|
||||
menuItems = persistentListOf(
|
||||
TangemDropdownMenuItem(
|
||||
title = stringReference("Hide Token"),
|
||||
textColor = themedColor { TangemTheme.colors.text.warning },
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,16 +4,10 @@ import android.content.res.Configuration
|
|||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -102,8 +96,7 @@ private fun ContentBody(state: TokenDetailsBalanceBlockUM.Content, isBalanceHidd
|
|||
targetState = state.tokenBalanceTypeUM.type,
|
||||
label = "Token balance type",
|
||||
) { currentType ->
|
||||
val tokenBalanceTypeUM = state.tokenBalanceTypeUM
|
||||
when (tokenBalanceTypeUM) {
|
||||
when (val tokenBalanceTypeUM = state.tokenBalanceTypeUM) {
|
||||
is TokenBalanceTypeUM.Multiple -> Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
|
|
@ -111,20 +104,20 @@ private fun ContentBody(state: TokenDetailsBalanceBlockUM.Content, isBalanceHidd
|
|||
) {
|
||||
Text(
|
||||
text = currentType.text.resolveReference(),
|
||||
style = TangemTheme.typography2.calloutSemibold15,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
style = TangemTheme.typography2.subheadlineMedium14,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_sort_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors2.graphic.neutral.secondary,
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x4),
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x5),
|
||||
)
|
||||
}
|
||||
TokenBalanceTypeUM.Single -> Text(
|
||||
text = currentType.text.resolveReference(),
|
||||
style = TangemTheme.typography2.calloutSemibold15,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
style = TangemTheme.typography2.subheadlineMedium14,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -26,7 +22,7 @@ internal fun ZeroBalanceActionsBlock(state: ZeroBalanceActionsUM, modifier: Modi
|
|||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = TangemTheme.dimens2.x10),
|
||||
.padding(bottom = TangemTheme.dimens2.x6),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
) {
|
||||
ActionRow(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue