Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-13 12:54:27 +05:00
commit 4f3ff79ac9
11 changed files with 132 additions and 60 deletions

View file

@ -692,6 +692,7 @@
<string name="koinos_mana_level_title">Manaレベル</string>
<string name="main_empty_tokens_list_message">暗号資産および取引の追跡を開始するには、トークンを追加してください</string>
<string name="main_manage_tokens">トークンの管理</string>
<string name="main_qr_scan_hint">QRコードをスキャンして送金するか、アプリに接続します。</string>
<string name="main_scan_card_warning_view_subtitle">すべてのネットワークにアクセスするには、カードをスキャンする必要があります。</string>
<string name="main_scan_card_warning_view_title">カードまたはリングをスキャンする</string>
<string name="main_swap_changelly_promotion_message">2月%2$s - %3$sの期間、Changelly経由のスワップは%1$sのサービス手数料となります。</string>

View file

@ -843,6 +843,7 @@
<string name="markets_token_details_trading_volume">Объем торгов (24ч)</string>
<string name="markets_token_details_trading_volume_24h_description">Общая сумма криптовалюты, которая была продана за последние 24 часа, показывающая уровень активности и ликвидности на рынке.</string>
<string name="markets_token_details_trading_volume_full">Объем торгов (24ч)</string>
<string name="markets_token_details_valuation_value_in_total">%s в сумме</string>
<string name="markets_token_details_volume">Объем</string>
<string name="markets_tooltip_message">Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из рынка</string>
<string name="markets_tooltip_title">Добавить токены</string>

View file

@ -737,6 +737,8 @@
<string name="markets_earn_common_title">Earn with Tangem</string>
<string name="markets_generate_addresses_notification">To generate addresses for selected networks, you must scan your Tangem Wallet card or ring</string>
<string name="markets_hint">To add tokens pull this up or tap the search bar</string>
<string name="markets_hint_part_one">Swipe up to explore the market</string>
<string name="markets_hint_part_two">Find new hidden gems</string>
<string name="markets_insights_info_description_message">This sections data is sourced from the following networks: %s</string>
<string name="markets_loading_error_title">Unable to load the data…</string>
<string name="markets_loading_no_data_title">No data</string>
@ -826,9 +828,11 @@
<string name="markets_token_details_total_supply">Total supply</string>
<string name="markets_token_details_total_supply_description">The maximum number of coins or tokens that can ever exist for a particular cryptocurrency</string>
<string name="markets_token_details_total_supply_full">Total supply</string>
<string name="markets_token_details_trading_interval">24h</string>
<string name="markets_token_details_trading_volume">Trading volume (24h)</string>
<string name="markets_token_details_trading_volume_24h_description">The total amount of a cryptocurrency that has been traded within the last 24 hours, indicating the level of activity and liquidity in the market</string>
<string name="markets_token_details_trading_volume_full">Trading volume (24h)</string>
<string name="markets_token_details_valuation_value_in_total">%s in total</string>
<string name="markets_token_details_volume">Volume</string>
<string name="markets_tooltip_message">Pull this up or tap the search bar to add tokens directly from the market</string>
<string name="markets_tooltip_title">Add tokens</string>

View file

