Updated on 2026-08-14
This commit is contained in:
parent
9cff1bbb89
commit
48ce9b4fd2
15 changed files with 1429 additions and 149 deletions
|
|
@ -30,6 +30,8 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
/** AndroidX libraries */
|
||||
implementation(AndroidX.fragmentKtx)
|
||||
|
||||
/** Compose */
|
||||
implementation(Compose.foundation)
|
||||
|
|
@ -38,4 +40,5 @@ dependencies {
|
|||
|
||||
/** Other libraries */
|
||||
implementation(Library.accompanistSystemUiController)
|
||||
implementation(Library.materialComponent)
|
||||
}
|
||||
|
|
@ -1,183 +1,582 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.ButtonElevation
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.ButtonColorType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TextColorType
|
||||
import com.tangem.core.ui.res.buttonColor
|
||||
import com.tangem.core.ui.res.textColor
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A103&t=TmfD6UBHPg9uYfev-4)
|
||||
* */
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
PrimaryStartIconButton(
|
||||
text = "Manage tokens",
|
||||
iconResId = R.drawable.ic_tangem,
|
||||
enabled = false,
|
||||
onClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
PrimaryStartIconButton(
|
||||
text = "Manage tokens",
|
||||
iconResId = R.drawable.ic_tangem,
|
||||
enabled = false,
|
||||
onClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {})
|
||||
}
|
||||
fun TextButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.textButtonColors,
|
||||
size = TangemButtonSize.Text,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary button with an icon at the beginning of the layout
|
||||
*
|
||||
* @param modifier button modifier
|
||||
* @param text button text
|
||||
* @param iconResId button icon res id
|
||||
* @param enabled controls the enabled state of the button
|
||||
* @param onClicked the lambda to be invoked when this button is pressed
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A258&t=WdN5XpixzZLlQAZO-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A62&t=TmfD6UBHPg9uYfev-4)
|
||||
* */
|
||||
@Composable
|
||||
fun PrimaryStartIconButton(
|
||||
fun TextButtonIconLeft(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
icon: Painter,
|
||||
enabled: Boolean = true,
|
||||
onClicked: () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8)))
|
||||
Text(text = text)
|
||||
}
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Left(icon),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.textButtonColors,
|
||||
size = TangemButtonSize.Text,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PrimaryButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary button with an icon at the end of the layout
|
||||
*
|
||||
* @param modifier button modifier
|
||||
* @param text button text
|
||||
* @param iconResId button icon res id
|
||||
* @param enabled controls the enabled state of the button
|
||||
* @param onClicked the lambda to be invoked when this button is pressed
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=68%3A47&t=WdN5XpixzZLlQAZO-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=68%3A47&t=TmfD6UBHPg9uYfev-4)
|
||||
* */
|
||||
@Composable
|
||||
fun PrimaryEndIconButton(
|
||||
fun PrimaryButtonIconRight(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
icon: Painter,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
onClicked: () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) {
|
||||
Text(text = text)
|
||||
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8)))
|
||||
Icon(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Right(icon),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A258&t=TmfD6UBHPg9uYfev-4)
|
||||
* */
|
||||
@Composable
|
||||
fun PrimaryButtonIconLeft(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
icon: Painter,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Left(icon),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PrimaryButtonRow(
|
||||
modifier: Modifier,
|
||||
fun SecondaryButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=99%3A50&t=TmfD6UBHPg9uYfev-4)
|
||||
* */
|
||||
@Composable
|
||||
fun SecondaryButtonIconRight(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
icon: Painter,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Right(icon),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A262&t=TmfD6UBHPg9uYfev-4)
|
||||
* */
|
||||
@Composable
|
||||
fun SecondaryButtonIconLeft(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
icon: Painter,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Left(icon),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
|
||||
// region Defaults
|
||||
@Composable
|
||||
private fun TangemButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
icon: TangemButtonIcon,
|
||||
onClick: () -> Unit,
|
||||
colors: ButtonColors,
|
||||
showProgress: Boolean,
|
||||
enabled: Boolean,
|
||||
onClicked: () -> Unit,
|
||||
content: @Composable (RowScope.() -> Unit),
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
elevation: ButtonElevation = TangemButtonsDefaults.elevation,
|
||||
) {
|
||||
Button(
|
||||
onClick = onClicked,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(dimensionResource(R.dimen.size48)),
|
||||
.width(IntrinsicSize.Min)
|
||||
.height(IntrinsicSize.Min)
|
||||
.heightIn(size.toHeightDp()),
|
||||
onClick = {
|
||||
if (!showProgress) {
|
||||
onClick()
|
||||
}
|
||||
},
|
||||
enabled = enabled,
|
||||
shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY),
|
||||
contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2),
|
||||
disabledBackgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.DISABLED),
|
||||
disabledContentColor = MaterialTheme.colors.textColor(type = TextColorType.DISABLED),
|
||||
),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
elevation = elevation,
|
||||
shape = size.toShape(),
|
||||
colors = colors,
|
||||
) {
|
||||
ButtonContent(
|
||||
text = text,
|
||||
buttonIcon = icon,
|
||||
colors = colors,
|
||||
showProgress = showProgress,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ButtonContent(
|
||||
text: String,
|
||||
buttonIcon: TangemButtonIcon,
|
||||
colors: ButtonColors,
|
||||
enabled: Boolean,
|
||||
showProgress: Boolean,
|
||||
) {
|
||||
val icon = @Composable { painter: Painter ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
painter = painter,
|
||||
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 {
|
||||
if (buttonIcon is TangemButtonIcon.Left) {
|
||||
icon(buttonIcon.painter)
|
||||
Spacer(modifier = Modifier.width(TangemTheme.dimens.size8))
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
style = TangemTypography.button,
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
)
|
||||
if (buttonIcon is TangemButtonIcon.Right) {
|
||||
Spacer(modifier = Modifier.width(TangemTheme.dimens.size8))
|
||||
icon(buttonIcon.painter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface TangemButtonIcon {
|
||||
val painter: Painter?
|
||||
|
||||
@Immutable
|
||||
data class Left(override val painter: Painter) : TangemButtonIcon
|
||||
|
||||
@Immutable
|
||||
data class Right(override val painter: Painter) : TangemButtonIcon
|
||||
|
||||
object None : TangemButtonIcon {
|
||||
override val painter: Painter? = null
|
||||
}
|
||||
}
|
||||
|
||||
enum class TangemButtonSize {
|
||||
Default,
|
||||
Text,
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemButtonSize.toHeightDp(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.size48
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.size40
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemButtonSize.toShape(): Shape = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
|
||||
}
|
||||
|
||||
private object TangemButtonsDefaults {
|
||||
val elevation: ButtonElevation
|
||||
@Composable get() = ButtonDefaults
|
||||
.elevation(
|
||||
defaultElevation = TangemTheme.dimens.elevation0,
|
||||
pressedElevation = TangemTheme.dimens.elevation0,
|
||||
)
|
||||
|
||||
val primaryButtonColors: ButtonColors
|
||||
@Composable 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 get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.button.secondary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val textButtonColors: ButtonColors
|
||||
@Composable get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.secondary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private 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(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
showProgress = true,
|
||||
text = "Manage tokens",
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
PrimaryButtonIconRight(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
PrimaryButtonIconLeft(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
enabled = false,
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
PrimaryButtonIconRight(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
enabled = false,
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
PrimaryButtonIconLeft(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
enabled = false,
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun PrimaryButtonPreview_Light() {
|
||||
TangemTheme {
|
||||
PrimaryButtonSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun PrimaryButtonPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
PrimaryButtonSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SecondaryButtonSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
SecondaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
SecondaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
showProgress = true,
|
||||
text = "Manage tokens",
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
SecondaryButtonIconRight(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
SecondaryButtonIconLeft(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
SecondaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
enabled = false,
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
SecondaryButtonIconRight(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
enabled = false,
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
SecondaryButtonIconLeft(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Manage tokens",
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
enabled = false,
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SecondaryButtonPreview_Light() {
|
||||
TangemTheme {
|
||||
SecondaryButtonSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SecondaryButtonPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
SecondaryButtonSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextButtonSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
TextButton(
|
||||
text = "Enabled",
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
TextButtonIconLeft(
|
||||
text = "Enabled",
|
||||
icon = painterResource(id = R.drawable.ic_plus),
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
TextButton(
|
||||
text = "Enabled",
|
||||
enabled = false,
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
TextButtonIconLeft(
|
||||
text = "Enabled",
|
||||
icon = painterResource(id = R.drawable.ic_plus),
|
||||
enabled = false,
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun TextButtonPreview_Light() {
|
||||
TangemTheme {
|
||||
TextButtonSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun TextButtonPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
TextButtonSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.ButtonColorType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TextColorType
|
||||
import com.tangem.core.ui.res.buttonColor
|
||||
import com.tangem.core.ui.res.textColor
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
PrimaryStartIconButton(
|
||||
text = "Manage tokens",
|
||||
iconResId = R.drawable.ic_tangem,
|
||||
enabled = false,
|
||||
onClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
PrimaryStartIconButton(
|
||||
text = "Manage tokens",
|
||||
iconResId = R.drawable.ic_tangem,
|
||||
enabled = false,
|
||||
onClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 48, showBackground = true)
|
||||
@Composable
|
||||
fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary button with an icon at the beginning of the layout
|
||||
*
|
||||
* @param modifier button modifier
|
||||
* @param text button text
|
||||
* @param iconResId button icon res id
|
||||
* @param enabled controls the enabled state of the button
|
||||
* @param onClicked the lambda to be invoked when this button is pressed
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A258&t=WdN5XpixzZLlQAZO-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Deprecated("Use PrimaryButtonIconRight instead")
|
||||
@Composable
|
||||
fun PrimaryStartIconButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
enabled: Boolean = true,
|
||||
onClicked: () -> Unit,
|
||||
) {
|
||||
PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8)))
|
||||
Text(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary button with an icon at the end of the layout
|
||||
*
|
||||
* @param modifier button modifier
|
||||
* @param text button text
|
||||
* @param iconResId button icon res id
|
||||
* @param enabled controls the enabled state of the button
|
||||
* @param onClicked the lambda to be invoked when this button is pressed
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=68%3A47&t=WdN5XpixzZLlQAZO-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Deprecated("Use PrimaryButtonIconLeft instead")
|
||||
@Composable
|
||||
fun PrimaryEndIconButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
enabled: Boolean = true,
|
||||
onClicked: () -> Unit,
|
||||
) {
|
||||
PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) {
|
||||
Text(text = text)
|
||||
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8)))
|
||||
Icon(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PrimaryButtonRow(
|
||||
modifier: Modifier,
|
||||
enabled: Boolean,
|
||||
onClicked: () -> Unit,
|
||||
content: @Composable (RowScope.() -> Unit),
|
||||
) {
|
||||
Button(
|
||||
onClick = onClicked,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(dimensionResource(R.dimen.size48)),
|
||||
enabled = enabled,
|
||||
shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY),
|
||||
contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2),
|
||||
disabledBackgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.DISABLED),
|
||||
disabledContentColor = MaterialTheme.colors.textColor(type = TextColorType.DISABLED),
|
||||
),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import com.google.accompanist.systemuicontroller.SystemUiController
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
|
||||
@Composable
|
||||
fun SystemBarsEffect(
|
||||
block: SystemUiController.() -> Unit,
|
||||
) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
SideEffect { block(systemUiController) }
|
||||
}
|
||||
|
|
@ -0,0 +1,414 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.interaction.InteractionSource
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextFieldColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
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.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
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.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=213%3A218&t=TmfD6UBHPg9uYfev-4)
|
||||
* */
|
||||
@Composable
|
||||
fun OutlineTextField(
|
||||
modifier: Modifier = Modifier,
|
||||
value: TextFieldValue,
|
||||
onValueChange: (TextFieldValue) -> Unit,
|
||||
label: String? = null,
|
||||
placeholder: String? = null,
|
||||
caption: String? = null,
|
||||
enabled: Boolean = true,
|
||||
isError: Boolean = false,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions(),
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
TangemTextField(
|
||||
modifier = modifier,
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
singleLine = true,
|
||||
label = label,
|
||||
placeholder = placeholder,
|
||||
caption = caption,
|
||||
enabled = enabled,
|
||||
isError = isError,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
}
|
||||
|
||||
// region Defaults
|
||||
@Composable
|
||||
private fun TangemTextField(
|
||||
modifier: Modifier = Modifier,
|
||||
value: TextFieldValue,
|
||||
singleLine: Boolean,
|
||||
onValueChange: (TextFieldValue) -> Unit,
|
||||
label: String? = null,
|
||||
placeholder: String? = null,
|
||||
caption: String? = null,
|
||||
enabled: Boolean = true,
|
||||
isError: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions(),
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
colors: TangemTextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
size: TangemTextFieldSize = TangemTextFieldSize.Default,
|
||||
onClear: () -> Unit = { onValueChange(TextFieldValue()) },
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
.heightIn(size.toHeightDp()),
|
||||
value = value,
|
||||
textStyle = TangemTypography.body1,
|
||||
onValueChange = onValueChange,
|
||||
singleLine = singleLine,
|
||||
enabled = enabled,
|
||||
isError = isError,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardActions = keyboardActions,
|
||||
keyboardOptions = keyboardOptions,
|
||||
interactionSource = interactionSource,
|
||||
shape = size.toShape(),
|
||||
colors = colors,
|
||||
label = {
|
||||
if (!label.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = label,
|
||||
style = TangemTypography.caption,
|
||||
color = colors.labelColor(
|
||||
enabled = enabled,
|
||||
error = isError,
|
||||
interactionSource = interactionSource,
|
||||
).value,
|
||||
)
|
||||
}
|
||||
},
|
||||
placeholder = {
|
||||
if (!placeholder.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = placeholder,
|
||||
style = TangemTypography.body1,
|
||||
color = colors.placeholderColor(enabled = enabled).value,
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
val iconRes by rememberUpdatedState(
|
||||
newValue = if (isError) R.drawable.ic_alert else R.drawable.ic_close,
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.size(32.dp),
|
||||
onClick = onClear,
|
||||
enabled = !isError && enabled,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
painter = painterResource(id = iconRes),
|
||||
tint = colors.trailingIconColor(enabled = enabled, isError = isError).value,
|
||||
contentDescription = "Clear input",
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = !caption.isNullOrEmpty(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
if (caption.isNullOrEmpty()) return@AnimatedVisibility
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = caption,
|
||||
style = TangemTypography.body1,
|
||||
color = colors.captionColor(enabled = enabled, isError = isError).value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class TangemTextFieldSize {
|
||||
Default
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemTextFieldSize.toHeightDp(): Dp = when (this) {
|
||||
TangemTextFieldSize.Default -> TangemTheme.dimens.size56
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemTextFieldSize.toShape(): Shape = when (this) {
|
||||
TangemTextFieldSize.Default -> TangemTheme.shapes.roundedCornersSmall2
|
||||
}
|
||||
|
||||
private object TangemTextFieldsDefault {
|
||||
val defaultTextFieldColors: TangemTextFieldColors
|
||||
@Composable @Stable get() = TangemTextFieldColors(
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
disabledTextColor = TangemTheme.colors.text.disabled,
|
||||
backgroundColor = Color.Transparent,
|
||||
cursorColor = TangemTheme.colors.icon.primary1,
|
||||
errorCursorColor = TangemTheme.colors.icon.warning,
|
||||
focusedIndicatorColor = TangemTheme.colors.icon.primary1,
|
||||
unfocusedIndicatorColor = TangemTheme.colors.stroke.primary,
|
||||
disabledIndicatorColor = TangemTheme.colors.stroke.primary,
|
||||
errorIndicatorColor = TangemTheme.colors.icon.warning,
|
||||
leadingIconColor = TangemTheme.colors.icon.informative,
|
||||
disabledLeadingIconColor = Color.Transparent,
|
||||
errorLeadingIconColor = TangemTheme.colors.icon.warning,
|
||||
trailingIconColor = TangemTheme.colors.icon.informative,
|
||||
disabledTrailingIconColor = Color.Transparent,
|
||||
errorTrailingIconColor = TangemTheme.colors.icon.warning,
|
||||
focusedLabelColor = TangemTheme.colors.text.primary1,
|
||||
unfocusedLabelColor = TangemTheme.colors.text.secondary,
|
||||
disabledLabelColor = TangemTheme.colors.text.disabled,
|
||||
errorLabelColor = TangemTheme.colors.icon.warning,
|
||||
placeholderColor = TangemTheme.colors.text.secondary,
|
||||
disabledPlaceholderColor = TangemTheme.colors.text.disabled,
|
||||
captionColor = TangemTheme.colors.text.tertiary,
|
||||
disabledCaptionColor = TangemTheme.colors.text.disabled,
|
||||
errorCaptionColor = TangemTheme.colors.icon.warning,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private data class TangemTextFieldColors(
|
||||
private val textColor: Color,
|
||||
private val disabledTextColor: Color,
|
||||
private val cursorColor: Color,
|
||||
private val errorCursorColor: Color,
|
||||
private val focusedIndicatorColor: Color,
|
||||
private val unfocusedIndicatorColor: Color,
|
||||
private val errorIndicatorColor: Color,
|
||||
private val disabledIndicatorColor: Color,
|
||||
private val leadingIconColor: Color,
|
||||
private val disabledLeadingIconColor: Color,
|
||||
private val errorLeadingIconColor: Color,
|
||||
private val trailingIconColor: Color,
|
||||
private val disabledTrailingIconColor: Color,
|
||||
private val errorTrailingIconColor: Color,
|
||||
private val backgroundColor: Color,
|
||||
private val focusedLabelColor: Color,
|
||||
private val unfocusedLabelColor: Color,
|
||||
private val disabledLabelColor: Color,
|
||||
private val errorLabelColor: Color,
|
||||
private val placeholderColor: Color,
|
||||
private val disabledPlaceholderColor: Color,
|
||||
private val captionColor: Color,
|
||||
private val disabledCaptionColor: Color,
|
||||
private val errorCaptionColor: Color,
|
||||
) : TextFieldColors {
|
||||
|
||||
@Composable
|
||||
override fun leadingIconColor(enabled: Boolean, isError: Boolean): State<Color> {
|
||||
return rememberUpdatedState(
|
||||
when {
|
||||
!enabled -> disabledLeadingIconColor
|
||||
isError -> errorLeadingIconColor
|
||||
else -> leadingIconColor
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun trailingIconColor(enabled: Boolean, isError: Boolean): State<Color> {
|
||||
return rememberUpdatedState(
|
||||
when {
|
||||
!enabled -> disabledTrailingIconColor
|
||||
isError -> errorTrailingIconColor
|
||||
else -> trailingIconColor
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun indicatorColor(
|
||||
enabled: Boolean,
|
||||
isError: Boolean,
|
||||
interactionSource: InteractionSource,
|
||||
): State<Color> {
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
|
||||
val targetValue = when {
|
||||
!enabled -> disabledIndicatorColor
|
||||
isError -> errorIndicatorColor
|
||||
focused -> focusedIndicatorColor
|
||||
else -> unfocusedIndicatorColor
|
||||
}
|
||||
return if (enabled) {
|
||||
animateColorAsState(targetValue, tween(durationMillis = 120))
|
||||
} else {
|
||||
rememberUpdatedState(targetValue)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun backgroundColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(backgroundColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun placeholderColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun labelColor(
|
||||
enabled: Boolean,
|
||||
error: Boolean,
|
||||
interactionSource: InteractionSource,
|
||||
): State<Color> {
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
|
||||
val targetValue = when {
|
||||
!enabled -> disabledLabelColor
|
||||
error -> errorLabelColor
|
||||
focused -> focusedLabelColor
|
||||
else -> unfocusedLabelColor
|
||||
}
|
||||
return rememberUpdatedState(targetValue)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun textColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(if (enabled) textColor else disabledTextColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun cursorColor(isError: Boolean): State<Color> {
|
||||
return rememberUpdatedState(if (isError) errorCursorColor else cursorColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun captionColor(enabled: Boolean, isError: Boolean): State<Color> {
|
||||
return rememberUpdatedState(
|
||||
newValue = when {
|
||||
!enabled -> disabledCaptionColor
|
||||
isError -> errorCaptionColor
|
||||
else -> captionColor
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion Defaults
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun OutlineTextFieldSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
val text = TextFieldValue(text = "Input")
|
||||
OutlineTextField(
|
||||
value = text,
|
||||
onValueChange = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
OutlineTextField(
|
||||
value = text,
|
||||
onValueChange = { /* no-op */ },
|
||||
label = "Default",
|
||||
placeholder = "Placeholder",
|
||||
caption = "Supporting text",
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
OutlineTextField(
|
||||
value = text,
|
||||
onValueChange = { /* no-op */ },
|
||||
enabled = false,
|
||||
label = "Disabled",
|
||||
placeholder = "Placeholder",
|
||||
caption = "Supporting text",
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
OutlineTextField(
|
||||
value = text,
|
||||
onValueChange = { /* no-op */ },
|
||||
isError = true,
|
||||
label = "Error",
|
||||
placeholder = "Placeholder",
|
||||
caption = "Supporting text",
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
OutlineTextField(
|
||||
value = TextFieldValue(),
|
||||
onValueChange = { /* no-op */ },
|
||||
label = "Default without value",
|
||||
placeholder = "Placeholder",
|
||||
caption = "Supporting text",
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun OutlineTextFieldPreview_Light() {
|
||||
TangemTheme {
|
||||
OutlineTextFieldSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun OutlineTextFieldPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
OutlineTextFieldSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -23,6 +23,7 @@ import com.tangem.core.ui.res.iconColor
|
|||
* @see <a href="https://www.figma.com/file/17JyRbuUEZ42DluaFEuGQk/Atoms?node-id=135%3A25&t=3dvxYMZBox4jxJc1-4">
|
||||
* Figma Component</a>
|
||||
*/
|
||||
@Deprecated(message = "Use Hand component instead")
|
||||
@Composable
|
||||
fun BottomSheetIndicator() {
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
package com.tangem.core.ui.components.atoms
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/17JyRbuUEZ42DluaFEuGQk/Atoms?node-id=135%3A25&t=Jo68S6ilyewVU8MV-4)
|
||||
* */
|
||||
@Composable
|
||||
fun Hand(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing8)
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(TangemTheme.dimens.size32)
|
||||
.height(TangemTheme.dimens.size4)
|
||||
.background(
|
||||
color = TangemTheme.colors.icon.inactive,
|
||||
shape = TangemTheme.shapes.roundedCornersSmall,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun HandSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Hand()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun HandPreview_Light() {
|
||||
TangemTheme {
|
||||
HandSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun HandPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
HandSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.tangem.core.ui.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
abstract class ComposeBottomSheetFragment<ScreenState> : BottomSheetDialogFragment() {
|
||||
open val initialBottomSheetState = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
@FloatRange(from = 0.0, to = 1.0)
|
||||
open val expandedHeightFraction: Float? = null
|
||||
|
||||
override fun getTheme(): Int = R.style.AppTheme_TransparentBottomSheetDialog
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
return createComposeView(inflater.context)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
|
||||
(dialog as BottomSheetDialog).behavior.apply {
|
||||
state = initialBottomSheetState
|
||||
skipCollapsed = true
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
private fun createComposeView(context: Context): View {
|
||||
return ComposeView(context).apply {
|
||||
setContent {
|
||||
TangemTheme {
|
||||
val backgroundColor = TangemTheme.colors.background.plain
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(
|
||||
color = backgroundColor,
|
||||
)
|
||||
}
|
||||
|
||||
ScreenContent(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.let {
|
||||
if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it
|
||||
}
|
||||
.background(
|
||||
color = backgroundColor,
|
||||
shape = TangemTheme.shapes.bottomSheet,
|
||||
),
|
||||
state = provideState().value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
protected abstract fun provideState(): State<ScreenState>
|
||||
|
||||
@Composable
|
||||
protected abstract fun ScreenContent(
|
||||
modifier: Modifier,
|
||||
state: ScreenState,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.core.ui.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
abstract class ComposeFragment<ScreenState> : Fragment() {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
return createComposeView(inflater.context)
|
||||
}
|
||||
|
||||
private fun createComposeView(context: Context): View {
|
||||
return ComposeView(context).apply {
|
||||
setContent {
|
||||
TangemTheme {
|
||||
val backgroundColor = TangemTheme.colors.background.primary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(
|
||||
color = backgroundColor,
|
||||
)
|
||||
}
|
||||
|
||||
ScreenContent(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = backgroundColor),
|
||||
state = provideState().value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
protected abstract fun provideState(): State<ScreenState>
|
||||
|
||||
@Composable
|
||||
protected abstract fun ScreenContent(
|
||||
modifier: Modifier,
|
||||
state: ScreenState,
|
||||
)
|
||||
}
|
||||
10
core/ui/src/main/res/drawable/ic_alert.xml
Normal file
10
core/ui/src/main/res/drawable/ic_alert.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M11,15H13V17H11V15ZM11,7H13V13H11V7ZM12,2C6.47,2 2,6.5 2,12C2,14.652 3.054,17.196 4.929,19.071C5.858,20 6.96,20.736 8.173,21.239C9.386,21.741 10.687,22 12,22C14.652,22 17.196,20.946 19.071,19.071C20.946,17.196 22,14.652 22,12C22,10.687 21.741,9.386 21.239,8.173C20.736,6.96 20,5.858 19.071,4.929C18.142,4 17.04,3.264 15.827,2.761C14.614,2.259 13.313,2 12,2ZM12,20C9.878,20 7.843,19.157 6.343,17.657C4.843,16.157 4,14.122 4,12C4,9.878 4.843,7.843 6.343,6.343C7.843,4.843 9.878,4 12,4C14.122,4 16.157,4.843 17.657,6.343C19.157,7.843 20,9.878 20,12C20,14.122 19.157,16.157 17.657,17.657C16.157,19.157 14.122,20 12,20Z" />
|
||||
</vector>
|
||||
10
core/ui/src/main/res/drawable/ic_close.xml
Normal file
10
core/ui/src/main/res/drawable/ic_close.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
|
||||
</vector>
|
||||
10
core/ui/src/main/res/drawable/ic_plus.xml
Normal file
10
core/ui/src/main/res/drawable/ic_plus.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
|
||||
</vector>
|
||||
12
core/ui/src/main/res/values/styles.xml
Normal file
12
core/ui/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme.TransparentBottomSheetDialog" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
|
||||
<item name="bottomSheetStyle">@style/TransparentBottomSheetDialog</item>
|
||||
<item name="backgroundTint">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="TransparentBottomSheetDialog" parent="Widget.MaterialComponents.BottomSheet">
|
||||
<item name="backgroundTint">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue