Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-22 12:48:29 +03:00
commit a1b910cc7e
149 changed files with 4442 additions and 1330 deletions

View file

@ -0,0 +1,88 @@
package com.tangem.core.ui.components
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
@Suppress("MagicNumber")
@Composable
fun TangemSwitch(
onCheckedChange: (Boolean) -> Unit,
checkedColor: Color = TangemTheme.colors.icon.accent,
uncheckedColor: Color = TangemTheme.colors.icon.informative,
size: Dp = 48.dp,
checked: Boolean = false,
enabled: Boolean = true,
) {
val transition = updateTransition(checked, label = "SwitchState")
val color by transition.animateColor(
transitionSpec = {
tween(durationMillis = 200, easing = FastOutLinearInEasing)
},
label = "",
) { isChecked ->
(if (isChecked) checkedColor else uncheckedColor)
.copy(alpha = if (enabled) 1f else .4f)
}
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier = Modifier
.clickable(
interactionSource = interactionSource,
indication = null,
enabled = enabled,
) {
onCheckedChange(!checked)
}
.indication(
interactionSource = interactionSource,
indication = rememberRipple(
bounded = false,
color = Color.Transparent,
),
),
) {
BoxWithConstraints(
modifier = Modifier
.width(size)
.height(size / 2)
.indication(interactionSource, null)
.background(color = color, shape = RoundedCornerShape(100)),
contentAlignment = Alignment.CenterStart,
) {
val roundCardSize = this.maxWidth / 2
val xOffset by transition.animateDp(
transitionSpec = {
tween(durationMillis = 150, easing = LinearOutSlowInEasing)
},
label = "xOffset",
) { enabled ->
if (enabled) this.maxWidth - roundCardSize else 0.dp
}
Box(
modifier = Modifier
.size(this.maxWidth / 2)
.offset(x = xOffset, y = 0.dp)
.padding(3.dp)
.background(color = Color.White, shape = RoundedCornerShape(100)),
)
}
}
}

View file

@ -0,0 +1,103 @@
package com.tangem.core.ui.components.appbar
import androidx.annotation.DrawableRes
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.TangemTheme
@Composable
fun AppBarWithBackButtonAndIcon(
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
text: String? = null,
@DrawableRes backIconRes: Int? = null,
@DrawableRes iconRes: Int? = null,
onIconClick: (() -> Unit)? = null,
backgroundColor: Color = TangemTheme.colors.background.secondary,
) {
Row(
modifier = modifier
.background(color = backgroundColor)
.fillMaxWidth()
.padding(all = TangemTheme.dimens.spacing16),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = painterResource(backIconRes ?: R.drawable.ic_back_24),
contentDescription = null,
modifier = Modifier
.size(size = TangemTheme.dimens.size24)
.clickable { onBackClick() },
tint = TangemTheme.colors.icon.primary1,
)
AnimatedContent(
targetState = text,
modifier = Modifier.weight(1f),
transitionSpec = { fadeIn().togetherWith(fadeOut()) },
label = "Toolbar title change",
) {
if (!it.isNullOrBlank()) {
Text(
text = it,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
style = TangemTheme.typography.subtitle1,
)
}
}
AnimatedContent(
targetState = iconRes,
transitionSpec = { (fadeIn() + scaleIn()).togetherWith(fadeOut() + scaleOut()) },
label = "Toolbar icon change",
) {
if (onIconClick != null && it != null) {
Icon(
painter = painterResource(it),
contentDescription = null,
modifier = Modifier
.size(size = TangemTheme.dimens.size24)
.clickable { onIconClick() },
tint = TangemTheme.colors.icon.primary1,
)
}
}
}
}
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
@Composable
private fun PreviewAppBarWithBackButtonAndIconInLightTheme() {
TangemTheme(isDark = false) {
AppBarWithBackButtonAndIcon(
text = "Title",
iconRes = R.drawable.ic_qrcode_scan_24,
onBackClick = {},
onIconClick = {},
)
}
}
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
@Composable
private fun PreviewAppBarWithBackButtonAndIconInDarkTheme() {
TangemTheme(isDark = true) {
AppBarWithBackButtonAndIcon(
text = "Title",
iconRes = R.drawable.ic_qrcode_scan_24,
onBackClick = {},
onIconClick = {},
)
}
}

View file

