Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-23 12:09:30 +03:00
commit f529d886b4
35 changed files with 1591 additions and 279 deletions

View file

@ -1,7 +1,17 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@ -16,6 +26,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.components.SpacerH8
import com.tangem.tap.common.compose.extensions.stringResourceDefault
import com.tangem.wallet.R
@ -137,8 +148,7 @@ fun ToggledRippleTheme(
@Preview
@Composable
fun ButtonTest() {
Scaffold(
) {
Scaffold {
Column(modifier = Modifier.padding(16.dp)) {
PreviewItem("Button") {
RectangleButton(text = "Some button") {}

View file

@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.tangem.core.ui.components.SpacerH16
import com.tangem.domain.DomainDialog
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
@ -105,7 +106,7 @@ fun <T> SimpleDialog(
modifier = Modifier.padding(22.dp)
) {
DialogTitle(title = title)
LazyColumn() {
LazyColumn {
items(items) { item ->
Row(
modifier = Modifier
@ -154,4 +155,4 @@ fun ErrorDialog(
}
}
)
}
}

View file

@ -1,106 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
[REDACTED_AUTHOR]
*/
// ***************************** Horizontal
@Composable
fun SpacerH(height: Dp, modifier: Modifier = Modifier) {
Spacer(modifier = modifier.height(height))
}
@Composable
fun SpacerH4(modifier: Modifier = Modifier) {
SpacerH(4.dp, modifier)
}
@Composable
fun SpacerH8(modifier: Modifier = Modifier) {
SpacerH(8.dp, modifier)
}
@Composable
fun SpacerH16(modifier: Modifier = Modifier) {
SpacerH(16.dp, modifier)
}
@Composable
fun SpacerH24(modifier: Modifier = Modifier) {
SpacerH(24.dp, modifier)
}
@Composable
fun SpacerH32(modifier: Modifier = Modifier) {
SpacerH(32.dp, modifier)
}
// ***************************** Vertical
@Composable
fun SpacerW(width: Dp, modifier: Modifier = Modifier) {
Spacer(modifier = modifier.width(width))
}
@Composable
fun SpacerW4(modifier: Modifier = Modifier) {
SpacerW(4.dp, modifier)
}
@Composable
fun SpacerW8(modifier: Modifier = Modifier) {
SpacerW(8.dp, modifier)
}
@Composable
fun SpacerW16(modifier: Modifier = Modifier) {
SpacerW(16.dp, modifier)
}
@Composable
fun SpacerW24(modifier: Modifier = Modifier) {
SpacerW(24.dp, modifier)
}
@Composable
fun SpacerW32(modifier: Modifier = Modifier) {
SpacerW(32.dp, modifier)
}
// ***************************** Size
@Composable
fun SpacerS(size: Dp, modifier: Modifier = Modifier) {
Spacer(modifier = modifier.size(size))
}
@Composable
fun SpacerS4(modifier: Modifier = Modifier) {
SpacerS(4.dp, modifier)
}
@Composable
fun SpacerS8(modifier: Modifier = Modifier) {
SpacerS(8.dp, modifier)
}
@Composable
fun SpacerS16(modifier: Modifier = Modifier) {
SpacerS(16.dp, modifier)
}
@Composable
fun SpacerS24(modifier: Modifier = Modifier) {
SpacerS(24.dp, modifier)
}
@Composable
fun SpacerS32(modifier: Modifier = Modifier) {
SpacerS(32.dp, modifier)
}

View file

@ -13,6 +13,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.common.module.ModuleMessage
import com.tangem.core.ui.components.SpacerH8
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
import com.tangem.wallet.R

View file

@ -1,6 +1,7 @@
package com.tangem.tap.common.extensions
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
@ -29,12 +30,10 @@ import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.R
import timber.log.Timber
private class Navigation
fun FragmentActivity.openFragment(
screen: AppScreen,
addToBackstack: Boolean,
fgShareTransition: FragmentShareTransition? = null
fgShareTransition: FragmentShareTransition? = null,
) {
val transaction = this.supportFragmentManager.beginTransaction()
val fragment = fragmentFactory(screen)
@ -48,9 +47,14 @@ fun FragmentActivity.openFragment(
}
}
}
transaction.replace(R.id.fragment_container, fragment, screen.name)
if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null)
transaction.commitAllowingStateLoss()
if (screen.isDialogFragment) {
(fragment as DialogFragment).show(transaction, screen.name)
if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null)
} else {
transaction.replace(R.id.fragment_container, fragment, screen.name)
if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null)
transaction.commitAllowingStateLoss()
}
}
fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {

View file

@ -9,7 +9,9 @@ data class NavigationState(
val activity: WeakReference<AppCompatActivity>? = null,
) : StateType
enum class AppScreen {
enum class AppScreen(
val isDialogFragment: Boolean = false,
) {
Home,
Shop,
Disclaimer,
@ -19,5 +21,5 @@ enum class AppScreen {
Details, DetailsSecurity, CardSettings, AppSettings, ResetToFactory,
AddTokens, AddCustomToken,
WalletConnectSessions,
QrScan
QrScan,
}

View file

@ -2,7 +2,11 @@ package com.tangem.tap.features.home.compose.content
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -20,8 +24,8 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.tap.common.compose.SpacerH16
import com.tangem.tap.common.compose.SpacerH32
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH32
import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation
import com.tangem.tap.features.home.compose.StoriesTextAnimation
import com.tangem.wallet.R

View file

@ -16,7 +16,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.tangem.tap.common.compose.SpacerH
import com.tangem.core.ui.components.SpacerH12
import com.tangem.tap.common.compose.extensions.dpSize
import com.tangem.tap.common.compose.extensions.halfHeight
import com.tangem.tap.common.compose.extensions.toPx
@ -64,7 +64,7 @@ fun StoriesCurrenciesContent(
targetOffset = animateTo.toPx(),
contentDescription = "Currency row",
)
SpacerH(12.dp)
SpacerH12()
}
}
}
@ -118,7 +118,7 @@ fun StoriesWeb3Content(
@Composable
private fun LightenBox(content: @Composable () -> Unit) {
Box() {
Box {
content()
Box(
modifier = Modifier
@ -130,10 +130,10 @@ private fun LightenBox(content: @Composable () -> Unit) {
Color.White.copy(alpha = 0f),
Color.White.copy(alpha = 0.75f),
Color.White.copy(alpha = 0.95f),
Color.White
)
)
)
Color.White,
),
),
),
) {}
}
}

