Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-28 21:49:51 +08:00
parent b97ea5faf8
commit 6145f3c5db
16 changed files with 716 additions and 478 deletions

View file

@ -2,19 +2,17 @@ package com.tangem.core.ui.components
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.res.TangemTheme
// region TextButton
@ -26,7 +24,7 @@ fun TextButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier,
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.None,
icon = TangemButtonIconPosition.None,
onClick = onClick,
enabled = enabled,
showProgress = false,
@ -49,7 +47,7 @@ fun TextButtonIconStart(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Start(iconResId),
icon = TangemButtonIconPosition.Start(iconResId),
onClick = onClick,
enabled = enabled,
showProgress = false,
@ -63,7 +61,7 @@ fun WarningTextButton(text: String, onClick: () -> Unit, modifier: Modifier = Mo
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.None,
icon = TangemButtonIconPosition.None,
onClick = onClick,
enabled = enabled,
showProgress = false,
@ -85,7 +83,7 @@ fun PrimaryButton(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.None,
icon = TangemButtonIconPosition.None,
onClick = onClick,
colors = TangemButtonsDefaults.primaryButtonColors,
enabled = enabled,
@ -108,7 +106,7 @@ fun PrimaryButtonIconEnd(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.End(iconResId),
icon = TangemButtonIconPosition.End(iconResId),
onClick = onClick,
colors = TangemButtonsDefaults.primaryButtonColors,
enabled = enabled,
@ -131,7 +129,7 @@ fun PrimaryButtonIconStart(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Start(iconResId),
icon = TangemButtonIconPosition.Start(iconResId),
onClick = onClick,
colors = TangemButtonsDefaults.primaryButtonColors,
enabled = enabled,
@ -152,7 +150,7 @@ fun SecondaryButton(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.None,
icon = TangemButtonIconPosition.None,
onClick = onClick,
colors = TangemButtonsDefaults.secondaryButtonColors,
enabled = enabled,
@ -175,7 +173,7 @@ fun SecondaryButtonIconEnd(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.End(iconResId),
icon = TangemButtonIconPosition.End(iconResId),
onClick = onClick,
colors = TangemButtonsDefaults.secondaryButtonColors,
enabled = enabled,
@ -198,7 +196,7 @@ fun SecondaryButtonIconStart(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Start(iconResId),
icon = TangemButtonIconPosition.Start(iconResId),
onClick = onClick,
colors = TangemButtonsDefaults.secondaryButtonColors,
enabled = enabled,
@ -214,7 +212,7 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif
modifier = modifier,
text = text,
textStyle = TangemTheme.typography.subtitle2,
icon = TangemButtonIcon.End(iconResId = R.drawable.ic_chevron_24),
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
onClick = onClick,
colors = TangemButtonsDefaults.selectorButtonColors,
showProgress = false,
@ -224,362 +222,6 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif
}
// endregion Other
// region Action
/**
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-305&t=3z98eFnTeyIx5TH5-4)
* */
@Composable
fun RoundedActionButton(
text: String,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Start(iconResId),
onClick = onClick,
enabled = enabled,
showProgress = false,
colors = TangemButtonsDefaults.secondaryButtonColors,
size = TangemButtonSize.RoundedAction,
)
}
/**
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1208-1395&t=3z98eFnTeyIx5TH5-4)
* */
@Composable
fun ActionButton(
text: String,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Start(iconResId),
onClick = onClick,
enabled = enabled,
showProgress = false,
colors = TangemButtonsDefaults.secondaryButtonColors,
size = TangemButtonSize.Action,
)
}
/**
* Same as [RoundedActionButton] but colored in primary background color
* */
@Composable
fun BackgroundActionButton(
text: String,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Start(iconResId),
onClick = onClick,
enabled = enabled,
showProgress = false,
colors = TangemButtonsDefaults.backgroundButtonColors,
size = TangemButtonSize.RoundedAction,
)
}
// endregion Action
// region Defaults
@Suppress("LongParameterList")
@Composable
private fun TangemButton(
text: String,
icon: TangemButtonIcon,
onClick: () -> Unit,
colors: ButtonColors,
showProgress: Boolean,
enabled: Boolean,
modifier: Modifier = Modifier,
size: TangemButtonSize = TangemButtonSize.Default,
elevation: ButtonElevation = TangemButtonsDefaults.elevation,
textStyle: TextStyle = TangemTheme.typography.button,
) {
Button(
modifier = modifier.heightIn(min = size.toHeightDp()),
onClick = { if (!showProgress) onClick() },
enabled = enabled,
elevation = elevation,
shape = size.toShape(),
colors = colors,
contentPadding = size.toContentPadding(icon = icon),
) {
ButtonContent(
text = text,
textStyle = textStyle,
buttonIcon = icon,
colors = colors,
showProgress = showProgress,
enabled = enabled,
size = size,
)
}
}
@Suppress("LongParameterList")
@Composable
private fun ButtonContent(
text: String,
textStyle: TextStyle,
buttonIcon: TangemButtonIcon,
colors: ButtonColors,
size: TangemButtonSize,
enabled: Boolean,
showProgress: Boolean,
) {
val icon = @Composable { iconResId: Int ->
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
painter = painterResource(id = iconResId),
tint = colors.contentColor(enabled = enabled).value,
contentDescription = null,
)
}
if (showProgress) {
Box(modifier = Modifier.wrapContentSize()) {
CircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.size(TangemTheme.dimens.size24),
color = colors.contentColor(enabled = enabled).value,
strokeWidth = TangemTheme.dimens.size4,
)
}
} else {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()),
) {
if (buttonIcon is TangemButtonIcon.Start) {
icon(buttonIcon.iconResId)
}
Text(
text = text,
style = textStyle,
color = colors.contentColor(enabled = enabled).value,
maxLines = 1,
)
if (buttonIcon is TangemButtonIcon.End) {
icon(buttonIcon.iconResId)
}
}
}
}
@Immutable
private sealed interface TangemButtonIcon {
val iconResId: Int?
data class Start(override val iconResId: Int) : TangemButtonIcon
data class End(override val iconResId: Int) : TangemButtonIcon
object None : TangemButtonIcon {
override val iconResId: Int? = null
}
}
private enum class TangemButtonSize {
Default,
Text,
Selector,
Action,
RoundedAction,
}
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toHeightDp(): Dp = when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.size48
TangemButtonSize.Text -> TangemTheme.dimens.size40
TangemButtonSize.Selector -> TangemTheme.dimens.size24
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> TangemTheme.dimens.size36
}
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toShape(): Shape = when (this) {
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge
}
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toIconPadding(): Dp = when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
TangemButtonSize.Selector -> 0.dp
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> TangemTheme.dimens.spacing8
}
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toContentPadding(icon: TangemButtonIcon): PaddingValues {
val horizontalPadding = this.toHorizontalContentPadding(icon = icon)
return when (this) {
TangemButtonSize.Default -> PaddingValues(
top = TangemTheme.dimens.spacing14,
bottom = TangemTheme.dimens.spacing14,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
TangemButtonSize.Text -> PaddingValues(
top = TangemTheme.dimens.spacing10,
bottom = TangemTheme.dimens.spacing10,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
TangemButtonSize.Selector -> PaddingValues(
top = TangemTheme.dimens.spacing0_5,
bottom = TangemTheme.dimens.spacing0_5,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> PaddingValues(
top = TangemTheme.dimens.spacing8,
bottom = TangemTheme.dimens.spacing8,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
}
}
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIcon): Pair<Dp, Dp> {
return when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
TangemButtonSize.Text -> when (icon) {
is TangemButtonIcon.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
is TangemButtonIcon.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
is TangemButtonIcon.End -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing14
}
TangemButtonSize.Selector -> TangemTheme.dimens.spacing0_5 to TangemTheme.dimens.spacing0_5
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> when (icon) {
is TangemButtonIcon.None -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing24
is TangemButtonIcon.Start -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing24
is TangemButtonIcon.End -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing16
}
}
}
private object TangemButtonsDefaults {
val elevation: ButtonElevation
@Composable get() = ButtonDefaults
.elevation(
defaultElevation = TangemTheme.dimens.elevation0,
pressedElevation = TangemTheme.dimens.elevation0,
)
val primaryButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.button.primary,
contentColor = TangemTheme.colors.text.primary2,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val secondaryButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.button.secondary,
contentColor = TangemTheme.colors.text.primary1,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val defaultTextButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = Color.Transparent,
contentColor = TangemTheme.colors.text.secondary,
disabledBackgroundColor = Color.Transparent,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val warningTextButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = Color.Transparent,
contentColor = TangemTheme.colors.text.warning,
disabledBackgroundColor = Color.Transparent,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val selectorButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = Color.Transparent,
contentColor = TangemTheme.colors.text.tertiary,
disabledBackgroundColor = Color.Transparent,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val backgroundButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.background.primary,
contentColor = TangemTheme.colors.text.primary1,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
)
}
@Immutable
private open class TangemButtonColors(
private val backgroundColor: Color,
private val contentColor: Color,
private val disabledBackgroundColor: Color,
private val disabledContentColor: Color,
) : ButtonColors {
@Composable
override fun backgroundColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(newValue = if (enabled) backgroundColor else disabledBackgroundColor)
}
@Composable
override fun contentColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(newValue = if (enabled) contentColor else disabledContentColor)
}
}
// endregion Defaults
// region Preview
@Composable
private fun PrimaryButtonSample() {
@ -730,34 +372,4 @@ private fun TextButtonPreview_DarkTheme() {
}
}
@Composable
private fun ActionButtonSample() {
Column(
modifier = Modifier.background(TangemTheme.colors.background.primary),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun ActionButtonPreview_LightTheme() {
TangemTheme {
ActionButtonSample()
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun ActionButtonPreview_DarkTheme() {
TangemTheme(isDark = true) {
ActionButtonSample()
}
}
// endregion Preview

View file

@ -0,0 +1,20 @@
package com.tangem.core.ui.components.buttons.actions
import androidx.annotation.DrawableRes
/**
* Action button config
*
* @property text text
* @property iconResId icon resource id
* @property onClick lambda be invoked when action component is clicked
* @property enabled enabled
*
[REDACTED_AUTHOR]
*/
data class ActionConfig(
val text: String,
@DrawableRes val iconResId: Int,
val onClick: () -> Unit,
val enabled: Boolean = true,
)

View file

@ -0,0 +1,96 @@
package com.tangem.core.ui.components.buttons.actions
import androidx.compose.foundation.layout.Column
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.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.res.TangemTheme
/**
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-305&t=3z98eFnTeyIx5TH5-4)
*/
@Composable
fun RoundedActionButton(config: ActionConfig, modifier: Modifier = Modifier) {
TangemButton(
modifier = modifier,
text = config.text,
icon = TangemButtonIconPosition.Start(config.iconResId),
onClick = config.onClick,
enabled = config.enabled,
showProgress = false,
colors = TangemButtonsDefaults.secondaryButtonColors,
size = TangemButtonSize.RoundedAction,
)
}
/**
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1208-1395&t=3z98eFnTeyIx5TH5-4)
*/
@Composable
fun ActionButton(config: ActionConfig, modifier: Modifier = Modifier) {
TangemButton(
modifier = modifier,
text = config.text,
icon = TangemButtonIconPosition.Start(config.iconResId),
onClick = config.onClick,
enabled = config.enabled,
showProgress = false,
colors = TangemButtonsDefaults.secondaryButtonColors,
size = TangemButtonSize.Action,
)
}
/**
* Same as [RoundedActionButton] but colored in primary background color
*/
@Composable
fun BackgroundActionButton(config: ActionConfig, modifier: Modifier = Modifier) {
TangemButton(
modifier = modifier,
text = config.text,
icon = TangemButtonIconPosition.Start(config.iconResId),
onClick = config.onClick,
enabled = config.enabled,
showProgress = false,
colors = TangemButtonsDefaults.backgroundButtonColors,
size = TangemButtonSize.RoundedAction,
)
}
@Preview(showBackground = true)
@Composable
private fun Preview_ActionButton_Light(@PreviewParameter(ActionStateProvider::class) state: ActionConfig) {
TangemTheme(isDark = false) {
Column {
RoundedActionButton(state)
ActionButton(state)
BackgroundActionButton(state)
}
}
}
@Preview(showBackground = true)
@Composable
private fun Preview_ActionButton_Dark(@PreviewParameter(ActionStateProvider::class) state: ActionConfig) {
TangemTheme {
Column {
RoundedActionButton(state)
ActionButton(state)
BackgroundActionButton(state)
}
}
}
private class ActionStateProvider : CollectionPreviewParameterProvider<ActionConfig>(
collection = listOf(
ActionConfig(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = {}),
ActionConfig(text = "Receive", iconResId = R.drawable.ic_arrow_down_24, enabled = false, onClick = {}),
),
)

View file

@ -0,0 +1,96 @@
package com.tangem.core.ui.components.buttons.common
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import com.tangem.core.ui.res.TangemTheme
@Suppress("LongParameterList")
@Composable
internal fun TangemButton(
text: String,
icon: TangemButtonIconPosition,
onClick: () -> Unit,
colors: ButtonColors,
showProgress: Boolean,
enabled: Boolean,
modifier: Modifier = Modifier,
size: TangemButtonSize = TangemButtonSize.Default,
elevation: ButtonElevation = TangemButtonsDefaults.elevation,
textStyle: TextStyle = TangemTheme.typography.button,
) {
Button(
modifier = modifier.heightIn(min = size.toHeightDp()),
onClick = { if (!showProgress) onClick() },
enabled = enabled,
elevation = elevation,
shape = size.toShape(),
colors = colors,
contentPadding = size.toContentPadding(icon = icon),
) {
ButtonContent(
text = text,
textStyle = textStyle,
buttonIcon = icon,
colors = colors,
showProgress = showProgress,
enabled = enabled,
size = size,
)
}
}
@Suppress("LongParameterList")
@Composable
private fun ButtonContent(
text: String,
textStyle: TextStyle,
buttonIcon: TangemButtonIconPosition,
colors: ButtonColors,
size: TangemButtonSize,
enabled: Boolean,
showProgress: Boolean,
) {
val icon = @Composable { iconResId: Int ->
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
painter = painterResource(id = iconResId),
tint = colors.contentColor(enabled = enabled).value,
contentDescription = null,
)
}
if (showProgress) {
Box(modifier = Modifier.wrapContentSize()) {
CircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.size(TangemTheme.dimens.size24),
color = colors.contentColor(enabled = enabled).value,
strokeWidth = TangemTheme.dimens.size4,
)
}
} else {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()),
) {
if (buttonIcon is TangemButtonIconPosition.Start) {
icon(buttonIcon.iconResId)
}
Text(
text = text,
style = textStyle,
color = colors.contentColor(enabled = enabled).value,
maxLines = 1,
)
if (buttonIcon is TangemButtonIconPosition.End) {
icon(buttonIcon.iconResId)
}
}
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.core.ui.components.buttons.common
import androidx.compose.material.ButtonColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.graphics.Color
internal class TangemButtonColors(
private val backgroundColor: Color,
private val contentColor: Color,
private val disabledBackgroundColor: Color,
private val disabledContentColor: Color,
) : ButtonColors {
@Composable
override fun backgroundColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(newValue = if (enabled) backgroundColor else disabledBackgroundColor)
}
@Composable
override fun contentColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(newValue = if (enabled) contentColor else disabledContentColor)
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.core.ui.components.buttons.common
import androidx.annotation.DrawableRes
internal sealed interface TangemButtonIconPosition {
val iconResId: Int?
data class Start(@DrawableRes override val iconResId: Int) : TangemButtonIconPosition
data class End(@DrawableRes override val iconResId: Int) : TangemButtonIconPosition
object None : TangemButtonIconPosition {
@DrawableRes
override val iconResId: Int? = null
}
}

View file

@ -0,0 +1,105 @@
package com.tangem.core.ui.components.buttons.common
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
internal enum class TangemButtonSize {
Default,
Text,
Selector,
Action,
RoundedAction,
}
@Composable
@ReadOnlyComposable
internal fun TangemButtonSize.toHeightDp(): Dp = when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.size48
TangemButtonSize.Text -> TangemTheme.dimens.size40
TangemButtonSize.Selector -> TangemTheme.dimens.size24
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> TangemTheme.dimens.size36
}
@Composable
@ReadOnlyComposable
internal fun TangemButtonSize.toShape(): Shape = when (this) {
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge
}
@Composable
@ReadOnlyComposable
internal fun TangemButtonSize.toIconPadding(): Dp = when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
TangemButtonSize.Selector -> 0.dp
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> TangemTheme.dimens.spacing8
}
@Composable
@ReadOnlyComposable
internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition): PaddingValues {
val horizontalPadding = this.toHorizontalContentPadding(icon = icon)
return when (this) {
TangemButtonSize.Default -> PaddingValues(
top = TangemTheme.dimens.spacing14,
bottom = TangemTheme.dimens.spacing14,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
TangemButtonSize.Text -> PaddingValues(
top = TangemTheme.dimens.spacing10,
bottom = TangemTheme.dimens.spacing10,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
TangemButtonSize.Selector -> PaddingValues(
top = TangemTheme.dimens.spacing0_5,
bottom = TangemTheme.dimens.spacing0_5,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> PaddingValues(
top = TangemTheme.dimens.spacing8,
bottom = TangemTheme.dimens.spacing8,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
}
}
@Composable
@ReadOnlyComposable
internal fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIconPosition): Pair<Dp, Dp> {
return when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
TangemButtonSize.Text -> when (icon) {
is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
is TangemButtonIconPosition.End -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing14
}
TangemButtonSize.Selector -> TangemTheme.dimens.spacing0_5 to TangemTheme.dimens.spacing0_5
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> when (icon) {
is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing24
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing24
is TangemButtonIconPosition.End -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing16
}
}
}

View file

@ -0,0 +1,78 @@
package com.tangem.core.ui.components.buttons.common
import androidx.compose.material.ButtonColors
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ButtonElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.res.TangemTheme
internal object TangemButtonsDefaults {
val elevation: ButtonElevation
@Composable get() = ButtonDefaults.elevation(
defaultElevation = TangemTheme.dimens.elevation0,
pressedElevation = TangemTheme.dimens.elevation0,
)
val primaryButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.button.primary,
contentColor = TangemTheme.colors.text.primary2,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val secondaryButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.button.secondary,
contentColor = TangemTheme.colors.text.primary1,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val defaultTextButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = Color.Transparent,
contentColor = TangemTheme.colors.text.secondary,
disabledBackgroundColor = Color.Transparent,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val warningTextButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = Color.Transparent,
contentColor = TangemTheme.colors.text.warning,
disabledBackgroundColor = Color.Transparent,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val selectorButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = Color.Transparent,
contentColor = TangemTheme.colors.text.tertiary,
disabledBackgroundColor = Color.Transparent,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val backgroundButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.background.primary,
contentColor = TangemTheme.colors.text.primary1,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
)
}

View file

@ -170,6 +170,14 @@ internal object WalletPreviewData {
),
)
val manageButtons = persistentListOf(
WalletManageButton.Buy(onClick = {}),
WalletManageButton.Send(onClick = {}),
WalletManageButton.Receive(onClick = {}),
WalletManageButton.Exchange(onClick = {}),
WalletManageButton.CopyAddress(onClick = {}),
)
val multicurrencyWalletScreenState = WalletStateHolder.MultiCurrencyContent(
onBackClick = {},
topBarConfig = walletTopBarConfig,
@ -273,5 +281,6 @@ internal object WalletPreviewData {
WalletNotification.NeedToBackup(onClick = {}),
WalletNotification.ScanCard(onClick = {}),
),
buttons = manageButtons,
)
}