@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Shape
@ -108,7 +109,7 @@ private inline fun RowScope.ButtonContentContainer(
if (showProgress) {
progressIndicator()
} else {
Column {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Row(horizontalArrangement = Arrangement.Center) {
if (buttonIcon is TangemButtonIconPosition.Start) {
icon(buttonIcon.iconResId)

View file

@ -9,6 +9,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon
@ -19,16 +21,19 @@ import kotlinx.coroutines.launch
@Composable
internal inline fun DefaultCurrencyIcon(
iconData: Any,
size: Dp,
alpha: Float,
colorFilter: ColorFilter?,
crossinline errorIcon: @Composable () -> Unit,
modifier: Modifier = Modifier,
) {
var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) }
var isBackgroundColorDefined by remember { mutableStateOf(false) }
val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb()
val isDarkTheme = isSystemInDarkTheme()
val coroutineScope = rememberCoroutineScope()
val pixelsSize = with(LocalDensity.current) { size.roundToPx() }
SubcomposeAsyncImage(
modifier = modifier
.background(
@ -38,17 +43,21 @@ internal inline fun DefaultCurrencyIcon(
.clip(TangemTheme.shapes.roundedCorners8),
model = ImageRequest.Builder(context = LocalContext.current)
.data(iconData)
.size(size = pixelsSize)
.memoryCacheKey(key = iconData.toString() + pixelsSize)
.crossfade(enable = true)
.allowHardware(false)
.allowHardware(enable = false)
.listener(
onSuccess = { _, result ->
if (isDarkTheme) {
if (!isBackgroundColorDefined && isDarkTheme) {
coroutineScope.launch {
val color = ImageBackgroundContrastChecker(
drawable = result.drawable,
backgroundColor = itemBackgroundColor,
).getContrastColorIfNeeded(isDarkTheme)
size = pixelsSize,
).getContrastColor(isDarkTheme = true)
iconBackgroundColor = color
isBackgroundColorDefined = true
}
}
},

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
@ -18,21 +19,23 @@ import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
@Composable
fun FiatIcon(
url: String?,
size: Dp,
modifier: Modifier = Modifier,
@DrawableRes fallbackResId: Int = R.drawable.ic_shape_circle,
) {
val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url
DefaultCurrencyIcon(
modifier = modifier,
iconData = iconData,
size = size,
alpha = 1f,
colorFilter = null,
errorIcon = {
Image(
painter = painterResource(id = fallbackResId),
contentDescription = null,
)
},
alpha = 1f,
colorFilter = null,
modifier = modifier,
)
}

View file

@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun ContentIcon(
@ -67,8 +68,10 @@ private fun CoinIcon(
val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url
DefaultCurrencyIcon(
modifier = modifier,
iconData = iconData,
size = TangemTheme.dimens.size36,
alpha = alpha,
colorFilter = colorFilter,
errorIcon = {
Image(
painter = painterResource(id = fallbackResId),
@ -77,8 +80,7 @@ private fun CoinIcon(
contentDescription = null,
)
},
alpha = alpha,
colorFilter = colorFilter,
modifier = modifier,
)
}
@ -94,11 +96,12 @@ private fun TokenIcon(
errorIcon()
} else {
DefaultCurrencyIcon(
modifier = modifier,
iconData = url,
errorIcon = errorIcon,
size = TangemTheme.dimens.size36,
alpha = alpha,
colorFilter = colorFilter,
errorIcon = errorIcon,
modifier = modifier,
)
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.core.ui.components.fields
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation
class AmountVisualTransformation(
private val symbol: String,
) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
return TransformedText(
buildAnnotatedString {
append(text)
if (text.isNotBlank()) {
append(" ")
append(symbol)
}
},
object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return text.length
}
override fun transformedToOriginal(offset: Int): Int {
return text.length
}
},
)
}
}

View file

@ -0,0 +1,60 @@
package com.tangem.core.ui.components.fields
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.input.VisualTransformation
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* Simple text field with placeholder
*/
@Composable
fun SimpleTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
placeholder: TextReference? = null,
singleLine: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
color: Color = TangemTheme.colors.text.primary1,
readOnly: Boolean = false,
) {
val focusRequester = remember { FocusRequester() }
BasicTextField(
value = value,
onValueChange = onValueChange,
textStyle = TangemTheme.typography.body2.copy(color = color),
cursorBrush = SolidColor(TangemTheme.colors.text.primary1),
singleLine = singleLine,
readOnly = readOnly,
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
decorationBox = { textValue ->
Box {
if (value.isBlank() && placeholder != null) {
Text(
text = placeholder.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.disabled,
modifier = Modifier,
)
}
textValue()
}
},
modifier = modifier
.focusRequester(focusRequester),
)
}

View file

@ -0,0 +1,152 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* [InputRowDefault](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-807&mode=design&t=86eKp9izWxUvmoCq-4)
*
* @param title title reference
* @param text primary text reference
* @param modifier modifier
* @param titleColor title color
* @param textColor text color
* @param iconRes action icon
* @param iconTint action icon tint
* @param onIconClick click on action icon
* @param showDivider show divider
* @see [InputRowEnter] for editable version
*/
@Composable
fun InputRowDefault(
title: TextReference,
text: TextReference,
modifier: Modifier = Modifier,
titleColor: Color = TangemTheme.colors.text.secondary,
textColor: Color = TangemTheme.colors.text.primary1,
iconRes: Int? = null,
iconTint: Color = TangemTheme.colors.icon.informative,
onIconClick: (() -> Unit)? = null,
showDivider: Boolean = false,
) {
DividerContainer(
modifier = modifier,
showDivider = showDivider,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12),
) {
Column(
modifier = Modifier
.weight(1f),
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = titleColor,
)
Text(
text = text.resolveReference(),
style = TangemTheme.typography.body2,
color = textColor,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
)
}
iconRes?.let {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = iconTint,
modifier = Modifier
.padding(
top = TangemTheme.dimens.spacing10,
bottom = TangemTheme.dimens.spacing10,
)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
) { onIconClick?.invoke() },
)
}
}
}
}
//region preview
@Preview
@Composable
private fun InputRowDefaultPreview_Light(
@PreviewParameter(InputRowDefaultPreviewDataProvider::class) data: InputRowDefaultPreviewData,
) {
TangemTheme {
InputRowDefault(
title = TextReference.Str(data.title),
text = TextReference.Str(data.text),
iconRes = data.iconRes,
showDivider = data.showDivider,
modifier = Modifier.background(TangemTheme.colors.background.action),
)
}
}
@Preview
@Composable
private fun InputRowDefaultPreview_Dark(
@PreviewParameter(InputRowDefaultPreviewDataProvider::class) data: InputRowDefaultPreviewData,
) {
TangemTheme(isDark = true) {
InputRowDefault(
title = TextReference.Str(data.title),
text = TextReference.Str(data.text),
iconRes = data.iconRes,
showDivider = data.showDivider,
modifier = Modifier.background(TangemTheme.colors.background.action),
)
}
}
private data class InputRowDefaultPreviewData(
val title: String,
val text: String,
val iconRes: Int?,
val showDivider: Boolean,
)
private class InputRowDefaultPreviewDataProvider : PreviewParameterProvider<InputRowDefaultPreviewData> {
override val values: Sequence<InputRowDefaultPreviewData>
get() = sequenceOf(
InputRowDefaultPreviewData(
title = "title",
text = "text",
iconRes = null,
showDivider = true,
),
InputRowDefaultPreviewData(
title = "title",
text = "text",
iconRes = R.drawable.ic_chevron_right_24,
showDivider = false,
),
)
}
//endregion

View file

@ -0,0 +1,167 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.components.fields.SimpleTextField
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* [InputRowEnter](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-799&mode=design&t=IQ5lBJEkFGU4WSvi-4)
*
* @param title title reference
* @param text primary text reference
* @param onValueChange text change callback
* @param modifier modifier
* @param titleColor title color
* @param textColor text color
* @param isSingleLine text
* @param visualTransformation applied transformation to text
* @param keyboardOptions keyboard options for field
* @param iconRes action icon
* @param iconTint action icon tint
* @param onIconClick click on action icon
* @param showDivider show divider
* @see [InputRowDefault] for read only version
*/
@Composable
fun InputRowEnter(
title: TextReference,
text: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
titleColor: Color = TangemTheme.colors.text.secondary,
textColor: Color = TangemTheme.colors.text.primary1,
isSingleLine: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
iconRes: Int? = null,
iconTint: Color = TangemTheme.colors.icon.informative,
onIconClick: (() -> Unit)? = null,
showDivider: Boolean = false,
) {
DividerContainer(
modifier = modifier,
showDivider = showDivider,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12),
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = titleColor,
)
SimpleTextField(
value = text,
onValueChange = onValueChange,
color = textColor,
singleLine = isSingleLine,
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing8),
)
}
iconRes?.let {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = iconTint,
modifier = Modifier
.padding(
top = TangemTheme.dimens.spacing10,
bottom = TangemTheme.dimens.spacing10,
)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
) { onIconClick?.invoke() },
)
}
}
}
}
//region preview
@Preview
@Composable
private fun InputRowEnterPreview_Light(
@PreviewParameter(InputRowEnterPreviewDataProvider::class) data: InputRowEnterPreviewData,
) {
TangemTheme {
InputRowEnter(
title = TextReference.Str(data.title),
text = data.text,
iconRes = data.iconRes,
showDivider = data.showDivider,
onValueChange = {},
modifier = Modifier.background(TangemTheme.colors.background.action),
)
}
}
@Preview
@Composable
private fun InputRowEnterPreview_Dark(
@PreviewParameter(InputRowEnterPreviewDataProvider::class) data: InputRowEnterPreviewData,
) {
TangemTheme(isDark = true) {
InputRowEnter(
title = TextReference.Str(data.title),
text = data.text,
iconRes = data.iconRes,
showDivider = data.showDivider,
onValueChange = {},
modifier = Modifier.background(TangemTheme.colors.background.action),
)
}
}
private data class InputRowEnterPreviewData(
val title: String,
val text: String,
val iconRes: Int?,
val showDivider: Boolean,
)
private class InputRowEnterPreviewDataProvider :
PreviewParameterProvider<InputRowEnterPreviewData> {
override val values: Sequence<InputRowEnterPreviewData>
get() = sequenceOf(
InputRowEnterPreviewData(
title = "title",
text = "text",
iconRes = null,
showDivider = true,
),
InputRowEnterPreviewData(
title = "title",
text = "text",
iconRes = R.drawable.ic_chevron_right_24,
showDivider = false,
),
)
}
//endregion

