From 4b07ed004f0dadddc6b1fc8d22afaa4f10c4128f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Dec 2025 15:19:26 +0500 Subject: [PATCH] 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))