View file

@ -21,9 +21,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.BackgroundActionButton
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.buttons.actions.ActionConfig
import com.tangem.core.ui.components.buttons.actions.BackgroundActionButton
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
@ -214,16 +215,20 @@ private fun TopBar(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
BackgroundActionButton(
config = ActionConfig(
text = stringResource(id = R.string.organize_tokens_sort_by_balance),
iconResId = R.drawable.ic_sort_24,
onClick = config.onSortByBalanceClick,
),
modifier = Modifier.weight(1f),
text = stringResource(id = R.string.organize_tokens_sort_by_balance),
iconResId = R.drawable.ic_sort_24,
onClick = config.onSortByBalanceClick,
)
BackgroundActionButton(
config = ActionConfig(
text = stringResource(id = R.string.organize_tokens_group),
iconResId = R.drawable.ic_group_24,
onClick = config.onGroupByNetworkClick,
),
modifier = Modifier.weight(1f),
text = stringResource(id = R.string.organize_tokens_group),
iconResId = R.drawable.ic_group_24,
onClick = config.onGroupByNetworkClick,
)
}
}

View file

@ -0,0 +1,79 @@
package com.tangem.feature.wallet.presentation.wallet.state
import com.tangem.core.ui.components.buttons.actions.ActionConfig
import com.tangem.feature.wallet.impl.R
/**
* Wallet manage button state
*
* @param config action config
*
[REDACTED_AUTHOR]
*/
sealed class WalletManageButton(val config: ActionConfig) {
/**
* Buy
*
* @param onClick lambda be invoked when manage button is clicked
*/
data class Buy(val onClick: () -> Unit) : WalletManageButton(
config = ActionConfig(
text = "Buy",
iconResId = R.drawable.ic_plus_24,
onClick = onClick,
),
)
/**
* Send
*
* @param onClick lambda be invoked when manage button is clicked
*/
data class Send(val onClick: () -> Unit) : WalletManageButton(
config = ActionConfig(
text = "Send",
iconResId = R.drawable.ic_arrow_up_24,
onClick = onClick,
),
)
/**
* Receive
*
* @param onClick lambda be invoked when manage button is clicked
*/
data class Receive(val onClick: () -> Unit) : WalletManageButton(
config = ActionConfig(
text = "Receive",
iconResId = R.drawable.ic_arrow_down_24,
onClick = onClick,
),
)
/**
* Exchange
*
* @param onClick lambda be invoked when manage button is clicked
*/
data class Exchange(val onClick: () -> Unit) : WalletManageButton(
config = ActionConfig(
text = "Exchange",
iconResId = R.drawable.ic_exchange_vertical_24,
onClick = onClick,
),
)
/**
* Copy address
*
* @param onClick lambda be invoked when manage button is clicked
*/
data class CopyAddress(val onClick: () -> Unit) : WalletManageButton(
config = ActionConfig(
text = "Copy address",
iconResId = R.drawable.ic_copy_24,
onClick = onClick,
),
)
}