View file

@ -0,0 +1,153 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.components.fields.SimpleTextField
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* Input Row Enter with Info variation.
* [Input Row Enter](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-799&mode=design&t=IQ5lBJEkFGU4WSvi-4)
* [Input Row Enter Info](https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=7854-33577&mode=design&t=6o23sqF8fDQdn4C5-4)
*
* @param title title reference
* @param text primary text reference
* @param onValueChange text change callback
* @param modifier modifier
* @param titleColor title color
* @param textColor text color
* @param isSingleLine text
* @param visualTransformation applied transformation to text
* @param keyboardOptions keyboard options for field
* @param showDivider show divider
*/
@Composable
fun InputRowEnterInfo(
title: TextReference,
text: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
info: TextReference? = null,
titleColor: Color = TangemTheme.colors.text.secondary,
textColor: Color = TangemTheme.colors.text.primary1,
infoColor: Color = TangemTheme.colors.text.tertiary,
isSingleLine: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
showDivider: Boolean = false,
) {
DividerContainer(
modifier = modifier,
showDivider = showDivider,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12),
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = titleColor,
)
Row {
SimpleTextField(
value = text,
onValueChange = onValueChange,
singleLine = isSingleLine,
color = textColor,
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing8)
.weight(1f),
)
info?.let {
Text(
text = it.resolveReference(),
style = TangemTheme.typography.body2,
color = infoColor,
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing8)
.align(Alignment.Bottom),
)
}
}
}
}
}
//region preview
@Preview
@Composable
private fun InputRowEnterInfoPreview_Light(
@PreviewParameter(InputRowEnterInfoPreviewDataProvider::class) data: InputRowEnterInfoPreviewData,
) {
TangemTheme {
InputRowEnterInfo(
title = data.title,
text = data.text,
info = data.info,
showDivider = data.showDivider,
onValueChange = {},
modifier = Modifier.background(TangemTheme.colors.background.action),
)
}
}
@Preview
@Composable
private fun InputRowEnterInfoPreview_Dark(
@PreviewParameter(InputRowEnterInfoPreviewDataProvider::class) data: InputRowEnterInfoPreviewData,
) {
TangemTheme(isDark = true) {
InputRowEnterInfo(
title = data.title,
text = data.text,
info = data.info,
showDivider = data.showDivider,
onValueChange = {},
modifier = Modifier.background(TangemTheme.colors.background.action),
)
}
}
private data class InputRowEnterInfoPreviewData(
val title: TextReference,
val text: String,
val showDivider: Boolean,
val info: TextReference?,
)
private class InputRowEnterInfoPreviewDataProvider :
PreviewParameterProvider<InputRowEnterInfoPreviewData> {
override val values: Sequence<InputRowEnterInfoPreviewData>
get() = sequenceOf(
InputRowEnterInfoPreviewData(
title = TextReference.Str("title"),
text = "text",
showDivider = true,
info = TextReference.Str("info"),
),
InputRowEnterInfoPreviewData(
title = TextReference.Str("title"),
text = "text",
showDivider = false,
info = null,
),
)
}
//endregion