@ -289,6 +289,7 @@ enum class TangemBadgeColor {
Red,
Gray,
Green,
GreenAlt,
}
@ReadOnlyComposable
@ -313,6 +314,12 @@ private fun getIconColor(type: TangemBadgeType, color: TangemBadgeColor) = when
-> TangemTheme.colors2.markers.iconGreen
TangemBadgeType.Solid -> TangemTheme.colors2.graphic.neutral.primaryInvertedConstant
}
TangemBadgeColor.GreenAlt -> when (type) {
TangemBadgeType.Outline,
TangemBadgeType.Tinted,
-> TangemTheme.colors2.markers.iconGreenAlt
TangemBadgeType.Solid -> TangemTheme.colors2.graphic.neutral.primaryInvertedConstant
}
}
@ReadOnlyComposable
@ -337,6 +344,12 @@ private fun getTextColor(type: TangemBadgeType, color: TangemBadgeColor) = when
-> TangemTheme.colors2.markers.textGreen
TangemBadgeType.Solid -> TangemTheme.colors2.text.neutral.primaryInvertedConstant
}
TangemBadgeColor.GreenAlt -> when (type) {
TangemBadgeType.Outline,
TangemBadgeType.Tinted,
-> TangemTheme.colors2.markers.textGreenAlt
TangemBadgeType.Solid -> TangemTheme.colors2.text.neutral.primaryInvertedConstant
}
}
@Suppress("CyclomaticComplexMethod")
@ -349,6 +362,7 @@ private fun Modifier.getBackgroundColor(type: TangemBadgeType, color: TangemBadg
TangemBadgeColor.Blue -> TangemTheme.colors2.markers.backgroundSolidBlue
TangemBadgeColor.Red -> TangemTheme.colors2.markers.backgroundSolidRed
TangemBadgeColor.Green -> TangemTheme.colors2.markers.backgroundSolidGreen
TangemBadgeColor.GreenAlt -> TangemTheme.colors2.markers.backgroundSolidGreenAlt
},
)
TangemBadgeType.Tinted -> background(
@ -357,6 +371,7 @@ private fun Modifier.getBackgroundColor(type: TangemBadgeType, color: TangemBadg
TangemBadgeColor.Blue -> TangemTheme.colors2.markers.backgroundTintedBlue
TangemBadgeColor.Red -> TangemTheme.colors2.markers.backgroundTintedRed
TangemBadgeColor.Green -> TangemTheme.colors2.markers.backgroundTintedGreen
TangemBadgeColor.GreenAlt -> TangemTheme.colors2.markers.backgroundTintedGreenAlt
},
)
TangemBadgeType.Outline -> {
@ -366,6 +381,7 @@ private fun Modifier.getBackgroundColor(type: TangemBadgeType, color: TangemBadg
TangemBadgeColor.Blue -> TangemTheme.colors2.markers.borderTintedBlue
TangemBadgeColor.Red -> TangemTheme.colors2.markers.borderTintedRed
TangemBadgeColor.Green -> TangemTheme.colors2.markers.borderTintedGreen
TangemBadgeColor.GreenAlt -> TangemTheme.colors2.markers.borderTintedGreenAlt
},
shape = shape,
width = 1.dp,
@ -410,6 +426,7 @@ private class TangemBadgePreviewProvider : PreviewParameterProvider<TangemBadgeC
TangemBadgeColor.Blue,
TangemBadgeColor.Red,
TangemBadgeColor.Green,
TangemBadgeColor.GreenAlt,
)
}
// endregion

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.ds.row.token
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeState
@ -148,10 +149,17 @@ sealed class TangemTokenRowUM : TangemRowUM {
sealed class PromoBannerUM {
data class Content(
val title: TextReference,
@param:DrawableRes val iconRes: Int,
val type: Type,
val onPromoBannerClick: () -> Unit,
val onCloseClick: () -> Unit,
val onPromoShown: () -> Unit = {},
) : PromoBannerUM()
) : PromoBannerUM() {
enum class Type {
Yield,
Staking,
}
}
data object Empty : PromoBannerUM()
}

View file

