diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt index 2eceba7401..60b4d4699a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt @@ -1,7 +1,6 @@ package com.tangem.core.ui.components.bottomsheets import android.content.res.Configuration -import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -212,14 +211,6 @@ inline fun BasicBottomSheet( Modal -> windowSize.height * MODAL_SHEET_MAX_HEIGHT } - val buttonHeight by animateDpAsState( - if (footer != null) { - 80.dp - } else { - 0.dp - }, - ) - val contentModifier = when (type) { Default -> Modifier.clip( RoundedCornerShape( @@ -260,7 +251,6 @@ inline fun BasicBottomSheet( Box( modifier = Modifier .fillMaxWidth() - .height(buttonHeight) .align(Alignment.BottomCenter), ) { if (footer != null) { 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/StatusTangemButton.kt similarity index 77% rename from core/ui/src/main/java/com/tangem/core/ui/ds/button/AccentTangemButton.kt rename to core/ui/src/main/java/com/tangem/core/ui/ds/button/StatusTangemButton.kt index 8c14fb6a7f..aa18decee3 100644 --- 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/StatusTangemButton.kt @@ -8,6 +8,7 @@ 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.runtime.ReadOnlyComposable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.tooling.preview.Preview @@ -27,14 +28,15 @@ import com.tangem.core.ui.res.TangemThemePreviewRedesign * @param modifier Modifier to be applied to the button. */ @Composable -fun AccentTangemButton(buttonUM: TangemButtonUM, modifier: Modifier = Modifier) { - AccentTangemButton( +fun StatusTangemButton(buttonUM: TangemButtonUM, modifier: Modifier = Modifier) { + StatusTangemButton( onClick = buttonUM.onClick, modifier = modifier, text = buttonUM.text, iconRes = buttonUM.iconRes, iconPosition = buttonUM.iconPosition, enabled = buttonUM.isEnabled, + type = buttonUM.type, size = buttonUM.size, state = buttonUM.state, shape = buttonUM.shape, @@ -57,35 +59,39 @@ fun AccentTangemButton(buttonUM: TangemButtonUM, modifier: Modifier = Modifier) [REDACTED_AUTHOR] */ @Composable -fun AccentTangemButton( +fun StatusTangemButton( onClick: () -> Unit, modifier: Modifier = Modifier, text: TextReference? = null, @DrawableRes iconRes: Int? = null, iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start, enabled: Boolean = true, + type: TangemButtonType = TangemButtonType.Accent, size: TangemButtonSize = TangemButtonSize.X15, state: TangemButtonState = TangemButtonState.Default, shape: TangemButtonShape = TangemButtonShape.Default, ) { + val statusColor = type.getStatusColor() + val contentColor = when (state) { + TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled + else -> TangemTheme.colors2.text.neutral.primaryInvertedConstant + } + val backgroundModifier = when (state) { + TangemButtonState.Disabled -> Modifier.background(TangemTheme.colors2.button.backgroundDisabled) + TangemButtonState.Default -> Modifier.background(statusColor) + TangemButtonState.Loading, + TangemButtonState.Pressed, + -> Modifier + .background(statusColor) + .background(TangemTheme.colors2.overlay.overlaySecondary) + } 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) - }, - ), + .then(backgroundModifier), text = text, - contentColor = TangemTheme.colors2.text.neutral.primaryInvertedConstant, + contentColor = contentColor, iconRes = iconRes, enabled = enabled, size = size, @@ -94,11 +100,19 @@ fun AccentTangemButton( ) } +@ReadOnlyComposable +@Composable +private fun TangemButtonType.getStatusColor() = when (this) { + TangemButtonType.Accent -> TangemTheme.colors2.button.backgroundAccent + TangemButtonType.Positive -> TangemTheme.colors2.button.backgroundPositive + else -> TangemTheme.colors2.button.backgroundPrimary +} + // region Preview @Composable @Preview(showBackground = true, widthDp = 480) @Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun AccentTangemButton_Preview( +private fun StatusTangemButton_Preview( @PreviewParameter(AccentTangemButtonPreviewProvider::class) params: TangemButtonState, ) { TangemThemePreviewRedesign { @@ -118,13 +132,14 @@ private fun AccentTangemButton_Preview( } else { TangemButtonIconPosition.End } - AccentTangemButton( + StatusTangemButton( onClick = {}, text = text, size = TangemButtonSize.X15, shape = shape, iconPosition = iconPosition, iconRes = R.drawable.ic_tangem_24, + type = TangemButtonType.Accent, state = params, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButton.kt index 6e8600bac6..7e78082811 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButton.kt @@ -39,13 +39,26 @@ fun TangemButton(buttonUM: TangemButtonUM, modifier: Modifier = Modifier) { state = buttonUM.state, shape = buttonUM.shape, ) - TangemButtonType.Accent -> AccentTangemButton( + TangemButtonType.Accent -> StatusTangemButton( onClick = buttonUM.onClick, modifier = modifier, text = buttonUM.text, iconRes = buttonUM.iconRes, iconPosition = buttonUM.iconPosition, enabled = buttonUM.isEnabled, + type = TangemButtonType.Positive, + size = buttonUM.size, + state = buttonUM.state, + shape = buttonUM.shape, + ) + TangemButtonType.Positive -> StatusTangemButton( + onClick = buttonUM.onClick, + modifier = modifier, + text = buttonUM.text, + iconRes = buttonUM.iconRes, + iconPosition = buttonUM.iconPosition, + enabled = buttonUM.isEnabled, + type = TangemButtonType.Positive, size = buttonUM.size, state = buttonUM.state, shape = buttonUM.shape, 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 index b9426982dd..66665e2f5d 100644 --- 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 @@ -1,9 +1,11 @@ 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.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.TextAutoSize @@ -15,6 +17,7 @@ import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.platform.testTag @@ -25,13 +28,13 @@ 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.components.SpacerW +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.test.BaseButtonTestTags +private const val LOADING_ANIMATION_DURATION = 150 + /** * A customizable button component that supports text, icons, and different states. * @@ -61,7 +64,7 @@ internal fun TangemButtonInternal( state: TangemButtonState = TangemButtonState.Default, ) { ProvideButtonRippleConfiguration { - Row( + Box( modifier = modifier .testTag(BaseButtonTestTags.BUTTON) .clickableSingle(enabled = enabled, onClick = onClick, role = Role.Button) @@ -70,63 +73,107 @@ internal fun TangemButtonInternal( width(size.toHeightDp()) } .conditionalCompose(text != null) { - padding(horizontal = size.toPaddingDp()) + padding(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()), + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .align(Alignment.Center) + .conditional(state == TangemButtonState.Loading) { + alpha(0f) + }, ) { - val wrappedIconRes = remember(this, iconRes) { requireNotNull(iconRes) } - TangemButtonIcon(iconRes = wrappedIconRes, state = state, iconColor = contentColor, size = size) - } + val isStartIcon = iconRes != null && iconPosition == TangemButtonIconPosition.Start + TangemButtonIcon( + iconRes = iconRes, + iconColor = contentColor, + isVisible = isStartIcon, + size = size, + ) + if (isStartIcon && (text != null || descriptionText != null)) { + SpacerW(TangemTheme.dimens2.x1) + } - AnimatedVisibility(text != null && state != TangemButtonState.Loading) { - val wrappedText = remember(this) { requireNotNull(text) } - val textStyle = size.toTextStyle() - Text( - text = wrappedText.resolveReference(), - style = textStyle, + ButtonContent( + text = text, + descriptionText = descriptionText, + contentColor = contentColor, + size = size, + ) + + val isEndIcon = iconRes != null && iconPosition == TangemButtonIconPosition.End + if (isEndIcon && (text != null || descriptionText != null)) { + SpacerW(TangemTheme.dimens2.x1) + } + TangemButtonIcon( + iconRes = iconRes, + iconColor = contentColor, + isVisible = isEndIcon, + size = size, + ) + } + AnimatedVisibility( + modifier = Modifier + .align(Alignment.Center) + .size(size.toContentSize()), + visible = state == TangemButtonState.Loading, + exit = fadeOut(animationSpec = tween(LOADING_ANIMATION_DURATION)), + enter = fadeIn(animationSpec = tween(LOADING_ANIMATION_DURATION)), + ) { + CircularProgressIndicator( color = contentColor, - textAlign = TextAlign.Center, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - autoSize = TextAutoSize.StepBased( - minFontSize = 12.sp, - maxFontSize = textStyle.fontSize, - ), - modifier = Modifier.testTag(BaseButtonTestTags.TEXT), + strokeWidth = 2.dp, + strokeCap = StrokeCap.Round, + modifier = Modifier.size(size.toContentSize()), ) } + } + } +} - AnimatedVisibility(descriptionText != null && state != TangemButtonState.Loading) { - val wrappedText = remember(this) { requireNotNull(descriptionText) } - val textStyle = TangemTheme.typography2.captionSemibold12 - Text( - text = wrappedText.resolveReference(), - style = textStyle, - color = TangemTheme.colors2.text.status.disabled, - 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 ButtonContent( + text: TextReference?, + descriptionText: TextReference?, + contentColor: Color, + size: TangemButtonSize, +) { + Column { + AnimatedVisibility(text != null) { + val wrappedText = remember(this) { text.orEmpty() } + 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(descriptionText != null) { + val wrappedText = remember(this) { descriptionText.orEmpty() } + val textStyle = TangemTheme.typography2.captionSemibold12 + Text( + text = wrappedText.resolveReference(), + style = textStyle, + color = TangemTheme.colors2.text.status.disabled, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + autoSize = TextAutoSize.StepBased( + minFontSize = 12.sp, + maxFontSize = textStyle.fontSize, + ), + modifier = Modifier.testTag(BaseButtonTestTags.TEXT), + ) } } } @@ -150,36 +197,21 @@ private inline fun ProvideButtonRippleConfiguration(crossinline content: @Compos @Composable private fun TangemButtonIcon( - @DrawableRes iconRes: Int, + @DrawableRes iconRes: Int?, iconColor: Color, - state: TangemButtonState, + isVisible: Boolean, 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, - ) - } + AnimatedVisibility( + visible = isVisible, + modifier = Modifier.size(size = size.toContentSize()), + ) { + val wrappedIconRes = remember(this) { requireNotNull(iconRes) } + Icon( + painter = painterResource(id = wrappedIconRes), + contentDescription = null, + tint = iconColor, + ) } } @@ -227,14 +259,30 @@ enum class TangemButtonSize { @ReadOnlyComposable @Composable internal fun toPaddingDp() = when (this) { - X7 -> TangemTheme.dimens2.x2 - X8, - X9, - X10, - -> TangemTheme.dimens2.x3 - X12, - X15, - -> TangemTheme.dimens2.x6 + X7 -> PaddingValues( + horizontal = TangemTheme.dimens2.x2, + vertical = TangemTheme.dimens2.x0_5, + ) + X8 -> PaddingValues( + horizontal = TangemTheme.dimens2.x3, + vertical = TangemTheme.dimens2.x1_5, + ) + X9 -> PaddingValues( + horizontal = TangemTheme.dimens2.x3, + vertical = TangemTheme.dimens2.x2, + ) + X10 -> PaddingValues( + horizontal = TangemTheme.dimens2.x3, + vertical = TangemTheme.dimens2.x2_5, + ) + X12 -> PaddingValues( + horizontal = TangemTheme.dimens2.x6, + vertical = TangemTheme.dimens2.x2_5, + ) + X15 -> PaddingValues( + horizontal = TangemTheme.dimens2.x6, + vertical = TangemTheme.dimens2.x4, + ) } @ReadOnlyComposable diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonUM.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonUM.kt index a3bb16c4da..6d96b1f577 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonUM.kt @@ -39,6 +39,7 @@ enum class TangemButtonType { Primary, Secondary, Accent, + Positive, Outline, PrimaryInverse, Ghost, 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 0cd61de19c..afa963b0fe 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 @@ -165,6 +165,7 @@ class TangemColors2 internal constructor( backgroundPrimary: Color, backgroundSecondary: Color, backgroundDisabled: Color, + backgroundAccent: Color, backgroundPositive: Color, backgroundPrimaryInverse: Color, textPrimary: Color, @@ -183,6 +184,8 @@ class TangemColors2 internal constructor( private set var backgroundPositive by mutableStateOf(backgroundPositive) private set + var backgroundAccent by mutableStateOf(backgroundAccent) + private set var backgroundPrimaryInverse by mutableStateOf(backgroundPrimaryInverse) private set var textPrimary by mutableStateOf(textPrimary) @@ -205,6 +208,7 @@ class TangemColors2 internal constructor( backgroundSecondary = other.backgroundSecondary backgroundDisabled = other.backgroundDisabled backgroundPositive = other.backgroundPositive + backgroundAccent = other.backgroundAccent backgroundPrimaryInverse = other.backgroundPrimaryInverse textPrimary = other.textPrimary textSecondary = other.textSecondary 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 index 292a6555e3..f8e2484ba3 100644 --- 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 @@ -11,6 +11,7 @@ data class TangemDimens2 internal constructor( val x0: Dp = 0.dp, val x0_5: Dp = 2.dp, val x1: Dp = 4.dp, + val x1_5: Dp = 6.dp, val x2: Dp = 8.dp, val x2_5: Dp = 10.dp, val x3: Dp = 12.dp, 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 619a08d5b8..724f09365d 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 @@ -112,7 +112,8 @@ private fun lightThemeColors2(): TangemColors2 { backgroundPrimary = TangemColorPalette.Dark6, backgroundSecondary = TangemColorPalette.Dark_10, backgroundDisabled = TangemColorPalette.Light3, - backgroundPositive = TangemColorPalette.Azure, + backgroundAccent = TangemColorPalette.Azure, + backgroundPositive = TangemColorPalette.Eucalyptus, backgroundPrimaryInverse = TangemColorPalette.White, textSecondary = TangemColorPalette.Dark6, textPrimary = TangemColorPalette.Light2, @@ -288,7 +289,8 @@ private fun darkThemeColors2(): TangemColors2 { backgroundPrimary = TangemColorPalette.Light1V2, backgroundSecondary = TangemColorPalette.Light_10, backgroundDisabled = TangemColorPalette.Dark5, - backgroundPositive = TangemColorPalette.Azure, + backgroundAccent = TangemColorPalette.Azure, + backgroundPositive = TangemColorPalette.Eucalyptus, backgroundPrimaryInverse = TangemColorPalette.Light_10, textSecondary = TangemColorPalette.Light4, textPrimary = TangemColorPalette.Dark4, diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/buttons/ButtonsStory.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/buttons/ButtonsStory.kt index 9d9a6a9c09..7a7815c402 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/buttons/ButtonsStory.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/buttons/ButtonsStory.kt @@ -82,7 +82,7 @@ internal fun ButtonsStory(modifier: Modifier = Modifier) { } item("accent") { ButtonSection(title = "Accent") { state, text, shape -> - AccentTangemButton( + StatusTangemButton( onClick = {}, text = if (text) stringReference("Continue") else null, iconRes = R.drawable.ic_tangem_24, @@ -92,6 +92,19 @@ internal fun ButtonsStory(modifier: Modifier = Modifier) { ) } } + item("positive") { + ButtonSection(title = "Positive") { state, text, shape -> + StatusTangemButton( + onClick = {}, + text = if (text) stringReference("Continue") else null, + iconRes = R.drawable.ic_tangem_24, + size = TangemButtonSize.X10, + type = TangemButtonType.Positive, + state = state, + shape = shape, + ) + } + } item("ghost") { ButtonSection(title = "Ghost") { state, text, shape -> GhostTangemButton( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletItemBlocks.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletItemBlocks.kt index 443bd5f959..56506408ef 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletItemBlocks.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletItemBlocks.kt @@ -26,7 +26,7 @@ internal fun LazyListScope.organizeTokens2(state: WalletUM, itemModifier: Modifi contentType = "OrganizeTokensButton", ) { TangemButton( - organizeButton, + buttonUM = organizeButton, modifier = itemModifier, ) }