View file

@ -0,0 +1,196 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* [Input Row Image](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-813&mode=design&t=IQ5lBJEkFGU4WSvi-4)
*
* @param title title reference
* @param subtitle subtitle reference
* @param caption caption reference
* @param tokenIconState token icon state [TokenIconState]
* @param modifier modifier
* @param titleColor title color
* @param subtitleColor subtitle color
* @param captionColor caption color
* @param iconRes action icon
* @param iconTint action icon tint
* @param onIconClick click on action icon
* @param showNetworkIcon show token network icon
* @param showDivider show divider
*/
@Composable
fun InputRowImage(
title: TextReference,
subtitle: TextReference,
caption: TextReference,
tokenIconState: TokenIconState,
modifier: Modifier = Modifier,
titleColor: Color = TangemTheme.colors.text.secondary,
subtitleColor: Color = TangemTheme.colors.text.primary1,
captionColor: Color = TangemTheme.colors.text.tertiary,
iconRes: Int? = null,
iconTint: Color = TangemTheme.colors.icon.informative,
onIconClick: (() -> Unit)? = null,
showNetworkIcon: Boolean = false,
showDivider: Boolean = false,
) {
DividerContainer(
modifier = modifier,
showDivider = showDivider,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12),
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = titleColor,
)
Row(
modifier = Modifier
.padding(
top = TangemTheme.dimens.spacing6,
),
) {
TokenIcon(
state = tokenIconState,
shouldDisplayNetwork = showNetworkIcon,
modifier = Modifier
.size(TangemTheme.dimens.size36),
)
Column(
modifier = Modifier
.weight(1f)
.padding(start = TangemTheme.dimens.spacing12),
) {
Text(
text = subtitle.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = subtitleColor,
)
Text(
text = caption.resolveReference(),
style = TangemTheme.typography.caption2,
color = captionColor,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing2),
)
}
iconRes?.let {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = iconTint,
modifier = Modifier
.align(CenterVertically)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
) { onIconClick?.invoke() },
)
}
}
}
}
}
//region preview
@Preview
@Composable
private fun InputRowInputEnterInfoPreview_Light(
@PreviewParameter(InputRowImagePreviewDataProvider::class) data: InputRowImagePreviewData,
) {
TangemTheme {
InputRowImage(
title = data.title,
modifier = Modifier.background(TangemTheme.colors.background.action),
subtitle = data.subtitle,
caption = data.caption,
tokenIconState = data.iconState,
iconRes = data.actionIconRes,
onIconClick = {},
showNetworkIcon = false,
showDivider = data.showDivider,
)
}
}
@Preview
@Composable
private fun InputRowImagePreview_Dark(
@PreviewParameter(InputRowImagePreviewDataProvider::class) data: InputRowImagePreviewData,
) {
TangemTheme(isDark = true) {
InputRowImage(
title = data.title,
modifier = Modifier.background(TangemTheme.colors.background.action),
subtitle = data.subtitle,
caption = data.caption,
tokenIconState = data.iconState,
iconRes = data.actionIconRes,
onIconClick = {},
showNetworkIcon = false,
showDivider = data.showDivider,
)
}
}
private data class InputRowImagePreviewData(
val title: TextReference,
val subtitle: TextReference,
val caption: TextReference,
val iconState: TokenIconState,
val showDivider: Boolean,
val actionIconRes: Int?,
val showNetworkIcon: Boolean = false,
)
private class InputRowImagePreviewDataProvider :
PreviewParameterProvider<InputRowImagePreviewData> {
override val values: Sequence<InputRowImagePreviewData>
get() = sequenceOf(
InputRowImagePreviewData(
title = TextReference.Str("title"),
subtitle = TextReference.Str("subtitle"),
caption = TextReference.Str("caption"),
iconState = TokenIconState.Locked,
actionIconRes = null,
showDivider = false,
showNetworkIcon = false,
),
InputRowImagePreviewData(
title = TextReference.Str("title"),
subtitle = TextReference.Str("subtitle"),
caption = TextReference.Str("caption"),
iconState = TokenIconState.Locked,
actionIconRes = R.drawable.ic_chevron_right_24,
showDivider = true,
showNetworkIcon = true,
),
)
}
//endregion

View file