View file

@ -0,0 +1,64 @@
package com.tangem.feature.wallet.presentation.wallet.state
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
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.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.buttons.actions.ActionButton
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import kotlinx.collections.immutable.ImmutableList
/**
* Wallet manage buttons
*
* @param buttons manage buttons
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun WalletManageButtons(buttons: ImmutableList<WalletManageButton>) {
Row(
modifier = Modifier
.horizontalScroll(state = rememberScrollState())
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
) {
buttons.forEach { button ->
key(button.config.text) {
ActionButton(config = button.config)
}
}
}
}
@Preview
@Composable
private fun Preview_WalletManageButtons_Light(
@PreviewParameter(WalletManageButtonProvider::class) buttons: ImmutableList<WalletManageButton>,
) {
TangemTheme(isDark = false) {
WalletManageButtons(buttons = buttons)
}
}
@Preview
@Composable
private fun Preview_WalletManageButtons_Dark(
@PreviewParameter(WalletManageButtonProvider::class) buttons: ImmutableList<WalletManageButton>,
) {
TangemTheme(isDark = true) {
WalletManageButtons(buttons = buttons)
}
}
private class WalletManageButtonProvider : CollectionPreviewParameterProvider<ImmutableList<WalletManageButton>>(
collection = listOf(WalletPreviewData.manageButtons),
)

View file

@ -53,6 +53,7 @@ internal sealed class WalletStateHolder(
* @property wallets list of wallets states
* @property contentItems content items
* @property notifications notifications
* @property buttons manage buttons
*/
data class SingleCurrencyContent(
override val onBackClick: () -> Unit,
@ -61,5 +62,6 @@ internal sealed class WalletStateHolder(
override val wallets: ImmutableList<WalletCardState>,
override val contentItems: ImmutableList<WalletContentItemState.SingleCurrencyItem>,
override val notifications: ImmutableList<WalletNotification>,
val buttons: ImmutableList<WalletManageButton>,
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems, notifications)
}