@ -30,6 +30,8 @@ internal object TangemTokenRowPreviewData {
val promoBannerUM: TangemTokenRowUM.PromoBannerUM.Content
get() = TangemTokenRowUM.PromoBannerUM.Content(
title = stringReference("Earn yield by supplying your crypto assets"),
iconRes = R.drawable.ic_yield_mode_mini_12,
type = TangemTokenRowUM.PromoBannerUM.Content.Type.Yield,
onPromoBannerClick = {},
onPromoShown = {},
onCloseClick = {},

View file

@ -15,6 +15,8 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.ds.badge.*
import com.tangem.core.ui.ds.image.TangemIconUM
@ -39,7 +41,18 @@ internal fun TokenRowPromoBanner(promoBannerUM: TangemTokenRowUM.PromoBannerUM.C
LaunchedEffect(promoBannerUM) {
promoBannerUM.onPromoShown()
}
val bgColor = TangemTheme.colors2.markers.backgroundTintedGreen
val bgColor = when (promoBannerUM.type) {
TangemTokenRowUM.PromoBannerUM.Content.Type.Yield -> TangemTheme.colors2.markers.backgroundTintedGreenAlt
TangemTokenRowUM.PromoBannerUM.Content.Type.Staking -> TangemTheme.colors2.markers.backgroundTintedBlue
}
val contentColor = when (promoBannerUM.type) {
TangemTokenRowUM.PromoBannerUM.Content.Type.Yield -> TangemTheme.colors2.markers.textGreenAlt
TangemTokenRowUM.PromoBannerUM.Content.Type.Staking -> TangemTheme.colors2.markers.textBlue
}
val badgeColor = when (promoBannerUM.type) {
TangemTokenRowUM.PromoBannerUM.Content.Type.Yield -> TangemBadgeColor.GreenAlt
TangemTokenRowUM.PromoBannerUM.Content.Type.Staking -> TangemBadgeColor.Blue
}
Column(
modifier = modifier,
) {
@ -63,9 +76,9 @@ internal fun TokenRowPromoBanner(promoBannerUM: TangemTokenRowUM.PromoBannerUM.C
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_analytics_up_24),
imageVector = ImageVector.vectorResource(id = promoBannerUM.iconRes),
contentDescription = null,
tint = TangemTheme.colors2.markers.textGreen,
tint = contentColor,
modifier = Modifier
.padding(vertical = TangemTheme.dimens2.x0_5)
.size(TangemTheme.dimens2.x3),
@ -73,14 +86,14 @@ internal fun TokenRowPromoBanner(promoBannerUM: TangemTokenRowUM.PromoBannerUM.C
Text(
text = promoBannerUM.title.resolveReference(),
style = TangemTheme.typography2.captionSemibold11,
color = TangemTheme.colors2.markers.textGreen,
color = contentColor,
modifier = Modifier
.padding(vertical = TangemTheme.dimens2.x0_5),
)
TangemBadge(
size = TangemBadgeSize.X4,
shape = TangemBadgeShape.Rounded,
color = TangemBadgeColor.Green,
color = badgeColor,
type = TangemBadgeType.Tinted,
tangemIconUM = TangemIconUM.Icon(R.drawable.ic_close_24),
iconPosition = TangemBadgeIconPosition.None,
@ -90,14 +103,30 @@ internal fun TokenRowPromoBanner(promoBannerUM: TangemTokenRowUM.PromoBannerUM.C
}
}
// region Preview
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_TokenRowPromoBanner() {
private fun Preview_TokenRowPromoBanner(
@PreviewParameter(TangemTokenRowPromoBannerPreviewProvider::class)
promoType: TangemTokenRowUM.PromoBannerUM.Content.Type,
) {
TangemThemePreviewRedesign {
TokenRowPromoBanner(
promoBannerUM = TangemTokenRowPreviewData.promoBannerUM,
modifier = Modifier.fillMaxWidth(),
promoBannerUM = TangemTokenRowPreviewData.promoBannerUM.copy(type = promoType),
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level1),
)
}
}
}
internal class TangemTokenRowPromoBannerPreviewProvider :
PreviewParameterProvider<TangemTokenRowUM.PromoBannerUM.Content.Type> {
override val values: Sequence<TangemTokenRowUM.PromoBannerUM.Content.Type>
get() = sequenceOf(
TangemTokenRowUM.PromoBannerUM.Content.Type.Yield,
TangemTokenRowUM.PromoBannerUM.Content.Type.Staking,
)
}
// endregion

View file

@ -0,0 +1,15 @@
<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="M17,6C17,5.448 17.448,5 18,5C18.552,5 19,5.448 19,6L19,18C19,18.552 18.552,19 18,19C17.448,19 17,18.552 17,18L17,6Z"
android:fillColor="#000000"/>
<path
android:pathData="M11,10C11,9.448 11.448,9 12,9C12.552,9 13,9.448 13,10L13,18C13,18.552 12.552,19 12,19C11.448,19 11,18.552 11,18L11,10Z"
android:fillColor="#000000"/>
<path
android:pathData="M5,14C5,13.448 5.448,13 6,13C6.552,13 7,13.448 7,14L7,18C7,18.552 6.552,19 6,19C5.448,19 5,18.552 5,18L5,14Z"
android:fillColor="#000000"/>
</vector>

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="M2.999,6.25C3.413,6.25 3.748,6.585 3.749,7V9C3.749,9.414 3.413,9.75 2.999,9.75C2.584,9.749 2.249,9.414 2.249,9V7C2.249,6.585 2.585,6.25 2.999,6.25ZM6,4.249C6.413,4.249 6.749,4.585 6.75,4.999V9C6.75,9.414 6.414,9.749 6,9.75C5.585,9.75 5.25,9.414 5.25,9V4.999C5.25,4.585 5.585,4.249 6,4.249ZM9,2.249C9.414,2.249 9.749,2.584 9.75,2.999V9C9.75,9.414 9.414,9.75 9,9.75C8.585,9.749 8.25,9.414 8.25,9V2.999C8.25,2.585 8.585,2.249 9,2.249Z"
android:fillColor="#34DF12"/>
</vector>

View file

@ -46,38 +46,38 @@ internal class WalletTokenCurrencyItemConverter(
stakingApyMap = stakingAvailabilityMap,
)
override fun convert(currencyStatus: CryptoCurrencyStatus): TangemTokenRowUM {
val earnApyInfo = earnApyConverter.convert(currencyStatus)
override fun convert(value: CryptoCurrencyStatus): TangemTokenRowUM {
val earnApyInfo = earnApyConverter.convert(value)
return TangemTokenRowUM.Content(
id = currencyStatus.currency.id.value,
id = value.currency.id.value,
headIconUM = TangemIconUM.Currency(
currencyIconState = currencyToIconStateConverter.convert(currencyStatus),
currencyIconState = currencyToIconStateConverter.convert(value),
),
titleUM = toCurrencyRowTitle(currencyStatus, earnApyInfo),
subtitleUM = toCurrencyRowSubtitle(currencyStatus),
topEndContentUM = toCurrencyRowTopEnd(currencyStatus),
bottomEndContentUM = toCurrencyRowBottomEnd(currencyStatus),
titleUM = toCurrencyRowTitle(value, earnApyInfo),
subtitleUM = toCurrencyRowSubtitle(value),
topEndContentUM = toCurrencyRowTopEnd(value),
bottomEndContentUM = toCurrencyRowBottomEnd(value),
promoBannerUM = toPromoBannerUM(
accountId,
currencyStatus,
value,
earnApyInfo.takeIf { shouldShowPromo },
),
onItemClick = when (currencyStatus.value) {
onItemClick = when (value.value) {
CryptoCurrencyStatus.Loading,
is CryptoCurrencyStatus.MissedDerivation,
-> null
else -> {
{
clickIntents.onTokenItemClick(accountId, currencyStatus)
clickIntents.onTokenItemClick(accountId, value)
}
}
},
onItemLongClick = when (currencyStatus.value) {
onItemLongClick = when (value.value) {
CryptoCurrencyStatus.Loading -> null
else -> {
{
clickIntents.onTokenItemLongClick(accountId, currencyStatus)
clickIntents.onTokenItemLongClick(accountId, value)
}
}
},
@ -280,6 +280,8 @@ internal class WalletTokenCurrencyItemConverter(
R.string.yield_module_main_screen_promo_banner_message,
wrappedList(earnApyInfo.apy),
),
iconRes = R.drawable.ic_yield_mode_mini_12,
type = TangemTokenRowUM.PromoBannerUM.Content.Type.Yield,
onPromoBannerClick = {
clickIntents.onYieldPromoClicked(currency)
clickIntents.onApyLabelClick(

View file

@ -5,9 +5,10 @@ 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.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -15,17 +16,13 @@ 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.extensions.stringResourceSafe
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(
@ -36,41 +33,28 @@ internal fun MarketsHint(isVisible: Boolean, modifier: Modifier = Modifier) {
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
Text(
text = "Swipe up to explore the market", // todo redesign main lokalise
style = TangemTheme.typography2.bodyRegular14,
text = stringResourceSafe(R.string.markets_hint_part_one),
style = TangemTheme.typography2.bodyRegular15,
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,
)
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1)) {
Text(
text = stringResourceSafe(R.string.markets_hint_part_two),
style = TangemTheme.typography2.bodyRegular15,
color = TangemTheme.colors2.text.neutral.tertiary,
textAlign = TextAlign.Center,
)
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_magic_default_24),
tint = TangemTheme.colors2.text.neutral.tertiary,
contentDescription = null,
modifier = Modifier.size(TangemTheme.dimens2.x5),
)
}
}
}
}