Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-01 13:58:38 +03:00
commit 9ba66ba084
179 changed files with 5757 additions and 1156 deletions

View file

@ -32,6 +32,12 @@ sealed class TopAppBarButtonUM(
enabled = enabled,
)
fun Close(enabled: Boolean = true, onCloseClick: () -> Unit) = Icon(
iconRes = R.drawable.ic_close_24,
onClicked = onCloseClick,
enabled = enabled,
)
fun Text(text: TextReference, onTextClicked: () -> Unit, enabled: Boolean = true) = Text(
text = text,
onClicked = onTextClicked,

View file

@ -1,6 +1,7 @@
package com.tangem.core.ui.components.bottomsheets.modal
import android.content.res.Configuration
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
@ -49,7 +50,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemModalBottomSheetWi
noinline onBack: (() -> Unit)? = null,
crossinline title: @Composable BoxScope.(T) -> Unit = {},
crossinline content: @Composable (T) -> Unit,
crossinline footer: @Composable (BoxScope.(T) -> Unit),
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
) {
val isAlwaysVisible = LocalBottomSheetAlwaysVisible.current
@ -84,7 +85,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultModalBottomSheetW
noinline onBack: (() -> Unit)? = null,
crossinline title: @Composable BoxScope.(T) -> Unit,
crossinline content: @Composable (T) -> Unit,
crossinline footer: @Composable (BoxScope.(T) -> Unit),
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
) {
var isVisible by remember { mutableStateOf(value = config.isShown) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
@ -118,7 +119,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewModalBottomSheetW
skipPartiallyExpanded: Boolean = true,
crossinline title: @Composable BoxScope.(T) -> Unit,
crossinline content: @Composable (T) -> Unit,
crossinline footer: @Composable BoxScope.(T) -> Unit,
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
) {
BasicModalBottomSheetWithFooter<T>(
config = config,
@ -145,7 +146,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
noinline onBack: (() -> Unit)? = null,
crossinline title: @Composable BoxScope.(T) -> Unit,
crossinline content: @Composable (T) -> Unit,
crossinline footer: @Composable (BoxScope.(T) -> Unit),
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
modifier: Modifier = Modifier,
) {
val model = config.content as? T ?: return
@ -156,8 +157,13 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
val scrollState = rememberScrollState(initial = initial)
val isKeyboardOpen by rememberIsKeyboardVisible()
val buttonHeight = TangemTheme.dimens.spacing80
val contentBottomPadding = TangemTheme.dimens.spacing80
val buttonHeight by animateDpAsState(
if (footer != null) {
80.dp
} else {
0.dp
},
)
// Offset calculation for keyboard scroll adjustment:
// 1) Button height (footer)
// 2) Column content bottom padding
@ -202,7 +208,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
Column(
modifier = Modifier
.verticalScroll(state = scrollState)
.padding(bottom = contentBottomPadding),
.padding(bottom = buttonHeight),
) {
content(model)
}
@ -218,7 +224,9 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
.height(buttonHeight)
.align(Alignment.BottomCenter),
) {
footer(model)
if (footer != null) {
footer(model)
}
}
}
}

View file

@ -25,7 +25,6 @@ import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.utils.StringsSigns.PASSWORD_VISUAL_CHAR
@ -159,14 +158,17 @@ private fun CellDecoration(
)
}
} else {
Text(
Box(
modifier = Modifier.sizeIn(minWidth = minWidth, minHeight = minHeight),
text = text,
style = TangemTheme.typography.h3,
color = color,
textAlign = TextAlign.Center,
lineHeight = 48.sp,
)
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = text,
style = TangemTheme.typography.h3,
color = color,
textAlign = TextAlign.Center,
)
}
}
}
}

View file

