Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-15 22:43:52 +03:00
commit d6f9f59866
1729 changed files with 67614 additions and 9361 deletions

View file

@ -28,8 +28,9 @@ dependencies {
implementation(deps.arrow.core)
implementation(deps.test.junit)
implementation(deps.test.truth)
testImplementation(projects.test.core)
testImplementation(deps.test.junit5)
testImplementation(deps.test.truth)
// region DI
implementation(deps.hilt.android)

View file

@ -33,7 +33,7 @@ dependencies {
implementation(deps.androidx.core.ktx)
/* Tests */
testImplementation(deps.test.junit)
testImplementation(deps.test.junit5)
testImplementation(deps.test.coroutine)
testImplementation(deps.test.truth)
testImplementation(deps.test.mockk)

View file

@ -59,11 +59,6 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data object Wallet : AppRoute(path = "/wallet")
@Serializable
data class AddFunds(
val userWalletId: UserWalletId,
) : AppRoute(path = "/add_funds/${userWalletId.stringValue}")
@Serializable
data class CurrencyDetails(
val userWalletId: UserWalletId,
@ -177,6 +172,11 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class WalletConnectSessions(val userWalletId: UserWalletId) : AppRoute(path = "/wallet_connect_sessions")
@Serializable
data class AddressBook(
val predefinedAddress: String? = null,
) : AppRoute(path = "/address_book/predefinedAddress/$predefinedAddress")
@Serializable
data class QrScanning(val source: Source) : AppRoute(path = "/$source/qr_scanning${source.path}") {
@ -256,6 +256,11 @@ sealed class AppRoute(val path: String) : Route {
val userWalletId: UserWalletId,
) : AppRoute(path = "/wallet_settings/${userWalletId.stringValue}")
@Serializable
data class PushNotificationSettings(
val userWalletId: UserWalletId,
) : AppRoute(path = "/push_notification_settings/${userWalletId.stringValue}")
@Serializable
data class WalletBackup(
val userWalletId: UserWalletId,
@ -296,6 +301,7 @@ sealed class AppRoute(val path: String) : Route {
val source: OnrampSource,
val userWalletId: UserWalletId,
val currency: CryptoCurrency,
val initialFiatAmount: SerializedBigDecimal? = null,
) : AppRoute(path = "/onramp/${userWalletId.stringValue}/${currency.symbol}"), RouteBundleParams {
override fun getBundle(): Bundle = bundle(serializer())
}
@ -498,6 +504,9 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class Kyc(val userWalletId: UserWalletId) : AppRoute(path = "/kyc")
@Serializable
data class Survey(val token: String, val displayId: String? = null) : AppRoute(path = "/survey")
@Serializable
data class YieldSupplyEntry(
val userWalletId: UserWalletId,

View file

@ -87,6 +87,10 @@ sealed class DeepLinkRoute {
data object PayAppMain : DeepLinkRoute() {
override val host: String = "pay-app-main"
}
data object Survey : DeepLinkRoute() {
override val host: String = "survey"
}
}
enum class DeepLinkScheme(val scheme: String) {

View file

@ -1,14 +1,14 @@
package com.tangem.common.routing.deeplink
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
internal class DeepLinkBuilderTest {
private lateinit var deepLinkBuilder: DeepLinkBuilder
@Before
@BeforeEach
fun setup() {
deepLinkBuilder = DeepLinkBuilder()
}

View file

@ -10,7 +10,7 @@ import com.tangem.common.routing.deeplink.DeeplinkConst.TRANSACTION_ID_KEY
import com.tangem.common.routing.deeplink.DeeplinkConst.TYPE_KEY
import com.tangem.common.routing.deeplink.DeeplinkConst.WALLET_ID_KEY
import com.tangem.domain.visa.model.TangemPayPushNotificationType
import org.junit.Test
import org.junit.jupiter.api.Test
internal class PayloadToDeeplinkConverterTest {

View file

@ -1,18 +1,17 @@
package com.tangem.common.uri
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import com.tangem.test.core.ProvideTestModels
import org.junit.jupiter.params.ParameterizedTest
/**
[REDACTED_AUTHOR]
*/
@RunWith(Parameterized::class)
class ExternalUrlValidatorTest(private val model: Model) {
class ExternalUrlValidatorTest {
@Test
fun test() {
@ParameterizedTest
@ProvideTestModels
fun test(model: Model) {
val actual = ExternalUrlValidator.isUriTrusted(externalUri = model.url)
Truth.assertThat(actual).isEqualTo(model.expected)
@ -21,8 +20,7 @@ class ExternalUrlValidatorTest(private val model: Model) {
companion object {
@JvmStatic
@Parameterized.Parameters
fun data(): Collection<Model> = listOf(
fun provideTestModels(): Collection<Model> = listOf(
// Trusted hosts — exact match
Model(url = "https://tangem.com", expected = true),
Model(url = "https://tangem.com/pricing/?promocode=tgapp20ups", expected = true),

View file

@ -1,11 +0,0 @@
package com.tangem.common.test
import com.tangem.utils.coroutines.AppCoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.TestScope
import kotlin.coroutines.CoroutineContext
class TestAppCoroutineScope(override val coroutineContext: CoroutineContext = Dispatchers.Unconfined) : AppCoroutineScope {
constructor(testScope: TestScope) : this(testScope.coroutineContext)
}

View file

@ -1,16 +0,0 @@
package com.tangem.common.test.datastore
import androidx.datastore.core.DataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.updateAndGet
class MockStateDataStore<T>(default: T) : DataStore<T> {
private val _data = MutableStateFlow(value = default)
override val data: Flow<T> = _data
override suspend fun updateData(transform: suspend (t: T) -> T): T {
return _data.updateAndGet { transform(it) }
}
}

View file

@ -108,14 +108,18 @@ class MockCryptoCurrencyFactory(private val userWallet: UserWallet.Cold = defaul
)
}
fun createToken(blockchain: Blockchain): CryptoCurrency.Token {
fun createToken(
blockchain: Blockchain,
id: String = "NEVER-MIND",
contractAddress: String = "NEVER-MIND",
): CryptoCurrency.Token {
return factory.createToken(
sdkToken = Token(
name = "NEVER-MIND",
symbol = "NEVER-MIND",
contractAddress = "NEVER-MIND",
contractAddress = contractAddress,
decimals = 8,
id = "NEVER-MIND",
id = id,
),
blockchain = blockchain,
extraDerivationPath = null,

View file

@ -23,6 +23,8 @@ import com.tangem.utils.Provider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.collections.immutable.toImmutableList
@Suppress("LongParameterList")
@ -34,7 +36,8 @@ class TokenActionsHandler @AssistedInject constructor(
private val urlOpener: UrlOpener,
private val analyticsEventHandler: AnalyticsEventHandler,
@Assisted private val currentAppCurrency: Provider<AppCurrency>,
@Assisted private val onHandleQuickAction: (HandledQuickAction) -> Unit,
@Assisted private val onHandleQuickAction: (action: HandledQuickAction, shouldDismiss: Boolean) -> Unit,
@Assisted private val coroutineScope: CoroutineScope,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val messageSender: UiMessageSender,
) {
@ -49,6 +52,18 @@ class TokenActionsHandler @AssistedInject constructor(
action = action,
cryptoCurrencyData = cryptoCurrencyData,
),
when (action) {
TokenActionsBSContentUM.Action.Receive,
TokenActionsBSContentUM.Action.CopyAddress,
TokenActionsBSContentUM.Action.Sell,
-> false
TokenActionsBSContentUM.Action.Send,
TokenActionsBSContentUM.Action.Stake,
TokenActionsBSContentUM.Action.YieldMode,
TokenActionsBSContentUM.Action.Buy,
TokenActionsBSContentUM.Action.Exchange,
-> true
},
)
val userWallet = cryptoCurrencyData.userWallet
if (userWallet is UserWallet.Cold && handleDemoMode(action, userWallet)) return
@ -106,12 +121,15 @@ class TokenActionsHandler @AssistedInject constructor(
}
private fun onSellClick(cryptoCurrencyData: CryptoCurrencyData) {
getOfframpUrlUseCase(
cryptoCurrencyStatus = cryptoCurrencyData.status,
appCurrencyCode = currentAppCurrency().code,
).onRight { url ->
urlOpener.openUrl(url)
analyticsEventHandler.send(OfframpAnalyticsEvent.ScreenOpened)
coroutineScope.launch {
getOfframpUrlUseCase(
userWalletId = cryptoCurrencyData.userWallet.walletId,
cryptoCurrencyStatus = cryptoCurrencyData.status,
appCurrencyCode = currentAppCurrency().code,
).onRight { url ->
urlOpener.openUrl(url)
analyticsEventHandler.send(OfframpAnalyticsEvent.ScreenOpened)
}
}
}
@ -164,7 +182,8 @@ class TokenActionsHandler @AssistedInject constructor(
interface Factory {
fun create(
currentAppCurrency: Provider<AppCurrency>,
onHandleQuickAction: (HandledQuickAction) -> Unit,
onHandleQuickAction: (HandledQuickAction, shouldDismiss: Boolean) -> Unit,
coroutineScope: CoroutineScope,
): TokenActionsHandler
}

View file

@ -7,11 +7,6 @@ plugins {
android {
namespace = "com.tangem.common.ui"
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies {
api(projects.common)
@ -56,5 +51,4 @@ dependencies {
/** Tests */
testImplementation(projects.test.core)
testRuntimeOnly(deps.test.junit5.engine)
}

View file

@ -8,8 +8,6 @@
<ID>BooleanPropertyNaming:GiveTxPermissionState.kt$CancelPermissionButton$val enabled: Boolean</ID>
<ID>BooleanPropertyNaming:NotificationUM.kt$NotificationUM.Error.ExceedsBalance$val mergeFeeNetworkName: Boolean = false</ID>
<ID>BooleanPropertyNaming:NotificationsFactory.kt$NotificationsFactory$val showNotification = sendingAmount + feeAmount &gt; balance - minimumRequirement.orZero()</ID>
<ID>BooleanPropertyNaming:TokenReceiveBottomSheetConfig.kt$TokenReceiveBottomSheetConfig$val showMemoDisclaimer: Boolean</ID>
<ID>MultilineLambdaItParameter:ExpressStatusItems.kt${ val itemInfo = expressTxs[it].info val (iconRes, tint) = when (itemInfo.iconState) { ExpressTransactionStateIconUM.Warning -&gt; { R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention } ExpressTransactionStateIconUM.Error -&gt; { R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning } ExpressTransactionStateIconUM.None -&gt; null to null } ExpressStatusItem( title = itemInfo.title, fromTokenIconState = itemInfo.fromCurrencyIcon, toTokenIconState = itemInfo.toCurrencyIcon, fromAmount = itemInfo.fromAmount, fromSymbol = itemInfo.fromAmountSymbol, toAmount = itemInfo.toAmount, toSymbol = itemInfo.toAmountSymbol, onClick = itemInfo.onClick, infoIconRes = iconRes, infoIconTint = tint, modifier = modifier.animateItem(), ) }</ID>
<ID>MultilineLambdaItParameter:TokenItemStateConverter.kt$TokenItemStateConverter.Companion${ it.key.equals( other = token.yieldSupplyKey(), ignoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId), ) }</ID>
<ID>NoNameShadowing:NavigationButtonsBlock.kt$navigationUM</ID>
<ID>NoNameShadowing:UserWalletItem.kt$balance</ID>

View file

@ -66,6 +66,12 @@ fun AccountIcon(
fun AccountIcon(name: TextReference, icon: AccountIconUM, size: AccountIconSize, modifier: Modifier = Modifier) {
when (icon) {
is AccountIconUM.Payment -> PaymentAccountIcon(size = size, modifier = modifier)
is AccountIconUM.Virtual -> AccountResIcon(
resId = icon.icon.getResId(),
color = icon.color.getUiColor(),
size = size,
modifier = modifier,
)
is AccountIconUM.CryptoPortfolio -> AccountIcon(
name = name,
icon = icon,

View file

@ -7,4 +7,9 @@ sealed class AccountIconUM {
data class CryptoPortfolio(val value: Icon, val color: Color) : AccountIconUM()
data object Payment : AccountIconUM()
data object Virtual : AccountIconUM() {
val icon: Icon = Icon.Safe
val color: Color = Color.VitalGreen
}
}

View file

@ -90,6 +90,10 @@ private fun PreviewAccountTitle() {
accountTitleUM = AccountTitleUM.Account.payment(prefixText = stringReference(StringsSigns.DOT)),
modifier = Modifier.padding(4.dp),
)
AccountTitle(
accountTitleUM = AccountTitleUM.Account.virtual(prefixText = stringReference(StringsSigns.DOT)),
modifier = Modifier.padding(4.dp),
)
}
}
}

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
/**
* A sealed interface representing the title of an account, which can be either a simple text
@ -31,6 +32,14 @@ sealed interface AccountTitleUM {
icon = AccountIconUM.Payment,
)
}
fun virtual(prefixText: TextReference = TextReference.EMPTY): Account {
return Account(
prefixText = prefixText,
name = stringReference("Virtual account"),
icon = AccountIconUM.Virtual,
)
}
}
}
}

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -100,6 +101,7 @@ private fun TokenHeader(
text = tokenName.text.resolveReference(),
style = TangemTheme.typography2.headingSemibold28,
color = TangemTheme.colors2.text.neutral.primary,
textAlign = TextAlign.Center,
)
}
TokenItemState.TitleState.Loading -> {

View file

@ -31,6 +31,7 @@ class AmountAccountConverter(
return when (account) {
is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(account.icon)
is Account.Payment -> AccountIconUM.Payment
is Account.Virtual -> AccountIconUM.Virtual
}
}
}

View file

@ -40,6 +40,7 @@ import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.currency.fiaticon.FiatIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.fields.AmountTextField
import com.tangem.core.ui.components.fields.TangemAmountTextFieldColors
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
@ -89,6 +90,7 @@ fun AmountFieldV2(
} else {
amountUM.amountTextField.cryptoAmount to amountUM.amountTextField.value
}
val colors = TangemAmountTextFieldColors
AmountTextField(
value = primaryValue,
decimals = primaryAmount.decimals,
@ -97,9 +99,10 @@ fun AmountFieldV2(
symbol = primaryAmount.currencySymbol,
currencyCode = currencyCode,
decimalFormat = decimalFormat,
symbolColor = TangemTheme.colors.text.disabled,
symbolColor = colors.disabledTextColor,
),
onValueChange = onValueChange,
colors = colors,
keyboardOptions = amountUM.amountTextField.keyboardOptions,
keyboardActions = amountUM.amountTextField.keyboardActions,
textStyle = TangemTheme.typography.head.copy(

View file

@ -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
@ -22,11 +16,16 @@ import androidx.compose.ui.draw.BlurredEdgeTreatment
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.innerShadow
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.shadow.Shadow
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
@ -36,22 +35,15 @@ 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.ui.test.TokenDetailsScreenTestTags
import com.tangem.core.res.R as CoreResR
private const val TINTED_BACKGROUND_ALPHA = 0.1f
@ -69,12 +61,13 @@ fun EarnBlock(state: EarnBlockUM, modifier: Modifier = Modifier) {
when (state) {
is EarnBlockUM.Loading -> EarnBlockLoading(modifier)
is EarnBlockUM.Content -> EarnBlockContent(state, modifier)
is EarnBlockUM.Promo -> EarnBlockPromo(state, 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 +84,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 +99,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 +107,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,
@ -132,15 +125,21 @@ private fun EarnBlockContent(state: EarnBlockUM.Content, modifier: Modifier = Mo
.padding(end = TangemTheme.dimens2.x2),
)
val subtitle = state.subtitleUM
if (subtitle is EarnBlockUM.SubtitleUM.Text) {
EarnBlockSubtitle(
val subtitleModifier = Modifier
.layoutId(TangemRowLayoutId.START_BOTTOM)
.padding(end = TangemTheme.dimens2.x2)
when (val subtitle = state.subtitleUM) {
is EarnBlockUM.SubtitleUM.Text -> EarnBlockSubtitle(
subtitle = subtitle,
type = state.type,
modifier = Modifier
.layoutId(TangemRowLayoutId.START_BOTTOM)
.padding(end = TangemTheme.dimens2.x2),
modifier = subtitleModifier,
)
is EarnBlockUM.SubtitleUM.AccentedText -> EarnBlockAccentedSubtitle(
subtitle = subtitle,
type = state.type,
modifier = subtitleModifier,
)
null -> Unit
}
EarnBlockTrailing(type = state.type, trailingUM = state.trailingUM, onClick = state.onClick)
@ -148,6 +147,74 @@ private fun EarnBlockContent(state: EarnBlockUM.Content, modifier: Modifier = Mo
)
}
@Composable
private fun EarnBlockPromo(state: EarnBlockUM.Promo, modifier: Modifier = Modifier) {
val shape = RoundedCornerShape(TangemTheme.dimens2.x5)
Column(
modifier = modifier
.clip(shape)
.backgroundModifier(state.type, state.backgroundUM, shape)
.padding(all = TangemTheme.dimens2.x4),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x3),
) {
Row(verticalAlignment = Alignment.CenterVertically) {
EarnBlockIcon(
type = state.type,
iconUM = state.iconUM,
modifier = Modifier.padding(end = TangemTheme.dimens2.x3),
)
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
modifier = Modifier.weight(1f),
) {
Text(
text = state.title.resolveAnnotatedReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors2.text.neutral.primary,
)
Text(
text = state.subtitle.resolveReference(),
style = TangemTheme.typography2.captionMedium12,
color = state.type.accentText(),
)
}
}
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2)) {
EarnBlockPromoButton(
text = resourceReference(CoreResR.string.common_learn_more),
type = TangemButtonType.Secondary,
onClick = state.onSecondaryClick,
modifier = Modifier.weight(1f),
)
EarnBlockPromoButton(
text = resourceReference(CoreResR.string.common_activate),
type = EarnBlockUM.TrailingUM.Button.Style.Default.buttonType(state.type),
onClick = state.onPrimaryClick,
modifier = Modifier.weight(1f),
)
}
}
}
@Composable
private fun EarnBlockPromoButton(
text: TextReference,
type: TangemButtonType,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
TangemButton(
buttonUM = TangemButtonUM(
text = text,
type = type,
size = TangemButtonSize.X9,
shape = TangemButtonShape.Rounded,
onClick = onClick,
),
modifier = modifier,
)
}
@Composable
private fun Modifier.backgroundModifier(
type: Type,
@ -192,20 +259,24 @@ private fun EarnBlockTrailing(type: Type, trailingUM: EarnBlockUM.TrailingUM?, o
)
}
is EarnBlockUM.TrailingUM.Balance -> {
if (!trailingUM.isBalanceHidden) {
Text(
text = trailingUM.fiatValue.resolveAnnotatedReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
modifier = Modifier.layoutId(TangemRowLayoutId.END_TOP),
)
Text(
text = trailingUM.cryptoValue.resolveReference(),
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
modifier = Modifier.layoutId(TangemRowLayoutId.END_BOTTOM),
)
val fiatModifier = Modifier.layoutId(TangemRowLayoutId.END_TOP).let {
if (type == Type.Staking) it.testTag(TokenDetailsScreenTestTags.STAKING_FIAT_AMOUNT) else it
}
val cryptoModifier = Modifier.layoutId(TangemRowLayoutId.END_BOTTOM).let {
if (type == Type.Staking) it.testTag(TokenDetailsScreenTestTags.STAKING_TOKEN_AMOUNT) else it
}
Text(
text = trailingUM.fiatValue.orMaskWithStars(trailingUM.isBalanceHidden).resolveAnnotatedReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
modifier = fiatModifier,
)
Text(
text = trailingUM.cryptoValue.orMaskWithStars(trailingUM.isBalanceHidden).resolveReference(),
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
modifier = cryptoModifier,
)
}
null -> Unit
}
@ -258,6 +329,29 @@ private fun EarnBlockSubtitle(subtitle: EarnBlockUM.SubtitleUM.Text, type: Type,
}
}
@Composable
private fun EarnBlockAccentedSubtitle(
subtitle: EarnBlockUM.SubtitleUM.AccentedText,
type: Type,
modifier: Modifier = Modifier,
) {
val baseText = subtitle.text.resolveReference()
val accentText = subtitle.accent.resolveReference()
val accentColor = type.accentText()
Text(
text = buildAnnotatedString {
append(baseText)
if (baseText.isNotEmpty() && !baseText.last().isWhitespace()) append(' ')
withStyle(SpanStyle(color = accentColor)) {
append(accentText)
}
},
style = subtitle.style.textStyle,
color = TangemTheme.colors2.text.neutral.tertiary,
modifier = modifier,
)
}
@Composable
private fun EarnBlockIcon(type: Type, iconUM: EarnBlockUM.IconUM, modifier: Modifier = Modifier) {
Box(
@ -361,7 +455,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 +463,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 +504,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 +520,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 +539,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(
@ -465,6 +559,22 @@ private class EarnBlockStakingPreviewProvider : CollectionPreviewParameterProvid
private class EarnBlockYieldSupplyPreviewProvider : CollectionPreviewParameterProvider<EarnBlockUM>(
collection = listOf(
// Promo — boosted APY offer: AccentSoft background, two buttons below
EarnBlockUM.Promo(
type = Type.YieldSupply,
backgroundUM = EarnBlockUM.BackgroundUM.AccentSoft,
iconUM = EarnBlockUM.IconUM.Glowing(iconRes = R.drawable.ic_yield_40),
title = annotatedReference(
buildAnnotatedString {
append("Special offer for Yield mode\nAPY ")
withStyle(SpanStyle(textDecoration = TextDecoration.LineThrough)) { append("5.1%") }
append(" x3 → 15.3%")
},
),
subtitle = stringReference("First time activation bonus!"),
onPrimaryClick = {},
onSecondaryClick = {},
),
// Available — promo entry: AccentSoft background, "More" button
EarnBlockUM.Content(
type = Type.YieldSupply,
@ -475,14 +585,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 +607,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 +615,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 +631,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 +640,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 +656,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 +665,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 +681,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 +699,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),
),

View file

@ -19,6 +19,21 @@ sealed interface EarnBlockUM {
val onClick: (() -> Unit)? = null,
) : EarnBlockUM
/**
* Promo variant a column with the [iconUM] + annotated multiline [title] + [subtitle] row on top
* and a pair of full-width buttons below. Button labels are fixed and hardcoded in the composable,
* so only their click handlers ([onSecondaryClick], [onPrimaryClick]) are exposed here.
*/
data class Promo(
val type: Type,
val backgroundUM: BackgroundUM,
val iconUM: IconUM,
val title: TextReference,
val subtitle: TextReference,
val onPrimaryClick: () -> Unit,
val onSecondaryClick: () -> Unit,
) : EarnBlockUM
enum class Type { Staking, YieldSupply }
@Immutable
@ -57,6 +72,12 @@ sealed interface EarnBlockUM {
val loader: Loader? = null,
) : SubtitleUM
data class AccentedText(
val text: TextReference,
val accent: TextReference,
val style: Style,
) : SubtitleUM
data class Loader(val tone: LoaderTone)
enum class Style { Large, Small }

View file

@ -2,9 +2,9 @@ package com.tangem.common.ui.expressStatus
import androidx.compose.runtime.Composable
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.res.TangemTheme
data class ExpressStatusBottomSheetConfig(
@ -18,7 +18,7 @@ fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) {
containerColor = TangemTheme.colors.background.tertiary,
) { content: ExpressStatusBottomSheetConfig ->
when (val state = content.value) {
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state)
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state, false)
}
}
}

View file

@ -3,13 +3,7 @@ package com.tangem.common.ui.expressStatus
import android.content.res.Configuration
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.Spacer
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.foundation.lazy.LazyListScope
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@ -18,14 +12,11 @@ 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.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.ui.R
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.*
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
@ -35,6 +26,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
@ -78,7 +70,8 @@ private fun ExpressTransactionItem(
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors2.surface.level3)
.clickable(onClick = info.onClick)
.padding(TangemTheme.dimens2.x4),
.padding(TangemTheme.dimens2.x4)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM),
) {
TitleRow(
title = info.title.resolveReference(),
@ -104,7 +97,9 @@ private fun TitleRow(title: String, infoIconRes: Int?, infoIconTint: Color?) {
text = title,
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary,
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TITLE),
)
if (infoIconRes != null && infoIconTint != null) {
Icon(
@ -126,32 +121,42 @@ private fun AmountsRow(info: ExpressTransactionStateInfoUM) {
CurrencyIcon(
state = info.fromCurrencyIcon,
shouldDisplayNetwork = false,
modifier = Modifier.size(TangemTheme.dimens.size18),
modifier = Modifier
.size(TangemTheme.dimens.size18)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_FROM_ICON),
)
EllipsisText(
text = info.fromAmount.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary,
ellipsis = TextEllipsis.OffsetEnd(info.fromAmountSymbol.length),
modifier = Modifier.weight(weight = 1f, fill = false),
modifier = Modifier
.weight(weight = 1f, fill = false)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_FROM_AMOUNT),
)
Icon(
painter = painterResource(R.drawable.ic_forward_24),
contentDescription = null,
tint = TangemTheme.colors3.icon.tertiary,
modifier = Modifier.size(TangemTheme.dimens.size18),
modifier = Modifier
.size(TangemTheme.dimens.size18)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_SWAP_ICON),
)
CurrencyIcon(
state = info.toCurrencyIcon,
shouldDisplayNetwork = false,
modifier = Modifier.size(TangemTheme.dimens.size18),
modifier = Modifier
.size(TangemTheme.dimens.size18)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_ICON),
)
EllipsisText(
text = info.toAmount.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary,
ellipsis = TextEllipsis.OffsetEnd(info.toAmountSymbol.length),
modifier = Modifier.weight(weight = 1f, fill = false),
modifier = Modifier
.weight(weight = 1f, fill = false)
.testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TO_AMOUNT),
)
}
}
@ -206,13 +211,17 @@ private val PreviewExpressTransactionState: ExpressTransactionStateUM = object :
onDisposeExpressStatus = {},
iconState = ExpressTransactionStateIconUM.None,
toAmount = stringReference("0,11441958 BTC"),
toAmountValue = "0.11441958".toBigDecimal(),
toFiatAmount = null,
toAmountSymbol = "BTC",
toCurrencyIcon = CurrencyIconState.Loading,
toAddress = "0x",
fromAmount = stringReference("100 SOL"),
fromAmountValue = "100".toBigDecimal(),
fromFiatAmount = null,
fromAmountSymbol = "SOL",
fromCurrencyIcon = CurrencyIconState.Loading,
fromAddress = "0x",
)
}
// endregion

View file

@ -9,18 +9,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH10
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.*
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
@Composable
fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) {
fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM, isExpressShareButtonEnabled: Boolean) {
Column(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
@ -70,6 +68,10 @@ fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) {
isAutoDisposable = state.activeStatus.isAutoDisposable,
onClick = state.info.onDisposeExpressStatus,
)
SpacerH24()
if (isExpressShareButtonEnabled) {
SpacerH(80.dp)
} else {
SpacerH24()
}
}
}

View file

@ -1,10 +1,13 @@
package com.tangem.common.ui.expressStatus.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.onramp.model.OnrampStatus
import java.math.BigDecimal
@Immutable
interface ExpressTransactionStateUM {
val info: ExpressTransactionStateInfoUM
@ -35,13 +38,17 @@ data class ExpressTransactionStateInfoUM(
val onDisposeExpressStatus: () -> Unit,
val iconState: ExpressTransactionStateIconUM,
val toAmount: TextReference,
val toAmountValue: BigDecimal,
val toFiatAmount: TextReference?,
val toAmountSymbol: String,
val toCurrencyIcon: CurrencyIconState,
val toAddress: String,
val fromAmount: TextReference,
val fromAmountValue: BigDecimal,
val fromFiatAmount: TextReference?,
val fromAmountSymbol: String,
val fromCurrencyIcon: CurrencyIconState,
val fromAddress: String?,
) {
val subtitle: TextReference
get() = buildExpressStatusSubtitle(activeStatus = activeStatus, date = timestampAgoFormatted)

View file

@ -14,10 +14,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.ds.row.TangemRowContainer
@ -61,15 +64,14 @@ fun TokenActionRow(
val accentColor = accentColor(isEnabled)
TangemRowContainer(
modifier = modifier
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
)
.clip(RoundedCornerShape(TangemTheme.dimens2.x5))
.background(color = TangemTheme.colors2.surface.level3)
.clickableWithHaptic(
onClick = onClick,
onLongClick = onLongClick,
hapticManager = hapticManager,
),
)
.semantics { if (!isEnabled) disabled() },
) {
LeadingIcon(iconRes = iconRes, accentColor = accentColor)
Text(