Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-02 10:24:41 +03:00
commit af09ce9ca4
877 changed files with 16091 additions and 6613 deletions

View file

@ -8,23 +8,20 @@ import com.tangem.utils.converter.Converter
class AccountIconItemStateConverter(
val size: AccountIconSize = AccountIconSize.Default,
) : Converter<Account, CurrencyIconState.CryptoPortfolio> {
) : Converter<Account.CryptoPortfolio, CurrencyIconState.CryptoPortfolio> {
override fun convert(value: Account): CurrencyIconState.CryptoPortfolio = when (value) {
is Account.CryptoPortfolio -> when {
value.icon.value == CryptoPortfolioIcon.Icon.Letter -> CurrencyIconState.CryptoPortfolio.Letter(
char = value.accountName.toUM().value,
color = value.icon.color.getUiColor(),
isGrayscale = false,
size = size,
)
else -> CurrencyIconState.CryptoPortfolio.Icon(
resId = value.icon.value.getResId(),
color = value.icon.color.getUiColor(),
isGrayscale = false,
size = size,
)
}
is Account.Payment -> TODO("[REDACTED_JIRA]")
override fun convert(value: Account.CryptoPortfolio): CurrencyIconState.CryptoPortfolio = when {
value.icon.value == CryptoPortfolioIcon.Icon.Letter -> CurrencyIconState.CryptoPortfolio.Letter(
char = value.accountName.toUM().value,
color = value.icon.color.getUiColor(),
isGrayscale = false,
size = size,
)
else -> CurrencyIconState.CryptoPortfolio.Icon(
resId = value.icon.value.getResId(),
color = value.icon.color.getUiColor(),
isGrayscale = false,
size = size,
)
}
}

View file

@ -19,23 +19,20 @@ class AccountPortfolioItemUMConverter(
private val isBalanceHidden: Boolean = false,
private val isEnabled: Boolean = true,
private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None,
) : Converter<Account, UserWalletItemUM> {
) : Converter<Account.CryptoPortfolio, UserWalletItemUM> {
override fun convert(value: Account): UserWalletItemUM {
return when (value) {
is Account.CryptoPortfolio -> with(value) {
UserWalletItemUM(
id = accountId.value,
name = accountName.toUM().value,
information = getInfo(account = this),
balance = getBalanceInfo(),
isEnabled = isEnabled,
endIcon = endIcon,
onClick = onClick,
imageState = getImageState(account = this),
)
}
is Account.Payment -> TODO("[REDACTED_JIRA]")
override fun convert(value: Account.CryptoPortfolio): UserWalletItemUM {
return with(value) {
UserWalletItemUM(
id = accountId.value,
name = accountName.toUM().value,
information = getInfo(account = this),
balance = getBalanceInfo(),
isEnabled = isEnabled,
endIcon = endIcon,
onClick = onClick,
imageState = getImageState(account = this),
)
}
}

View file

@ -73,7 +73,7 @@ class AmountFieldChangeTransformer(
fiatValue = fiatValue,
isError = isCheckFailed,
error = when {
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
isExceedBalance -> resourceReference(R.string.common_insufficient_balance)
isLessThanMinimumIfProvided -> {
val minimumAmount = minimumTransactionAmount.amount.format {
crypto(cryptoCurrencyStatus.currency)

View file

@ -62,6 +62,7 @@ fun AmountFieldV2(
onValuePastedTriggerDismiss: () -> Unit,
onCurrencyChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
reserveSpaceForError: Boolean = true,
) {
val decimalFormat = rememberDecimalFormat()
@ -120,12 +121,13 @@ fun AmountFieldV2(
AmountSecondary(
amountUM = amountUM,
onCurrencyChange = onCurrencyChange,
reserveSpaceForError = reserveSpaceForError,
)
}
}
@Composable
private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -> Unit) {
private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -> Unit, reserveSpaceForError: Boolean) {
Box(
modifier = Modifier
.fillMaxWidth()
@ -144,29 +146,33 @@ private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -
AmountFieldCurrencyInfo(
amountUM = amountUM,
onCurrencyChange = onCurrencyChange,
modifier = if (reserveSpaceForError) Modifier.padding(bottom = 16.dp) else Modifier,
)
AmountFieldError(
isError = amountUM.amountTextField.isError,
isWarning = amountUM.amountTextField.isWarning,
error = amountUM.amountTextField.error,
reserveSpaceForError = reserveSpaceForError,
modifier = Modifier
.align(BottomCenter)
.padding(top = 24.dp),
.align(BottomCenter),
)
}
}
}
@Composable
private fun BoxScope.AmountFieldCurrencyInfo(amountUM: AmountState.Data, onCurrencyChange: (Boolean) -> Unit) {
private fun BoxScope.AmountFieldCurrencyInfo(
amountUM: AmountState.Data,
onCurrencyChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
val isFiatAvailable = amountUM.amountTextField.fiatAmount.value != null
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
modifier = modifier
.align(TopCenter)
.padding(bottom = 16.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
@ -255,13 +261,14 @@ private fun AmountFieldError(
isError: Boolean,
isWarning: Boolean,
error: TextReference,
reserveSpaceForError: Boolean,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = isError || isWarning,
enter = fadeIn(),
exit = fadeOut(),
modifier = modifier,
modifier = if (reserveSpaceForError) modifier.padding(top = 24.dp) else modifier,
label = "Error field appearance animation",
) {
val errorText = remember(this, error) { error }
@ -271,7 +278,13 @@ private fun AmountFieldError(
style = TangemTheme.typography.caption2,
color = color,
textAlign = TextAlign.Center,
modifier = Modifier.testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT),
modifier = if (reserveSpaceForError) {
Modifier.testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT)
} else {
Modifier
.padding(top = 24.dp)
.testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT)
},
)
}
}
@ -324,6 +337,7 @@ private fun AmountFieldV2_Preview(@PreviewParameter(AmountFieldV2PreviewProvider
onValueChange = {},
onValuePastedTriggerDismiss = { },
onCurrencyChange = {},
reserveSpaceForError = false,
modifier = Modifier.background(TangemTheme.colors.background.action),
)
}

View file

@ -40,13 +40,13 @@ internal fun String.checkExceedBalance(
maxEnterAmount: EnterAmountBoundary,
amountTextField: AmountFieldModel,
): Boolean {
val currencyCryptoAmount = maxEnterAmount.amount ?: BigDecimal.ZERO
val currencyFiatAmount = maxEnterAmount.fiatAmount ?: BigDecimal.ZERO
val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals)
val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals)
return if (amountTextField.isFiatValue) {
val currencyFiatAmount = maxEnterAmount.fiatAmount ?: return false
fiatDecimal > currencyFiatAmount
} else {
val currencyCryptoAmount = maxEnterAmount.amount ?: return false
cryptoDecimal > currencyCryptoAmount
}
}

View file

@ -42,6 +42,7 @@ enum class ApproveType(val text: TextReference) {
data class ApprovePermissionButton(
val isEnabled: Boolean,
val isLoading: Boolean = false,
val isHoldToConfirm: Boolean = false,
val onClick: () -> Unit,
)

View file

@ -0,0 +1,98 @@
package com.tangem.common.ui.notifications
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
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.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.notifications.CloseableIconButton
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.ForceDarkTheme
import com.tangem.core.ui.res.TangemTheme
private const val GRADIENT_START_COLOR = 0xFF252934
private const val GRADIENT_END_COLOR = 0xFF12141E
private const val GRADIENT_OFFSET_X = 164f
private const val GRADIENT_OFFSET_Y = 39f
private const val GRADIENT_RADIUS = 82f
@Composable
fun CreatePaymentAccountNotification(
onClick: () -> Unit,
onCloseClick: () -> Unit,
@DrawableRes image: Int,
title: TextReference,
subtitle: TextReference,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(
brush = Brush.radialGradient(
colors = listOf(Color(GRADIENT_START_COLOR), Color(GRADIENT_END_COLOR)),
center = Offset(GRADIENT_OFFSET_X, GRADIENT_OFFSET_Y),
radius = GRADIENT_RADIUS,
),
)
.clickable(onClick = onClick),
) {
Image(
modifier = Modifier.size(78.dp),
painter = painterResource(id = image),
contentDescription = null,
)
Column(
modifier = Modifier
.padding(start = 78.dp, top = 12.dp, end = 12.dp, bottom = 12.dp)
.align(Alignment.CenterStart),
) {
Text(
modifier = Modifier.padding(end = TangemTheme.dimens.size32),
text = title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.constantWhite,
)
Text(
text = subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
CloseableIconButton(
onClick = onCloseClick,
modifier = Modifier.align(alignment = Alignment.TopEnd),
iconTint = TangemTheme.colors.icon.inactive,
)
}
}
@Preview(widthDp = 360)
@Composable
private fun CreatePaymentAccountNotification_Preview() {
ForceDarkTheme {
CreatePaymentAccountNotification(
onClick = {},
onCloseClick = {},
image = R.drawable.img_tangem_pay_visa_banner,
title = resourceReference(R.string.tangempay_onboarding_banner_title),
subtitle = resourceReference(R.string.tangempay_onboarding_banner_description),
)
}
}

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
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
@ -43,6 +44,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.WalletSettingsScreenTestTags
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.StringsSigns.DOT
import com.tangem.utils.StringsSigns.THREE_STARS
@ -64,7 +66,8 @@ fun UserWalletItem(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size68)
.padding(all = TangemTheme.dimens.spacing12),
.padding(all = TangemTheme.dimens.spacing12)
.testTag(WalletSettingsScreenTestTags.USER_ACCOUNT_ITEM),
)
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessage
import com.tangem.core.ui.message.dialog.Dialogs
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.common.wallets.error.UnlockWalletError
import com.tangem.domain.common.wallets.error.UnlockWalletError.UnableToUnlock.Reason
@ -22,12 +23,7 @@ inline fun UnlockWalletError.handle(
when (this) {
UnlockWalletError.AlreadyUnlocked -> onAlreadyUnlocked()
UnlockWalletError.ScannedCardWalletNotMatched -> {
showMessage(
DialogMessage(
title = resourceReference(R.string.common_warning),
message = resourceReference(R.string.error_wrong_wallet_tapped),
),
)
showMessage(Dialogs.wrongWalletTapped())
}
UnlockWalletError.UserCancelled -> onUserCancelled()
UnlockWalletError.UserWalletNotFound -> {