Updated on 2026-08-14
This commit is contained in:
commit
e4754fed3b
483 changed files with 9315 additions and 4084 deletions
|
|
@ -0,0 +1,134 @@
|
|||
package com.tangem.common.ui.account
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.account.AccountIconPreviewData.randomAccountIcon
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
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.TangemThemePreview
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.Color
|
||||
|
||||
enum class AccountIconSize {
|
||||
Default, Large, Medium, Small, ExtraSmall
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an account icon that can either show a letter (derived from [name])
|
||||
* or a predefined vector resource (from [icon]).
|
||||
*
|
||||
* The background color is determined by the icon's [CryptoPortfolioIconUM.color],
|
||||
* and the icon size, text style, and box modifier are adapted based on the given [size].
|
||||
*
|
||||
* @param name The text reference used to resolve and display the first letter
|
||||
* when [icon] is set to [CryptoPortfolioIcon.Icon.Letter].
|
||||
* @param icon The account icon definition, which can be a letter or a drawable resource.
|
||||
* @param size The size of the icon, defined by [AccountIconSize].
|
||||
*/
|
||||
@Composable
|
||||
fun AccountIcon(
|
||||
name: TextReference,
|
||||
icon: CryptoPortfolioIconUM,
|
||||
size: AccountIconSize,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val boxModifier = modifier.selectBoxModifier(size)
|
||||
val iconSize = Modifier.selectIconSize(size)
|
||||
val textStyle = when (size) {
|
||||
AccountIconSize.Default -> TangemTheme.typography.h3
|
||||
AccountIconSize.Large -> TangemTheme.typography.h1
|
||||
AccountIconSize.Medium -> TangemTheme.typography.subtitle1
|
||||
AccountIconSize.Small -> TangemTheme.typography.subtitle2
|
||||
AccountIconSize.ExtraSmall -> TangemTheme.typography.caption1
|
||||
}
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = boxModifier.background(icon.color.getUiColor()),
|
||||
) {
|
||||
val icon = icon.value
|
||||
val letter = name.resolveReference().firstOrNull()
|
||||
when {
|
||||
icon == CryptoPortfolioIcon.Icon.Letter -> Text(
|
||||
text = letter?.uppercase() ?: "",
|
||||
style = textStyle,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
else -> Icon(
|
||||
modifier = iconSize,
|
||||
tint = TangemTheme.colors.text.constantWhite,
|
||||
imageVector = ImageVector.vectorResource(id = icon.getResId()),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.selectIconSize(size: AccountIconSize): Modifier = when (size) {
|
||||
AccountIconSize.Default -> this.size(20.dp)
|
||||
AccountIconSize.Large -> this.size(40.dp)
|
||||
AccountIconSize.Medium -> this.size(16.dp)
|
||||
AccountIconSize.Small -> this.size(12.dp)
|
||||
AccountIconSize.ExtraSmall -> this.size(8.dp)
|
||||
}
|
||||
|
||||
private fun Modifier.selectBoxModifier(size: AccountIconSize): Modifier = when (size) {
|
||||
AccountIconSize.Default -> size(36.dp).clip(RoundedCornerShape(10.dp))
|
||||
AccountIconSize.Large -> size(88.dp).clip(RoundedCornerShape(24.dp))
|
||||
AccountIconSize.Medium -> size(28.dp).clip(RoundedCornerShape(8.dp))
|
||||
AccountIconSize.Small -> size(20.dp).clip(RoundedCornerShape(6.dp))
|
||||
AccountIconSize.ExtraSmall -> size(14.dp).clip(RoundedCornerShape(4.dp))
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_AccountIcon() {
|
||||
TangemThemePreview {
|
||||
Sample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Sample() {
|
||||
val name = stringReference("Account Name")
|
||||
Row(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.Default)
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.Large)
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.Medium)
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.Small)
|
||||
AccountIcon(name = name, randomAccountIcon(), size = AccountIconSize.ExtraSmall)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.Default)
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.Large)
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.Medium)
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.Small)
|
||||
AccountIcon(name = name, randomAccountIcon(letter = true), size = AccountIconSize.ExtraSmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object AccountIconPreviewData {
|
||||
|
||||
fun randomAccountIcon(letter: Boolean = false) = CryptoPortfolioIconUM(
|
||||
value = if (letter) CryptoPortfolioIcon.Icon.Letter else CryptoPortfolioIcon.Icon.entries.random(),
|
||||
color = Color.entries.random(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package com.tangem.common.ui.account
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.common.ui.R
|
||||
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.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* Displays a row representing an account with an icon, title, and subtitle.
|
||||
*
|
||||
* The row consists of:
|
||||
* - An [AccountIcon] on the left.
|
||||
* - A column with the [title] and [subtitle] texts, which can be displayed in normal
|
||||
* or reversed order depending on [isReverse].
|
||||
*
|
||||
* The layout uses horizontal spacing between the icon and text, and vertical spacing
|
||||
* between the title and subtitle.
|
||||
*
|
||||
* @param title The main text shown in the row, usually representing the account name.
|
||||
* @param subtitle The secondary text, typically providing additional details about the account.
|
||||
* @param icon The account icon definition, displayed using [AccountIcon].
|
||||
* @param isReverse If `true`, the [subtitle] is displayed above the [title].
|
||||
* Otherwise, the [title] is displayed above the [subtitle].
|
||||
*/
|
||||
@Composable
|
||||
fun AccountRow(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
icon: CryptoPortfolioIconUM,
|
||||
modifier: Modifier = Modifier,
|
||||
isReverse: Boolean = false,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
AccountIcon(
|
||||
name = title,
|
||||
icon = icon,
|
||||
size = AccountIconSize.Default,
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
|
||||
) {
|
||||
if (isReverse) {
|
||||
Subtitle(subtitle)
|
||||
Title(title)
|
||||
} else {
|
||||
Title(title)
|
||||
Subtitle(subtitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Title(title: TextReference) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Subtitle(subtitle: TextReference) {
|
||||
Text(
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
text = subtitle.resolveReference(),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
Sample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Sample() {
|
||||
val name = stringReference("Main account")
|
||||
val info = stringReference("10 tokens in 2 networks")
|
||||
val subtitle = resourceReference(R.string.account_form_name)
|
||||
fun icon(letter: Boolean = false) = AccountIconPreviewData.randomAccountIcon(letter)
|
||||
Column(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
AccountRow(title = name, subtitle = info, icon = icon())
|
||||
AccountRow(title = name, subtitle = subtitle, icon = icon(), isReverse = true)
|
||||
}
|
||||
}
|
||||
|
|
@ -47,4 +47,7 @@ fun CryptoPortfolioIcon.Icon.getResId(): Int {
|
|||
CryptoPortfolioIcon.Icon.Package -> R.drawable.ic_package_24
|
||||
CryptoPortfolioIcon.Icon.Gift -> R.drawable.ic_gift_24
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun CryptoPortfolioIcon.toUM() = CryptoPortfolioIconUM(domainModel = this)
|
||||
fun CryptoPortfolioIconUM.toDomain() = CryptoPortfolioIcon.ofCustomAccount(value = this.value, color = this.color)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.common.ui.account
|
||||
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.Color
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.Icon
|
||||
|
||||
data class CryptoPortfolioIconUM(
|
||||
val value: Icon,
|
||||
val color: Color,
|
||||
) {
|
||||
constructor(domainModel: CryptoPortfolioIcon) : this(
|
||||
value = domainModel.value,
|
||||
color = domainModel.color,
|
||||
)
|
||||
}
|
||||
|
|
@ -60,6 +60,7 @@ class AmountStateConverter(
|
|||
return AmountState.Data(
|
||||
title = value.title,
|
||||
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
|
||||
availableBalanceShort = stringReference(crypto),
|
||||
tokenName = stringReference(status.currency.name),
|
||||
tokenIconState = iconStateConverter.convert(status),
|
||||
amountTextField = amountFieldConverter.convert(value.value),
|
||||
|
|
@ -130,6 +131,7 @@ class AmountStateConverterV2(
|
|||
} else {
|
||||
resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat))
|
||||
},
|
||||
availableBalanceShort = stringReference(crypto),
|
||||
tokenName = stringReference(cryptoCurrencyStatus.currency.name),
|
||||
tokenIconState = iconStateConverter.convert(cryptoCurrencyStatus.currency),
|
||||
amountTextField = amountFieldConverter.convert(value.value),
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class AmountBoundaryUpdateTransformer(
|
|||
|
||||
return prevState.copy(
|
||||
availableBalance = availableBalance,
|
||||
availableBalanceShort = stringReference(crypto),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,8 @@ sealed class AmountState {
|
|||
/**
|
||||
* @param isPrimaryButtonEnabled indicates if next state button enabled
|
||||
* @param title title
|
||||
* @param availableBalance user crypto currency balance
|
||||
* @param availableBalance user crypto currency balance with fiat balance
|
||||
* @param availableBalanceShort user crypto currency balance without fiat balance
|
||||
* @param tokenIconState crypto currency icon state
|
||||
* @param segmentedButtonConfig currency switcher config
|
||||
* @param selectedButton selected currency index
|
||||
|
|
@ -33,6 +34,7 @@ sealed class AmountState {
|
|||
override val isRedesignEnabled: Boolean,
|
||||
val title: TextReference,
|
||||
val availableBalance: TextReference,
|
||||
val availableBalanceShort: TextReference,
|
||||
val tokenName: TextReference,
|
||||
val tokenIconState: CurrencyIconState,
|
||||
val segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ object AmountStatePreviewData {
|
|||
val amountState = AmountState.Data(
|
||||
isPrimaryButtonEnabled = false,
|
||||
title = stringReference("Family Wallet"),
|
||||
availableBalance = stringReference("2 130,88 USDT (2 129,92 \$)"),
|
||||
availableBalance = stringReference("2 130,88 USDT • 2 129,92 \$)"),
|
||||
availableBalanceShort = stringReference("2 130,88 USDT"),
|
||||
tokenIconState = CurrencyIconState.Loading,
|
||||
segmentedButtonConfig = persistentListOf(
|
||||
AmountSegmentedButtonsConfig(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ 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.platform.testTag
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -25,6 +26,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StakingSendDetailsScreenTestTags
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Composable
|
||||
|
|
@ -63,7 +65,8 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
|
|||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens.spacing24),
|
||||
.padding(top = TangemTheme.dimens.spacing24)
|
||||
.testTag(StakingSendDetailsScreenTestTags.PRIMARY_AMOUNT),
|
||||
)
|
||||
Text(
|
||||
text = secondAmount,
|
||||
|
|
@ -72,7 +75,8 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
|
|||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens.spacing8),
|
||||
.padding(top = TangemTheme.dimens.spacing8)
|
||||
.testTag(StakingSendDetailsScreenTestTags.SECONDARY_AMOUNT),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ fun AmountBlockV2(
|
|||
|
||||
AmountBlockV2(
|
||||
title = amountState.title,
|
||||
balance = amountState.availableBalance,
|
||||
balance = amountState.availableBalanceShort,
|
||||
currencyTitle = currencyTitle,
|
||||
currencyIconState = amountState.tokenIconState,
|
||||
firstAmount = firstAmount,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
|
||||
|
|
@ -22,6 +23,7 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.StakingSendScreenTestTags
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
||||
private const val AMOUNT_BUTTONS_KEY = "amountButtonsKey"
|
||||
|
|
@ -77,7 +79,8 @@ internal fun LazyListScope.buttons(
|
|||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing10,
|
||||
horizontal = TangemTheme.dimens.spacing34,
|
||||
),
|
||||
)
|
||||
.testTag(StakingSendScreenTestTags.MAX_BUTTON),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +93,8 @@ private fun AmountCurrencyButton(button: AmountSegmentedButtonsConfig, isSegment
|
|||
.fillMaxSize()
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing10,
|
||||
),
|
||||
)
|
||||
.testTag(StakingSendScreenTestTags.CURRENCY_BUTTON),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
@ -102,13 +106,13 @@ private fun AmountCurrencyButton(button: AmountSegmentedButtonsConfig, isSegment
|
|||
url = button.iconUrl,
|
||||
size = TangemTheme.dimens.size18,
|
||||
isGrayscale = !isSegmentedButtonsEnabled,
|
||||
modifier = iconModifier,
|
||||
modifier = iconModifier.testTag(StakingSendScreenTestTags.FIAT_ICON),
|
||||
)
|
||||
} else if (button.iconState != null) {
|
||||
CurrencyIcon(
|
||||
state = button.iconState,
|
||||
shouldDisplayNetwork = false,
|
||||
modifier = iconModifier,
|
||||
modifier = iconModifier.testTag(StakingSendScreenTestTags.CURRENCY_ICON),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.Alignment.Companion.TopCenter
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
|
|
@ -28,6 +29,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto
|
|||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.StakingSendScreenTestTags
|
||||
import com.tangem.core.ui.utils.rememberDecimalFormat
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
|
|
@ -116,7 +118,8 @@ private fun AmountSecondary(amountField: AmountFieldModel, appCurrencyCode: Stri
|
|||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.align(TopCenter)
|
||||
.padding(bottom = TangemTheme.dimens.spacing32),
|
||||
.padding(bottom = TangemTheme.dimens.spacing32)
|
||||
.testTag(StakingSendScreenTestTags.SECONDARY_AMOUNT),
|
||||
)
|
||||
AmountFieldError(
|
||||
isError = amountField.isError,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.R
|
||||
|
|
@ -29,6 +30,7 @@ import com.tangem.core.ui.extensions.orMaskWithStars
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.StakingSendScreenTestTags
|
||||
|
||||
private const val AMOUNT_FIELD_KEY = "amountFieldKey"
|
||||
|
||||
|
|
@ -52,7 +54,8 @@ internal fun LazyListScope.amountField(
|
|||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing14),
|
||||
.padding(top = TangemTheme.dimens.spacing14)
|
||||
.testTag(StakingSendScreenTestTags.AMOUNT_CONTAINER_TITLE),
|
||||
)
|
||||
|
||||
val balance = amountState.availableBalance.orMaskWithStars(isBalanceHidden).resolveReference()
|
||||
|
|
@ -66,7 +69,8 @@ internal fun LazyListScope.amountField(
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing2),
|
||||
.padding(top = TangemTheme.dimens.spacing2)
|
||||
.testTag(StakingSendScreenTestTags.AMOUNT_CONTAINER_TEXT),
|
||||
)
|
||||
}
|
||||
CurrencyIcon(
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ package com.tangem.common.ui.amountScreen.utils
|
|||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
import com.tangem.core.ui.format.bigdecimal.approximateAmount
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import java.math.BigDecimal
|
||||
|
||||
fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? {
|
||||
|
|
@ -19,12 +21,19 @@ fun getFiatString(
|
|||
appCurrency: AppCurrency,
|
||||
approximate: Boolean = false,
|
||||
): String {
|
||||
if (value == null || rate == null) return EMPTY_BALANCE_SIGN
|
||||
if (value == null || rate == null) return DASH_SIGN
|
||||
val feeValue = value.multiply(rate)
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = feeValue,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
withApproximateSign = approximate,
|
||||
)
|
||||
return feeValue.format {
|
||||
if (approximate) {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
).approximateAmount()
|
||||
} else {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,9 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -22,18 +25,18 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.common.ui.navigationButtons.preview.NavigationButtonsPreview
|
||||
import com.tangem.core.ui.components.Keyboard
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.buttons.common.contentColor
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.isNullOrEmpty
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import com.tangem.core.ui.utils.singleEvent
|
||||
import com.tangem.core.ui.test.StakingSendScreenTestTags
|
||||
|
||||
@Composable
|
||||
fun NavigationButtonsBlock(
|
||||
|
|
@ -47,7 +50,7 @@ fun NavigationButtonsBlock(
|
|||
modifier = modifier.fillMaxWidth(),
|
||||
) {
|
||||
InfoText(footerText)
|
||||
ExtraButtons(state?.extraButtons, state?.txUrl)
|
||||
DoneButtons(state?.extraButtons)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
|
|
@ -58,9 +61,33 @@ fun NavigationButtonsBlock(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavigationButtonsBlockV2(
|
||||
navigationUM: NavigationUM,
|
||||
modifier: Modifier = Modifier,
|
||||
footerText: TextReference? = null,
|
||||
) {
|
||||
val navigationUM = navigationUM as? NavigationUM.Content
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
) {
|
||||
InfoText(footerText)
|
||||
DoneButtons(navigationUM?.secondaryPairButtonsUM)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
PreviousButton(navigationUM?.prevButton)
|
||||
NavigationPrimaryButton(navigationUM?.primaryButton, modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavigationPrimaryButton(primaryButton: NavigationButton?, modifier: Modifier = Modifier) {
|
||||
val wrappedButton by rememberNavigationButton(primaryButton)
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
AnimatedContent(
|
||||
targetState = wrappedButton,
|
||||
transitionSpec = { navigationButtonsTransition() },
|
||||
|
|
@ -83,7 +110,12 @@ fun NavigationPrimaryButton(primaryButton: NavigationButton?, modifier: Modifier
|
|||
TangemButton(
|
||||
text = button.textReference.resolveReference(),
|
||||
enabled = button.isEnabled,
|
||||
onClick = button.onClick,
|
||||
onClick = {
|
||||
if (button.isHapticClick) {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
button.onClick()
|
||||
},
|
||||
showProgress = button.showProgress,
|
||||
colors = color,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
|
|
@ -116,40 +148,47 @@ private fun PreviousButton(prevButton: NavigationButton?) {
|
|||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.button.secondary)
|
||||
.clickable(onClick = button.onClick)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
.padding(TangemTheme.dimens.spacing12)
|
||||
.testTag(StakingSendScreenTestTags.PREVIOUS_BUTTON),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExtraButtons(extraButtons: ImmutableList<NavigationButton>?, txUrl: String?) {
|
||||
fun DoneButtons(pairButtons: Pair<NavigationButton, NavigationButton>?, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(
|
||||
visible = !txUrl.isNullOrBlank() && extraButtons != null,
|
||||
visible = pairButtons != null,
|
||||
enter = slideInVertically(initialOffsetY = { it / 2 }).plus(fadeIn()),
|
||||
exit = slideOutVertically(targetOffsetY = { it / 2 }).plus(fadeOut()),
|
||||
label = "Animate show sent state buttons",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
) {
|
||||
val buttons = remember(this) { requireNotNull(extraButtons) }
|
||||
val (leftButton, rightButton) = remember(this) { requireNotNull(pairButtons) }
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
buttons.forEach { button ->
|
||||
val icon = button.iconRes?.let { TangemButtonIconPosition.Start(iconResId = it) }
|
||||
?: TangemButtonIconPosition.None
|
||||
TangemButton(
|
||||
text = button.textReference.resolveReference(),
|
||||
icon = icon,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
onClick = rememberHapticFeedback(state = button, onAction = button.onClick),
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = button.isEnabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
)
|
||||
}
|
||||
SecondaryButtonIconStart(
|
||||
text = leftButton.textReference.resolveReference(),
|
||||
iconResId = requireNotNull(leftButton.iconRes),
|
||||
onClick = {
|
||||
singleEvent {
|
||||
leftButton.onClick()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
SecondaryButtonIconStart(
|
||||
text = rightButton.textReference.resolveReference(),
|
||||
iconResId = requireNotNull(rightButton.iconRes),
|
||||
onClick = {
|
||||
singleEvent {
|
||||
rightButton.onClick()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.common.ui.navigationButtons
|
|||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
sealed class NavigationButtonsState {
|
||||
data object Empty : NavigationButtonsState()
|
||||
|
|
@ -10,7 +9,7 @@ sealed class NavigationButtonsState {
|
|||
data class Data(
|
||||
val primaryButton: NavigationButton?,
|
||||
val prevButton: NavigationButton?,
|
||||
val extraButtons: ImmutableList<NavigationButton>,
|
||||
val extraButtons: Pair<NavigationButton, NavigationButton>?,
|
||||
val txUrl: String? = null,
|
||||
val onTextClick: (String) -> Unit,
|
||||
) : NavigationButtonsState()
|
||||
|
|
|
|||
|
|
@ -5,29 +5,25 @@ import com.tangem.common.ui.navigationButtons.NavigationButton
|
|||
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal object NavigationButtonsPreview {
|
||||
|
||||
private val extraButtons = persistentListOf(
|
||||
NavigationButton(
|
||||
textReference = resourceReference(R.string.common_explore),
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
showProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
NavigationButton(
|
||||
textReference = resourceReference(R.string.common_share),
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
showProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
private val extraButtons = NavigationButton(
|
||||
textReference = resourceReference(R.string.common_explore),
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
showProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
) to NavigationButton(
|
||||
textReference = resourceReference(R.string.common_share),
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
showProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
)
|
||||
|
||||
private val prev = NavigationButton(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.tangem.common.ui.userwallet
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -22,6 +24,7 @@ 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 coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.common.ui.R
|
||||
|
|
@ -184,6 +187,19 @@ fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modi
|
|||
radius = TangemTheme.dimens.size2,
|
||||
)
|
||||
}
|
||||
is UserWalletItemUM.ImageState.MobileWallet -> {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.field.focused,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
)
|
||||
.padding(6.dp),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_mobile_wallet_icon_24),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
is UserWalletItemUM.ImageState.Image -> {
|
||||
val verifiedArtwork = imageState.artwork.verifiedArtwork
|
||||
if (verifiedArtwork != null) {
|
||||
|
|
@ -364,6 +380,18 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider<UserWal
|
|||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_3".encodeToByteArray()),
|
||||
name = stringReference("Multi Card"),
|
||||
information = UserWalletItemUM.Information.Failed,
|
||||
balance = UserWalletItemUM.Balance.Loaded(
|
||||
value = "1.2345 BTC",
|
||||
isFlickering = false,
|
||||
),
|
||||
imageState = UserWalletItemUM.ImageState.MobileWallet,
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
private fun getInformation(cardCount: Int): UserWalletItemUM.Information.Loaded {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.tangem.common.ui.userwallet.converter
|
|||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.label.entity.LabelStyle
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.components.label.entity.LabelStyle
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -35,6 +35,7 @@ class UserWalletItemUMConverter(
|
|||
private val appCurrency: AppCurrency? = null,
|
||||
private val balance: TotalFiatBalance? = null,
|
||||
private val isBalanceHidden: Boolean = false,
|
||||
private val authMode: Boolean = false,
|
||||
private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None,
|
||||
private val artwork: ArtworkModel? = null,
|
||||
) : Converter<UserWallet, UserWalletItemUM> {
|
||||
|
|
@ -48,24 +49,38 @@ class UserWalletItemUMConverter(
|
|||
name = stringReference(name),
|
||||
information = getInfo(userWallet = this),
|
||||
balance = getBalanceInfo(userWallet = this),
|
||||
isEnabled = !isLocked,
|
||||
isEnabled = isEnabled(userWallet = this),
|
||||
endIcon = endIcon,
|
||||
onClick = { onClick(value.walletId) },
|
||||
imageState = artwork?.let {
|
||||
UserWalletItemUM.ImageState.Image(artworkUMConverter.convert(it))
|
||||
} ?: UserWalletItemUM.ImageState.Loading,
|
||||
label = if (this is UserWallet.Hot && !this.backedUp) {
|
||||
LabelUM(
|
||||
text = resourceReference(R.string.hw_backup_no_backup),
|
||||
style = LabelStyle.WARNING,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
imageState = getImageState(userWallet = value),
|
||||
label = getLabelOrNull(userWallet = this),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEnabled(userWallet: UserWallet): Boolean {
|
||||
return authMode || userWallet.isLocked.not()
|
||||
}
|
||||
|
||||
private fun getLabelOrNull(userWallet: UserWallet): LabelUM? {
|
||||
return if (authMode.not() && userWallet is UserWallet.Hot && !userWallet.backedUp) {
|
||||
LabelUM(
|
||||
text = resourceReference(R.string.hw_backup_no_backup),
|
||||
style = LabelStyle.WARNING,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getImageState(userWallet: UserWallet): UserWalletItemUM.ImageState {
|
||||
return when {
|
||||
userWallet is UserWallet.Hot -> UserWalletItemUM.ImageState.MobileWallet
|
||||
artwork != null -> UserWalletItemUM.ImageState.Image(artworkUMConverter.convert(artwork))
|
||||
else -> UserWalletItemUM.ImageState.Loading
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInfo(userWallet: UserWallet): UserWalletItemUM.Information.Loaded {
|
||||
val text = when (userWallet) {
|
||||
is UserWallet.Cold -> {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ data class UserWalletItemUM(
|
|||
|
||||
data object Loading : ImageState()
|
||||
|
||||
data object MobileWallet : ImageState()
|
||||
|
||||
data class Image(
|
||||
val artwork: ArtworkUM,
|
||||
) : ImageState()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue