Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-04 16:46:41 +03:00
commit 512ab6d54d
206 changed files with 8933 additions and 630 deletions

View file

@ -13,6 +13,7 @@ import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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
@ -162,11 +163,12 @@ fun CardWithIcon(
* >Figma component</a>
*/
@Composable
fun IconWithTitleAndDescription(
internal fun IconWithTitleAndDescription(
title: String,
description: String,
description: String?,
icon: @Composable () -> Unit,
additionalContent: @Composable () -> Unit = {},
iconBackground: Color = TangemTheme.colors.background.secondary,
) {
Row(
modifier = Modifier
@ -182,7 +184,7 @@ fun IconWithTitleAndDescription(
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.background.secondary,
color = iconBackground,
shape = CircleShape,
)
.height(TangemTheme.dimens.size40)
@ -205,12 +207,14 @@ fun IconWithTitleAndDescription(
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
)
SpacerH4()
Text(
text = description,
color = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.body2,
)
if (description != null) {
SpacerH4()
Text(
text = description,
color = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.body2,
)
}
}
additionalContent()

View file

@ -23,7 +23,7 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun TangemSwitch(
onCheckedChange: (Boolean) -> Unit,
checkedColor: Color = TangemTheme.colors.icon.accent,
checkedColor: Color = TangemTheme.colors.control.checked,
uncheckedColor: Color = TangemTheme.colors.icon.informative,
size: Dp = 48.dp,
checked: Boolean = false,

View file

@ -9,6 +9,7 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
@ -36,6 +37,28 @@ fun WarningCard(title: String, description: String, icon: @Composable (() -> Uni
)
}
/**
* A card with a warning icon to the left and title without description shown to the right of it.
*
* @param title title of the warning in bold
*
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=290%3A217&t=yMepJZTRh5bLkOoJ-1"
* >Figma component</a>
*/
@Composable
fun WarningCardTitleOnly(title: String, icon: @Composable (() -> Unit)? = null) {
WarningCardMaterial3Style(
content = {
WarningBody(
title = title,
description = null,
icon = icon,
iconBackground = TangemTheme.colors.button.disabled,
)
},
)
}
/**
* [WarningCard], but clickable (with an 'greater then' icon to the left)
*
@ -105,7 +128,8 @@ fun RefreshableWaringCard(
@Composable
private fun WarningBody(
title: String,
description: String,
description: String?,
iconBackground: Color = TangemTheme.colors.background.secondary,
icon: @Composable (() -> Unit)? = null,
additionalContent: @Composable () -> Unit = {},
) {
@ -119,6 +143,7 @@ private fun WarningBody(
contentDescription = null,
)
},
iconBackground = iconBackground,
)
}
@ -136,6 +161,20 @@ private fun WarningCardSurface(onClick: (() -> Unit)? = null, content: @Composab
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun WarningCardMaterial3Style(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) {
Card(
shape = RoundedCornerShape(TangemTheme.dimens.radius16),
backgroundColor = TangemTheme.colors.button.disabled,
elevation = TangemTheme.dimens.elevation0,
onClick = onClick ?: {},
enabled = onClick != null,
) {
content()
}
}
// endregion elements
// region Preview

View file

@ -4,10 +4,13 @@ import androidx.annotation.DrawableRes
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -39,7 +42,10 @@ fun AppBarWithBackButtonAndIcon(
contentDescription = null,
modifier = Modifier
.size(size = TangemTheme.dimens.size24)
.clickable { onBackClick() },
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
) { onBackClick() },
tint = TangemTheme.colors.icon.primary1,
)
AnimatedContent(

View file

@ -6,6 +6,7 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.res.TangemTheme
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
@ -20,6 +21,7 @@ import kotlinx.coroutines.launch
@Composable
inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
config: TangemBottomSheetConfig,
contentColor: Color = TangemTheme.colors.background.primary,
crossinline content: @Composable ColumnScope.(T) -> Unit,
) {
var isVisible by remember { mutableStateOf(value = config.isShow) }
@ -29,7 +31,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
ModalBottomSheet(
onDismissRequest = config.onDismissRequest,
sheetState = sheetState,
containerColor = TangemTheme.colors.background.primary,
containerColor = contentColor,
shape = TangemTheme.shapes.bottomSheetLarge,
dragHandle = { TangemBottomSheetDraggableHeader() },
) {

View file

@ -1,4 +1,4 @@
package com.tangem.core.ui.components.fields
package com.tangem.core.ui.components.fields.visualtransformations
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString

View file

@ -0,0 +1,87 @@
package com.tangem.core.ui.components.transactions
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
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.toTimeFormat
/**
* Common transaction done screen title
*
* @param titleRes title resource
* @param date transaction timestamp in millis
*/
@Composable
fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painter = painterResource(id = R.drawable.ic_empty_in_process_64),
contentDescription = null,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing8)
.size(TangemTheme.dimens.size64),
)
Text(
text = stringResource(id = titleRes),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing32),
)
Text(
text = stringResource(id = R.string.send_date_format, date.toDateFormat(), date.toTimeFormat()),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing4),
)
}
}
// region Previews
@Preview
@Composable
private fun TransactionDoneTitlePreview_Light() {
TangemTheme {
TransactionDoneTitle(
titleRes = R.string.sent_transaction_sent_title,
date = 0,
modifier = Modifier
.background(TangemTheme.colors.background.tertiary)
.padding(TangemTheme.dimens.spacing16),
)
}
}
@Preview
@Composable
private fun TransactionDoneTitlePreview_Dark() {
TangemTheme(isDark = true) {
TransactionDoneTitle(
titleRes = R.string.sent_transaction_sent_title,
date = 0,
modifier = Modifier
.background(TangemTheme.colors.background.tertiary)
.padding(TangemTheme.dimens.spacing16),
)
}
}
// endregion

View file

@ -7,18 +7,34 @@ import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import com.tangem.core.ui.res.TangemTheme
fun Modifier.roundedShapeItemDecoration(currentIndex: Int, lastIndex: Int): Modifier = composed {
val modifierWithHorizontalPadding = this.padding(horizontal = TangemTheme.dimens.spacing16)
fun Modifier.roundedShapeItemDecoration(
currentIndex: Int,
lastIndex: Int,
addDefaultPadding: Boolean = true,
): Modifier = composed {
val modifier = if (addDefaultPadding) this.padding(horizontal = TangemTheme.dimens.spacing16) else this
val isSingleItem = currentIndex == 0 && lastIndex == 0
when {
isSingleItem -> {
modifierWithHorizontalPadding
.padding(top = TangemTheme.dimens.spacing14)
modifier
.then(
if (addDefaultPadding) {
Modifier.padding(top = TangemTheme.dimens.spacing14)
} else {
Modifier
},
)
.clip(shape = TangemTheme.shapes.roundedCornersXMedium)
}
currentIndex == 0 -> {
modifierWithHorizontalPadding
.padding(top = TangemTheme.dimens.spacing14)
modifier
.then(
if (addDefaultPadding) {
Modifier.padding(top = TangemTheme.dimens.spacing14)
} else {
Modifier
},
)
.clip(
shape = RoundedCornerShape(
topStart = TangemTheme.dimens.radius16,
@ -27,7 +43,7 @@ fun Modifier.roundedShapeItemDecoration(currentIndex: Int, lastIndex: Int): Modi
)
}
currentIndex == lastIndex -> {
modifierWithHorizontalPadding
modifier
.clip(
shape = RoundedCornerShape(
bottomStart = TangemTheme.dimens.radius16,
@ -35,6 +51,6 @@ fun Modifier.roundedShapeItemDecoration(currentIndex: Int, lastIndex: Int): Modi
),
)
}
else -> modifierWithHorizontalPadding
else -> modifier
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.core.ui.event
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.NonRestartableComposable
import kotlinx.coroutines.launch
/**
* A Composable function that reacts to a given [StateEvent], executing the provided action only once when the event
@ -17,8 +18,8 @@ import androidx.compose.runtime.NonRestartableComposable
fun <A> EventEffect(event: StateEvent<A>, onTrigger: suspend (data: A) -> Unit) {
LaunchedEffect(event) {
if (event is StateEvent.Triggered<A>) {
onTrigger(event.data)
event.onConsume()
launch { onTrigger(event.data) }
.invokeOnCompletion { event.onConsume() }
}
}
}

View file

@ -32,6 +32,16 @@ fun CryptoCurrency.Token.tryGetBackgroundForTokenIcon(
): Color {
if (isGrayscale) return TangemColorPalette.Dark2
return tryGetBackgroundForTokenIcon(contractAddress = contractAddress, fallbackColor = fallbackColor)
}
/**
* Tries to extract a background color from the contract address of a token.
*
* @param fallbackColor The color to use as a fallback.
* @return The extracted background color or the fallback color if extraction fails or if it is a test network token.
*/
fun tryGetBackgroundForTokenIcon(contractAddress: String, fallbackColor: Color = TangemColorPalette.Black): Color {
return try {
val colorHex = "#" + contractAddress.substring(range = COLOR_HEX_START_INDEX..COLOR_HEX_END_INDEX)
Color(colorHex.toColorInt())

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.utils
import com.tangem.domain.tokens.model.CryptoCurrency
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.NumberFormat
@ -24,6 +25,10 @@ object BigDecimalFormatter {
return formatter.format(cryptoAmount) + "\u2009$cryptoCurrency"
}
fun formatCryptoAmount(cryptoAmount: BigDecimal?, cryptoCurrency: CryptoCurrency): String {
return formatCryptoAmount(cryptoAmount, cryptoCurrency.symbol, cryptoCurrency.decimals)
}
fun formatFiatAmount(
fiatAmount: BigDecimal?,
fiatCurrencyCode: String,

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M28.384,0.616C27.896,0.128 27.104,0.128 26.616,0.616C26.128,1.104 26.128,1.896 26.616,2.384L31.002,6.769C17.519,7.294 6.75,18.389 6.75,32C6.75,45.945 18.055,57.25 32,57.25C45.945,57.25 57.25,45.945 57.25,32C57.25,31.31 56.69,30.75 56,30.75C55.31,30.75 54.75,31.31 54.75,32C54.75,44.564 44.564,54.75 32,54.75C19.435,54.75 9.25,44.564 9.25,32C9.25,19.785 18.878,9.818 30.959,9.273L26.616,13.616C26.128,14.104 26.128,14.896 26.616,15.384C27.104,15.872 27.896,15.872 28.384,15.384L34.884,8.884C35.372,8.396 35.372,7.604 34.884,7.116L28.384,0.616ZM39,22.75H40.25V24V26V26.518L39.884,26.884L33.768,33L39.884,39.116L40.25,39.482V40V42V43.25H39H25H23.75V42V40V39.482L24.116,39.116L30.232,33L24.116,26.884L23.75,26.518V26V24V22.75H25H39ZM26.25,25.482L32,31.232L37.75,25.482V25.25H26.25V25.482ZM37.75,40.518L32,34.768L26.25,40.518V40.75H37.75V40.518Z"
android:fillColor="#C9C9C9"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M12,22C10.633,22 9.342,21.737 8.125,21.212C6.908,20.688 5.846,19.971 4.938,19.063C4.029,18.154 3.313,17.092 2.787,15.875C2.263,14.658 2,13.367 2,12C2,10.617 2.263,9.321 2.787,8.113C3.313,6.904 4.029,5.846 4.938,4.938C5.846,4.029 6.908,3.313 8.125,2.787C9.342,2.263 10.633,2 12,2C13.383,2 14.679,2.263 15.887,2.787C17.096,3.313 18.154,4.029 19.063,4.938C19.971,5.846 20.688,6.904 21.212,8.113C21.737,9.321 22,10.617 22,12C22,13.367 21.737,14.658 21.212,15.875C20.688,17.092 19.971,18.154 19.063,19.063C18.154,19.971 17.096,20.688 15.887,21.212C14.679,21.737 13.383,22 12,22ZM12,19.95C12.433,19.35 12.808,18.725 13.125,18.075C13.442,17.425 13.7,16.733 13.9,16H10.1C10.3,16.733 10.558,17.425 10.875,18.075C11.192,18.725 11.567,19.35 12,19.95ZM9.4,19.55C9.1,19 8.837,18.429 8.613,17.837C8.387,17.246 8.2,16.633 8.05,16H5.1C5.583,16.833 6.188,17.558 6.912,18.175C7.637,18.792 8.467,19.25 9.4,19.55ZM14.6,19.55C15.533,19.25 16.362,18.792 17.087,18.175C17.813,17.558 18.417,16.833 18.9,16H15.95C15.8,16.633 15.613,17.246 15.387,17.837C15.163,18.429 14.9,19 14.6,19.55ZM4.25,14H7.65C7.6,13.667 7.563,13.337 7.537,13.012C7.512,12.688 7.5,12.35 7.5,12C7.5,11.65 7.512,11.313 7.537,10.988C7.563,10.663 7.6,10.333 7.65,10H4.25C4.167,10.333 4.104,10.663 4.063,10.988C4.021,11.313 4,11.65 4,12C4,12.35 4.021,12.688 4.063,13.012C4.104,13.337 4.167,13.667 4.25,14ZM9.65,14H14.35C14.4,13.667 14.438,13.337 14.462,13.012C14.488,12.688 14.5,12.35 14.5,12C14.5,11.65 14.488,11.313 14.462,10.988C14.438,10.663 14.4,10.333 14.35,10H9.65C9.6,10.333 9.563,10.663 9.538,10.988C9.512,11.313 9.5,11.65 9.5,12C9.5,12.35 9.512,12.688 9.538,13.012C9.563,13.337 9.6,13.667 9.65,14ZM16.35,14H19.75C19.833,13.667 19.896,13.337 19.938,13.012C19.979,12.688 20,12.35 20,12C20,11.65 19.979,11.313 19.938,10.988C19.896,10.663 19.833,10.333 19.75,10H16.35C16.4,10.333 16.438,10.663 16.462,10.988C16.487,11.313 16.5,11.65 16.5,12C16.5,12.35 16.487,12.688 16.462,13.012C16.438,13.337 16.4,13.667 16.35,14ZM15.95,8H18.9C18.417,7.167 17.813,6.442 17.087,5.825C16.362,5.208 15.533,4.75 14.6,4.45C14.9,5 15.163,5.571 15.387,6.162C15.613,6.754 15.8,7.367 15.95,8ZM10.1,8H13.9C13.7,7.267 13.442,6.575 13.125,5.925C12.808,5.275 12.433,4.65 12,4.05C11.567,4.65 11.192,5.275 10.875,5.925C10.558,6.575 10.3,7.267 10.1,8ZM5.1,8H8.05C8.2,7.367 8.387,6.754 8.613,6.162C8.837,5.571 9.1,5 9.4,4.45C8.467,4.75 7.637,5.208 6.912,5.825C6.188,6.442 5.583,7.167 5.1,8Z"
android:fillColor="#1E1E1E"/>
</group>
</vector>