Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-23 15:56:47 +01:00
parent 385cc79a32
commit a3f2c421bf
69 changed files with 749 additions and 542 deletions

View file

@ -47,14 +47,15 @@ inline fun <reified T> SegmentedButtons(
selectedColor: Color = TangemTheme.colors.background.action,
dividerColor: Color = TangemTheme.colors.stroke.primary,
showIndication: Boolean = true,
selectedItem: T? = null,
initialSelectedItem: T? = null,
isEnabled: Boolean = true,
crossinline buttonContent: @Composable (T) -> Unit,
) {
if (config.isEmpty() || config.size == 1) return
var selected by remember {
val selectedIndex = if (selectedItem == null) 0 else config.indexOf(selectedItem)
mutableIntStateOf(selectedIndex)
var selectedIndex by remember {
val index = if (initialSelectedItem == null) 0 else config.indexOf(initialSelectedItem)
mutableIntStateOf(index)
}
Row(
@ -69,7 +70,7 @@ inline fun <reified T> SegmentedButtons(
val rightRadius = if (index == config.lastIndex) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0
val animateColor by animateColorAsState(
targetValue = if (index == selected) selectedColor else color,
targetValue = if (index == selectedIndex) selectedColor else color,
label = "Segmented Button Selected Color Animation",
animationSpec = spring(stiffness = Spring.StiffnessMedium),
)
@ -86,11 +87,12 @@ inline fun <reified T> SegmentedButtons(
),
)
.clickable(
enabled = isEnabled,
interactionSource = remember { MutableInteractionSource() },
indication = if (showIndication) LocalIndication.current else null,
) {
selectedIndex = index
onClick(config[index])
selected = index
},
) {
buttonContent.invoke(config[index])

View file

@ -4,11 +4,16 @@ import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
private const val GRAY_SCALE_SATURATION = 0f
private const val GRAY_SCALE_ALPHA = 0.4f
/**
* Simple icon from network
*
@ -20,16 +25,19 @@ import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
fun FiatIcon(
url: String?,
size: Dp,
isGrayscale: Boolean,
modifier: Modifier = Modifier,
@DrawableRes fallbackResId: Int = R.drawable.ic_shape_circle,
) {
val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url
val alpha = if (isGrayscale) GRAY_SCALE_ALPHA else 1f
val colorFilter = if (isGrayscale) GrayscaleColorFilter else null
DefaultCurrencyIcon(
iconData = iconData,
size = size,
alpha = 1f,
colorFilter = null,
alpha = alpha,
colorFilter = colorFilter,
errorIcon = {
Image(
painter = painterResource(id = fallbackResId),
@ -38,4 +46,7 @@ fun FiatIcon(
},
modifier = modifier,
)
}
}
private val GrayscaleColorFilter: ColorFilter
get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) })

View file

@ -20,6 +20,26 @@ class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrencyStatus, Token
}
}
fun convertCustom(
value: CryptoCurrencyStatus,
forceGrayscale: Boolean,
showCustomTokenBadge: Boolean,
): TokenIconState {
return when (val currency = value.currency) {
is CryptoCurrency.Coin -> getIconStateForCoin(
coin = currency,
isUnreachable = value.value.isError,
forceGrayscale = forceGrayscale,
)
is CryptoCurrency.Token -> getIconStateForToken(
token = currency,
isErrorStatus = value.value.isError,
forceGrayscale = forceGrayscale,
showCustomBadge = showCustomTokenBadge,
)
}
}
fun convert(currency: CryptoCurrency): TokenIconState {
return when (currency) {
is CryptoCurrency.Coin -> getIconStateForCoin(currency, isUnreachable = false)
@ -27,18 +47,27 @@ class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrencyStatus, Token
}
}
private fun getIconStateForCoin(coin: CryptoCurrency.Coin, isUnreachable: Boolean): TokenIconState.CoinIcon {
private fun getIconStateForCoin(
coin: CryptoCurrency.Coin,
isUnreachable: Boolean,
forceGrayscale: Boolean = false,
): TokenIconState.CoinIcon {
return TokenIconState.CoinIcon(
url = coin.iconUrl,
fallbackResId = coin.networkIconResId,
isGrayscale = coin.network.isTestnet || isUnreachable,
isGrayscale = forceGrayscale || coin.network.isTestnet || isUnreachable,
showCustomBadge = coin.isCustom,
)
}
private fun getIconStateForToken(token: CryptoCurrency.Token, isErrorStatus: Boolean): TokenIconState {
val isGrayscale = token.network.isTestnet || isErrorStatus
val background = token.tryGetBackgroundForTokenIcon(isGrayscale)
private fun getIconStateForToken(
token: CryptoCurrency.Token,
isErrorStatus: Boolean,
showCustomBadge: Boolean = true,
forceGrayscale: Boolean = false,
): TokenIconState {
val grayScale = forceGrayscale || token.network.isTestnet || isErrorStatus
val background = token.tryGetBackgroundForTokenIcon(grayScale)
val tint = getTintForTokenIcon(background)
return if (token.isCustom && token.iconUrl == null) {
@ -46,16 +75,16 @@ class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrencyStatus, Token
tint = tint,
background = background,
networkBadgeIconResId = token.networkIconResId,
isGrayscale = isGrayscale,
isGrayscale = grayScale,
)
} else {
TokenIconState.TokenIcon(
url = token.iconUrl,
networkBadgeIconResId = token.networkIconResId,
isGrayscale = isGrayscale,
isGrayscale = grayScale,
fallbackTint = tint,
fallbackBackground = background,
showCustomBadge = token.isCustom, // `true` for tokens with custom derivation
showCustomBadge = token.isCustom && showCustomBadge, // `true` for tokens with custom derivation
)
}
}

View file

@ -50,7 +50,7 @@ fun PasteButton(isPasteButtonVisible: Boolean, onClick: (String) -> Unit, modifi
modifier = modifier,
) {
Text(
text = "Paste",
text = stringResource(R.string.common_paste),
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary2,
modifier = Modifier

View file

@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.toDateFormat
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.core.ui.utils.toTimeFormat
/**
@ -47,7 +47,11 @@ fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifie
.padding(top = TangemTheme.dimens.spacing16),
)
Text(
text = stringResource(id = R.string.send_date_format, date.toDateFormat(), date.toTimeFormat()),
text = stringResource(
id = R.string.send_date_format,
date.toTimeFormat(DateTimeFormatters.dateFormatter),
date.toTimeFormat(),
),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier

View file

@ -40,6 +40,29 @@ object BigDecimalFormatter {
}
}
fun formatCryptoAmountUncapped(
cryptoAmount: BigDecimal?,
cryptoCurrency: CryptoCurrency,
locale: Locale = Locale.getDefault(),
): String {
if (cryptoAmount == null) return EMPTY_BALANCE_SIGN
val formatter = NumberFormat.getNumberInstance(locale).apply {
maximumFractionDigits = cryptoCurrency.decimals
minimumFractionDigits = 2
isGroupingUsed = true
roundingMode = RoundingMode.DOWN
}
return formatter.format(cryptoAmount).let {
if (cryptoCurrency.symbol.isEmpty()) {
it
} else {
it + "\u2009${cryptoCurrency.symbol}"
}
}
}
fun formatCryptoAmount(
cryptoAmount: BigDecimal?,
cryptoCurrency: CryptoCurrency,

View file

@ -11,7 +11,7 @@ import org.joda.time.format.DateTimeFormatter
* If [this] timestamp is today or yesterday, returns relative date,
* otherwise returns formatting date.
*/
fun Long.toDateFormat(formatter: DateTimeFormatter = DateTimeFormatters.dateFormatter): String {
fun Long.toDateFormatWithTodayYesterday(formatter: DateTimeFormatter = DateTimeFormatters.dateFormatter): String {
val localDate = DateTime(this, DateTimeZone.getDefault())
return if (localDate.isToday() || localDate.isYesterday()) {
DateUtils.getRelativeTimeSpanString(