Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-27 17:52:48 +05:00
parent 128faaec8d
commit 31e0591ef0
20 changed files with 1242 additions and 20 deletions

View file

@ -8,7 +8,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@Suppress("LargeClass")
internal object WalletPreviewData {
internal object WalletPreviewDataLegacy {
val topBarConfig by lazy { WalletTopBarConfig(onDetailsClick = {}) }

View file

@ -15,7 +15,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarConfig
import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy.topBarConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.utils.StringsSigns.DASH_SIGN
import kotlinx.collections.immutable.persistentListOf

View file

@ -0,0 +1,22 @@
package com.tangem.feature.wallet.presentation.preview
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletActionButtons
import kotlinx.collections.immutable.persistentListOf
internal object WalletPreviewData {
val wallets by lazy {
mapOf(
UserWalletId(stringValue = "123") to WalletBalancePreview.content,
UserWalletId(stringValue = "321") to WalletBalancePreview.loading,
UserWalletId(stringValue = "24") to WalletBalancePreview.error,
)
}
val actionButtons = persistentListOf(
WalletActionButtons.Buy({}, false).buttonUM,
WalletActionButtons.Swap({}, false).buttonUM,
WalletActionButtons.Sell({}, false).buttonUM,
)
}

View file

@ -0,0 +1,89 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.feature.wallet.impl.R
private const val STARS_INLINE_CONTENT_ID = "stars"
@Composable
internal fun MarketsHint(isVisible: Boolean, modifier: Modifier = Modifier) {
AnimatedVisibility(
modifier = modifier,
visible = isVisible,
enter = fadeIn(animationSpec = tween(durationMillis = 300)),
exit = fadeOut(animationSpec = tween(durationMillis = 300)),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "Swipe up to explore the market", // todo redesign main lokalise
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.primary,
textAlign = TextAlign.Center,
)
Text(
text = buildAnnotatedString {
append("Find new hidden gems ") // todo redesign main lokalise
appendInlineContent(
STARS_INLINE_CONTENT_ID,
alternateText = "\uDBC0\uDDBF",
)
},
inlineContent = mapOf(
STARS_INLINE_CONTENT_ID to InlineTextContent(
placeholder = Placeholder(
width = TangemTheme.typography2.bodyRegular14.fontSize,
height = TangemTheme.typography2.bodyRegular14.fontSize,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
),
children = {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_magic_default_24),
tint = TangemTheme.colors2.text.neutral.tertiary,
contentDescription = null,
)
},
),
),
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
textAlign = TextAlign.Center,
)
}
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun MarketsHint_Preview() {
TangemThemePreviewRedesign {
MarketsHint(
isVisible = true,
)
}
}
// endregion

View file