@ -0,0 +1,168 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.fields.SimpleTextField
import com.tangem.core.ui.components.icons.identicon.IdentIcon
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.components.inputrow.inner.PasteButton
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* [Input Row Recipient](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-826&mode=design&t=IQ5lBJEkFGU4WSvi-4)
*
* @param title title reference
* @param value recipient address
* @param placeholder placeholder
* @param onValueChange callback for value change
* @param onPasteClick callback for paste
* @param modifier composable modifier
* @param singleLine is single line text
* @param error error text
* @param isError is error flag
* @param showDivider show divider
*
* @see InputRowRecipientDefault for readonly version
*/
@Composable
fun InputRowRecipient(
title: TextReference,
value: String,
placeholder: TextReference,
onValueChange: (String) -> Unit,
onPasteClick: (String) -> Unit,
modifier: Modifier = Modifier,
singleLine: Boolean = false,
error: TextReference? = null,
isError: Boolean = false,
showDivider: Boolean = false,
) {
val (titleText, color) = if (isError && error != null) {
error to TangemTheme.colors.text.warning
} else {
title to TangemTheme.colors.text.secondary
}
DividerContainer(
modifier = modifier,
showDivider = showDivider,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12),
) {
Text(
text = titleText.resolveReference(),
style = TangemTheme.typography.caption2,
color = color,
)
Row(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing8),
) {
IdentIcon(
address = value,
modifier = Modifier
.align(CenterVertically)
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
.size(TangemTheme.dimens.size36)
.background(TangemTheme.colors.background.tertiary),
)
SimpleTextField(
value = value,
placeholder = placeholder,
onValueChange = onValueChange,
singleLine = singleLine,
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12)
.weight(1f)
.align(CenterVertically),
)
PasteButton(
isPasteButtonVisible = value.isBlank(),
onClick = onPasteClick,
modifier = Modifier
.align(CenterVertically)
.padding(start = TangemTheme.dimens.spacing8),
)
}
}
}
}
//region preview
@Preview
@Composable
private fun InputRowRecipientPreview_Light(
@PreviewParameter(InputRowRecipientPreviewDataProvider::class) value: InputRowRecipientPreviewData,
) {
TangemTheme {
InputRowRecipient(
value = value.value,
title = TextReference.Res(R.string.send_recipient),
placeholder = TextReference.Res(R.string.send_optional_field),
error = TextReference.Str("Error"),
isError = value.isError,
showDivider = true,
onValueChange = {},
onPasteClick = {},
modifier = Modifier.background(TangemTheme.colors.background.primary),
)
}
}
@Preview
@Composable
private fun InputRowRecipientPreview_Dark(
@PreviewParameter(InputRowRecipientPreviewDataProvider::class) value: InputRowRecipientPreviewData,
) {
TangemTheme(isDark = true) {
InputRowRecipient(
value = value.value,
title = TextReference.Res(R.string.send_recipient),
placeholder = TextReference.Res(R.string.send_optional_field),
error = TextReference.Str("Error"),
isError = value.isError,
showDivider = true,
onValueChange = {},
onPasteClick = {},
modifier = Modifier.background(TangemTheme.colors.background.primary),
)
}
}
private data class InputRowRecipientPreviewData(
val value: String,
val isError: Boolean,
)
private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider<InputRowRecipientPreviewData> {
override val values: Sequence<InputRowRecipientPreviewData>
get() = sequenceOf(
InputRowRecipientPreviewData(
value = "",
isError = false,
),
InputRowRecipientPreviewData(
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
isError = false,
),
InputRowRecipientPreviewData(
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
isError = true,
),
)
}
//endregion

View file

@ -0,0 +1,106 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.icons.identicon.IdentIcon
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* Read only version of [InputRowRecipient].
* [Input Row Recipient](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-826&mode
* =design&t=IQ5lBJEkFGU4WSvi-4)
*
* @param title title reference
* @param value recipient address
* @param modifier composable modifier
* @param titleColor title color
* @param showDivider show divider
* @see InputRowRecipient for editable version
*/
@Composable
fun InputRowRecipientDefault(
title: TextReference,
value: String,
modifier: Modifier = Modifier,
titleColor: Color = TangemTheme.colors.text.secondary,
showDivider: Boolean = false,
) {
DividerContainer(
modifier = modifier,
showDivider = showDivider,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12),
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = titleColor,
)
Row(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing8),
) {
IdentIcon(
address = value,
modifier = Modifier
.align(Alignment.CenterVertically)
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
.size(TangemTheme.dimens.size36)
.background(TangemTheme.colors.background.tertiary),
)
Text(
text = value,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12)
.weight(1f)
.align(Alignment.CenterVertically),
)
}
}
}
}
//region preview
@Preview
@Composable
private fun InputRowRecipientPreview_Light() {
TangemTheme {
InputRowRecipientDefault(
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
title = TextReference.Res(R.string.send_recipient),
showDivider = true,
modifier = Modifier.background(TangemTheme.colors.background.primary),
)
}
}
@Preview
@Composable
private fun InputRowRecipientPreview_Dark() {
TangemTheme(isDark = true) {
InputRowRecipientDefault(
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
title = TextReference.Res(R.string.send_recipient),
showDivider = false,
modifier = Modifier.background(TangemTheme.colors.background.primary),
)
}
}
//endregion

View file

@ -0,0 +1,31 @@
package com.tangem.core.ui.components.inputrow.inner
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Divider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.TangemTheme
@Composable
fun DividerContainer(
modifier: Modifier = Modifier,
showDivider: Boolean = false,
paddingValues: PaddingValues = PaddingValues(start = TangemTheme.dimens.spacing12),
content: @Composable () -> Unit,
) {
Box(modifier = modifier) {
content()
if (showDivider) {
Divider(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(paddingValues),
color = TangemTheme.colors.stroke.primary,
thickness = TangemTheme.dimens.size0_5,
)
}
}
}

View file