View file

@ -16,7 +16,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.tap.common.compose.SpacerS8
import com.tangem.core.ui.components.SpacerS8
import com.tangem.wallet.R
@Composable

View file

@ -4,7 +4,12 @@ import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
@ -13,7 +18,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.tap.common.compose.SpacerW4
import com.tangem.core.ui.components.SpacerW4
@Composable
fun StoriesProgressBar(

View file

@ -6,14 +6,25 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.SpacerH8
import com.tangem.domain.common.form.Field
import com.tangem.domain.features.addCustomToken.TokenBlockchainField
import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
import com.tangem.domain.features.addCustomToken.TokenField
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.*
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenContractAddressChanged
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDecimalsChanged
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDerivationPathChanged
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNameChanged
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNetworkChanged
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenSymbolChanged
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
import com.tangem.domain.redux.domainStore
import com.tangem.tap.common.compose.*
import com.tangem.tap.common.compose.BlockchainSpinner
import com.tangem.tap.common.compose.ClearButton
import com.tangem.tap.common.compose.ClosePopupTrigger
import com.tangem.tap.common.compose.OutlinedTextFieldWidget
import com.tangem.tap.common.compose.PasteButton
import com.tangem.tap.common.compose.TitleSubtitle
import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.wallet.R

View file

@ -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)
}

View file

@ -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

View file

@ -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,
)
}

View file