@ -0,0 +1,144 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideIn
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.*
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.sheetscaffold.TangemSheetState
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.MarketTooltipTestTags
import com.tangem.core.ui.utils.lineTo
import com.tangem.core.ui.utils.moveTo
import com.tangem.core.ui.utils.toPx
import com.tangem.feature.wallet.impl.R
import kotlinx.coroutines.delay
import kotlin.math.roundToInt
@Composable
internal fun MarketsTooltip(
availableHeight: Dp,
bottomSheetState: TangemSheetState,
isVisible: Boolean,
modifier: Modifier = Modifier,
) {
val density = LocalDensity.current
val tooltipOffset by remember {
derivedStateOf {
val bottomSheetOffset = try {
// Can throw exception during the first composition
with(density) { bottomSheetState.requireOffset().toDp() }
} catch (e: Exception) {
0.dp
}
bottomSheetOffset - availableHeight
}
}
var isVisibleWrapped by remember { mutableStateOf(value = false) }
LaunchedEffect(isVisible) {
if (isVisible) {
delay(timeMillis = 300)
}
isVisibleWrapped = isVisible
}
val slideOffset = 40.dp.toPx()
AnimatedVisibility(
modifier = modifier
.offset { IntOffset(x = 0, y = tooltipOffset.roundToPx()) }
.testTag(MarketTooltipTestTags.CONTAINER),
visible = isVisibleWrapped,
enter = slideIn(
animationSpec = spring(
stiffness = Spring.StiffnessLow,
visibilityThreshold = IntOffset.VisibilityThreshold,
),
initialOffset = { _ -> IntOffset(y = -slideOffset.roundToInt(), x = 0) },
) + fadeIn(),
exit = fadeOut(),
) {
MarketsTooltipContent()
}
}
@Composable
internal fun MarketsTooltipContent(modifier: Modifier = Modifier) {
val backgroundColor = TangemTheme.colors.background.action
val cornerRadius = CornerRadius(x = 14.dp.toPx())
val tipDpSize = DpSize(width = 20.dp, height = 8.dp)
Column(
modifier = modifier
.padding(bottom = tipDpSize.height)
.drawBehind {
val rect = size.toRect()
val tipSize = tipDpSize.toSize()
val tipRect = Rect(
offset = Offset(
x = rect.center.x - tipSize.center.x,
y = rect.bottom,
),
size = tipSize,
)
drawRoundRect(color = backgroundColor, cornerRadius = cornerRadius)
val tipPath = Path().apply {
moveTo(tipRect.topLeft)
lineTo(tipRect.bottomCenter)
lineTo(tipRect.topRight)
}
drawPath(color = backgroundColor, path = tipPath)
}
.padding(all = 12.dp),
verticalArrangement = Arrangement.spacedBy(space = 4.dp),
horizontalAlignment = Alignment.Start,
) {
Text(
text = stringResourceSafe(id = R.string.markets_tooltip_title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = stringResourceSafe(id = R.string.markets_tooltip_message),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.secondary,
)
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun MarketsTooltip_Preview() {
TangemThemePreviewRedesign {
MarketsTooltipContent()
}
}
// endregion

View file

@ -9,14 +9,14 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.SimpleSettingsRow
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.getDefaultRowColors
import com.tangem.core.ui.components.getWarningRowColors
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy
import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonConfig
import kotlinx.collections.immutable.ImmutableList
@ -64,5 +64,5 @@ private fun ActionsBottomSheetContent_Light(
}
private class ActionsBottomSheetContentConfigProvider : CollectionPreviewParameterProvider<ActionsBottomSheetConfig>(
collection = listOf(WalletPreviewData.actionsBottomSheet),
collection = listOf(WalletPreviewDataLegacy.actionsBottomSheet),
)

View file

@ -0,0 +1,49 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import com.tangem.core.ui.ds.button.TangemButton
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.TangemPayMainScreenBlock
internal fun LazyListScope.nftCollections2(state: WalletUM, itemModifier: Modifier) {
(state as? WalletUM.Content)?.let { content ->
item(key = "NFTCollections", contentType = "NFTCollections") {
WalletNFTItem(
modifier = itemModifier,
state = content.nftState,
)
}
}
}
internal fun LazyListScope.organizeTokens2(state: WalletUM, itemModifier: Modifier) {
val organizeButton = state.tokensListUM.organizeButtonUM
if (organizeButton != null) {
item(
key = "OrganizeTokensButton",
contentType = "OrganizeTokensButton",
) {
TangemButton(
organizeButton,
modifier = itemModifier,
)
}
}
}
internal fun LazyListScope.tangemPay(walletUM: WalletUM, isBalanceHiding: Boolean, modifier: Modifier = Modifier) {
if (walletUM is WalletState.MultiCurrency) {
item(
key = "TangemPayMainScreenBlock",
contentType = walletUM.tangemPayState::class.java,
) {
TangemPayMainScreenBlock(
state = walletUM.tangemPayState,
isBalanceHidden = isBalanceHiding,
modifier = modifier,
)
}
}
}

View file

@ -26,7 +26,7 @@ import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletCard
import kotlinx.collections.immutable.ImmutableList
@ -106,7 +106,7 @@ private fun Preview_WalletsList() {
TangemThemePreview {
WalletsList(
lazyListState = rememberLazyListState(),
wallets = WalletPreviewData.wallets.values.toPersistentList(),
wallets = WalletPreviewDataLegacy.wallets.values.toPersistentList(),
isBalanceHidden = false,
)
}

View file

@ -0,0 +1,205 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
import com.tangem.core.ui.ds.topbar.collapsing.snapToExitUntilCollapsed
import com.tangem.core.ui.extensions.orEmpty
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.MainScreenTestTags
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 kotlinx.collections.immutable.ImmutableList
private const val MIN_SCALE = 0.75f
private const val MAX_SCALE = 1f
@Composable
internal fun WalletBalance(
walletBalanceUM: WalletBalanceUM,
behavior: TangemCollapsingAppBarBehavior,
buttons: ImmutableList<TangemButtonUM>,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
val collapsedFraction = behavior.state.collapsedFraction
val alpha = 1f - collapsedFraction
val scale = alpha.coerceIn(MIN_SCALE, MAX_SCALE)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.alpha(alpha)
.scale(scale)
.snapToExitUntilCollapsed(behavior)
.fillMaxWidth()
.padding(top = 64.dp)
.statusBarsPadding(),
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
) {
Balance(
walletBalanceUM = walletBalanceUM,
isBalanceHidden = isBalanceHidden,
)
SpacerH(TangemTheme.dimens2.x3)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
Text(
text = walletBalanceUM.name,
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
)
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_tangem_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.tertiaryConstant,
modifier = Modifier.size(TangemTheme.dimens2.x6),
)
}
}
SpacerH(TangemTheme.dimens2.x2)
ActionButtons(buttons)
SpacerH(TangemTheme.dimens2.x6)
}
}
@Composable
private fun Balance(walletBalanceUM: WalletBalanceUM, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
AnimatedContent(
targetState = walletBalanceUM,
label = "Update the balance",
modifier = modifier.testTag(MainScreenTestTags.WALLET_BALANCE),
transitionSpec = {
fadeIn(animationSpec = tween(durationMillis = 220, delayMillis = 90)) togetherWith
fadeOut(animationSpec = tween(durationMillis = 90))
},
) { balanceUM ->
when (balanceUM) {
is WalletBalanceUM.Content -> {
Text(
text = balanceUM.balance.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
style = TangemTheme.typography2.titleRegular44.applyBladeBrush(
isEnabled = balanceUM.isBalanceFlickering,
textColor = TangemTheme.colors2.text.neutral.primary,
),
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.Error,
is WalletBalanceUM.Loading,
-> {
TextShimmer(
text = "123456",
style = TangemTheme.typography2.titleRegular44,
radius = TangemTheme.dimens2.x25,
textSizeHeight = true,
)
}
}
}
}
@Composable
private fun ActionButtons(buttons: ImmutableList<TangemButtonUM>) {
Row(
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
buttons.fastForEach { button ->
key(button.text) {
Column(
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x2_5),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
horizontalAlignment = Alignment.CenterHorizontally,
) {
SecondaryTangemButton(
iconRes = button.iconRes,
onClick = button.onClick,
shape = TangemButtonShape.Rounded,
)
Text(
text = button.text.orEmpty().resolveReference(),
style = TangemTheme.typography2.bodySemibold15,
color = TangemTheme.colors2.text.neutral.primary,
)
}
}
}
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun WalletBalance_Preview(@PreviewParameter(WalletBalancePreviewProvider::class) params: WalletBalanceUM) {
TangemThemePreviewRedesign {
WalletBalance(
walletBalanceUM = params,
behavior = rememberTangemExitUntilCollapsedScrollBehavior(),
buttons = WalletPreviewData.actionButtons,
isBalanceHidden = false,
)
}
}
private class WalletBalancePreviewProvider : PreviewParameterProvider<WalletBalanceUM> {
override val values: Sequence<WalletBalanceUM>
get() = sequenceOf(
WalletBalancePreview.content,
WalletBalancePreview.content.copy(isBalanceFlickering = true),
WalletBalancePreview.loading,
WalletBalancePreview.error,
)
}
// endregion

View file

@ -12,7 +12,6 @@ import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.*
@ -45,7 +44,7 @@ import com.tangem.core.ui.res.TangemDimens
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDropDownItems
@ -375,22 +374,22 @@ private fun Preview_WalletCard(
private class WalletCardStateProvider : CollectionPreviewParameterProvider<WalletCardState>(
collection = listOf(
WalletPreviewData.walletCardContentState,
WalletPreviewData.walletCardContentState.copy(
WalletPreviewDataLegacy.walletCardContentState,
WalletPreviewDataLegacy.walletCardContentState.copy(
balance = "0.00",
),
WalletPreviewData.walletCardContentState.copy(
WalletPreviewDataLegacy.walletCardContentState.copy(
title = "Title",
additionalInfo = WalletAdditionalInfo(
hideable = false,
content = TextReference.Str("3 cards"),
),
),
WalletPreviewData.walletCardContentState.copy(
WalletPreviewDataLegacy.walletCardContentState.copy(
isBalanceFlickering = true,
),
WalletPreviewData.walletCardLoadingState,
WalletPreviewData.walletCardErrorState,
WalletPreviewDataLegacy.walletCardLoadingState,
WalletPreviewDataLegacy.walletCardErrorState,
),
)

View file

@ -1,12 +1,79 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.rememberOverscrollEffect
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.paging.compose.LazyPagingItems
import com.tangem.common.ui.notifications.notifications
import com.tangem.common.ui.notifications.notificationsCarousel
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.txHistoryItems
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItems
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItems2
import com.tangem.feature.wallet.presentation.wallet.ui.components.nftCollections2
import com.tangem.feature.wallet.presentation.wallet.ui.components.organizeTokens2
import com.tangem.feature.wallet.presentation.wallet.ui.components.tangemPay
import kotlinx.collections.immutable.toPersistentList
@Composable
internal fun WalletListContent(
currentWallet: WalletUM,
isBalanceHidden: Boolean,
listState: LazyListState,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
val containerColor = TangemTheme.colors2.surface.level1
val movableItemModifier = Modifier.padding(horizontal = TangemTheme.dimens2.x3)
val itemModifier = movableItemModifier.padding(top = TangemTheme.dimens2.x3)
LazyColumn(
modifier = modifier,
state = listState,
contentPadding = contentPadding,
horizontalAlignment = Alignment.CenterHorizontally,
overscrollEffect = rememberOverscrollEffect(),
) {
notifications(
notifications = currentWallet.notifications.map { it.messageUM }
.toPersistentList(),
contentColor = containerColor,
modifier = movableItemModifier,
)
notificationsCarousel(
containerColor = containerColor,
modifier = movableItemModifier,
notifications = currentWallet.notifications.map { it.messageUM }
.toPersistentList(),
)
tangemPay(
walletUM = currentWallet,
isBalanceHiding = isBalanceHidden,
modifier = itemModifier,
)
tokensListItems2(
walletTokensListUM = currentWallet.tokensListUM,
modifier = movableItemModifier,
isBalanceHidden = isBalanceHidden,
)
nftCollections2(state = currentWallet, itemModifier = itemModifier)
organizeTokens2(state = currentWallet, itemModifier = itemModifier)
}
}
/**
* Wallet content

View file

@ -0,0 +1,49 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds.TangemPagerIndicator
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
private const val MIN_SCALE = 0.75f
private const val MAX_SCALE = 1f
@Composable
internal fun WalletPagerIndicator(pagerState: PagerState, behavior: TangemCollapsingAppBarBehavior) {
val collapsedFraction = behavior.state.collapsedFraction
val alpha = MAX_SCALE - collapsedFraction
val scale = alpha.coerceIn(MIN_SCALE, MAX_SCALE)
Box(
modifier = Modifier
.graphicsLayer {
scaleY = scale
translationY = behavior.state.heightOffset
}
.fillMaxWidth()
.height(
with(LocalDensity.current) {
behavior.state.heightOffsetLimit.toDp().unaryMinus()
},
)
.alpha(alpha),
) {
TangemPagerIndicator(
pagerState = pagerState,
modifier = Modifier
.padding(top = 248.dp)
.scale(scaleY = 1f, scaleX = scale)
.fillMaxWidth(),
)
}
}

View file

@ -3,22 +3,77 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import android.content.res.Configuration
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.LocalPowerSavingState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig
import dev.chrisbanes.haze.HazeProgressive
private const val VISIBILITY_THRESHOLD = 0.5f
/**
* Wallet screen collapsing top bar
*
* @param topBarConfig top bar config
* @param walletBalance wallet balance text reference
* @param behavior collapsing behavior
*/
@Composable
internal fun WalletTopBar(
topBarConfig: WalletTopBarConfig,
walletBalance: TextReference?,
behavior: TangemCollapsingAppBarBehavior,
) {
Surface(
color = Color.Unspecified,
contentColor = Color.Unspecified,
modifier = Modifier.hazeEffectTangem {
progressive =
HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f)
},
) {
val isPowerSaving by LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsStateWithLifecycle()
val wrappedBalance = remember(behavior.state.collapsedFraction) {
if (behavior.state.collapsedFraction > VISIBILITY_THRESHOLD) walletBalance else null
}
TangemTopBar(
title = wrappedBalance,
startIconRes = R.drawable.ic_tangem_24,
endIconRes = R.drawable.ic_more_default_24,
onEndContentClick = topBarConfig.onDetailsClick,
isGhostButtons = !isPowerSaving,
modifier = Modifier
.testTag(MainScreenTestTags.TOP_BAR),
)
}
}
/**
* Wallet screen top bar
*
* @param config component config
*/
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun WalletTopBar(config: WalletTopBarConfig) {
@ -46,6 +101,21 @@ internal fun WalletTopBar(config: WalletTopBarConfig) {
@Composable
private fun Preview_WalletTopBar() {
TangemThemePreview {
WalletTopBar(config = WalletPreviewData.topBarConfig)
WalletTopBar(config = WalletPreviewDataLegacy.topBarConfig)
}
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun WalletTopBar_Preview() {
TangemThemePreviewRedesign {
WalletTopBar(
topBarConfig = WalletTopBarConfig(onDetailsClick = {}),
walletBalance = stringReference("$ 8923,05"),
behavior = rememberTangemExitUntilCollapsedScrollBehavior(),
)
}
}
// endregion

View file

@ -4,6 +4,9 @@ import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.lazy.LazyListLayoutInfo
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.mapSaver
import com.tangem.utils.extensions.mapNotNullValues
/**
* Animate scroll [LazyListState].
@ -28,4 +31,32 @@ private fun calculateOffset(layoutInfo: LazyListLayoutInfo, prevIndex: Int, newI
private fun LazyListLayoutInfo.getItemSizeWithSpacing(): Int {
return viewportSize.width - afterContentPadding - beforeContentPadding + mainAxisItemSpacing
}
/**
* Saver for [LazyListState] map, where key is page index, and value is [LazyListState] of this page.
*/
internal fun lazyListStateMapSaver(pageCount: Int): Saver<MutableMap<Int, LazyListState>, Any> {
return mapSaver(
save = { map ->
map.mapKeys { it.key.toString() }
.mapValues { listState ->
listState.value.firstVisibleItemIndex to listState.value.firstVisibleItemScrollOffset
}
},
restore = { restoredMap ->
@Suppress("UNCHECKED_CAST")
val typedMap = restoredMap as? Map<String, Pair<Int, Int>> ?: return@mapSaver null
typedMap.mapKeys { it.key.toInt() }
.mapNotNullValues { (_, value) ->
val (index, offset) = value
LazyListState(index, offset)
}
.toMutableMap()
.apply {
repeat(pageCount) { putIfAbsent(it, LazyListState()) }
}
},
)
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.516,6.27C11.403,6.27 11.343,6.2 11.325,6.095C11.066,4.674 11.092,4.604 9.587,4.332C9.475,4.306 9.414,4.245 9.414,4.131C9.414,4.025 9.475,3.955 9.587,3.938C11.092,3.666 11.066,3.596 11.325,2.175C11.343,2.07 11.403,2 11.516,2C11.628,2 11.689,2.07 11.706,2.175C11.965,3.596 11.939,3.666 13.444,3.938C13.548,3.955 13.617,4.025 13.617,4.131C13.617,4.245 13.548,4.306 13.444,4.332C11.939,4.604 11.965,4.674 11.706,6.095C11.689,6.2 11.628,6.27 11.516,6.27ZM7.33,12.302C7.174,12.302 7.062,12.189 7.036,12.013C6.759,9.672 6.646,9.611 4.294,9.225C4.112,9.199 4,9.102 4,8.927C4,8.769 4.112,8.664 4.259,8.637C6.629,8.182 6.759,8.19 7.036,5.849C7.062,5.674 7.174,5.56 7.33,5.56C7.494,5.56 7.606,5.674 7.624,5.84C7.926,8.217 8.013,8.295 10.4,8.637C10.547,8.655 10.66,8.769 10.66,8.927C10.66,9.093 10.547,9.199 10.4,9.225C8.013,9.69 7.935,9.69 7.624,12.031C7.606,12.189 7.494,12.302 7.33,12.302ZM13.193,22C12.969,22 12.804,21.833 12.77,21.597C12.121,16.801 11.472,16.16 6.802,15.538C6.551,15.512 6.387,15.336 6.387,15.099C6.387,14.872 6.551,14.696 6.802,14.67C11.481,14.144 12.156,13.407 12.77,8.602C12.804,8.366 12.969,8.208 13.193,8.208C13.418,8.208 13.583,8.366 13.626,8.602C14.24,13.407 14.906,14.144 19.594,14.67C19.836,14.696 20,14.872 20,15.099C20,15.336 19.836,15.512 19.594,15.538C14.906,16.064 14.24,16.801 13.626,21.597C13.583,21.833 13.418,22 13.193,22Z"
android:fillColor="#000000"/>
</vector>