From 6f095384c12d62c541691c68a7bd747597a60ecc Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Dec 2025 15:12:48 +0500 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .../tangem/core/ui/extensions/ModifierExt.kt | 15 + .../tangem/core/ui/res/TangemColorPalette.kt | 24 ++ .../com/tangem/core/ui/res/TangemColors2.kt | 4 + .../com/tangem/core/ui/res/TangemDimens2.kt | 39 ++ .../com/tangem/core/ui/res/TangemTheme.kt | 18 + .../tangem/core/ui/res/TangemThemePreview.kt | 26 ++ .../tangem/core/ui/res/TangemThemeRedesign.kt | 9 +- .../tangem/core/ui/res/TangemTypography.kt | 6 - .../tangem/core/ui/res/TangemTypography2.kt | 348 ++++++++++++++++++ tangem-android-tools | 2 +- 10 files changed, 481 insertions(+), 10 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens2.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography2.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt index cab83ed0d6..7b85a6004d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt @@ -48,6 +48,21 @@ fun Modifier.conditional(condition: Boolean, modifier: Modifier.() -> Modifier): } } +/** + * Conditionally applies a modifier based on a boolean condition. + */ +@Composable +fun Modifier.conditionalCompose( + condition: Boolean, + modifier: @Composable Modifier.() -> Modifier = { Modifier }, +): Modifier { + return if (condition) { + then(modifier(Modifier)) + } else { + this + } +} + /** * Conditionally applies a modifier based on a boolean condition. */ diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColorPalette.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColorPalette.kt index b3bfdd9a48..04801d50d7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColorPalette.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColorPalette.kt @@ -18,6 +18,18 @@ object TangemColorPalette { val Dark6 = Color(0xFF1E1E1E) // endregion Dark + // region Dark Alpha + val Dark_10 = Color(0x1A1E1E1E) + val Dark_20 = Color(0x331E1E1E) + val Dark_30 = Color(0x4D1E1E1E) + val Dark_40 = Color(0x661E1E1E) + val Dark_50 = Color(0x801E1E1E) + val Dark_60 = Color(0x991E1E1E) + val Dark_70 = Color(0xB31E1E1E) + val Dark_80 = Color(0xCC1E1E1E) + val Dark_90 = Color(0xE61E1E1E) + // endregion Dark Alpha + // region Light val Light1 = Color(0xFFF5F5F5) val Light1V2 = Color(0xFFF4F4F4) @@ -27,6 +39,18 @@ object TangemColorPalette { val Light5 = Color(0xFFB0B0B0) // endregion Light + // region Light Alpha + val Light_10 = Color(0x1AFFFFFF) + val Light_20 = Color(0x33FFFFFF) + val Light_30 = Color(0x4DFFFFFF) + val Light_40 = Color(0x66FFFFFF) + val Light_50 = Color(0x80FFFFFF) + val Light_60 = Color(0x99FFFFFF) + val Light_70 = Color(0xB3FFFFFF) + val Light_80 = Color(0xCCFFFFFF) + val Light_90 = Color(0xE6FFFFFF) + // endregion Light Alpha + // region Green val Green = Color(0xFF0C9F3D) val Meadow = Color(0xFF1ACE80) diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors2.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors2.kt index 68ce8e0a85..7ba4a56c58 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors2.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors2.kt @@ -162,6 +162,7 @@ class TangemColors2 internal constructor( backgroundSecondary: Color, backgroundDisabled: Color, backgroundPositive: Color, + backgroundPrimaryInverse: Color, textPrimary: Color, textSecondary: Color, textDisabled: Color, @@ -178,6 +179,8 @@ class TangemColors2 internal constructor( private set var backgroundPositive by mutableStateOf(backgroundPositive) private set + var backgroundPrimaryInverse by mutableStateOf(backgroundPrimaryInverse) + private set var textPrimary by mutableStateOf(textPrimary) private set var textSecondary by mutableStateOf(textSecondary) @@ -198,6 +201,7 @@ class TangemColors2 internal constructor( backgroundSecondary = other.backgroundSecondary backgroundDisabled = other.backgroundDisabled backgroundPositive = other.backgroundPositive + backgroundPrimaryInverse = other.backgroundPrimaryInverse textPrimary = other.textPrimary textSecondary = other.textSecondary textDisabled = other.textDisabled diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens2.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens2.kt new file mode 100644 index 0000000000..292a6555e3 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens2.kt @@ -0,0 +1,39 @@ +package com.tangem.core.ui.res + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Suppress("ConstructorParameterNaming") +@ConsistentCopyVisibility +@Immutable +data class TangemDimens2 internal constructor( + val x0: Dp = 0.dp, + val x0_5: Dp = 2.dp, + val x1: Dp = 4.dp, + val x2: Dp = 8.dp, + val x2_5: Dp = 10.dp, + val x3: Dp = 12.dp, + val x4: Dp = 16.dp, + val x5: Dp = 20.dp, + val x6: Dp = 24.dp, + val x7: Dp = 28.dp, + val x8: Dp = 32.dp, + val x9: Dp = 36.dp, + val x10: Dp = 40.dp, + val x11: Dp = 44.dp, + val x12: Dp = 48.dp, + val x13: Dp = 52.dp, + val x14: Dp = 56.dp, + val x15: Dp = 60.dp, + val x16: Dp = 64.dp, + val x17: Dp = 68.dp, + val x18: Dp = 72.dp, + val x19: Dp = 76.dp, + val x20: Dp = 80.dp, + val x21: Dp = 84.dp, + val x22: Dp = 88.dp, + val x23: Dp = 92.dp, + val x24: Dp = 96.dp, + val x25: Dp = 100.dp, +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index 38b1726319..834910220f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -154,11 +154,21 @@ object TangemTheme { @ReadOnlyComposable get() = LocalTangemTypography.current + val typography2: TangemTypography2 + @Composable + @ReadOnlyComposable + get() = TangemTypography2(InterFamily) + val dimens: TangemDimens @Composable @ReadOnlyComposable get() = LocalTangemDimens.current + val dimens2: TangemDimens2 + @Composable + @ReadOnlyComposable + get() = LocalTangemDimens2.current + val shapes: TangemShapes @Composable @ReadOnlyComposable @@ -343,10 +353,18 @@ internal val LocalTangemTypography = staticCompositionLocalOf { TangemTypography(RobotoFamily) } +internal val LocalTangemTypography2 = staticCompositionLocalOf { + TangemTypography2(InterFamily) +} + private val LocalTangemDimens = staticCompositionLocalOf { TangemDimens() } +private val LocalTangemDimens2 = staticCompositionLocalOf { + TangemDimens2() +} + private val LocalTangemShapes = staticCompositionLocalOf { error("No TangemShapes provided") } diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt index dd15fd2212..6b72e745b6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt @@ -37,6 +37,32 @@ fun TangemThemePreview( } } +@Composable +fun TangemThemePreviewRedesign( + isDark: Boolean? = null, + alwaysShowBottomSheets: Boolean = true, + rtl: Boolean = false, + content: @Composable () -> Unit, +) { + val isDarkTheme = isDark ?: isSystemInDarkTheme() + + CompositionLocalProvider( + LocalBottomSheetAlwaysVisible provides alwaysShowBottomSheets, + LocalLayoutDirection provides if (rtl) LayoutDirection.Rtl else LayoutDirection.Ltr, + ) { + BoxWithConstraints { + TangemTheme( + isDark = isDarkTheme, + windowSize = rememberWindowSizePreview(maxWidth, maxHeight), + ) { + TangemThemeRedesign( + content = content, + ) + } + } + } +} + /** * This is used to make the bottom sheet always visible in the Preview and should be `true` only in the Preview. * */ diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt index 476a412a20..ce425e93f7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt @@ -1,4 +1,5 @@ @file:Suppress("LongMethod") + package com.tangem.core.ui.res import androidx.compose.material3.MaterialTheme @@ -22,7 +23,7 @@ fun TangemThemeRedesign(content: @Composable () -> Unit) { CompositionLocalProvider( LocalTangemColors provides themeColors, LocalTangemColors2 provides if (LocalIsInDarkTheme.current) darkThemeColors2() else lightThemeColors2(), - LocalTangemTypography provides TangemTypography(InterFamily), + LocalTangemTypography2 provides TangemTypography2(InterFamily), LocalRootBackgroundColor provides remember(rootBackgroundColor) { mutableStateOf(rootBackgroundColor) }, ) { content() @@ -97,9 +98,10 @@ private fun lightThemeColors2(): TangemColors2 { ) val button = TangemColors2.Button( backgroundPrimary = TangemColorPalette.Dark6, - backgroundSecondary = TangemColorPalette.Dark6.copy(alpha = 0.1f), + backgroundSecondary = TangemColorPalette.Dark_10, backgroundDisabled = TangemColorPalette.Light3, backgroundPositive = TangemColorPalette.Azure, + backgroundPrimaryInverse = TangemColorPalette.White, textSecondary = TangemColorPalette.Dark6, textPrimary = TangemColorPalette.Light2, textDisabled = text.neutral.tertiary, @@ -236,9 +238,10 @@ private fun darkThemeColors2(): TangemColors2 { ) val button = TangemColors2.Button( backgroundPrimary = TangemColorPalette.Light1V2, - backgroundSecondary = TangemColorPalette.White.copy(alpha = 0.1f), + backgroundSecondary = TangemColorPalette.Light_10, backgroundDisabled = TangemColorPalette.Dark5, backgroundPositive = TangemColorPalette.Azure, + backgroundPrimaryInverse = TangemColorPalette.Light_10, textSecondary = TangemColorPalette.Light4, textPrimary = TangemColorPalette.Dark4, textDisabled = text.neutral.secondary, diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt index d50a43f6ed..a86e82124c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt @@ -4,7 +4,6 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.unit.TextUnit @@ -17,11 +16,6 @@ internal val RobotoFamily = FontFamily( Font(R.font.roboto_medium, FontWeight.Medium), ) -internal val InterFamily = FontFamily( - Font(R.font.inter_regular), - Font(R.font.inter_italic, style = FontStyle.Italic), -) - @Immutable class TangemTypography internal constructor( fontFamily: FontFamily, diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography2.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography2.kt new file mode 100644 index 0000000000..4973fefe6f --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography2.kt @@ -0,0 +1,348 @@ +package com.tangem.core.ui.res + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.LineHeightStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.tangem.core.ui.R + +internal val InterFamily = FontFamily( + Font(R.font.inter_regular), + Font(R.font.inter_italic, style = FontStyle.Italic), +) + +@Stable +class TangemTypography2 internal constructor( + fontFamily: FontFamily, +) { + val titleRegular44: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 44.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = TextUnit(value = 0.37f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 48f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingRegular34: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 34.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(value = 0.37f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 40f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingBold34: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 34.sp, + fontWeight = FontWeight.Bold, + letterSpacing = TextUnit(value = 0.37f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 40f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingRegular28: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 28.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = TextUnit(value = 0.36f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 36f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingBold28: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 28.sp, + fontWeight = FontWeight.Bold, + letterSpacing = TextUnit(value = 0.36f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 36f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingRegular22: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 22.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(value = 0.35f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 28f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingBold22: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + letterSpacing = TextUnit(value = 0.35f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 28f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingRegular20: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 20.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(value = 0.38f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 24f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingSemibold20: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 20.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = TextUnit(value = 0.38f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 24f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingRegular17: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 17.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(value = -0.41f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val headingSemibold17: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 17.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = TextUnit(value = -0.2f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val bodyRegular16: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(value = -0.32f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val bodySemibold16: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = TextUnit(value = -0.32f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val bodyRegular15: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(value = -0.24f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val bodySemibold15: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 15.sp, + fontWeight = FontWeight.Medium, + letterSpacing = TextUnit(value = -0.1f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val bodyRegular14: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + letterSpacing = TextUnit(value = -0.1f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val captionRegular13: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 13.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(value = -0.08f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val captionSemibold13: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val captionRegular12: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 12.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(value = 0f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val captionSemibold12: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val captionRegular11: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 11.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(value = 0.07f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 12f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + + val captionSemibold11: TextStyle = TextStyle( + fontFamily = fontFamily, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = TextUnit(value = 0.15f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 12f, type = TextUnitType.Sp), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360, heightDp = 1500) +@Preview(showBackground = true, widthDp = 360, heightDp = 1500, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun TangemTypography2_Preview() { + TangemThemePreviewRedesign { + val typographyList = sequenceOf( + TangemTheme.typography2.titleRegular44, + TangemTheme.typography2.headingRegular34, + TangemTheme.typography2.headingBold34, + TangemTheme.typography2.headingRegular28, + TangemTheme.typography2.headingBold28, + TangemTheme.typography2.headingRegular22, + TangemTheme.typography2.headingBold22, + TangemTheme.typography2.headingRegular20, + TangemTheme.typography2.headingSemibold20, + TangemTheme.typography2.headingRegular17, + TangemTheme.typography2.headingSemibold17, + TangemTheme.typography2.bodyRegular16, + TangemTheme.typography2.bodySemibold16, + TangemTheme.typography2.bodyRegular15, + TangemTheme.typography2.bodySemibold15, + TangemTheme.typography2.bodyRegular14, + TangemTheme.typography2.captionRegular13, + TangemTheme.typography2.captionSemibold13, + TangemTheme.typography2.captionRegular12, + TangemTheme.typography2.captionSemibold12, + TangemTheme.typography2.captionRegular11, + TangemTheme.typography2.captionSemibold11, + ) + + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .background(TangemTheme.colors2.surface.level1) + .padding(4.dp), + ) { + typographyList.forEach { textStyle -> + Box(modifier = Modifier.heightIn(min = 60.dp)) { + Text( + text = "Lorem ipsum", + style = textStyle, + color = TangemTheme.colors2.text.neutral.primary, + modifier = Modifier.align(Alignment.Center), + ) + } + } + } + } +} +// endregion \ No newline at end of file diff --git a/tangem-android-tools b/tangem-android-tools index d7950d60bb..fa10299aec 160000 --- a/tangem-android-tools +++ b/tangem-android-tools @@ -1 +1 @@ -Subproject commit d7950d60bb4c6353f2aa3364073c6ec72167c666 +Subproject commit fa10299aec0b3a06fda9bf6d050e0aa79609d33b From 4b07ed004f0dadddc6b1fc8d22afaa4f10c4128f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Dec 2025 15:19:26 +0500 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../core/ui/ds/button/AccentTangemButton.kt | 125 +++++++++ .../core/ui/ds/button/GhostTangemButton.kt | 111 ++++++++ .../core/ui/ds/button/OutlineTangemButton.kt | 132 +++++++++ .../ds/button/PrimaryInverseTangemButton.kt | 127 +++++++++ .../core/ui/ds/button/PrimaryTangemButton.kt | 122 +++++++++ .../ui/ds/button/SecondaryTangemButton.kt | 125 +++++++++ .../core/ui/ds/button/TangemButtonInternal.kt | 256 ++++++++++++++++++ .../tangem/core/ui/extensions/ModifierExt.kt | 17 +- 8 files changed, 999 insertions(+), 16 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/button/AccentTangemButton.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/button/GhostTangemButton.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/button/OutlineTangemButton.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/button/PrimaryInverseTangemButton.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/button/PrimaryTangemButton.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/button/SecondaryTangemButton.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonInternal.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/AccentTangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/AccentTangemButton.kt new file mode 100644 index 0000000000..da0e951bfa --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/AccentTangemButton.kt @@ -0,0 +1,125 @@ +package com.tangem.core.ui.ds.button + +import android.content.res.Configuration +import androidx.annotation.DrawableRes +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.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +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 com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign + +/** + * [Accent Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8004-26798) + * + * @param onClick Lambda to be invoked when the button is clicked. + * @param modifier Modifier to be applied to the button. + * @param text TextReference for the button label. + * @param iconRes Drawable resource ID for the icon to be displayed in the button. + * @param iconPosition Position of the icon (Start or End). + * @param enabled Boolean indicating whether the button is enabled. + * @param size TangemButtonSize defining the size of the button. + * @param state TangemButtonState defining the current state of the button. + * @param shape TangemButtonShape defining the shape of the button. + * +[REDACTED_AUTHOR] + */ +@Composable +fun AccentTangemButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: TextReference? = null, + @DrawableRes iconRes: Int? = null, + iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start, + enabled: Boolean = true, + size: TangemButtonSize = TangemButtonSize.X15, + state: TangemButtonState = TangemButtonState.Default, + shape: TangemButtonShape = TangemButtonShape.Default, +) { + TangemButtonInternal( + onClick = onClick, + modifier = modifier + .clip(shape.toShape(size)) + .then( + when (state) { + TangemButtonState.Disabled, + TangemButtonState.Default, + -> Modifier.background(TangemTheme.colors2.button.backgroundPositive) + TangemButtonState.Loading, + TangemButtonState.Pressed, + -> Modifier + .background(TangemTheme.colors2.button.backgroundPositive) + .background(TangemTheme.colors2.overlay.overlaySecondary) + }, + ), + text = text, + contentColor = TangemTheme.colors2.text.neutral.primaryInvertedConstant, + iconRes = iconRes, + enabled = enabled, + size = size, + state = state, + iconPosition = iconPosition, + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 480) +@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun AccentTangemButton_Preview( + @PreviewParameter(AccentTangemButtonPreviewProvider::class) params: TangemButtonState, +) { + TangemThemePreviewRedesign { + Row( + horizontalArrangement = Arrangement.spacedBy(21.dp), + modifier = Modifier + .background(TangemTheme.colors2.surface.level1) + .padding(8.dp), + ) { + repeat(4) { yIndex -> + val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded + val text = if (yIndex % 2 == 1) null else stringReference("Button") + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + repeat(2) { xIndex -> + val iconPosition = if (xIndex == 1) { + TangemButtonIconPosition.Start + } else { + TangemButtonIconPosition.End + } + AccentTangemButton( + onClick = {}, + text = text, + size = TangemButtonSize.X15, + shape = shape, + iconPosition = iconPosition, + iconRes = R.drawable.ic_tangem_24, + state = params, + ) + } + } + } + } + } +} + +private class AccentTangemButtonPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TangemButtonState.Default, + TangemButtonState.Pressed, + TangemButtonState.Loading, + TangemButtonState.Disabled, + ) +} +// endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/GhostTangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/GhostTangemButton.kt new file mode 100644 index 0000000000..16b1f51876 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/GhostTangemButton.kt @@ -0,0 +1,111 @@ +package com.tangem.core.ui.ds.button + +import android.content.res.Configuration +import androidx.annotation.DrawableRes +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.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +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 com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign + +/** + * [Ghost Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=5854-4804) + * + * @param onClick Lambda to be invoked when the button is clicked. + * @param modifier Modifier to be applied to the button. + * @param text TextReference for the button label. + * @param iconRes Drawable resource ID for the icon to be displayed in the button. + * @param iconPosition Position of the icon (Start or End). + * @param enabled Boolean indicating whether the button is enabled. + * @param size TangemButtonSize defining the size of the button. + * @param state TangemButtonState defining the current state of the button. + * +[REDACTED_AUTHOR] + */ +@Composable +fun GhostTangemButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: TextReference? = null, + @DrawableRes iconRes: Int? = null, + enabled: Boolean = true, + size: TangemButtonSize = TangemButtonSize.X15, + state: TangemButtonState = TangemButtonState.Default, + iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start, +) { + val contentColor = when (state) { + TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled + else -> TangemTheme.colors2.text.neutral.primary + } + TangemButtonInternal( + onClick = onClick, + modifier = modifier, + text = text, + contentColor = contentColor, + enabled = enabled, + size = size, + state = state, + iconPosition = iconPosition, + iconRes = iconRes, + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 480) +@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun GhostTangemButton_Preview( + @PreviewParameter(GhostTangemButtonPreviewProvider::class) params: TangemButtonState, +) { + TangemThemePreviewRedesign { + Row( + horizontalArrangement = Arrangement.spacedBy(21.dp), + modifier = Modifier + .background(TangemTheme.colors2.surface.level1) + .padding(8.dp), + ) { + repeat(4) { yIndex -> + val text = if (yIndex % 2 == 1) null else stringReference("Button") + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + repeat(2) { xIndex -> + val iconPosition = if (xIndex == 1) { + TangemButtonIconPosition.Start + } else { + TangemButtonIconPosition.End + } + GhostTangemButton( + onClick = {}, + text = text, + size = TangemButtonSize.X15, + iconPosition = iconPosition, + iconRes = R.drawable.ic_tangem_24, + state = params, + ) + } + } + } + } + } +} + +private class GhostTangemButtonPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TangemButtonState.Default, + TangemButtonState.Pressed, + TangemButtonState.Loading, + TangemButtonState.Disabled, + ) +} +// endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/OutlineTangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/OutlineTangemButton.kt new file mode 100644 index 0000000000..e86db3a8b8 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/OutlineTangemButton.kt @@ -0,0 +1,132 @@ +package com.tangem.core.ui.ds.button + +import android.content.res.Configuration +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +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 com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign + +/** + * [Outline Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=5854-4800) + * + * @param onClick Lambda to be invoked when the button is clicked. + * @param modifier Modifier to be applied to the button. + * @param text TextReference for the button label. + * @param iconRes Drawable resource ID for the icon to be displayed in the button. + * @param iconPosition Position of the icon (Start or End). + * @param enabled Boolean indicating whether the button is enabled. + * @param size TangemButtonSize defining the size of the button. + * @param state TangemButtonState defining the current state of the button. + * @param shape TangemButtonShape defining the shape of the button. + * +[REDACTED_AUTHOR] + */ +@Composable +fun OutlineTangemButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: TextReference? = null, + @DrawableRes iconRes: Int? = null, + iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start, + enabled: Boolean = true, + size: TangemButtonSize = TangemButtonSize.X15, + state: TangemButtonState = TangemButtonState.Default, + shape: TangemButtonShape = TangemButtonShape.Default, +) { + val backgroundModifier = when (state) { + TangemButtonState.Loading, + TangemButtonState.Pressed, + TangemButtonState.Disabled, + TangemButtonState.Default, + -> Modifier + .background(TangemTheme.colors2.surface.level1) + .border( + width = 1.dp, + color = TangemTheme.colors2.border.neutral.primary, + shape = shape.toShape(size), + ) + } + val contentColor = when (state) { + TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled + else -> TangemTheme.colors2.text.neutral.primary + } + TangemButtonInternal( + onClick = onClick, + modifier = modifier + .clip(shape.toShape(size)) + .then(backgroundModifier), + text = text, + contentColor = contentColor, + iconRes = iconRes, + enabled = enabled, + size = size, + state = state, + iconPosition = iconPosition, + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 480) +@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun OutlineTangemButton_Preview( + @PreviewParameter(OutlineTangemButtonPreviewProvider::class) params: TangemButtonState, +) { + TangemThemePreviewRedesign { + Row( + horizontalArrangement = Arrangement.spacedBy(21.dp), + modifier = Modifier + .background(TangemTheme.colors2.surface.level1) + .padding(8.dp), + ) { + repeat(4) { yIndex -> + val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded + val text = if (yIndex % 2 == 1) null else stringReference("Button") + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + repeat(2) { xIndex -> + val iconPosition = if (xIndex == 0) { + TangemButtonIconPosition.Start + } else { + TangemButtonIconPosition.End + } + OutlineTangemButton( + onClick = {}, + text = text, + size = TangemButtonSize.X15, + shape = shape, + iconPosition = iconPosition, + iconRes = R.drawable.ic_tangem_24, + state = params, + ) + } + } + } + } + } +} + +private class OutlineTangemButtonPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TangemButtonState.Default, + TangemButtonState.Pressed, + TangemButtonState.Loading, + TangemButtonState.Disabled, + ) +} +// endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/PrimaryInverseTangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/PrimaryInverseTangemButton.kt new file mode 100644 index 0000000000..8fc4f8644e --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/PrimaryInverseTangemButton.kt @@ -0,0 +1,127 @@ +package com.tangem.core.ui.ds.button + +import android.content.res.Configuration +import androidx.annotation.DrawableRes +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.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +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 com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign + +/** + * [Primary Inverse Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=7545-78314) + * + * @param onClick Lambda to be invoked when the button is clicked. + * @param modifier Modifier to be applied to the button. + * @param text TextReference for the button label. + * @param iconRes Drawable resource ID for the icon to be displayed in the button. + * @param iconPosition Position of the icon (Start or End). + * @param enabled Boolean indicating whether the button is enabled. + * @param size TangemButtonSize defining the size of the button. + * @param state TangemButtonState defining the current state of the button. + * @param shape TangemButtonShape defining the shape of the button. + * +[REDACTED_AUTHOR] + */ +@Composable +fun PrimaryInverseTangemButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: TextReference? = null, + @DrawableRes iconRes: Int? = null, + iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start, + enabled: Boolean = true, + size: TangemButtonSize = TangemButtonSize.X15, + state: TangemButtonState = TangemButtonState.Default, + shape: TangemButtonShape = TangemButtonShape.Default, +) { + val backgroundModifier = when (state) { + TangemButtonState.Default -> Modifier.background(TangemTheme.colors2.button.backgroundPrimaryInverse) + TangemButtonState.Disabled -> Modifier.background(TangemTheme.colors2.button.backgroundDisabled) + TangemButtonState.Loading, + TangemButtonState.Pressed, + -> Modifier + .background(TangemTheme.colors2.button.backgroundPrimaryInverse) + .background(TangemTheme.colors2.overlay.overlayPrimary) + } + val contentColor = when (state) { + TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled + else -> TangemTheme.colors2.text.neutral.primary + } + TangemButtonInternal( + onClick = onClick, + modifier = modifier + .clip(shape.toShape(size)) + .then(backgroundModifier), + text = text, + contentColor = contentColor, + enabled = enabled, + size = size, + state = state, + iconPosition = iconPosition, + iconRes = iconRes, + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 480) +@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun PrimaryInverseTangemButton_Preview( + @PreviewParameter(PrimaryInverseTangemButtonPreviewProvider::class) params: TangemButtonState, +) { + TangemThemePreviewRedesign { + Row( + horizontalArrangement = Arrangement.spacedBy(21.dp), + modifier = Modifier + .background(TangemTheme.colors2.surface.level2) + .padding(8.dp), + ) { + repeat(4) { yIndex -> + val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded + val text = if (yIndex % 2 == 1) null else stringReference("Button") + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + repeat(2) { xIndex -> + val iconPosition = if (xIndex == 0) { + TangemButtonIconPosition.Start + } else { + TangemButtonIconPosition.End + } + PrimaryInverseTangemButton( + onClick = {}, + text = text, + size = TangemButtonSize.X15, + shape = shape, + iconPosition = iconPosition, + iconRes = R.drawable.ic_tangem_24, + state = params, + ) + } + } + } + } + } +} + +private class PrimaryInverseTangemButtonPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TangemButtonState.Default, + TangemButtonState.Pressed, + TangemButtonState.Loading, + TangemButtonState.Disabled, + ) +} +// endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/PrimaryTangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/PrimaryTangemButton.kt new file mode 100644 index 0000000000..74487c813b --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/PrimaryTangemButton.kt @@ -0,0 +1,122 @@ +package com.tangem.core.ui.ds.button + +import android.content.res.Configuration +import androidx.annotation.DrawableRes +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.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +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 com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign + +/** + * [Primary Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=5854-4732&t=euYo1qCxPlQl3Fa6-4) + * + * @param onClick Lambda to be invoked when the button is clicked. + * @param modifier Modifier to be applied to the button. + * @param text TextReference for the button label. + * @param iconRes Drawable resource ID for the icon to be displayed in the button. + * @param iconPosition Position of the icon (Start or End). + * @param enabled Boolean indicating whether the button is enabled. + * @param size TangemButtonSize defining the size of the button. + * @param state TangemButtonState defining the current state of the button. + * @param shape TangemButtonShape defining the shape of the button. + * +[REDACTED_AUTHOR] + */ +@Composable +fun PrimaryTangemButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: TextReference? = null, + @DrawableRes iconRes: Int? = null, + iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start, + enabled: Boolean = true, + size: TangemButtonSize = TangemButtonSize.X15, + state: TangemButtonState = TangemButtonState.Default, + shape: TangemButtonShape = TangemButtonShape.Default, +) { + val backgroundModifier = when (state) { + TangemButtonState.Loading, + TangemButtonState.Default, + -> Modifier.background(TangemTheme.colors2.button.backgroundPrimary) + TangemButtonState.Disabled -> Modifier.background(TangemTheme.colors2.button.backgroundDisabled) + TangemButtonState.Pressed -> Modifier + .background(TangemTheme.colors2.button.backgroundPrimary) + .background(TangemTheme.colors2.overlay.overlaySecondary) + } + val contentColor = when (state) { + TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled + else -> TangemTheme.colors2.text.neutral.primaryInverted + } + TangemButtonInternal( + onClick = onClick, + modifier = modifier + .clip(shape.toShape(size)) + .then(backgroundModifier), + text = text, + contentColor = contentColor, + enabled = enabled, + size = size, + state = state, + iconPosition = iconPosition, + iconRes = iconRes, + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 480) +@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun PrimaryTangemButton_Preview( + @PreviewParameter(PrimaryTangemButtonPreviewProvider::class) params: TangemButtonState, +) { + TangemThemePreviewRedesign { + Row( + horizontalArrangement = Arrangement.spacedBy(21.dp), + modifier = Modifier + .background(TangemTheme.colors2.surface.level1) + .padding(8.dp), + ) { + repeat(4) { yIndex -> + val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded + val text = if (yIndex % 2 == 1) null else stringReference("Button") + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + repeat(TangemButtonIconPosition.entries.size) { xIndex -> + PrimaryTangemButton( + onClick = {}, + text = text, + size = TangemButtonSize.X15, + shape = shape, + iconPosition = TangemButtonIconPosition.entries[xIndex], + iconRes = R.drawable.ic_tangem_24, + state = params, + ) + } + } + } + } + } +} + +private class PrimaryTangemButtonPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TangemButtonState.Default, + TangemButtonState.Pressed, + TangemButtonState.Loading, + TangemButtonState.Disabled, + ) +} +// endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/SecondaryTangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/SecondaryTangemButton.kt new file mode 100644 index 0000000000..8c43685df8 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/SecondaryTangemButton.kt @@ -0,0 +1,125 @@ +package com.tangem.core.ui.ds.button + +import android.content.res.Configuration +import androidx.annotation.DrawableRes +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.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +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 com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign + +/** + * [Secondary Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=5854-4796) + * + * @param onClick Lambda to be invoked when the button is clicked. + * @param modifier Modifier to be applied to the button. + * @param text TextReference for the button label. + * @param iconRes Drawable resource ID for the icon to be displayed in the button. + * @param iconPosition Position of the icon (Start or End). + * @param enabled Boolean indicating whether the button is enabled. + * @param size TangemButtonSize defining the size of the button. + * @param state TangemButtonState defining the current state of the button. + * @param shape TangemButtonShape defining the shape of the button. + * +[REDACTED_AUTHOR] + */ +@Composable +fun SecondaryTangemButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: TextReference? = null, + @DrawableRes iconRes: Int? = null, + iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start, + enabled: Boolean = true, + size: TangemButtonSize = TangemButtonSize.X15, + state: TangemButtonState = TangemButtonState.Default, + shape: TangemButtonShape = TangemButtonShape.Default, +) { + val backgroundModifier = when (state) { + TangemButtonState.Loading, + TangemButtonState.Default, + -> Modifier.background(TangemTheme.colors2.button.backgroundSecondary) + TangemButtonState.Disabled -> Modifier.background(TangemTheme.colors2.button.backgroundDisabled) + TangemButtonState.Pressed -> Modifier.background(TangemTheme.colors2.overlay.overlayPrimary) + } + val contentColor = when (state) { + TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled + else -> TangemTheme.colors2.text.neutral.primary + } + TangemButtonInternal( + onClick = onClick, + modifier = modifier + .clip(shape.toShape(size)) + .then(backgroundModifier), + text = text, + contentColor = contentColor, + iconRes = iconRes, + enabled = enabled, + size = size, + state = state, + iconPosition = iconPosition, + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 480) +@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun SecondaryTangemButton_Preview( + @PreviewParameter(SecondaryTangemButtonPreviewProvider::class) params: TangemButtonState, +) { + TangemThemePreviewRedesign { + Row( + horizontalArrangement = Arrangement.spacedBy(21.dp), + modifier = Modifier + .background(TangemTheme.colors2.surface.level1) + .padding(8.dp), + ) { + repeat(4) { yIndex -> + val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded + val text = if (yIndex % 2 == 1) null else stringReference("Button") + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + repeat(2) { xIndex -> + val iconPosition = if (xIndex == 0) { + TangemButtonIconPosition.Start + } else { + TangemButtonIconPosition.End + } + SecondaryTangemButton( + onClick = {}, + text = text, + size = TangemButtonSize.X15, + shape = shape, + iconPosition = iconPosition, + iconRes = R.drawable.ic_tangem_24, + state = params, + ) + } + } + } + } + } +} + +private class SecondaryTangemButtonPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TangemButtonState.Default, + TangemButtonState.Pressed, + TangemButtonState.Loading, + TangemButtonState.Disabled, + ) +} +// endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonInternal.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonInternal.kt new file mode 100644 index 0000000000..abc8dcf003 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonInternal.kt @@ -0,0 +1,256 @@ +package com.tangem.core.ui.ds.button + +import androidx.annotation.DrawableRes +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.TextAutoSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.clickableSingle +import com.tangem.core.ui.extensions.conditionalCompose +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.test.BaseButtonTestTags + +/** + * A customizable button component that supports text, icons, and different states. + * + * @param onClick Lambda to be invoked when the button is clicked. + * @param modifier Modifier to be applied to the button. + * @param text TextReference for the button label. + * @param iconRes Drawable resource ID for the icon to be displayed in the button. + * @param iconPosition Position of the icon (Start or End). + * @param enabled Boolean indicating whether the button is enabled. + * @param contentColor Color of the button content (text and icon). + * @param size TangemButtonSize defining the size of the button. + * @param state TangemButtonState defining the current state of the button. + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun TangemButtonInternal( + onClick: () -> Unit, + modifier: Modifier = Modifier, + text: TextReference? = null, + @DrawableRes iconRes: Int? = null, + iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start, + enabled: Boolean = true, + contentColor: Color = TangemTheme.colors2.text.neutral.primary, + size: TangemButtonSize = TangemButtonSize.X15, + state: TangemButtonState = TangemButtonState.Default, +) { + Row( + modifier = modifier + .testTag(BaseButtonTestTags.BUTTON) + .height(size.toHeightDp()) + .conditionalCompose(text == null) { + width(size.toHeightDp()) + } + .clickableSingle(enabled = enabled, onClick = onClick, role = Role.Button) + .conditionalCompose(text != null) { + padding(horizontal = size.toPaddingDp()) + } + .animateContentSize(), + horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + ) { + AnimatedVisibility( + visible = iconRes != null && iconPosition == TangemButtonIconPosition.Start, + modifier = Modifier.size(size = size.toContentSize()), + ) { + val wrappedIconRes = remember(this) { requireNotNull(iconRes) } + TangemButtonIcon(iconRes = wrappedIconRes, state = state, iconColor = contentColor, size = size) + } + + AnimatedVisibility(text != null && state != TangemButtonState.Loading) { + val wrappedText = remember(this) { requireNotNull(text) } + val textStyle = size.toTextStyle() + Text( + text = wrappedText.resolveReference(), + style = textStyle, + color = contentColor, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + autoSize = TextAutoSize.StepBased( + minFontSize = 12.sp, + maxFontSize = textStyle.fontSize, + ), + modifier = Modifier.testTag(BaseButtonTestTags.TEXT), + ) + } + + AnimatedVisibility( + visible = iconRes != null && iconPosition == TangemButtonIconPosition.End, + modifier = Modifier.size(size = size.toContentSize()), + ) { + val wrappedIconRes = remember(this) { requireNotNull(iconRes) } + TangemButtonIcon(iconRes = wrappedIconRes, state = state, iconColor = contentColor, size = size) + } + } +} + +@Composable +private fun TangemButtonIcon( + @DrawableRes iconRes: Int, + iconColor: Color, + state: TangemButtonState, + size: TangemButtonSize, +) { + AnimatedContent(state) { targetState -> + when (targetState) { + TangemButtonState.Loading -> CircularProgressIndicator( + color = iconColor, + strokeWidth = 2.dp, + strokeCap = StrokeCap.Round, + modifier = Modifier.padding( + when (size) { + TangemButtonSize.X7, + TangemButtonSize.X8, + TangemButtonSize.X9, + TangemButtonSize.X10, + -> 0.5.dp + TangemButtonSize.X12, + TangemButtonSize.X15, + -> 4.5.dp + }, + ), + ) + else -> Icon( + painter = painterResource(id = iconRes), + contentDescription = null, + tint = iconColor, + ) + } + } +} + +/** + * Defines the shape of the Tangem button. + */ +enum class TangemButtonShape { + Default, + Rounded, + ; + + @ReadOnlyComposable + @Composable + internal fun toShape(size: TangemButtonSize) = RoundedCornerShape( + when (this) { + Default -> size.toShapeRadius() + Rounded -> 100.dp + }, + ) +} + +/** + * Defines the size of the Tangem button. + */ +enum class TangemButtonSize { + X7, + X8, + X9, + X10, + X12, + X15, + ; + + @ReadOnlyComposable + @Composable + internal fun toHeightDp() = when (this) { + X7 -> TangemTheme.dimens2.x7 + X8 -> TangemTheme.dimens2.x8 + X9 -> TangemTheme.dimens2.x9 + X10 -> TangemTheme.dimens2.x10 + X12 -> TangemTheme.dimens2.x12 + X15 -> TangemTheme.dimens2.x15 + } + + @ReadOnlyComposable + @Composable + internal fun toPaddingDp() = when (this) { + X7 -> TangemTheme.dimens2.x2 + X8, + X9, + X10, + -> TangemTheme.dimens2.x3 + X12, + X15, + -> TangemTheme.dimens2.x6 + } + + @ReadOnlyComposable + @Composable + internal fun toContentSize() = when (this) { + X7, + X8, + X9, + X10, + -> TangemTheme.dimens2.x5 + X12, + X15, + -> TangemTheme.dimens2.x7 + } + + @ReadOnlyComposable + @Composable + internal fun toShapeRadius() = when (this) { + X7, + X8, + X9, + X10, + -> TangemTheme.dimens2.x2 + X12 -> TangemTheme.dimens2.x3 + X15 -> TangemTheme.dimens2.x4 + } + + @ReadOnlyComposable + @Composable + internal fun toTextStyle(): TextStyle = when (this) { + X7 -> TangemTheme.typography2.bodyRegular14 + X8, + X9, + X10, + X12, + X15, + -> TangemTheme.typography2.bodySemibold16 + } +} + +/** + * Defines the state of the Tangem button. + */ +enum class TangemButtonState { + Default, + Disabled, + Pressed, + Loading, +} + +/** + * Defines the position of the icon in the Tangem button. + */ +enum class TangemButtonIconPosition { + Start, + End, +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt index 7b85a6004d..1f0a7b1208 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt @@ -54,23 +54,8 @@ fun Modifier.conditional(condition: Boolean, modifier: Modifier.() -> Modifier): @Composable fun Modifier.conditionalCompose( condition: Boolean, - modifier: @Composable Modifier.() -> Modifier = { Modifier }, -): Modifier { - return if (condition) { - then(modifier(Modifier)) - } else { - this - } -} - -/** - * Conditionally applies a modifier based on a boolean condition. - */ -@Composable -fun Modifier.conditionalCompose( - condition: Boolean, - modifier: @Composable Modifier.() -> Modifier = { Modifier }, otherModifier: @Composable Modifier.() -> Modifier = { this }, + modifier: @Composable Modifier.() -> Modifier = { Modifier }, ): Modifier { return if (condition) { then(modifier(Modifier))