@ -0,0 +1,80 @@
package com.tangem.core.ui.components.inputrow.inner
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
/**
* Paste button with cross icon. Retrieves text from clipboard.
* [Paste button](https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=7853-33535&mode=design&t=6o23sqF8fDQdn4C5-4)
*
* @param isPasteButtonVisible is paste button visible
* @param onClick action callback
* @param modifier composable modifier
*/
@Composable
fun PasteButton(isPasteButtonVisible: Boolean, onClick: (String) -> Unit, modifier: Modifier = Modifier) {
val clipboardManager = LocalClipboardManager.current
val hapticFeedback = LocalHapticFeedback.current
if (isPasteButtonVisible) {
Box(modifier = modifier) {
Text(
text = "Paste",
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary2,
modifier = Modifier
.background(
color = TangemTheme.colors.button.primary,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(
horizontal = TangemTheme.dimens.spacing10,
vertical = TangemTheme.dimens.spacing2,
)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(radius = TangemTheme.dimens.radius8),
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onClick(
clipboardManager
.getText()
?.toString()
.orEmpty(),
)
},
),
)
}
} else {
Icon(
painter = painterResource(id = R.drawable.ic_close_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = stringResource(R.string.common_close),
modifier = modifier
.size(TangemTheme.dimens.size20)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(radius = TangemTheme.dimens.radius10),
onClick = { onClick("") },
),
)
}
}

View file