@ -1,12 +1,17 @@
@file:Suppress("unused")
package com.tangem.core.ui.components
import androidx.annotation.DimenRes
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun VerticalSpacer(@DimenRes spaceResId: Int) {
@ -16,4 +21,105 @@ fun VerticalSpacer(@DimenRes spaceResId: Int) {
@Composable
fun HorizontalSpacer(@DimenRes spaceResId: Int) {
Spacer(modifier = Modifier.width(dimensionResource(id = spaceResId)))
}
}
// region Horizontal
@Composable
fun SpacerH(height: Dp, modifier: Modifier = Modifier) {
Spacer(modifier = modifier.height(height))
}
@Composable
fun SpacerH4(modifier: Modifier = Modifier) {
SpacerH(4.dp, modifier)
}
@Composable
fun SpacerH8(modifier: Modifier = Modifier) {
SpacerH(8.dp, modifier)
}
@Composable
fun SpacerH12(modifier: Modifier = Modifier) {
SpacerH(12.dp, modifier)
}
@Composable
fun SpacerH16(modifier: Modifier = Modifier) {
SpacerH(16.dp, modifier)
}
@Composable
fun SpacerH24(modifier: Modifier = Modifier) {
SpacerH(24.dp, modifier)
}
@Composable
fun SpacerH32(modifier: Modifier = Modifier) {
SpacerH(32.dp, modifier)
}
// endregion Horizontal
// region Vertical
@Composable
fun SpacerW(width: Dp, modifier: Modifier = Modifier) {
Spacer(modifier = modifier.width(width))
}
@Composable
fun SpacerW4(modifier: Modifier = Modifier) {
SpacerW(4.dp, modifier)
}
@Composable
fun SpacerW8(modifier: Modifier = Modifier) {
SpacerW(8.dp, modifier)
}
@Composable
fun SpacerW16(modifier: Modifier = Modifier) {
SpacerW(16.dp, modifier)
}
@Composable
fun SpacerW24(modifier: Modifier = Modifier) {
SpacerW(24.dp, modifier)
}
@Composable
fun SpacerW32(modifier: Modifier = Modifier) {
SpacerW(32.dp, modifier)
}
// endregion Vertical
// region Size
@Composable
fun SpacerS(size: Dp, modifier: Modifier = Modifier) {
Spacer(modifier = modifier.size(size))
}
@Composable
fun SpacerS4(modifier: Modifier = Modifier) {
SpacerS(4.dp, modifier)
}
@Composable
fun SpacerS8(modifier: Modifier = Modifier) {
SpacerS(8.dp, modifier)
}
@Composable
fun SpacerS16(modifier: Modifier = Modifier) {
SpacerS(16.dp, modifier)
}
@Composable
fun SpacerS24(modifier: Modifier = Modifier) {
SpacerS(24.dp, modifier)
}
@Composable
fun SpacerS32(modifier: Modifier = Modifier) {
SpacerS(32.dp, modifier)
}
// endregion Size

View file

@ -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) }
}

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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,
)
}

View file

@ -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,
)
}

View 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>

View 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>

View 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>

View file

@ -5,7 +5,7 @@
<string name="shop_2_cards">2 карты</string>
<string name="shop_shipping">Доставка</string>
<string name="shop_free">Бесплатно</string>
<string name="shop_i_have_a_promo_code">У меня есть промо-код...</string>
<string name="shop_i_have_a_promo_code">У меня есть промо-код</string>
<string name="shop_total">Итого</string>
<string name="shop_other_payment_methods">Другие способы оплаты</string>
<string name="shop_buy_now">Купить сейчас</string>

View file

@ -5,7 +5,7 @@
<string name="shop_2_cards">2 cards</string>
<string name="shop_shipping">Shipping</string>
<string name="shop_free">Free</string>
<string name="shop_i_have_a_promo_code">I have a promo code...</string>
<string name="shop_i_have_a_promo_code">I have a promo code</string>
<string name="shop_total">Total</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_buy_now">Buy now</string>

View 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>