View file

@ -1,28 +1,26 @@
package com.tangem.feature.wallet.presentation.wallet.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.RoundedActionButton
import com.tangem.core.ui.components.buttons.actions.ActionConfig
import com.tangem.core.ui.components.buttons.actions.RoundedActionButton
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.transactions.Transaction
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
@ -30,9 +28,11 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem
import com.tangem.feature.wallet.presentation.common.component.TokenItem
import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState
import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButtons
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletCardsList
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletTopBar
import com.tangem.feature.wallet.presentation.wallet.ui.decorations.walletContentItemDecoration
/**
* Wallet screen
@ -49,15 +49,29 @@ internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifie
topBar = { WalletTopBar(config = state.topBarConfig) },
containerColor = TangemTheme.colors.background.secondary,
) { scaffoldPaddings ->
val lastContentItemIndex = remember(state.contentItems) { state.contentItems.lastIndex }
LazyColumn(
modifier = modifier
.padding(scaffoldPaddings)
.padding(paddingValues = scaffoldPaddings)
.fillMaxSize(),
contentPadding = PaddingValues(
horizontal = TangemTheme.dimens.spacing16,
vertical = TangemTheme.dimens.spacing8,
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
item { WalletCardsList(wallets = state.wallets) }
item {
WalletCardsList(
wallets = state.wallets,
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
)
}
if (state is WalletStateHolder.SingleCurrencyContent) {
item { WalletManageButtons(buttons = state.buttons) }
}
items(items = state.notifications, itemContent = { Notification(state = it.state) })
itemsIndexed(
items = state.contentItems,
@ -70,36 +84,19 @@ internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifie
is WalletContentItemState.SingleCurrencyItem.Transaction -> index
}
},
) { index, item ->
val itemModifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.clipFirstAndLastItems(index, lastContentItemIndex)
when (item) {
is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> {
NetworkGroupItem(networkName = item.networkName, modifier = itemModifier)
}
is WalletContentItemState.MultiCurrencyItem.Token -> {
TokenItem(state = item.state, modifier = itemModifier)
}
is WalletContentItemState.SingleCurrencyItem.Title -> {
SingleCurrencyTitle(config = item, modifier = itemModifier)
}
is WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle -> {
TransactionGroupTitle(config = item, modifier = itemModifier)
}
is WalletContentItemState.SingleCurrencyItem.Transaction -> {
Transaction(state = item.state, modifier = itemModifier)
}
}
}
itemContent = { index, item ->
ContentItem(currentIndex = index, lastIndex = state.contentItems.lastIndex, item = item)
},
)
if (state is WalletStateHolder.MultiCurrencyContent) {
item {
RoundedActionButton(
text = stringResource(id = R.string.organize_tokens_title),
iconResId = R.drawable.ic_filter_24,
onClick = state.onOrganizeTokensClick,
config = ActionConfig(
text = stringResource(id = R.string.organize_tokens_title),
iconResId = R.drawable.ic_filter_24,
onClick = state.onOrganizeTokensClick,
),
modifier = Modifier.padding(top = TangemTheme.dimens.spacing14),
)
}
@ -108,6 +105,29 @@ internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifie
}
}
@Composable
private fun ContentItem(currentIndex: Int, lastIndex: Int, item: WalletContentItemState) {
val itemModifier = Modifier.walletContentItemDecoration(currentIndex, lastIndex)
when (item) {
is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> {
NetworkGroupItem(networkName = item.networkName, modifier = itemModifier)
}
is WalletContentItemState.MultiCurrencyItem.Token -> {
TokenItem(state = item.state, modifier = itemModifier)
}
is WalletContentItemState.SingleCurrencyItem.Title -> {
SingleCurrencyTitle(config = item, modifier = itemModifier)
}
is WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle -> {
TransactionGroupTitle(config = item, modifier = itemModifier)
}
is WalletContentItemState.SingleCurrencyItem.Transaction -> {
Transaction(state = item.state, modifier = itemModifier)
}
}
}
@Composable
private fun SingleCurrencyTitle(
config: WalletContentItemState.SingleCurrencyItem.Title,
@ -166,31 +186,6 @@ private fun TransactionGroupTitle(
)
}
private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed {
when (index) {
0 -> {
this
.padding(top = TangemTheme.dimens.spacing14)
.clip(
RoundedCornerShape(
topStart = TangemTheme.dimens.radius16,
topEnd = TangemTheme.dimens.radius16,
),
)
}
lastItemIndex -> {
this
.clip(
RoundedCornerShape(
bottomStart = TangemTheme.dimens.radius16,
bottomEnd = TangemTheme.dimens.radius16,
),
)
}
else -> this
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Composable

View file

@ -23,21 +23,21 @@ import kotlinx.collections.immutable.persistentListOf
/**
* Wallets list
*
* @param wallets list of wallet state
* @param wallets list of wallet state
* @param modifier modifier
*
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun WalletCardsList(wallets: ImmutableList<WalletCardState>) {
internal fun WalletCardsList(wallets: ImmutableList<WalletCardState>, modifier: Modifier = Modifier) {
val horizontalCardPadding = TangemTheme.dimens.spacing16
val itemWidth = LocalConfiguration.current.screenWidthDp.dp - horizontalCardPadding * 2
val lazyListState = rememberLazyListState()
LazyRow(
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
modifier = modifier.background(color = TangemTheme.colors.background.secondary),
state = lazyListState,
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState),
) {

View file

@ -0,0 +1,36 @@
package com.tangem.feature.wallet.presentation.wallet.ui.decorations
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import com.tangem.core.ui.res.TangemTheme
/**
[REDACTED_AUTHOR]
*/
internal fun Modifier.walletContentItemDecoration(currentIndex: Int, lastItemIndex: Int): Modifier = composed {
when (currentIndex) {
0 -> {
this
.padding(top = TangemTheme.dimens.spacing14)
.clip(
RoundedCornerShape(
topStart = TangemTheme.dimens.radius16,
topEnd = TangemTheme.dimens.radius16,
),
)
}
lastItemIndex -> {
this
.clip(
RoundedCornerShape(
bottomStart = TangemTheme.dimens.radius16,
bottomEnd = TangemTheme.dimens.radius16,
),
)
}
else -> this
}
}