Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-15 19:05:34 +05:00
parent f6a9dd4fd5
commit a258763b81
12 changed files with 102 additions and 12 deletions

View file

@ -3,6 +3,7 @@ package com.tangem.common.ui.tokens
import com.tangem.common.ui.R
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.icons.IconTint
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
import com.tangem.core.ui.components.token.state.TokenItemState
@ -220,13 +221,12 @@ class TokenItemStateConverter(
if (!status.getStakedBalance().isZero()) {
TokenItemState.FiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_staking_24,
useAccentColor = true,
tint = IconTint.Accent,
).let(::add)
}
if (status.value.sources.total == StatusSource.ONLY_CACHE) {
TokenItemState.FiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_error_sync_24,
useAccentColor = false,
).let(::add)
}
}.toImmutableList(),

View file

@ -0,0 +1,10 @@
package com.tangem.core.ui.components.icons
import androidx.compose.runtime.Stable
@Stable
enum class IconTint {
Accent,
Warning,
Inactive,
}

View file

@ -22,6 +22,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.components.audits.AuditLabelUM
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.icons.IconTint
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.token.internal.*
import com.tangem.core.ui.components.token.state.TokenItemState
@ -488,7 +489,14 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
fiatAmountState = FiatAmountState.Content(
text = "3213123123321312312312312312 $",
icons = persistentListOf(
FiatAmountState.Content.IconUM(R.drawable.ic_error_sync_24, useAccentColor = false),
FiatAmountState.Content.IconUM(
iconRes = R.drawable.img_attention_20,
tint = IconTint.Warning,
),
FiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_error_sync_24,
tint = IconTint.Inactive,
),
stakingIcon,
),
isFlickering = true,
@ -534,7 +542,10 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
TokenItemState.Draggable(
id = UUID.randomUUID().toString(),
iconState = tokenIconState,
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
titleState = TokenItemState.TitleState.Content(
text = stringReference(value = "Polygon"),
earnApy = stringReference("Earn 5%"),
),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "3 172,14 $"),
),
TokenItemState.Content(
@ -603,7 +614,7 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
companion object {
val stakingIcon = FiatAmountState.Content.IconUM(R.drawable.ic_staking_24, useAccentColor = true)
val stakingIcon = FiatAmountState.Content.IconUM(R.drawable.ic_staking_24, tint = IconTint.Accent)
val coinIconState
get() = CurrencyIconState.CoinIcon(

View file

@ -18,6 +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.icons.IconTint
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.res.TangemTheme
@ -75,10 +76,10 @@ private fun ContentFiatAmount(
Icon(
modifier = Modifier.size(12.dp),
painter = rememberVectorPainter(image = ImageVector.vectorResource(icon.iconRes)),
tint = if (icon.useAccentColor) {
TangemTheme.colors.icon.accent
} else {
TangemTheme.colors.icon.inactive
tint = when (icon.tint) {
IconTint.Accent -> TangemTheme.colors.icon.accent
IconTint.Warning -> TangemTheme.colors.icon.attention
IconTint.Inactive -> TangemTheme.colors.icon.inactive
},
contentDescription = null,
)

View file

@ -2,7 +2,9 @@ package com.tangem.core.ui.components.token.internal
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -13,8 +15,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as TokenTitleState
@ -56,6 +60,11 @@ private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Mo
hasPending = state.hasPending,
modifier = Modifier.align(alignment = Alignment.CenterVertically),
)
YieldSupplyApyLabel(
apy = state.earnApy,
modifier = Modifier.align(alignment = Alignment.CenterVertically),
)
}
}
@ -71,6 +80,25 @@ private fun CurrencyNameText(name: String, isAvailable: Boolean, modifier: Modif
)
}
@Composable
private fun YieldSupplyApyLabel(apy: TextReference?, modifier: Modifier = Modifier) {
AnimatedVisibility(visible = apy != null, modifier = modifier) {
Box(
modifier = modifier.background(
color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
shape = TangemTheme.shapes.roundedCornersSmall2,
),
) {
Text(
text = apy?.resolveReference().orEmpty(),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.accent,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
)
}
}
}
@Composable
private fun PendingTransactionImage(hasPending: Boolean, modifier: Modifier = Modifier) {
AnimatedVisibility(visible = hasPending, modifier = modifier) {

View file

@ -3,6 +3,7 @@ package com.tangem.core.ui.components.token.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.audits.AuditLabelUM
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.icons.IconTint
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
@ -167,6 +168,7 @@ sealed class TokenItemState {
val text: TextReference,
val hasPending: Boolean = false,
val isAvailable: Boolean = true,
val earnApy: TextReference? = null,
) : TitleState()
data object Loading : TitleState()
@ -204,7 +206,7 @@ sealed class TokenItemState {
data class IconUM(
val iconRes: Int,
val useAccentColor: Boolean,
val tint: IconTint = IconTint.Inactive,
)
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M5.353,1.622C5.641,1.123 6.362,1.123 6.65,1.622L10.85,8.875C11.139,9.375 10.778,10.001 10.2,10.001H1.802C1.224,10.001 0.864,9.375 1.153,8.875L5.353,1.622ZM5.502,8.002V9.002H6.502V8.002H5.502ZM5.502,4.002V7.002H6.502V4.002H5.502Z"
android:fillColor="#000000"/>
</vector>

View file

@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier
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.icons.IconTint
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.haptic.TangemHapticEffect
@ -100,9 +101,9 @@ private class PortfolioTokenUMProvider : CollectionPreviewParameterProvider<Port
tokenItemState = (tokenUM.tokenItemState as TokenItemState.Content).copy(
fiatAmountState = contentFiatAmount.copy(
icons = persistentListOf(
TokenItemState.FiatAmountState.Content.IconUM(
TokenFiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_staking_24,
useAccentColor = true,
tint = IconTint.Accent,
),
),
),

View file

@ -115,6 +115,7 @@ dependencies {
implementation(projects.features.sendV2.api)
implementation(projects.features.kyc.api)
implementation(projects.features.tokenRecieve.api)
implementation(projects.features.yieldSupply.api)
/** Common modules */
implementation(projects.common)

View file

@ -66,6 +66,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.UsedOutdatedData,
is WalletNotification.UnlockVisaAccess,
is WalletNotification.FinishWalletActivation,
is WalletNotification.Warning.YeildSupplyApprove, // TODO apply correct event
-> null
is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport
is WalletNotification.Critical.SeedPhraseSecondNotification -> MainScreen.NoticeSeedPhraseSupportSecond

View file

@ -31,6 +31,7 @@ import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
import com.tangem.utils.coroutines.combine6
import com.tangem.utils.extensions.addIf
@ -54,6 +55,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
private val onrampSepaAvailableUseCase: OnrampSepaAvailableUseCase,
private val getOnrampCountryUseCase: GetOnrampCountryUseCase,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
@ -82,6 +84,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
addWarningNotifications(cardTypesResolver, maybeTokenList, isNeedToBackup, clickIntents)
addYieldSupplyNotifications(maybeTokenList)
val hasCriticalOrWarning = any { notification ->
notification is WalletNotification.Critical || notification is WalletNotification.Warning
}
@ -268,6 +272,15 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
)
}
private fun MutableList<WalletNotification>.addYieldSupplyNotifications(
tokenList: Lce<TokenListError, TokenList>,
) {
addIf(
element = WalletNotification.Warning.YeildSupplyApprove,
condition = tokenList.hasNetworksWithLendingsWithoutApprove(),
)
}
private fun MutableList<WalletNotification>.addWarningNotifications(
cardTypesResolver: CardTypesResolver?,
tokenList: Lce<TokenListError, TokenList>,
@ -298,6 +311,14 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
return tokenList.flattenCurrencies().any { it.value is CryptoCurrencyStatus.Unreachable }
}
private fun Lce<TokenListError, TokenList>.hasNetworksWithLendingsWithoutApprove(): Boolean {
val tokenList = getOrNull(isPartialContentAccepted = false) ?: return false
val yieldSupplyEnabled = yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled
return yieldSupplyEnabled && tokenList.flattenCurrencies().any {
it.value.yieldSupplyStatus?.isAllowedToSpend == false
}
}
private fun MutableList<WalletNotification>.addRateTheAppNotification(
isReadyToShowRating: Boolean,
clickIntents: WalletClickIntents,

View file

@ -138,6 +138,11 @@ sealed class WalletNotification(val config: NotificationConfig) {
formatArgs = wrappedList(count),
),
)
data object YeildSupplyApprove : Warning(
title = resourceReference(R.string.yield_module_main_view_approve_notification_title),
subtitle = resourceReference(R.string.yield_module_main_view_approve_notification_description),
)
}
sealed class Informational(