@ -55,6 +55,7 @@ data class TangemDimens internal constructor(
val size20: Dp = 20.dp,
val size24: Dp = 24.dp,
val size28: Dp = 28.dp,
val size30: Dp = 30.dp,
val size32: Dp = 32.dp,
val size34: Dp = 34.dp,
val size36: Dp = 36.dp,

View file

@ -9,12 +9,14 @@ import java.util.Locale
@Suppress("MagicNumber")
object DateTimeFormatters {
private const val DDMMYYYY = "dd.MM.yyyy"
/**
* Two SS means, SHORT style for date and time.
* If pattern contains "a", it means time is in 12 hour format.
* [Documentation](https://www.joda.org/joda-time/apidocs/org/joda/time/format/DateTimeFormat.html)
*/
val timeFormatter by lazy {
val timeFormatter: DateTimeFormatter by lazy {
val is12HourFormat = DateTimeFormat.patternForStyle("SS", Locale.getDefault()).contains("a")
if (is12HourFormat) {
DateTimeFormatterBuilder()
@ -35,7 +37,7 @@ object DateTimeFormatters {
}
}
val dateFormatter by lazy {
val dateFormatter: DateTimeFormatter by lazy {
DateTimeFormatterBuilder()
.appendDayOfMonth(1)
.appendLiteral(' ')
@ -46,6 +48,13 @@ object DateTimeFormatters {
.withLocale(Locale.getDefault())
}
val dateDDMMYYYY: DateTimeFormatter by lazy {
DateTimeFormatterBuilder()
.appendPattern(DDMMYYYY)
.toFormatter()
.withLocale(Locale.getDefault())
}
fun formatTime(formatter: DateTimeFormatter = timeFormatter, time: DateTime): String {
return formatter.print(time)
}

View file

@ -0,0 +1,33 @@
package com.tangem.core.ui.utils
import android.text.format.DateUtils
import com.tangem.utils.extensions.isToday
import com.tangem.utils.extensions.isYesterday
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.format.DateTimeFormatter
/**
* If [this] timestamp is today or yesterday, returns relative date,
* otherwise returns formatting date.
*/
fun Long.toDateFormat(formatter: DateTimeFormatter = DateTimeFormatters.dateFormatter): String {
val localDate = DateTime(this, DateTimeZone.getDefault())
return if (localDate.isToday() || localDate.isYesterday()) {
DateUtils.getRelativeTimeSpanString(
this,
DateTime.now().millis,
DateUtils.DAY_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE,
).toString()
} else {
DateTimeFormatters.formatDate(formatter = formatter, date = localDate)
}
}
/**
* Returns formatted time according to [formatter].
*/
fun Long.toTimeFormat(formatter: DateTimeFormatter = DateTimeFormatters.timeFormatter): String {
return DateTimeFormatters.formatTime(formatter = formatter, time = DateTime(this, DateTimeZone.getDefault()))
}

View file

@ -13,12 +13,12 @@ class ImageBackgroundContrastChecker(
private val image: Bitmap,
private val backgroundColor: Int,
) {
constructor(drawable: Drawable, backgroundColor: Int) : this(
image = drawable.toBitmap(),
constructor(drawable: Drawable, backgroundColor: Int, size: Int) : this(
image = drawable.toBitmap(width = size, height = size),
backgroundColor = backgroundColor,
)
suspend fun getContrastColorIfNeeded(isDarkTheme: Boolean): Color {
suspend fun getContrastColor(isDarkTheme: Boolean): Color {
return if (isLowContrast()) {
if (isDarkTheme) Color.White else Color.Black
} else {
@ -26,7 +26,7 @@ class ImageBackgroundContrastChecker(
}
}
suspend fun isLowContrast(): Boolean {
private suspend fun isLowContrast(): Boolean {
val palette = generatePaletteAsync(bitmap = image)
val color = palette.getDominantColor(backgroundColor)
val contrast = ColorUtils.calculateContrast(color, backgroundColor)

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M15.316,6.992C14.311,6.992 13.538,7.568 12.995,8.794C10.963,6.753 7.995,5.045 5.043,4.806C4.152,4.746 4.031,5.519 4.011,5.98C3.816,9.464 6.319,12.606 9.521,13.127C8.61,15.057 7.359,16.684 5.776,18.093C5.387,18.421 5.545,18.934 5.727,19.241C6.61,20.688 9.408,21.692 11.137,21.056C11.667,20.863 11.74,20.513 11.74,20.163V17.849C13.534,17.653 16.32,16.201 15.927,12.273L15.899,11.979C17.227,11.573 18.158,10.6 18.158,9.549C18.158,8.298 16.858,6.992 15.316,6.992ZM15.389,8.388C16.219,8.388 16.867,9.011 16.867,9.549C16.867,10.015 16.316,10.557 15.008,10.749C14.68,10.792 14.49,11.018 14.514,11.33L14.579,12.299C14.809,15.795 12.283,16.265 10.854,16.389C10.558,16.415 10.364,16.641 10.364,16.931V19.736C10.364,19.851 10.339,19.916 10.202,19.928C9.278,20.039 7.687,19.442 7.149,18.81C7.08,18.72 7.08,18.643 7.132,18.596C8.837,17.072 10.214,15.095 11.149,12.747C11.34,12.29 11.121,11.898 10.7,11.893C7.768,11.868 5.549,9.575 5.387,6.45C5.383,6.326 5.428,6.3 5.533,6.322C8.145,6.736 10.574,8.303 12.085,9.848C12.437,10.207 12.667,10.361 13,10.361C13.538,10.361 13.805,10.096 14.081,9.498C14.368,8.862 14.656,8.388 15.389,8.388ZM13.996,8.26L15.283,7.782C14.344,5.408 13.016,3.769 11.242,2.68C10.586,2.283 9.918,2.56 9.732,3.342C9.4,4.691 9.258,5.912 9.449,7.218L10.987,7.995C10.655,6.714 10.663,5.647 10.902,4.418C10.922,4.285 11.028,4.242 11.129,4.319C12.34,5.22 13.255,6.458 13.996,8.26ZM16.749,10.77L19.417,11.185C19.988,11.27 20.256,10.408 19.677,10.143L17.263,9.058L16.749,10.77Z"
android:fillColor="#0099FF"/>
</vector>

View file

@ -0,0 +1,7 @@
<vector android:height="24dp" android:viewportHeight="25"
android:viewportWidth="25" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<group>
<clip-path android:pathData="M0.676,0.968h24v24h-24z"/>
<path android:fillColor="#909090" android:pathData="M5.676,19.968H6.938L17.174,9.731L15.913,8.47L5.676,18.706V19.968ZM4.176,21.468V18.083L17.366,4.899C17.518,4.761 17.685,4.655 17.867,4.58C18.05,4.505 18.242,4.468 18.442,4.468C18.643,4.468 18.837,4.503 19.025,4.575C19.213,4.646 19.379,4.759 19.524,4.914L20.745,6.151C20.9,6.295 21.011,6.462 21.077,6.651C21.143,6.839 21.176,7.028 21.176,7.216C21.176,7.417 21.142,7.609 21.073,7.792C21.004,7.975 20.895,8.142 20.745,8.293L7.561,21.468H4.176ZM16.532,9.112L15.913,8.47L17.174,9.731L16.532,9.112Z"/>
</group>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M15.564,18.109C16.941,18.109 17.59,17.357 17.59,16.486C17.59,14.706 16.348,14.153 14.183,14.153C13.347,14.153 13.085,14.205 12.874,14.253L12.432,13.087C11.6,10.885 10.06,9.95 8.352,9.95C8.097,9.95 7.799,9.95 7.493,10.018C7.071,10.109 6.896,10.288 6.896,10.555C6.896,10.937 7.186,11.2 7.58,11.196C7.815,11.196 8.066,11.156 8.336,11.156C9.682,11.156 10.601,11.916 11.389,13.819L12.078,15.51C12.619,15.403 13.184,15.307 14.135,15.307C15.262,15.307 16.121,15.67 16.121,16.314C16.121,16.561 15.934,16.776 15.564,16.776C14.919,16.776 14.438,16.633 13.821,16.633C13.16,16.633 12.424,16.697 12.189,16.697C11.922,16.697 11.715,16.681 11.512,16.493L8.667,13.711C6.593,13.827 5.268,13.341 4.452,12.211C3.887,12.255 3.465,12.056 3.465,11.646C3.465,11.228 3.918,10.981 4.519,10.981C5.148,9.003 6.322,8.107 8.054,8.107C10.044,8.107 11.767,9.648 13.574,10.543C14.115,10.802 14.712,10.921 15.071,10.921C16.034,10.921 16.702,10.356 16.917,9.305L12.858,6.539C12.555,6.336 12.209,6.05 12.209,5.902C12.209,5.584 12.814,5.401 13.295,5.401C14.207,5.401 14.896,5.879 15.588,6.671L17.705,9.102C19.504,9.106 20.667,10.372 20.667,11.988C20.667,12.935 20.249,13.206 18.983,13.206C18.259,13.206 17.343,12.931 16.476,12.462C15.226,12.776 13.916,13.465 13.092,14.281L14.235,14.691C14.971,14.217 15.779,13.891 16.316,13.684C17.212,14.197 18.195,14.42 18.947,14.416C20.901,14.408 22,13.707 22,11.98C22,9.795 20.44,8.167 18.318,8.048L17.033,6.209C16.034,4.788 14.876,4 13.28,4C12.113,4 10.601,4.633 10.601,5.767C10.601,6.48 11.174,6.933 11.994,7.479C12.754,7.98 14.398,9.003 15.278,9.556C15.21,9.596 15.126,9.628 15.027,9.628C14.792,9.628 14.362,9.465 14.056,9.258C11.648,7.992 10.104,6.675 8.066,6.675C5.829,6.675 4.197,7.845 3.417,10.149C2.537,10.308 2,10.906 2,11.753C2,12.776 2.792,13.377 3.755,13.389C4.364,14.424 6.004,15.256 8.03,15.097L10.573,17.52C10.983,17.922 11.5,18.07 12.161,18.07C12.706,18.07 13.192,17.942 13.861,17.942C14.557,17.942 14.999,18.109 15.564,18.109ZM7.389,18.838C8.826,18.838 10.247,18.352 11.063,17.385L10.107,16.593C9.69,17.234 8.639,17.624 7.429,17.624C6.649,17.624 6.088,17.369 6.088,16.927C6.088,16.72 6.243,16.553 6.529,16.553C6.907,16.553 7.282,16.661 7.755,16.661C8.499,16.661 9.132,16.549 9.63,16.115L8.758,15.252C8.452,15.467 8.078,15.526 7.759,15.526C7.158,15.522 6.808,15.403 6.326,15.403C5.339,15.403 4.675,16.004 4.675,16.907C4.675,18.113 5.761,18.838 7.389,18.838ZM18.537,11.622C18.876,11.622 19.15,11.347 19.15,11.009C19.15,10.675 18.876,10.396 18.537,10.396C18.203,10.396 17.924,10.675 17.924,11.009C17.924,11.347 18.203,11.622 18.537,11.622Z"
android:fillColor="#909090"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M4,4H10V10H4V4ZM20,4V10H14V4H20ZM14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15ZM16,15V18H18V15H16ZM4,20V14H10V20H4ZM6,6V8H8V6H6ZM16,6V8H18V6H16ZM6,16V18H8V16H6ZM4,11H6V13H4V11ZM9,11H13V15H11V13H9V11ZM11,6H13V10H11V6ZM2,2V6H0V2C0,1.47 0.211,0.961 0.586,0.586C0.961,0.211 1.47,0 2,0L6,0V2H2ZM22,0C22.53,0 23.039,0.211 23.414,0.586C23.789,0.961 24,1.47 24,2V6H22V2H18V0H22ZM2,18V22H6V24H2C1.47,24 0.961,23.789 0.586,23.414C0.211,23.039 0,22.53 0,22V18H2ZM22,22V18H24V22C24,22.53 23.789,23.039 23.414,23.414C23.039,23.789 22.53,24 22,24H18V22H22Z"
android:fillColor="#1E1E1E"/>
</group>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M9.408,17.317C11.952,17.317 12.77,15.774 14.392,15.699C16.461,15.551 17.283,14.326 18.957,14.326C20.756,14.326 22,13.667 22,12.497C22,10.076 20.634,8.665 18.879,8.665C17.231,8.665 16.213,9.635 15.865,11.172C15.739,11.701 15.506,12.038 15.177,12.267L14.943,11.842C13.348,8.917 11.848,7.732 9.416,7.725C6.906,7.732 5.469,8.876 3.703,12.179C2.985,13.53 2,13.708 2,14.618C2,15.277 2.529,15.699 3.462,15.699C3.666,15.699 3.881,15.699 4.088,15.699C6.002,15.699 7.043,17.317 9.408,17.317ZM9.408,15.87C7.513,15.87 6.332,14.267 4.54,14.267C4.344,14.267 4.155,14.27 3.925,14.281L3.899,14.189C4.281,13.844 4.632,13.489 4.943,12.863C6.391,10.09 7.454,9.176 9.416,9.168C11.493,9.176 12.548,10.394 13.755,12.612C14.166,13.367 14.484,13.878 14.895,14.252C14.736,14.244 14.54,14.252 14.336,14.27C12.352,14.415 11.293,15.87 9.408,15.87ZM16.424,13.963C16.247,13.807 16.076,13.608 15.932,13.348C16.528,13.145 16.924,12.663 17.105,11.79C17.335,10.623 17.924,10.05 18.879,10.05C19.856,10.05 20.663,10.972 20.663,12.319C20.663,12.66 19.916,12.974 19.175,12.974C17.816,12.974 17.131,13.422 16.424,13.963ZM19.012,11.982C19.323,11.982 19.582,11.723 19.582,11.412C19.582,11.097 19.323,10.842 19.012,10.842C18.697,10.842 18.442,11.097 18.442,11.412C18.442,11.723 18.697,11.982 19.012,11.982ZM9.364,12.73C11.367,12.73 12.851,11.805 13.74,10.864L13.207,9.913C12.193,11.035 10.852,11.649 9.364,11.649C7.853,11.649 6.513,11.042 5.517,9.909L4.988,10.868C5.869,11.801 7.354,12.73 9.364,12.73ZM6.935,15.614C7.587,14.94 8.201,13.434 8.268,12.438L7.328,12.149C7.287,12.941 6.639,14.552 5.976,15.189L6.935,15.614ZM11.893,15.614L12.855,15.189C12.189,14.548 11.541,12.945 11.5,12.149L10.56,12.43C10.597,13.419 11.215,14.922 11.893,15.614ZM3.907,18.024C5.195,18.024 6.484,17.476 7.476,16.451L6.476,15.884C5.491,16.673 4.71,17.014 3.977,17.014C3.551,17.014 3.325,16.869 3.325,16.658C3.325,16.507 3.455,16.399 3.755,16.318C4.04,16.233 5.151,15.907 5.676,15.573L4.74,15.229C4.366,15.392 3.87,15.466 3.455,15.611C2.792,15.811 2.159,16.062 2.159,16.832C2.159,17.58 2.889,18.024 3.907,18.024ZM14.736,18.024C15.754,18.024 16.487,17.584 16.487,16.832C16.487,15.996 15.762,15.748 15.021,15.551C14.636,15.448 14.229,15.374 13.903,15.233L12.97,15.577C13.496,15.907 14.603,16.24 14.892,16.318C15.191,16.399 15.317,16.507 15.317,16.662C15.317,16.873 15.091,17.017 14.666,17.017C13.933,17.017 13.151,16.677 12.17,15.884L11.171,16.451C12.163,17.447 13.455,18.024 14.736,18.024Z"
android:fillColor="#909090"/>
</vector>