@ -0,0 +1,104 @@
package com.tangem.core.ui.components.grid
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import com.tangem.core.ui.components.grid.entity.EnumeratedTwoColumnGridItem
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
/**
* EnumeratedTwoColumnGrid component
*
* @param items component items
* @param modifier composable modifier
*
*/
@Composable
fun EnumeratedTwoColumnGrid(items: ImmutableList<EnumeratedTwoColumnGridItem>, modifier: Modifier = Modifier) {
VerticalGrid(
modifier = modifier,
items = items,
) { item ->
Row(
modifier = Modifier.padding(all = TangemTheme.dimens.size8),
verticalAlignment = Alignment.CenterVertically,
) {
if (LocalLayoutDirection.current == LayoutDirection.Ltr) {
Text(
modifier = Modifier.width(TangemTheme.dimens.size40),
text = "${item.index}.",
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
Text(
text = item.mnemonic,
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary1,
)
} else {
Text(
text = item.mnemonic,
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary1,
)
Text(
modifier = Modifier.width(TangemTheme.dimens.size40),
text = "${item.index}.",
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
}
}
}
}
@Composable
private inline fun <T> VerticalGrid(
items: ImmutableList<T>,
modifier: Modifier = Modifier,
crossinline content: @Composable (T) -> Unit,
) {
val columnLength = items.size / 2
Row(
modifier = modifier,
horizontalArrangement = Arrangement.SpaceEvenly,
) {
repeat(2) { index ->
Column {
for (i in 0 until columnLength) {
val item = items[index * columnLength + i]
content(item)
}
}
}
}
}
@Preview(widthDp = 360, heightDp = 640, showBackground = true)
@Preview(widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview() {
TangemThemePreview {
EnumeratedTwoColumnGrid(
items = List(24) {
EnumeratedTwoColumnGridItem(
index = it + 1,
mnemonic = "word${it + 1}",
)
}.toImmutableList(),
)
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.components.grid.entity
data class EnumeratedTwoColumnGridItem(
val index: Int,
val mnemonic: String,
)

View file

@ -27,7 +27,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
@ -63,8 +62,8 @@ fun Notification(
NotificationConfig.IconTint.Unspecified -> null
NotificationConfig.IconTint.Accent -> TangemTheme.colors.icon.accent
NotificationConfig.IconTint.Attention -> TangemTheme.colors.icon.attention
NotificationConfig.IconTint.Warning -> TangemTheme.colors.icon.warning
},
iconSize: Dp = 20.dp,
isEnabled: Boolean = true,
) {
NotificationBaseContainer(
@ -78,7 +77,7 @@ fun Notification(
MainContent(
iconResId = config.iconResId,
iconTint = iconTint,
iconSize = iconSize,
iconSize = config.iconSize,
title = config.title,
titleColor = titleColor,
subtitle = config.subtitle,

View file

@ -1,6 +1,8 @@
package com.tangem.core.ui.components.notifications
import androidx.annotation.DrawableRes
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.extensions.TextReference
/**
@ -26,6 +28,7 @@ data class NotificationConfig(
val onCloseClick: (() -> Unit)? = null,
val showArrowIcon: Boolean = onClick != null,
val iconTint: IconTint = IconTint.Unspecified,
val iconSize: Dp = 20.dp,
) {
sealed class ButtonsState {
@ -62,5 +65,6 @@ data class NotificationConfig(
Unspecified,
Accent,
Attention,
Warning,
}
}

View file

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<group>
<clip-path
android:pathData="M0,0h36v36h-36z"/>
<path
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:fillColor="#EBEBEB"/>
<path
android:pathData="M21.682,9.25H14.318C13.156,9.25 12.576,9.25 12.132,9.476C11.742,9.675 11.424,9.992 11.226,10.382C11,10.826 11,11.406 11,12.568V13.8H25V12.568C25,11.406 25,10.826 24.774,10.382C24.575,9.992 24.258,9.674 23.868,9.476C23.424,9.25 22.844,9.25 21.682,9.25Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M20.333,17.65H25V23.432C25,24.594 25,25.174 24.773,25.618C24.574,26.008 24.258,26.326 23.868,26.524C23.423,26.75 22.843,26.75 21.681,26.75H20.334L20.333,17.65Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M11,17.65H15.666V26.75H14.318C13.157,26.75 12.576,26.75 12.132,26.524C11.742,26.326 11.425,26.008 11.226,25.618C11,25.174 11,24.594 11,23.432V17.65Z"
android:fillColor="#1E1E1E"/>
</group>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.999,1.846C13.546,1.846 14.796,2.398 15.854,2.976C16.977,3.588 17.75,4.132 18.658,4.438C19.267,4.642 20.029,4.812 20.464,5.322C20.812,5.73 20.891,6.27 20.979,6.78C22,12.753 19.662,19.35 13.72,21.75C13.155,21.978 12.718,22.154 12.002,22.154C11.286,22.154 10.849,21.978 10.284,21.75C4.342,19.35 2.002,12.748 3.022,6.78C3.11,6.27 3.189,5.73 3.537,5.322C3.972,4.812 4.734,4.642 5.343,4.438C6.25,4.132 7.023,3.588 8.145,2.976C9.202,2.398 10.452,1.846 11.999,1.846ZM16.903,8.645C16.577,8.346 16.072,8.368 15.773,8.694L10.963,13.949L9.146,11.966C8.848,11.64 8.343,11.618 8.017,11.916C7.691,12.214 7.669,12.72 7.967,13.046L10.373,15.675C10.524,15.84 10.739,15.934 10.963,15.935C11.187,15.935 11.402,15.84 11.554,15.675L16.953,9.774C17.251,9.449 17.229,8.943 16.903,8.645Z"
android:fillColor="#656565"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M13.513,2.602C13.689,2.603 13.859,2.604 14.024,2.607C14.049,2.608 14.074,2.609 14.098,2.609C14.688,2.624 15.209,2.654 15.665,2.716C16.585,2.84 17.344,3.102 17.943,3.701C18.543,4.301 18.805,5.059 18.929,5.979C19.049,6.87 19.049,8.006 19.049,9.43V14.074C19.005,14.067 18.964,14.06 18.926,14.055C18.764,14.033 18.598,14.022 18.455,14.018L18.121,14.015H12.122C11.101,12.924 9.649,12.225 8.019,12.225C7.018,12.225 6.087,12.484 5.281,12.936V9.43C5.281,8.006 5.282,6.87 5.401,5.979C5.525,5.059 5.787,4.301 6.387,3.701C6.986,3.102 7.745,2.84 8.665,2.716C9.121,2.655 9.641,2.624 10.231,2.609C10.255,2.609 10.28,2.608 10.306,2.607C10.47,2.604 10.641,2.603 10.816,2.602C11.22,2.597 11.652,2.596 12.115,2.596H12.215C12.677,2.596 13.109,2.597 13.513,2.602ZM11.276,5.039C10.786,5.039 10.389,5.436 10.389,5.927C10.389,6.417 10.786,6.815 11.276,6.815H13.053C13.543,6.815 13.941,6.417 13.941,5.927C13.941,5.436 13.543,5.039 13.053,5.039H11.276Z"
android:fillColor="#656565"/>
<path
android:pathData="M8.019,14.225C9.252,14.225 10.337,14.846 10.997,15.79C11.077,15.904 11.117,15.961 11.168,15.987C11.219,16.014 11.283,16.015 11.409,16.015H18.121C18.302,16.015 18.493,16.014 18.653,16.036C18.84,16.062 19.069,16.127 19.26,16.321C19.372,16.436 19.437,16.573 19.475,16.67C19.515,16.776 19.545,16.889 19.566,16.998C19.61,17.216 19.631,17.465 19.633,17.705C19.635,17.944 19.618,18.197 19.575,18.422C19.554,18.534 19.524,18.652 19.48,18.763C19.44,18.867 19.373,19.002 19.26,19.118C19.069,19.313 18.84,19.379 18.653,19.404C18.57,19.416 18.313,19.42 18.087,19.423C17.901,19.425 17.807,19.426 17.743,19.479C17.679,19.533 17.661,19.624 17.625,19.805C17.542,20.223 17.415,20.85 17.387,20.938C17.337,21.092 17.251,21.275 17.074,21.424C16.895,21.574 16.698,21.625 16.535,21.646L14.904,21.641C14.718,21.615 14.488,21.551 14.297,21.355C14.108,21.162 14.047,20.933 14.023,20.748C14.001,20.587 14.001,20.395 14.001,20.209V19.896C14.001,19.674 14.001,19.563 13.933,19.494C13.864,19.426 13.752,19.426 13.531,19.426H11.68C11.534,19.426 11.46,19.425 11.404,19.459C11.349,19.493 11.311,19.564 11.236,19.705C10.622,20.866 9.416,21.663 8.019,21.663C5.989,21.663 4.365,19.986 4.365,17.943C4.365,15.901 5.989,14.225 8.019,14.225ZM8.079,16.77C7.433,16.77 6.91,17.296 6.91,17.944C6.91,18.593 7.434,19.118 8.079,19.118H8.09C8.735,19.118 9.259,18.593 9.259,17.944C9.259,17.296 8.736,16.77 8.09,16.77H8.079Z"
android:fillColor="#656565"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,3C14.623,3 16.75,5.127 16.75,7.75V9.75H18C18.552,9.75 19,10.198 19,10.75V19.75C19,20.302 18.552,20.75 18,20.75H6C5.448,20.75 5,20.302 5,19.75V10.75C5,10.198 5.448,9.75 6,9.75H7.25V7.75C7.25,5.127 9.377,3 12,3ZM12,4.5C10.205,4.5 8.75,5.955 8.75,7.75V9.75H15.25V7.75C15.25,5.955 13.795,4.5 12,4.5Z"
android:fillColor="#656565"/>
</vector>

View file

@ -6,11 +6,11 @@
android:viewportHeight="24">
<path
android:pathData="M15.682,3.25H8.318C7.157,3.25 6.576,3.25 6.132,3.476C5.742,3.675 5.424,3.992 5.226,4.382C5,4.826 5,5.407 5,6.568V7.8H19V6.568C19,5.407 19,4.826 18.774,4.382C18.575,3.992 18.258,3.674 17.868,3.476C17.424,3.25 16.844,3.25 15.682,3.25Z"
android:fillColor="#1E1E1E"/>
android:fillColor="@color/icon_primary1"/>
<path
android:pathData="M14.333,11.65H19V17.432C19,18.594 19,19.174 18.773,19.618C18.574,20.008 18.258,20.326 17.868,20.524C17.423,20.75 16.843,20.75 15.681,20.75H14.334L14.333,11.65Z"
android:fillColor="#1E1E1E"/>
android:fillColor="@color/icon_primary1"/>
<path
android:pathData="M5,11.65H9.666V20.75H8.318C7.157,20.75 6.576,20.75 6.132,20.524C5.742,20.326 5.425,20.008 5.226,19.618C5,19.174 5,18.594 5,17.432V11.65Z"
android:fillColor="#1E1E1E"/>
android:fillColor="@color/icon_primary1"/>
</vector>

View file

@ -0,0 +1,18 @@
<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="M17.778,0L46.222,0A17.778,17.778 0,0 1,64 17.778L64,46.222A17.778,17.778 0,0 1,46.222 64L17.778,64A17.778,17.778 0,0 1,0 46.222L0,17.778A17.778,17.778 0,0 1,17.778 0z"
android:fillColor="#EBEBEB"/>
<path
android:pathData="M28.525,31.48V45.021H26.519C24.791,45.021 23.927,45.021 23.267,44.684C22.686,44.39 22.215,43.917 21.919,43.337C21.582,42.676 21.582,41.812 21.582,40.083V31.48H28.525Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M42.414,40.083C42.414,41.812 42.414,42.676 42.077,43.337C41.781,43.917 41.31,44.39 40.73,44.684C40.069,45.021 39.205,45.021 37.476,45.021H35.471V32.441L35.47,31.48H42.414V40.083Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M37.477,18.981C39.205,18.981 40.069,18.981 40.73,19.318C41.31,19.612 41.781,20.085 42.077,20.666C42.414,21.326 42.414,22.19 42.414,23.918V25.751H21.582V23.918C21.582,22.19 21.582,21.326 21.919,20.666C22.213,20.085 22.686,19.614 23.267,19.318C23.927,18.981 24.791,18.981 26.519,18.981H37.477Z"
android:fillColor="#1E1E1E"/>
</vector>

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="82dp"
android:height="82dp"
android:viewportWidth="450"
android:viewportHeight="450">
<path
android:fillColor="@color/icon_primary1"
android:pathData="M284.96,82H165.04C146.12,82 136.67,82 129.44,85.68C123.08,88.93 117.91,94.08 114.68,100.44C111,107.67 111,117.12 111,136.04V156.1H339V136.04C339,117.12 339,107.67 335.32,100.44C332.07,94.08 326.92,88.91 320.56,85.68C313.33,82 303.88,82 284.96,82Z" />
<path
android:fillColor="@color/icon_primary1"
android:pathData="M262.99,218.8H338.99V312.96C338.99,331.88 338.99,341.33 335.31,348.56C332.07,354.92 326.91,360.09 320.56,363.32C313.32,367 303.87,367 284.95,367H263.01L262.99,218.8Z" />
<path
android:fillColor="@color/icon_primary1"
android:pathData="M111,218.8H186.99V367H165.04C146.12,367 136.67,367 129.44,363.32C123.08,360.09 117.93,354.92 114.68,348.56C111,341.33 111,331.88 111,312.96V218.8Z" />
</vector>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="icon_primary1">#FFFFFFFF</color>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="icon_primary1">#FF1E1E1E</color>
</resources>