Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-21 12:43:59 +03:00
commit fe2cfac54d
1140 changed files with 23493 additions and 8579 deletions

View file

@ -32,9 +32,9 @@ fun AppBarWithBackButton(
TangemTopAppBar(
modifier = modifier,
title = text,
startButton = TopAppBarButtonUM(
startButton = TopAppBarButtonUM.Icon(
iconRes = iconRes ?: R.drawable.ic_back_24,
onIconClicked = onBackClick,
onClicked = onBackClick,
),
containerColor = containerColor,
)

View file

@ -27,14 +27,14 @@ fun AppBarWithBackButtonAndIcon(
title = text,
subtitle = subtitle,
containerColor = backgroundColor,
startButton = TopAppBarButtonUM(
startButton = TopAppBarButtonUM.Icon(
iconRes = backIconRes ?: R.drawable.ic_back_24,
onIconClicked = onBackClick,
onClicked = onBackClick,
),
endButton = if (iconRes != null && onIconClick != null) {
TopAppBarButtonUM(
TopAppBarButtonUM.Icon(
iconRes = iconRes,
onIconClicked = onIconClick,
onClicked = onIconClick,
)
} else {
null

View file

@ -10,6 +10,7 @@ import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -20,6 +21,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TopAppBarTestTags
/**
* [TangemTopAppBar] height options.
@ -127,6 +129,7 @@ fun TangemTopAppBar(
TopAppBarButton(
button = endButton,
tint = iconTint,
modifier = Modifier.testTag(TopAppBarTestTags.MORE_BUTTON),
)
}
},
@ -172,6 +175,7 @@ fun TangemTopAppBar(
TopAppBarButton(
button = startButton,
tint = iconTint,
modifier = Modifier.testTag(TopAppBarTestTags.CLOSE_BUTTON),
)
}
}
@ -222,6 +226,7 @@ private fun TopAppBarTitle(
color = textColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.testTag(TopAppBarTestTags.TITLE),
)
AnimatedVisibility(
@ -296,25 +301,25 @@ private class BasicTopAppBarPMPreviewProvider : PreviewParameterProvider<BasicTo
height = TangemTopAppBarHeight.BOTTOM_SHEET,
),
BasicTopAppBarPM(
startButton = TopAppBarButtonUM(
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_scan_24,
onIconClicked = {},
onClicked = {},
),
endButton = TopAppBarButtonUM(
endButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_more_vertical_24,
onIconClicked = {},
onClicked = {},
),
),
BasicTopAppBarPM(
startButton = TopAppBarButtonUM(
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_scan_24,
onIconClicked = {},
onClicked = {},
),
),
BasicTopAppBarPM(
endButton = TopAppBarButtonUM(
endButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_more_vertical_24,
onIconClicked = {},
onClicked = {},
),
height = TangemTopAppBarHeight.BOTTOM_SHEET,
),
@ -322,13 +327,13 @@ private class BasicTopAppBarPMPreviewProvider : PreviewParameterProvider<BasicTo
title = "1234567891011121314151617181920",
subtitle = "12345678910111213141516171819202122232425",
titleAlignment = Alignment.Start,
startButton = TopAppBarButtonUM(
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_scan_24,
onIconClicked = {},
onClicked = {},
),
endButton = TopAppBarButtonUM(
endButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_more_vertical_24,
onIconClicked = {},
onClicked = {},
),
),
)

View file

@ -1,27 +1,49 @@
package com.tangem.core.ui.components.appbar
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@Composable
fun TopAppBarButton(button: TopAppBarButtonUM, tint: Color, modifier: Modifier = Modifier) {
IconButton(
enabled = button.enabled,
modifier = modifier.size(TangemTheme.dimens.size32),
onClick = button.onIconClicked,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = button.iconRes),
tint = tint,
contentDescription = null,
)
when (button) {
is TopAppBarButtonUM.Icon -> {
IconButton(
enabled = button.enabled,
modifier = modifier.size(TangemTheme.dimens.size32),
onClick = button.onClicked,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = button.iconRes),
tint = tint,
contentDescription = null,
)
}
}
is TopAppBarButtonUM.Text -> {
Text(
modifier = modifier
.conditional(button.enabled) {
clickable { button.onClicked() }
}
.padding(4.dp),
text = button.text.resolveReference(),
color = tint,
style = TangemTheme.typography.body1,
)
}
}
}

View file

@ -2,21 +2,39 @@ package com.tangem.core.ui.components.appbar.models
import androidx.annotation.DrawableRes
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
data class TopAppBarButtonUM(
@DrawableRes val iconRes: Int,
val onIconClicked: () -> Unit,
val enabled: Boolean = true,
sealed class TopAppBarButtonUM(
open val onClicked: () -> Unit,
open val enabled: Boolean = true,
) {
data class Icon(
@DrawableRes val iconRes: Int,
override val onClicked: () -> Unit,
override val enabled: Boolean = true,
) : TopAppBarButtonUM(onClicked, enabled)
data class Text(
val text: TextReference,
override val onClicked: () -> Unit,
override val enabled: Boolean = true,
) : TopAppBarButtonUM(onClicked, enabled)
@Suppress("FunctionName")
companion object {
fun Back(onBackClicked: () -> Unit) = Back(true, onBackClicked)
fun Back(enabled: Boolean = true, onBackClicked: () -> Unit) = TopAppBarButtonUM(
fun Back(enabled: Boolean = true, onBackClicked: () -> Unit) = Icon(
iconRes = R.drawable.ic_back_24,
onIconClicked = onBackClicked,
onClicked = onBackClicked,
enabled = enabled,
)
fun Text(text: TextReference, onTextClicked: () -> Unit, enabled: Boolean = true) = Text(
text = text,
onClicked = onTextClicked,
enabled = enabled,
)
}

View file

@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.components.label.Label
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@ -38,6 +39,7 @@ fun BlockItem(model: BlockUM, modifier: Modifier = Modifier) {
)
Text(
modifier = Modifier.weight(1f),
text = model.text.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = when (model.accentType) {
@ -48,6 +50,8 @@ fun BlockItem(model: BlockUM, modifier: Modifier = Modifier) {
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
model.label?.let { Label(it) }
}
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.core.ui.components.block.model
import androidx.annotation.DrawableRes
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.TextReference
data class BlockUM(
@ -8,6 +9,7 @@ data class BlockUM(
@DrawableRes val iconRes: Int,
val onClick: () -> Unit,
val accentType: AccentType = AccentType.NONE,
val label: LabelUM? = null,
) {
enum class AccentType {

View file

@ -179,6 +179,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheet(
onBack = onBack,
dragHandle = null,
content = bsContent,
scrimColor = TangemTheme.colors.overlay.secondary,
)
} else {
ModalBottomSheet(
@ -190,6 +191,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheet(
contentWindowInsets = { WindowInsetsZero },
dragHandle = null,
content = bsContent,
scrimColor = TangemTheme.colors.overlay.secondary,
)
}
}
@ -200,58 +202,62 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheet(
@Composable
private fun TangemModalBottomSheet_Preview() {
TangemThemePreview {
TangemModalBottomSheet<TangemBottomSheetConfigContentPreviewConfig>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = TangemBottomSheetConfigContentPreviewConfig(),
),
title = {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = {},
)
},
content = {
Column(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
modifier = Modifier
.size(56.dp)
.clip(RoundedCornerShape(100))
.background(TangemTheme.colors.icon.informative.copy(alpha = 0.1f))
.padding(12.dp),
painter = rememberVectorPainter(
ImageVector.vectorResource(R.drawable.ic_alert_24),
),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
Box(
Modifier.background(TangemTheme.colors.background.tertiary),
) {
TangemModalBottomSheet<TangemBottomSheetConfigContentPreviewConfig>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = TangemBottomSheetConfigContentPreviewConfig(),
),
title = {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = {},
)
SpacerH24()
Text(
text = "Unsuported networks",
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
SpacerH8()
Text(
text = "Tangem does not currently support a required network by React App.",
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
SpacerH(48.dp)
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = "Go it",
onClick = {},
)
}
},
)
},
content = {
Column(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
modifier = Modifier
.size(56.dp)
.clip(RoundedCornerShape(100))
.background(TangemTheme.colors.icon.informative.copy(alpha = 0.1f))
.padding(12.dp),
painter = rememberVectorPainter(
ImageVector.vectorResource(R.drawable.ic_alert_24),
),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
SpacerH24()
Text(
text = "Unsuported networks",
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
SpacerH8()
Text(
text = "Tangem does not currently support a required network by React App.",
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
SpacerH(48.dp)
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = "Go it",
onClick = {},
)
}
},
)
}
}
}

View file

@ -32,6 +32,7 @@ import com.tangem.core.ui.res.LocalBottomSheetAlwaysVisible
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.core.ui.utils.toPx
/**
* Modal bottom sheet with [content], [footer] and optional [title].
@ -154,6 +155,21 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
val initial = 0
val scrollState = rememberScrollState(initial = initial)
val isKeyboardOpen by rememberIsKeyboardVisible()
val buttonHeight = TangemTheme.dimens.spacing80
val contentBottomPadding = TangemTheme.dimens.spacing80
// Offset calculation for keyboard scroll adjustment:
// 1) Button height (footer)
// 2) Column content bottom padding
// 3) Additional spacing (40dp) for visual comfort when keyboard is open
val scrollOffset = buttonHeight.toPx() + buttonHeight.toPx() + 40.dp.toPx()
LaunchedEffect(isKeyboardOpen) {
if (isKeyboardOpen) {
scrollState.animateScrollTo(scrollState.value + scrollOffset.toInt())
}
}
Column(
modifier = Modifier
.systemBarsPadding()
@ -186,7 +202,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
Column(
modifier = Modifier
.verticalScroll(state = scrollState)
.padding(bottom = TangemTheme.dimens.spacing80),
.padding(bottom = contentBottomPadding),
) {
content(model)
}
@ -199,7 +215,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
Box(
modifier = Modifier
.fillMaxWidth()
.height(80.dp)
.height(buttonHeight)
.align(Alignment.BottomCenter),
) {
footer(model)
@ -219,6 +235,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
onBack = onBack,
dragHandle = null,
content = bsContent,
scrimColor = TangemTheme.colors.overlay.secondary,
)
} else {
ModalBottomSheet(
@ -230,6 +247,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
contentWindowInsets = { WindowInsetsZero },
dragHandle = null,
content = bsContent,
scrimColor = TangemTheme.colors.overlay.secondary,
)
}
}

View file

@ -192,6 +192,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) },
onBack = onBack,
content = bsContent,
scrimColor = TangemTheme.colors.overlay.secondary,
)
} else {
ModalBottomSheet(
@ -203,6 +204,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
contentWindowInsets = { WindowInsetsZero },
dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) },
content = bsContent,
scrimColor = TangemTheme.colors.overlay.secondary,
)
}
}

View file

@ -57,9 +57,9 @@ private fun Preview_TangemBottomSheetTitle() {
TangemThemePreview {
TangemBottomSheetTitle(
title = "Title",
endButton = TopAppBarButtonUM(
endButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_information_24,
onIconClicked = {},
onClicked = {},
),
containerColor = TangemTheme.colors.background.secondary,
)

View file

@ -11,6 +11,7 @@ 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.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
@ -21,6 +22,7 @@ import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -32,7 +34,9 @@ fun HorizontalActionChips(
contentPadding: PaddingValues = PaddingValues(TangemTheme.dimens.spacing0),
) {
LazyRow(
modifier = modifier.fillMaxWidth(),
modifier = modifier
.fillMaxWidth()
.testTag(TokenDetailsScreenTestTags.HORIZONTAL_ACTION_CHIPS),
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
verticalAlignment = Alignment.CenterVertically,
contentPadding = contentPadding,

View file

@ -20,6 +20,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -33,6 +34,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
/**
* Rounded action button
@ -98,7 +100,7 @@ fun ActionButton(
),
)
},
modifier = modifier,
modifier = modifier.testTag(TokenDetailsScreenTestTags.ACTION_BUTTON),
color = color,
containerColor = containerColor,
)

View file

@ -25,10 +25,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.DialogTestTags
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.utils.MultipleClickPreventer
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LongMethod")
@Composable
fun TangemButton(
text: String,
@ -51,7 +51,7 @@ fun TangemButton(
Button(
modifier = modifier
.heightIn(min = size.toHeightDp())
.testTag(DialogTestTags.BUTTON),
.testTag(BaseButtonTestTags.BUTTON),
onClick = {
multipleClickPreventer.processEvent { if (!showProgress) onClick() }
},
@ -78,7 +78,8 @@ fun TangemButton(
ResizableText(
modifier = Modifier
.weight(1f, fill = false)
.heightIn(MinButtonContentSize, maxContentSize),
.heightIn(MinButtonContentSize, maxContentSize)
.testTag(BaseButtonTestTags.TEXT),
text = text,
style = textStyle,
color = colors.contentColor(enabled = enabled).value,
@ -92,7 +93,8 @@ fun TangemButton(
Icon(
modifier = Modifier
.buttonContentSize(maxContentSize)
.padding(vertical = 2.dp),
.padding(vertical = 2.dp)
.testTag(BaseButtonTestTags.ICON),
painter = painterResource(id = iconResId),
tint = colors.contentColor(enabled = enabled).value,
contentDescription = null,

View file

@ -3,10 +3,11 @@ package com.tangem.core.ui.components.buttons.small
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -41,20 +42,19 @@ fun TangemIconButton(
background: Color = TangemTheme.colors.button.secondary,
iconTint: Color = TangemTheme.colors.icon.secondary,
) {
IconButton(
onClick = onClick,
Icon(
painter = rememberVectorPainter(ImageVector.vectorResource(iconRes)),
contentDescription = "",
tint = iconTint,
modifier = modifier
.size(24.dp)
.clip(shape)
.background(background)
.size(24.dp),
) {
Icon(
painter = rememberVectorPainter(ImageVector.vectorResource(iconRes)),
contentDescription = "",
tint = iconTint,
modifier = Modifier.size(16.dp),
)
}
.padding(4.dp)
.clickable(
onClick = onClick,
),
)
}
// region Preview

View file

@ -5,7 +5,7 @@ import com.tangem.core.ui.extensions.getTintForTokenIcon
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.extensions.tryGetBackgroundForTokenIcon
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.utils.converter.Converter
/**

View file

@ -12,12 +12,8 @@ import androidx.compose.ui.Alignment.Companion.TopCenter
import androidx.compose.ui.Alignment.Companion.TopStart
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ParagraphIntrinsics
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.createFontFamilyResolver
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
@ -28,6 +24,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.StakingSendScreenTestTags
import com.tangem.core.ui.utils.*
import java.math.BigDecimal
import java.text.DecimalFormat
@ -77,24 +74,10 @@ fun AmountTextField(
) {
val decimalFormat = rememberDecimalFormat()
BoxWithConstraints(modifier = modifier) {
var fontSize = textStyle.fontSize
if (isAutoResize) {
val calculateIntrinsics = @Composable {
val transformedText = visualTransformation.filter(AnnotatedString(value)).text.text
ParagraphIntrinsics(
text = transformedText,
style = textStyle.copy(fontSize = fontSize),
density = LocalDensity.current,
fontFamilyResolver = createFontFamilyResolver(LocalContext.current),
)
}
var intrinsics = calculateIntrinsics()
with(LocalDensity.current) {
while (intrinsics.maxIntrinsicWidth > maxWidth.toPx()) {
fontSize *= reduceFactor
intrinsics = calculateIntrinsics()
}
}
val fontSize = if (isAutoResize) {
resizeFont(visualTransformation, value, textStyle, reduceFactor)
} else {
textStyle.fontSize
}
val textColor = if (value.isBlank()) TangemTheme.colors.text.disabled else color
SimpleTextField(
@ -121,7 +104,9 @@ fun AmountTextField(
singleLine = true,
readOnly = !isEnabled,
visualTransformation = visualTransformation,
modifier = Modifier.background(backgroundColor),
modifier = Modifier
.background(backgroundColor)
.testTag(StakingSendScreenTestTags.INPUT_TEXT_FIELD),
)
}
}

View file

@ -0,0 +1,204 @@
package com.tangem.core.ui.components.fields
import android.annotation.SuppressLint
import androidx.annotation.FloatRange
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ParagraphIntrinsics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.createFontFamilyResolver
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.TextUnit
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* Simple text field for auto size input.
* Can display aligned placeholder.
*
* @param value initial text
* @param onValueChange callback
* @param isAutoResize is text font auto resize
* @param reduceFactor font resize factor
* @param textStyle text and placeholder styles
* @param textFieldModifier modifier for [SimpleTextField]
* @param boxModifier modifier for [BoxWithConstraints]
* @see [SimpleTextField] for other text field params
*/
@SuppressLint("UnusedBoxWithConstraintsScope")
@Composable
fun AutoSizeTextField(
value: String,
onValueChange: (String) -> Unit,
// region AutoSize
isAutoResize: Boolean = true,
@FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
reduceFactor: Double = 0.9,
// region TextField
textFieldModifier: Modifier = Modifier,
boxModifier: Modifier = Modifier,
placeholder: TextReference? = null,
singleLine: Boolean = isAutoResize,
centered: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
color: Color = TangemTheme.colors.text.primary1,
textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color),
placeholderColor: Color = TangemTheme.colors.text.disabled,
readOnly: Boolean = false,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
isValuePasted: Boolean = false,
onValuePastedTriggerDismiss: () -> Unit = {},
decorationBox: (@Composable (innerTextField: @Composable () -> Unit) -> Unit)? = null,
) {
BoxWithConstraints(modifier = boxModifier) {
val fontSize = if (isAutoResize) {
resizeFont(visualTransformation, value, textStyle, reduceFactor)
} else {
textStyle.fontSize
}
val textColor = if (value.isBlank()) TangemTheme.colors.text.disabled else color
SimpleTextField(
value = value,
onValueChange = onValueChange,
textStyle = textStyle.copy(
fontSize = fontSize,
textDirection = TextDirection.ContentOrLtr,
),
isValuePasted = isValuePasted,
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
color = textColor,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
placeholder = placeholder,
placeholderColor = placeholderColor,
singleLine = singleLine,
interactionSource = interactionSource,
readOnly = readOnly,
centered = centered,
visualTransformation = visualTransformation,
decorationBox = decorationBox,
modifier = textFieldModifier,
)
}
}
@Composable
internal fun BoxWithConstraintsScope.resizeFont(
visualTransformation: VisualTransformation,
value: String,
textStyle: TextStyle,
reduceFactor: Double,
): TextUnit {
var result = textStyle.fontSize
val calculateIntrinsics = @Composable {
val transformedText = visualTransformation.filter(AnnotatedString(value)).text.text
ParagraphIntrinsics(
text = transformedText,
style = textStyle.copy(fontSize = result),
density = LocalDensity.current,
fontFamilyResolver = createFontFamilyResolver(LocalContext.current),
)
}
var intrinsics = calculateIntrinsics()
with(LocalDensity.current) {
while (intrinsics.maxIntrinsicWidth > maxWidth.toPx()) {
result *= reduceFactor
intrinsics = calculateIntrinsics()
}
}
return result
}
// region preview
@Preview(widthDp = 360, showBackground = true)
@Composable
private fun AmountTextFieldPreview(
@PreviewParameter(AutoSizeTextFieldPreviewProvider::class) data: AutoSizeTextFieldPreviewData,
) {
var text by remember { mutableStateOf(data.value) }
TangemThemePreview {
AutoSizeTextField(
textFieldModifier = Modifier.fillMaxWidth(),
value = text,
onValueChange = { text = it },
centered = data.centered,
isAutoResize = data.isAutoResize,
placeholder = data.placeholder,
)
}
}
private class AutoSizeTextFieldPreviewProvider : PreviewParameterProvider<AutoSizeTextFieldPreviewData> {
override val values = sequenceOf(
AutoSizeTextFieldPreviewData(
value = "AutoSizeTextField",
placeholder = stringReference("placeholder"),
isAutoResize = true,
centered = false,
),
AutoSizeTextFieldPreviewData(
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
placeholder = stringReference("placeholder"),
isAutoResize = true,
centered = false,
),
AutoSizeTextFieldPreviewData(
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
placeholder = stringReference("Placeholder"),
isAutoResize = true,
centered = false,
),
AutoSizeTextFieldPreviewData(
value = "",
placeholder = stringReference("Placeholder"),
isAutoResize = true,
centered = false,
),
AutoSizeTextFieldPreviewData(
value = "AutoSizeTextField",
placeholder = stringReference("Placeholder"),
isAutoResize = false,
centered = true,
),
AutoSizeTextFieldPreviewData(
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
placeholder = stringReference("Placeholder"),
isAutoResize = false,
centered = true,
),
AutoSizeTextFieldPreviewData(
value = "",
placeholder = stringReference("Placeholder"),
isAutoResize = false,
centered = true,
),
)
}
private data class AutoSizeTextFieldPreviewData(
val value: String,
val placeholder: TextReference,
val isAutoResize: Boolean,
val centered: Boolean,
)
// endregion

View file

@ -2,6 +2,7 @@ package com.tangem.core.ui.components.fields
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@ -36,9 +37,9 @@ fun PinTextField(
value: String,
length: Int,
isPasswordVisual: Boolean,
pinTextColor: PinTextColor,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
wrongCode: Boolean = false,
) {
val focusRequester = remember { FocusRequester() }
val textFieldValue = remember(value) {
@ -72,7 +73,7 @@ fun PinTextField(
CellDecoration(
length = length,
isPasswordVisual = isPasswordVisual,
wrongCode = wrongCode,
pinTextColor = pinTextColor,
value = value,
)
},
@ -84,17 +85,25 @@ fun PinTextField(
}
}
@Suppress("MagicNumber")
enum class PinTextColor {
Primary,
WrongCode,
Success,
}
@Suppress("MagicNumber", "LongMethod")
@Composable
private fun CellDecoration(
length: Int,
wrongCode: Boolean,
pinTextColor: PinTextColor,
value: String,
modifier: Modifier = Modifier,
isPasswordVisual: Boolean = false,
) {
val textMeasurer = rememberTextMeasurer()
val width = textMeasurer.measure("0")
val minSize = textMeasurer.measure("0")
val minWidth = maxOf(minSize.size.width.dp + 8.dp, 24.dp + 3.dp) // 24.dp is the minimum width of a pin cell
val minHeight = maxOf(minSize.size.height.dp, 48.dp) // 48.dp is the minimum height of a pin cell
Row(
modifier = modifier,
@ -107,6 +116,18 @@ private fun CellDecoration(
""
}
val color = when (pinTextColor) {
PinTextColor.Primary -> {
if (isPasswordVisual) {
TangemTheme.colors.icon.informative
} else {
TangemTheme.colors.text.primary1
}
}
PinTextColor.WrongCode -> TangemTheme.colors.icon.warning
PinTextColor.Success -> TangemTheme.colors.icon.accent
}
Box(
modifier = Modifier
.background(
@ -119,26 +140,34 @@ private fun CellDecoration(
targetState = char,
transitionSpec = {
(
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
slideInVertically(animationSpec = tween(330, delayMillis = 0))
fadeIn(animationSpec = tween(90, delayMillis = 90)) +
slideInVertically(animationSpec = tween(220, delayMillis = 0))
)
.togetherWith(
fadeOut(animationSpec = tween(90)) + slideOutVertically(tween(220)),
)
},
) { text ->
Text(
modifier = Modifier.sizeIn(minWidth = width.size.width.dp + 8.dp, minHeight = 48.dp),
text = text,
style = TangemTheme.typography.h3,
color = if (wrongCode) {
TangemTheme.colors.text.warning
} else {
TangemTheme.colors.text.primary1
},
textAlign = TextAlign.Center,
lineHeight = 48.sp,
)
if (isPasswordVisual && text.isNotEmpty()) {
Canvas(
Modifier.sizeIn(minWidth = minWidth, minHeight = minHeight),
) {
drawCircle(
color = color,
radius = 4.dp.toPx(),
center = center,
)
}
} else {
Text(
modifier = Modifier.sizeIn(minWidth = minWidth, minHeight = minHeight),
text = text,
style = TangemTheme.typography.h3,
color = color,
textAlign = TextAlign.Center,
lineHeight = 48.sp,
)
}
}
}
}
@ -152,10 +181,18 @@ private fun Preview() {
var text by remember { mutableStateOf("123") }
Column {
PinTextField(
value = text,
onValueChange = { text = it },
isPasswordVisual = true,
pinTextColor = PinTextColor.Success,
length = 6,
)
PinTextField(
value = text,
onValueChange = { text = it },
isPasswordVisual = false,
pinTextColor = PinTextColor.Primary,
length = 6,
)

View file

@ -11,16 +11,20 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
@ -35,6 +39,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SelectCountryBottomSheetTestTags
@Composable
fun SearchBar(
@ -45,6 +50,7 @@ fun SearchBar(
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }
val interactionSource = remember { MutableInteractionSource() }
BasicTextField(
@ -57,7 +63,9 @@ fun SearchBar(
} else {
state.onActiveChange(false)
}
},
}
.focusRequester(focusRequester)
.testTag(SelectCountryBottomSheetTestTags.SEARCH_BAR),
enabled = enabled,
value = state.query,
onValueChange = state.onQueryChange,
@ -89,6 +97,10 @@ fun SearchBar(
)
},
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
@Suppress("LongParameterList")

View file

@ -8,6 +8,7 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@ -17,6 +18,7 @@ import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@ -39,6 +41,7 @@ fun SimpleTextField(
textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color),
placeholderColor: Color = TangemTheme.colors.text.disabled,
readOnly: Boolean = false,
centered: Boolean = false,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
isValuePasted: Boolean = false,
onValuePastedTriggerDismiss: () -> Unit = {},
@ -80,6 +83,8 @@ fun SimpleTextField(
onValuePastedTriggerDismiss()
}
}
var textStyle = textStyle.copy(color = color)
if (centered) textStyle = textStyle.copy(textAlign = TextAlign.Center)
BasicTextField(
value = textFieldValue,
@ -91,7 +96,7 @@ fun SimpleTextField(
if (stringChangedSinceLastInvocation) onValueChange(newTextFieldValueState.text)
},
textStyle = textStyle.copy(color = color),
textStyle = textStyle,
cursorBrush = SolidColor(TangemTheme.colors.text.primary1),
singleLine = singleLine,
readOnly = readOnly,
@ -105,6 +110,7 @@ fun SimpleTextField(
value = value,
textStyle = textStyle,
textValue = textValue,
centered = centered,
color = placeholderColor,
)
},
@ -118,10 +124,11 @@ private fun SimpleTextPlaceholder(
placeholder: TextReference?,
value: String,
textStyle: TextStyle,
centered: Boolean,
textValue: @Composable () -> Unit,
color: Color = TangemTheme.colors.text.disabled,
) {
Box {
Box(contentAlignment = if (centered) Alignment.Center else Alignment.TopStart) {
if (value.isBlank() && placeholder != null) {
AnimatedContent(
targetState = placeholder,

View file

@ -16,6 +16,7 @@ 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.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -26,6 +27,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.BaseBlockTestTags
/**
* [InputRowDefault](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-807&mode=design&t=86eKp9izWxUvmoCq-4)
@ -64,20 +66,24 @@ fun InputRowDefault(
) {
Column(
modifier = Modifier
.weight(1f),
.weight(1f)
.testTag(BaseBlockTestTags.BLOCK),
) {
title?.let {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = titleColor,
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing8),
modifier = Modifier
.padding(bottom = TangemTheme.dimens.spacing8)
.testTag(BaseBlockTestTags.BLOCK_TITLE),
)
}
Text(
text = text.resolveReference(),
style = TangemTheme.typography.body2,
color = textColor,
modifier = Modifier.testTag(BaseBlockTestTags.BLOCK_TEXT),
)
}
iconRes?.let {

View file

@ -10,6 +10,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment.Companion.CenterEnd
@ -19,6 +20,7 @@ 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 androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.small.TangemIconButton
import com.tangem.core.ui.components.fields.SimpleTextField
@ -69,11 +71,12 @@ fun InputRowRecipient(
showDivider: Boolean = false,
isLoading: Boolean = false,
isValuePasted: Boolean = false,
resolvedAddress: String? = null,
) {
val (titleText, color) = if (isError && error != null) {
error to TangemTheme.colors.text.warning
} else {
title to TangemTheme.colors.text.secondary
title to TangemTheme.colors.text.tertiary
}
DividerContainer(
modifier = modifier,
@ -144,11 +147,15 @@ fun InputRowRecipient(
} else {
TangemTheme.colors.text.primary2
},
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing8),
modifier = Modifier.padding(start = TangemTheme.dimens.spacing8),
)
}
}
ResolvedAddressRow(
isLoading = isLoading,
resolvedAddress = resolvedAddress,
)
}
}
}
@ -203,6 +210,38 @@ private fun RowScope.InputIcon(isLoading: Boolean, value: String) {
}
}
@Composable
private fun ResolvedAddressRow(isLoading: Boolean, resolvedAddress: String?) {
AnimatedContent(
targetState = if (resolvedAddress.isNullOrBlank() || isLoading) {
ResolvedState.Hide
} else {
ResolvedState.Show(resolvedAddress)
},
label = "Resolved Address",
) { state ->
if (state is ResolvedState.Show) {
Column {
HorizontalDivider(
thickness = 0.5.dp,
modifier = Modifier.padding(top = 12.dp, bottom = 12.dp),
color = TangemTheme.colors.stroke.primary,
)
Text(
text = state.address,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
}
private sealed interface ResolvedState {
data object Hide : ResolvedState
data class Show(val address: String) : ResolvedState
}
//region preview
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@ -224,6 +263,7 @@ private fun InputRowRecipientPreview(
onQrCodeClick = {},
modifier = Modifier.background(TangemTheme.colors.background.primary),
isRedesignEnabled = false,
resolvedAddress = value.resolvedAddress,
)
}
}
@ -232,6 +272,7 @@ private data class InputRowRecipientPreviewData(
val value: String,
val isError: Boolean,
val isLoading: Boolean = false,
val resolvedAddress: String? = null,
)
private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider<InputRowRecipientPreviewData> {
@ -250,6 +291,12 @@ private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider<In
isLoading = true,
isError = true,
),
InputRowRecipientPreviewData(
value = "vitalik.eth",
isLoading = false,
isError = true,
resolvedAddress = "0x391316d97a07027a0702c8A002c8A0C25d8470",
),
)
}
//endregion

View file

@ -0,0 +1,104 @@
package com.tangem.core.ui.components.label
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* Label component
*
* @param state component state
* @param modifier composable modifier
*
* @see <a href="https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=4480-1459&t=2QTpi1G7FeTexTFS-4">Figma</a>
*/
@Composable
fun Label(state: LabelUM, modifier: Modifier = Modifier) {
val backgroundColor by animateColorAsState(
targetValue = when (state.style) {
LabelStyle.ACCENT -> TangemTheme.colors.text.accent.copy(alpha = 0.1f)
LabelStyle.REGULAR -> TangemTheme.colors.control.unchecked
LabelStyle.WARNING -> TangemTheme.colors.text.warning.copy(alpha = 0.1f)
},
)
val textColor by animateColorAsState(
targetValue = when (state.style) {
LabelStyle.ACCENT -> TangemTheme.colors.text.accent
LabelStyle.REGULAR -> TangemTheme.colors.text.secondary
LabelStyle.WARNING -> TangemTheme.colors.text.warning
},
)
AnimatedContent(targetState = state.text) { text ->
Box(
modifier = modifier
.padding(horizontal = 4.dp)
.background(
color = backgroundColor,
shape = TangemTheme.shapes.roundedCorners8,
)
.padding(horizontal = 8.dp, vertical = 4.dp),
) {
Text(
text = text.resolveReference(),
style = TangemTheme.typography.caption1,
color = textColor,
)
}
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun LabelPreview() {
TangemThemePreview {
Column(
modifier = Modifier.padding(16.dp),
) {
Label(
state = LabelUM(
text = TextReference.Str("Regular Label"),
style = LabelStyle.REGULAR,
),
)
Spacer(modifier = Modifier.height(8.dp))
Label(
state = LabelUM(
text = TextReference.Str("Accent Label"),
style = LabelStyle.ACCENT,
),
)
Spacer(modifier = Modifier.height(8.dp))
Label(
state = LabelUM(
text = TextReference.Str("Warning Label"),
style = LabelStyle.WARNING,
),
)
}
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.core.ui.components.label.entity
import com.tangem.core.ui.extensions.TextReference
data class LabelUM(
val text: TextReference,
val style: LabelStyle,
)
enum class LabelStyle {
REGULAR, ACCENT, WARNING,
}

View file

@ -20,7 +20,7 @@ import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.utils.StringsSigns.DASH_SIGN
/**
* Market price block
@ -120,7 +120,7 @@ private fun PriceBlock(state: MarketPriceBlockState, priceWidthDp: Dp) {
)
}
} else {
Price(price = BigDecimalFormatter.EMPTY_BALANCE_SIGN, modifier = priceModifier)
Price(price = DASH_SIGN, modifier = priceModifier)
}
}
}

View file

@ -20,6 +20,7 @@ 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.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
@ -35,6 +36,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState as NotificationButtonsState
/**
@ -206,6 +208,7 @@ internal fun TextsBlock(
text = titleText,
color = titleColor,
style = TangemTheme.typography.button,
modifier = Modifier.testTag(NotificationTestTags.TITLE),
)
SpacerH(height = TangemTheme.dimens.spacing2)
@ -217,6 +220,7 @@ internal fun TextsBlock(
text = subtitleText,
color = subtitleColor,
style = TangemTheme.typography.caption2,
modifier = Modifier.testTag(NotificationTestTags.TEXT),
)
}
}

View file

@ -23,6 +23,7 @@ import androidx.constraintlayout.compose.Visibility
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.audits.AuditLabel
import com.tangem.core.ui.components.audits.AuditLabelUM
import com.tangem.core.ui.components.badge.Badge
@ -50,8 +51,6 @@ private const val DISABLED_ICON_ALPHA = 0.4f
fun ProviderChooseCrypto(providerChooseUM: ProviderChooseUM, onClick: () -> Unit, modifier: Modifier = Modifier) {
ConstraintLayout(
modifier = modifier
.background(TangemTheme.colors.background.action)
.clip(RoundedCornerShape(14.dp))
.selectedBorder(isSelected = providerChooseUM.isSelected)
.clickable(
enabled = !providerChooseUM.hasError(),
@ -133,13 +132,23 @@ private fun IconContent(iconUrl: String, modifier: Modifier = Modifier) {
SubcomposeAsyncImage(
modifier = modifier
.size(40.dp)
.clip(RoundedCornerShape(8.dp))
.background(TangemColorPalette.Light1),
.clip(RoundedCornerShape(8.dp)),
model = ImageRequest.Builder(context = LocalContext.current)
.data(iconUrl)
.crossfade(enable = true)
.allowHardware(false)
.build(),
loading = {
RectangleShimmer(radius = 8.dp)
},
error = {
Box(
modifier = Modifier.background(
color = TangemColorPalette.Light1,
shape = RoundedCornerShape(8.dp),
),
)
},
contentDescription = null,
)
}

View file

@ -14,6 +14,7 @@ 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.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
@ -23,6 +24,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.StakingDetailsScreenTestTags
@Suppress("LongParameterList")
@Composable
@ -54,7 +56,8 @@ fun RoundableCornersRow(
.padding(
horizontal = TangemTheme.dimens.spacing16,
vertical = TangemTheme.dimens.spacing12,
),
)
.testTag(StakingDetailsScreenTestTags.PARAMETER_BLOCK),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
) {
@ -63,6 +66,7 @@ fun RoundableCornersRow(
color = startTextColor,
maxLines = 1,
style = startTextStyle,
modifier = Modifier.testTag(StakingDetailsScreenTestTags.PARAMETER_NAME),
)
if (iconResId != null && iconClick != null) {
Icon(
@ -85,6 +89,7 @@ fun RoundableCornersRow(
color = endTextColor,
maxLines = 1,
style = endTextStyle,
modifier = Modifier.testTag(StakingDetailsScreenTestTags.PARAMETER_VALUE),
)
}
}

View file

@ -2,7 +2,6 @@ package com.tangem.core.ui.components.rows
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -13,6 +12,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
@ -22,14 +22,15 @@ import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SelectNetworkFeeBottomSheetTestTags
import com.tangem.utils.StringsSigns
@Composable
fun SelectorRowItem(
@StringRes titleRes: Int,
title: TextReference,
@DrawableRes iconRes: Int,
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(TangemTheme.dimens.spacing12),
@ -68,7 +69,8 @@ fun SelectorRowItem(
Row(
modifier = Modifier
.fillMaxWidth()
.padding(paddingValues),
.padding(paddingValues)
.testTag(SelectNetworkFeeBottomSheetTestTags.SELECTOR_ITEM),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
@ -77,7 +79,7 @@ fun SelectorRowItem(
contentDescription = null,
)
Text(
text = stringResourceSafe(titleRes),
text = title.resolveReference(),
style = textStyle,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing8),
@ -148,7 +150,7 @@ private fun RowScope.SelectorValueContent(
private fun SelectorRowItemPreview() {
TangemThemePreview {
SelectorRowItem(
titleRes = R.string.common_fee_selector_option_slow,
title = resourceReference(R.string.common_fee_selector_option_slow),
iconRes = R.drawable.ic_tortoise_24,
preDot = TextReference.Str("1000 ETH"),
postDot = TextReference.Str("1000 $"),

View file

@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -28,6 +29,7 @@ import com.tangem.core.ui.components.stories.model.StoryConfig
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SwapStoriesScreenTestTags
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -101,7 +103,8 @@ inline fun <reified T : StoryConfig> StoriesContainer(
interactionSource = remember { MutableInteractionSource() },
indication = LocalIndication.current,
onClick = { config.onClose(watchedCounter) },
),
)
.testTag(SwapStoriesScreenTestTags.CLOSE_BUTTON),
)
}
}

View file

@ -94,6 +94,7 @@ fun getActiveIconRes(blockchainId: String): Int {
"zklink", "zklink/test" -> R.drawable.img_zklink_22
"vanar-chain", "vanar-chain/test" -> R.drawable.img_vanar_22
"pepecoin", "pepecoin/test" -> R.drawable.img_pepecoin_22
"hyperliquid", "hyperliquid/test" -> R.drawable.img_hyperliquid_22
else -> R.drawable.ic_alert_24
}
}
@ -186,6 +187,7 @@ fun getActiveIconResByCoinId(coinId: String): Int {
"zklink", "zklink/test" -> R.drawable.img_zklink_22
"vanar-chain", "vanar-chain/test" -> R.drawable.img_vanar_22
"pepecoin-network", "pepecoin-network/test" -> R.drawable.img_pepecoin_22
"hyperliquid", "hyperliquid/test" -> R.drawable.img_hyperliquid_22
else -> R.drawable.ic_alert_24
}
}
@ -281,6 +283,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
"zklink", "zklink/test" -> R.drawable.ic_zklink_22
"vanar-chain", "vanar-chain/test" -> R.drawable.ic_vanar_22
"pepecoin", "pepecoin/test" -> R.drawable.ic_pepecoin_22
"hyperliquid", "hyperliquid/test" -> R.drawable.ic_hyperliquid_22
else -> R.drawable.ic_alert_24
}
}

View file

@ -1,49 +0,0 @@
package com.tangem.core.ui.extensions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import timber.log.Timber
/**
* The ViewModel is scoped to the parent route Navigation graph
* and is provided using the Hilt-generated ViewModel factory
*
* ```
* val navController = rememberNavController()
*
* navigation(
* route = "parent",
* startDestination = "parent/1"
* ) {
* composable("route/1") { entry ->
* val viewModel = entry.parentHiltViewModel(navController)
* }
* composable("route/2") { entry ->
* val viewModel = entry.parentHiltViewModel(navController)
* }
* composable("route/3") { entry ->
* val viewModel = entry.parentHiltViewModel(navController)
* }
* }
* ```
*
* @param navController NavController within the common NavGraph
* @throws Exception if there is no parent route
*/
@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.parentHiltViewModel(navController: NavController): T {
val viewModelStoreOwner = remember(this) {
try {
navController.getBackStackEntry(this.destination.parent!!.id)
} catch (e: Exception) {
Timber.tag("scopedViewModel").e(e, "There is no parent route'")
throw e
}
}
return hiltViewModel<T>(viewModelStoreOwner)
}

View file

@ -1,43 +0,0 @@
package com.tangem.core.ui.extensions
import android.R
import android.content.Context
import android.graphics.Color.*
import android.view.WindowManager
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import kotlin.math.sqrt
@Deprecated("Use only in legacy fragments")
fun Fragment.setStatusBarColor(@ColorRes colorResId: Int) {
with(requireActivity().window) {
clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = ContextCompat.getColor(requireContext(), colorResId)
val view = view ?: return
val windowInsetsController = WindowCompat.getInsetsController(this, view)
windowInsetsController.isAppearanceLightStatusBars = luminance(requireContext(), colorResId)
}
}
// TODO replace by android.graphics.luminance() after bump min API to 24
@Suppress("MagicNumber")
fun luminance(context: Context, @ColorRes colorRes: Int): Boolean {
val color = context.resources.getColor(colorRes, null)
if (R.color.transparent == color) return true
var rtnValue = false
val rgb = intArrayOf(red(color), green(color), blue(color))
val brightness = sqrt(
rgb[0] * rgb[0] * .241 +
rgb[1] * rgb[1] * .691 +
rgb[2] * rgb[2] * .068,
).toInt()
// color is light
if (brightness >= 200) {
rtnValue = true
}
return rtnValue
}

View file

@ -4,7 +4,6 @@ import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@ -72,26 +71,24 @@ fun Modifier.conditionalCompose(
fun Modifier.selectedBorder(
isSelected: Boolean,
width: Dp = 2.5.dp,
color: Color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
color: Color = TangemTheme.colors.text.accent,
radius: Dp = 16.dp,
) = conditionalCompose(
condition = isSelected,
modifier = {
border(
outsetBorder(
width = width,
color = color,
shape = RoundedCornerShape(radius),
color = color.copy(alpha = 0.15f),
shape = RoundedCornerShape(radius + 2.dp),
)
.padding(width)
.border(
width = 1.dp,
color = TangemTheme.colors.text.accent,
shape = RoundedCornerShape(radius - 2.dp),
color = color,
shape = RoundedCornerShape(radius),
)
.clip(RoundedCornerShape(radius - 2.dp))
.clip(RoundedCornerShape(radius))
},
otherModifier = {
padding(width)
.clip(RoundedCornerShape(radius - 2.dp))
clip(RoundedCornerShape(radius))
},
)

View file

@ -121,7 +121,10 @@ fun BigDecimalFiatFormat.price(): BigDecimalFormat = BigDecimalFormat { value ->
private fun BigDecimal.isLessThanThreshold() = this > BigDecimal.ZERO && this < FIAT_FORMAT_THRESHOLD
private fun getFiatPriceAmountWithScale(value: BigDecimal): Pair<BigDecimal, Int> {
/**
* Returns amount with correct scale
*/
fun getFiatPriceAmountWithScale(value: BigDecimal): Pair<BigDecimal, Int> {
return if (value < BigDecimal.ONE) {
val leadingZeroes = value.scale() - value.precision()
val scale = leadingZeroes + FRACTIONAL_PART_LENGTH_AFTER_LEADING_ZEROES

View file

@ -16,6 +16,7 @@ class TangemColors internal constructor(
control: Control,
stroke: Stroke,
field: Field,
overlay: Overlay,
) {
var text by mutableStateOf(text)
private set
@ -31,6 +32,7 @@ class TangemColors internal constructor(
private set
var field by mutableStateOf(field)
private set
var overlay by mutableStateOf(overlay)
@Stable
class Text internal constructor(
@ -220,6 +222,22 @@ class TangemColors internal constructor(
}
}
@Stable
class Overlay internal constructor(
primary: Color,
secondary: Color,
) {
var primary by mutableStateOf(primary)
private set
var secondary by mutableStateOf(secondary)
private set
fun update(other: Overlay) {
primary = other.primary
secondary = other.secondary
}
}
fun update(other: TangemColors) {
text.update(other.text)
icon.update(other.icon)
@ -228,5 +246,6 @@ class TangemColors internal constructor(
control.update(other.control)
stroke.update(other.stroke)
field.update(other.field)
overlay.update(other.overlay)
}
}

View file

@ -4,9 +4,9 @@ import android.app.Activity
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
@ -90,7 +90,7 @@ fun TangemTheme(
val rootBackgroundColor = rememberedColors.background.secondary
MaterialTheme(
colors = materialThemeColors(colors = themeColors, isDark = isDark),
colorScheme = tangemColorScheme(colors = themeColors),
) {
CompositionLocalProvider(
LocalTangemColors provides rememberedColors,
@ -143,21 +143,51 @@ object TangemTheme {
@Stable
@Composable
private fun materialThemeColors(colors: TangemColors, isDark: Boolean): Colors {
return Colors(
private fun tangemColorScheme(colors: TangemColors): ColorScheme {
return ColorScheme(
primary = colors.background.primary,
primaryVariant = colors.background.secondary,
secondary = colors.button.primary,
secondaryVariant = colors.text.accent,
background = colors.background.primary,
surface = colors.background.secondary,
error = colors.text.warning,
onPrimary = colors.text.primary1,
primaryContainer = colors.background.secondary,
onPrimaryContainer = colors.background.action,
inversePrimary = colors.background.action,
secondary = colors.button.primary,
onSecondary = colors.text.primary1,
secondaryContainer = colors.background.secondary,
onSecondaryContainer = colors.text.primary1,
tertiary = colors.background.tertiary,
onTertiary = colors.text.tertiary,
tertiaryContainer = colors.background.tertiary,
onTertiaryContainer = colors.text.tertiary,
background = colors.background.primary,
onBackground = colors.text.primary1,
surface = colors.background.secondary,
surfaceVariant = colors.background.tertiary,
onSurface = colors.text.primary1,
onSurfaceVariant = colors.text.secondary,
surfaceTint = colors.background.tertiary,
inverseSurface = colors.button.disabled,
inverseOnSurface = colors.button.primary,
surfaceBright = colors.background.secondary,
surfaceDim = colors.background.tertiary,
surfaceContainer = colors.background.tertiary,
surfaceContainerHigh = colors.background.tertiary,
surfaceContainerHighest = colors.background.tertiary,
surfaceContainerLow = colors.background.tertiary,
surfaceContainerLowest = colors.background.tertiary,
error = colors.text.warning,
errorContainer = colors.background.tertiary,
onErrorContainer = colors.text.primary2,
onError = colors.text.primary2,
isLight = !isDark,
outline = colors.stroke.primary,
outlineVariant = colors.stroke.secondary,
scrim = colors.stroke.transparency,
)
}
@ -208,6 +238,10 @@ private fun lightThemeColors(): TangemColors {
primary = TangemColorPalette.Light1,
focused = TangemColorPalette.Light2,
),
overlay = TangemColors.Overlay(
primary = TangemColorPalette.Black.copy(alpha = 0.4f),
secondary = TangemColorPalette.Black.copy(alpha = 0.7f),
),
)
}
@ -258,6 +292,10 @@ private fun darkThemeColors(): TangemColors {
primary = TangemColorPalette.Dark5,
focused = TangemColorPalette.Dark4,
),
overlay = TangemColors.Overlay(
primary = TangemColorPalette.Black.copy(alpha = 0.4f),
secondary = TangemColorPalette.Black.copy(alpha = 0.7f),
),
)
}

View file

@ -1,72 +0,0 @@
package com.tangem.core.ui.screen
import android.app.Dialog
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.ReadOnlyComposable
import androidx.compose.ui.Modifier
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.res.TangemTheme
/**
* An abstract base class for bottom sheet dialogs that use Compose for UI rendering.
* Extends [BottomSheetDialogFragment] and implements [ComposeScreen] interface.
*/
abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment(), ComposeScreen {
/**
* The initial state of the bottom sheet. Default is [BottomSheetBehavior.STATE_EXPANDED].
*/
open val initialBottomSheetState = BottomSheetBehavior.STATE_EXPANDED
/**
* The fraction of the screen height that the bottom sheet should take when expanded.
* Default is `null`, indicating that the height will be determined by the content.
*/
@FloatRange(from = 0.0, to = 1.0)
open val expandedHeightFraction: Float? = null
override val screenModifier: Modifier
@Composable
@ReadOnlyComposable
get() = Modifier
.fillMaxWidth()
.let {
if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it
}
.background(
color = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.bottomSheet,
)
override fun getTheme(): Int = R.style.AppTheme_TransparentBottomSheetDialog
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return createComposeView(
context = inflater.context,
activity = requireActivity(),
overrideSystemBarColors = false,
)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
(dialog as BottomSheetDialog).behavior.apply {
state = initialBottomSheetState
skipCollapsed = true
}
return dialog
}
}

View file

@ -1,50 +0,0 @@
package com.tangem.core.ui.screen
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import com.tangem.core.ui.R
/**
* An abstract base class for fragments that use Compose for UI rendering.
* Extends [Fragment] and implements [ComposeScreen] interface.
*/
abstract class ComposeFragment : Fragment(), ComposeScreen {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val isTransitionsInflated = TransitionInflater.from(requireContext()).inflateTransitions()
return createComposeView(inflater.context, requireActivity()).also {
it.isTransitionGroup = isTransitionsInflated
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
/*
* We need to manually dispatch configuration changes to the Compose view.
*
* `android:configChanges="uiMode"` is set in the manifest.
* */
view?.dispatchConfigurationChanged(newConfig)
}
/**
* Inflates transitions for the fragment. Override this method to customize
* enter and exit transitions for the fragment.
*
* @return `true` if transitions were inflated; `false` otherwise.
*/
protected open fun TransitionInflater.inflateTransitions(): Boolean {
enterTransition = inflateTransition(R.transition.fade)
exitTransition = inflateTransition(R.transition.fade)
return true
}
}

View file

@ -0,0 +1,7 @@
package com.tangem.core.ui.test
object BaseBlockTestTags {
const val BLOCK = "BASE_BLOCK"
const val BLOCK_TITLE = "BASE_BLOCK_TITLE"
const val BLOCK_TEXT = "BASE_BLOCK_REWARDS_TEXT"
}

View file

@ -0,0 +1,7 @@
package com.tangem.core.ui.test
object BaseButtonTestTags {
const val BUTTON = "BASE_BUTTON"
const val ICON = "BASE_BUTTON_ICON"
const val TEXT = "BASE_BUTTON_TEXT"
}

View file

@ -0,0 +1,16 @@
package com.tangem.core.ui.test
object BuyTokenDetailsScreenTestTags {
const val EXPAND_FIAT_LIST_BUTTON = "BUY_TOKEN_DETAILS_SCREEN_EXPAND_FIAT_LIST_BUTTON"
const val FIAT_CURRENCY_ICON = "BUY_TOKEN_DETAILS_SCREEN_FIAT_CURRENCY_ICON"
const val FIAT_AMOUNT_TEXT_FIELD = "BUY_TOKEN_DETAILS_SCREEN_FIAT_AMOUNT_TEXT_FIELD"
const val TOKEN_AMOUNT = "BUY_TOKEN_DETAILS_SCREEN_TOKEN_AMOUNT"
const val PROVIDER_LOADING_TITLE = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_LOADING_TITLE"
const val PROVIDER_LOADING_TEXT = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_LOADING_TITLE"
const val PROVIDER_TITLE = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_TITLE"
const val PROVIDER_TEXT = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_TEXT"
const val TOS_BLOCK = "BUY_TOKEN_DETAILS_SCREEN_TOS_BLOCK"
}

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.test
object BuyTokenFiatListTestTags {
const val LAZY_LIST = "BUY_TOKEN_FIAT_LIST_LAZY_LIST"
const val LAZY_LIST_ITEM = "BUY_TOKEN_FIAT_LIST_LAZY_LIST_ITEM"
}

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.test
object BuyTokenScreenTestTags {
const val LAZY_LIST = "BUY_TOKEN_SCREEN_LAZY_LIST"
const val LAZY_LIST_ITEM = "BUY_TOKEN_SCREEN_LAZY_LIST_ITEM"
}

View file

@ -2,5 +2,4 @@ package com.tangem.core.ui.test
object DialogTestTags {
const val DIALOG_CONTAINER = "DIALOG_CONTAINER"
const val BUTTON = "DIALOG_BUTTON"
}

View file

@ -3,4 +3,5 @@ package com.tangem.core.ui.test
object DisclaimerScreenTestTags {
const val SCREEN_CONTAINER = "DISCLAIMER_SCREEN_CONTAINER"
const val ACCEPT_BUTTON = "DISCLAIMER_SCREEN_ACCEPT_BUTTON"
const val WEB_VIEW = "DISCLAIMER_SCREEN_WEB_VIEW"
}

View file

@ -8,4 +8,5 @@ object MainScreenTestTags {
const val WALLET_BALANCE = "MAIN_SCREEN_WALLET_BALANCE"
const val WALLET_LIST_ITEM = "MAIN_SCREEN_WALLET_LIST_ITEM"
const val ORGANIZE_TOKENS_BUTTON = "MAIN_SCREEN_ORGANIZE_TOKENS_BUTTON"
const val MULTI_CURRENCY_ACTION_BUTTON = "MAIN_SCREEN_MULTI_CURRENCY_ACTION_BUTTON"
}

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.test
object NotificationTestTags {
const val TITLE = "NOTIFICATION_TITLE"
const val TEXT = "NOTIFICATION_TEXT"
}

View file

@ -0,0 +1,8 @@
package com.tangem.core.ui.test
object ReferralProgramScreenTestTags {
const val IMAGE = "REFERRAL_PROGRAM_SCREEN_IMAGE"
const val CONDITION_BLOCK = "REFERRAL_PROGRAM_SCREEN_CONDITION_BLOCK"
const val INFO_FOR_YOU_TEXT = "REFERRAL_PROGRAM_SCREEN_INFO_FOR_YOU_TEXT"
const val INFO_FOR_YOUR_FRIEND_TEXT = "REFERRAL_PROGRAM_INFO_FOR_YOUR_FRIEND_TEXT"
}

View file

@ -0,0 +1,5 @@
package com.tangem.core.ui.test
object ResidenceSettingsScreenTestTags {
const val COUNTRY_NAME = "RESIDENCE_SETTINGS_SCREEN_COUNTRY_NAME"
}

View file

@ -0,0 +1,12 @@
package com.tangem.core.ui.test
object SelectCountryBottomSheetTestTags {
const val LAZY_LIST = "SELECT_COUNTRY_BOTTOM_SHEET_LAZY_LIST"
const val COUNTRY_ITEM = "SELECT_COUNTRY_BOTTOM_SHEET_COUNTRY_ITEM"
const val UNAVAILABLE_COUNTRY_ITEM = "SELECT_COUNTRY_BOTTOM_SHEET_UNAVAILABLE_COUNTRY_ITEM"
const val SEARCH_BAR = "SELECT_COUNTRY_BOTTOM_SHEET_SEARCH_BAR"
const val COUNTRY_ICON = "SELECT_COUNTRY_BOTTOM_SHEET_COUNTRY_ICON"
const val UNAVAILABLE_COUNTRY_ICON = "SELECT_COUNTRY_BOTTOM_SHEET_UNAVAILABLE_COUNTRY_ICON"
const val COUNTRY_NAME = "SELECT_COUNTRY_BOTTOM_SHEET_COUNTRY_NAME"
}

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.test
object SelectNetworkFeeBottomSheetTestTags {
const val READ_MORE_TEXT = "SELECT_NETWORK_FEE_READ_MORE_TEXT"
const val SELECTOR_ITEM = "SELECT_NETWORK_FEE_SELECTOR_ITEM"
}

View file

@ -0,0 +1,7 @@
package com.tangem.core.ui.test
object SelectPaymentMethodBottomSheetTestTags {
const val LAZY_LIST = "SELECT_PAYMENT_METHOD_LAZY_LIST"
const val PAYMENT_METHOD_ICON = "PAYMENT_METHOD_NAME"
}

View file

@ -0,0 +1,18 @@
package com.tangem.core.ui.test
object SelectProviderBottomSheetTestTags {
const val PAYMENT_METHOD_ICON = "SELECT_PROVIDER_BOTTOM_SHEET_PAYMENT_METHOD_ICON"
const val PAYMENT_METHOD_TITLE = "SELECT_PROVIDER_BOTTOM_SHEET_PAYMENT_METHOD_TITLE"
const val PAYMENT_METHOD_NAME = "SELECT_PROVIDER_BOTTOM_SHEET_PAYMENT_METHOD_NAME"
const val PAYMENT_METHOD_EXPAND_BUTTON = "SELECT_PROVIDER_BOTTOM_SHEET_PAYMENT_METHOD_EXPAND_BUTTON"
const val TOKEN_AMOUNT = "SELECT_PROVIDER_BOTTOM_SHEET_TOKEN_AMOUNT"
const val AVAILABLE_PROVIDER_NAME = "SELECT_PROVIDER_BOTTOM_SHEET_AVAILABLE_PROVIDER_NAME"
const val AVAILABLE_PROVIDER_ITEM = "SELECT_PROVIDER_BOTTOM_SHEET_AVAILABLE_PROVIDER_ITEM"
const val UNAVAILABLE_PROVIDER_ITEM = "SELECT_PROVIDER_BOTTOM_SHEET_UNAVAILABLE_PROVIDER_ITEM"
const val UNAVAILABLE_PROVIDER_NAME = "SELECT_PROVIDER_BOTTOM_SHEET_UNAVAILABLE_PROVIDER_NAME"
const val UNAVAILABLE_PROVIDER_SUBTITLE = "SELECT_PROVIDER_BOTTOM_SHEET_UNAVAILABLE_PROVIDER_SUBTITLE"
const val MORE_PROVIDERS_ICON = "SELECT_PROVIDER_BOTTOM_SHEET_MORE_PROVIDERS_ICON"
const val MORE_PROVIDERS_TEXT = "SELECT_PROVIDER_BOTTOM_SHEET_MORE_PROVIDERS_TEXT"
const val BEST_RATE_LABEL = "SELECT_PROVIDER_BOTTOM_SHEET_BEST_RATE_LABEL"
}

View file

@ -0,0 +1,15 @@
package com.tangem.core.ui.test
object StakingDetailsScreenTestTags {
const val SCREEN_CONTAINER = "TOKEN_DETAILS_SCREEN_CONTAINER"
const val BANNER_IMAGE = "TOKEN_DETAILS_SCREEN_BANNER_IMAGE"
const val BANNER_TEXT = "TOKEN_DETAILS_SCREEN_BANNER_TEXT"
const val PARAMETER_BLOCK = "STAKING_DETAILS_PARAMETER_BLOCK"
const val PARAMETER_NAME = "STAKING_DETAILS_PARAMETER_NAME"
const val PARAMETER_VALUE = "STAKING_DETAILS_PARAMETER_VALUE"
const val TOS_TEXT = "STAKING_DETAILS_TOS_TEXT"
const val ACTIVE_STAKING_BLOCK = "STAKING_DETAILS_ACTIVE_STAKING_BLOCK"
}

View file

@ -0,0 +1,10 @@
package com.tangem.core.ui.test
object StakingSendDetailsScreenTestTags {
const val PRIMARY_AMOUNT = "STAKING_SEND_DETAILS_SCREEN_PRIMARY_AMOUNT"
const val SECONDARY_AMOUNT = "TAKING_SEND_DETAILS_SCREEN_SECONDARY_AMOUNT"
const val VALIDATOR_BLOCK = "TAKING_SEND_DETAILS_SCREEN_VALIDATOR_BLOCK"
const val NETWORK_FEE_BLOCK = "TAKING_SEND_DETAILS_SCREEN_NETWORK_FEE_BLOCK"
}

View file

@ -0,0 +1,16 @@
package com.tangem.core.ui.test
object StakingSendScreenTestTags {
const val SCREEN_CONTAINER = "STAKING_SEND_SCREEN_CONTAINER"
const val AMOUNT_CONTAINER_TITLE = "STAKING_SEND_SCREEN_AMOUNT_CONTAINER_TITLE"
const val AMOUNT_CONTAINER_TEXT = "STAKING_SEND_SCREEN_AMOUNT_CONTAINER_TEXT"
const val INPUT_TEXT_FIELD = "STAKING_SEND_SCREEN_INPUT_TEXT_FIELD"
const val SECONDARY_AMOUNT = "STAKING_SEND_SCREEN_SECONDARY_AMOUNT"
const val CURRENCY_BUTTON = "STAKING_SEND_SCREEN_CURRENCY_BUTTON"
const val FIAT_ICON = "STAKING_SEND_SCREEN_FIAT_ICON"
const val CURRENCY_ICON = "STAKING_SEND_SCREEN_CURRENCY_ICON"
const val MAX_BUTTON = "STAKING_SEND_SCREEN_MAX_BUTTON"
const val PREVIOUS_BUTTON = "STAKING_SEND_SCREEN_PREVIOUS_BUTTON"
}

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.test
object SwapStoriesScreenTestTags {
const val SCREEN_CONTAINER = "SWAP_STORIES_SCREEN_CONTAINER"
const val CLOSE_BUTTON = "SWAP_STORIES_SCREEN_CLOSE_BUTTON"
}

View file

@ -0,0 +1,14 @@
package com.tangem.core.ui.test
object SwapTokenScreenTestTags {
const val SWAP_BLOCK_HEADER = "SWAP_TOKEN_SCREEN_SWAP_BLOCK"
const val BALANCE = "SWAP_TOKEN_SCREEN_BALANCE"
const val SWAP_TEXT_FIELD = "SWAP_TOKEN_SCREEN_SWAP_TEXT_FIELD"
const val RECEIVE_TEXT_FIELD = "SWAP_TOKEN_SCREEN_RECEIVE_TEXT_FIELD"
const val RECEIVE_AMOUNT_SHIMMER = "SWAP_TOKEN_SCREEN_RECEIVE_AMOUNT_SHIMMER"
const val PROVIDERS_BLOCK = "SWAP_TOKEN_SCREEN_PROVIDERS_BLOCK"
const val SWAP_BUTTON = "SWAP_TOKEN_SCREEN_SWAP_BUTTON"
const val TOKEN = "SWAP_TOKEN_SCREEN_TOKEN"
const val TOKEN_NAME = "SWAP_TOKEN_SCREEN_TOKEN_NAME"
const val TOKEN_ICON = "SWAP_TOKEN_SCREEN_TOKEN_ICON"
}

View file

@ -2,4 +2,19 @@ package com.tangem.core.ui.test
object TokenDetailsScreenTestTags {
const val SCREEN_CONTAINER = "TOKEN_DETAILS_SCREEN_CONTAINER"
const val TOKEN_TITLE = "TOKEN_DETAILS_SCREEN_TOKEN_TITLE"
const val ACTION_BUTTON = "TOKEN_DETAILS_SCREEN_ACTION_BUTTON"
const val HORIZONTAL_ACTION_CHIPS = "TOKEN_DETAILS_SCREEN_HORIZONTAL_ACTION_CHIPS"
const val STAKING_BLOCK = "TOKEN_DETAILS_SCREEN_STAKING_BLOCK"
const val STAKING_AVAILABLE_BLOCK = "TOKEN_DETAILS_SCREEN_STAKING_AVAILABLE_BLOCK"
const val STAKING_CURRENCY_ICON = "TOKEN_DETAILS_SCREEN_STAKING_STAKING_CURRENCY_ICON"
const val STAKING_SERVICE_TITLE = "TOKEN_DETAILS_SCREEN_STAKING_STAKING_SERVICE_TITLE"
const val STAKING_SERVICE_TEXT = "TOKEN_DETAILS_SCREEN_STAKING_STAKING_SERVICE_TEXT"
const val STAKING_FIAT_AMOUNT = "TOKEN_DETAILS_SCREEN_STAKING_FIAT_AMOUNT"
const val STAKING_DOT = "TOKEN_DETAILS_SCREEN_STAKING_DOT"
const val STAKING_TOKEN_AMOUNT = "TOKEN_DETAILS_SCREEN_STAKING_TOKEN_AMOUNT"
const val STAKING_REWARD_VALUE = "TOKEN_DETAILS_SCREEN_STAKING_REWARD_VALUE"
const val STAKING_CHEVRON_ICON = "TOKEN_DETAILS_SCREEN_STAKING_CHEVRON_ICON"
}

View file

@ -0,0 +1,7 @@
package com.tangem.core.ui.test
object TopAppBarTestTags {
const val TITLE = "TOP_APP_BAR_TITLE"
const val MORE_BUTTON = "TOP_APP_BAR_MORE_BUTTON"
const val CLOSE_BUTTON = "TOP_APP_BAR_CLOSE_BUTTON"
}

View file

@ -0,0 +1,54 @@
package com.tangem.core.ui.utils
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
/**
[REDACTED_AUTHOR]
*/
typealias AnimatedValue = Pair<Float, Float>
@Composable
fun AnimatedValue.toAnimatable(
isPaused: Boolean,
duration: Int,
easing: Easing = LinearEasing,
): Animatable<Float, AnimationVector1D> {
return animatable(
values = this,
isPaused = isPaused,
duration = duration,
easing = easing,
)
}
@Composable
fun animatable(
values: AnimatedValue,
duration: Int,
isPaused: Boolean = false,
easing: Easing = LinearEasing,
): Animatable<Float, AnimationVector1D> {
val animatable = remember { Animatable(values.first) }
LaunchedEffect(isPaused) {
if (isPaused) {
animatable.stop()
} else {
animatable.animateTo(
targetValue = values.second,
animationSpec = tween(
durationMillis = duration,
easing = easing,
),
)
}
}
return animatable
}

View file

@ -1,147 +0,0 @@
package com.tangem.core.ui.utils
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.StringsSigns.LOWER_SIGN
import com.tangem.utils.StringsSigns.TILDE_SIGN
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.NumberFormat
import java.util.Currency
import java.util.Locale
@Suppress("LargeClass")
@Deprecated("Use BigDecimal.format")
object BigDecimalFormatter {
const val EMPTY_BALANCE_SIGN = DASH_SIGN
private const val CAN_BE_LOWER_SIGN = LOWER_SIGN
private val FIAT_FORMAT_THRESHOLD = BigDecimal("0.01")
private const val FIAT_MARKET_DEFAULT_DIGITS = 2
private const val FIAT_MARKET_EXTENDED_DIGITS = 6
private const val FRACTIONAL_PART_LENGTH_AFTER_LEADING_ZEROES = 4
private val usdCurrency = Currency.getInstance("USD")
@Deprecated("Use BigDecimal.format")
fun formatFiatAmount(
fiatAmount: BigDecimal?,
fiatCurrencyCode: String,
fiatCurrencySymbol: String,
decimals: Int = FIAT_MARKET_DEFAULT_DIGITS,
locale: Locale = Locale.getDefault(),
withApproximateSign: Boolean = false,
): String {
if (fiatAmount == null) return EMPTY_BALANCE_SIGN
val formatterCurrency = getCurrency(fiatCurrencyCode)
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
currency = formatterCurrency
maximumFractionDigits = decimals
minimumFractionDigits = decimals
roundingMode = RoundingMode.HALF_UP
}
return if (fiatAmount.checkFiatThreshold()) {
buildString {
append(CAN_BE_LOWER_SIGN)
append(
formatter.format(FIAT_FORMAT_THRESHOLD)
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol),
)
}
} else {
val formattedAmount = formatter.format(fiatAmount)
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
if (withApproximateSign) {
buildString {
append(TILDE_SIGN)
append(formattedAmount)
}
} else {
formattedAmount
}
}
}
@Deprecated("Use BigDecimal.format")
fun formatFiatAmountUncapped(
fiatAmount: BigDecimal?,
fiatCurrencyCode: String,
fiatCurrencySymbol: String,
locale: Locale = Locale.getDefault(),
): String {
if (fiatAmount == null) return EMPTY_BALANCE_SIGN
val formatterCurrency = getCurrency(fiatCurrencyCode)
val digits = if (fiatAmount.checkFiatThreshold()) {
FIAT_MARKET_EXTENDED_DIGITS
} else {
FIAT_MARKET_DEFAULT_DIGITS
}
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
currency = formatterCurrency
maximumFractionDigits = digits
minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS
roundingMode = RoundingMode.HALF_UP
}
return formatter.format(fiatAmount)
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
}
@Deprecated("Use BigDecimal.format")
fun formatFiatPriceUncapped(
fiatAmount: BigDecimal?,
fiatCurrencyCode: String,
fiatCurrencySymbol: String,
locale: Locale = Locale.getDefault(),
): String {
if (fiatAmount == null) return EMPTY_BALANCE_SIGN
val formatterCurrency = getCurrency(fiatCurrencyCode)
val (formattedAmount, finalScale) = getFiatPriceUncappedWithScale(value = fiatAmount)
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
currency = formatterCurrency
maximumFractionDigits = finalScale
minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS
roundingMode = RoundingMode.HALF_UP
}
return formatter.format(formattedAmount)
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
}
@Deprecated("Use BigDecimal.format")
fun getFiatPriceUncappedWithScale(value: BigDecimal): Pair<BigDecimal, Int> {
return if (value < BigDecimal.ONE) {
val leadingZeroes = value.scale() - value.precision()
val scale = leadingZeroes + FRACTIONAL_PART_LENGTH_AFTER_LEADING_ZEROES
val amount = value
.setScale(scale, RoundingMode.HALF_UP)
.stripTrailingZeros()
amount to amount.scale()
} else {
value to FIAT_MARKET_DEFAULT_DIGITS
}
}
private fun getCurrency(code: String): Currency {
return runCatching { Currency.getInstance(code) }
.getOrElse { e ->
// Currency code is not valid ISO 4217 code
if (e is IllegalArgumentException) {
usdCurrency
} else {
throw e
}
}
}
private fun BigDecimal.checkFiatThreshold() = this > BigDecimal.ZERO && this < FIAT_FORMAT_THRESHOLD
}

View file

@ -1,9 +1,15 @@
package com.tangem.core.ui.utils
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import kotlin.math.roundToInt
@Stable
@Composable
@ -15,4 +21,16 @@ fun convertPxToDp(px: Float): Dp = convertPxToDp(px, density = LocalDensity.curr
fun Dp.toPx(density: Float): Float = this.value * density
fun convertPxToDp(px: Float, density: Float): Dp = Dp(value = px / density)
fun convertPxToDp(px: Float, density: Float): Dp = Dp(value = px / density)
fun Context.dpToPx(dp: Float): Float = dp * resources.displayMetrics.density
fun Context.pxToDp(px: Float): Float = (px / resources.displayMetrics.density).roundToInt().toFloat()
@Composable
fun Painter.dpSize(): DpSize = DpSize(
intrinsicSize.width.pxToDp().dp,
intrinsicSize.height.pxToDp().dp,
)
@Composable
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)

View file

@ -0,0 +1,20 @@
package com.tangem.core.ui.utils
import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.core.graphics.drawable.toBitmap
/**
[REDACTED_AUTHOR]
*/
@Composable
fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap {
val drawable = requireNotNull(AppCompatResources.getDrawable(LocalContext.current, drawableId)) {
"drawable is null"
}
return drawable.toBitmap().asImageBitmap()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

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="M12,18L16,14L14.6,12.6L13,14.2V10H11V14.2L9.4,12.6L8,14L12,18ZM5,8V19H19V8H5ZM5,21C4.45,21 3.979,20.804 3.588,20.413C3.196,20.021 3,19.55 3,19V6.525C3,6.292 3.037,6.067 3.112,5.85C3.188,5.633 3.3,5.433 3.45,5.25L4.7,3.725C4.883,3.492 5.113,3.313 5.387,3.188C5.662,3.063 5.95,3 6.25,3H17.75C18.05,3 18.337,3.063 18.612,3.188C18.888,3.313 19.117,3.492 19.3,3.725L20.55,5.25C20.7,5.433 20.813,5.633 20.888,5.85C20.962,6.067 21,6.292 21,6.525V19C21,19.55 20.804,20.021 20.413,20.413C20.021,20.804 19.55,21 19,21H5ZM5.4,6H18.6L17.75,5H6.25L5.4,6Z"
android:fillColor="#000000"/>
</group>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M20,10.912C20,17.156 16.358,19.16 14.439,17.376C12.86,15.921 12.39,12.847 10.014,12.531C6.999,12.133 6.738,16.346 4.754,16.346C2.444,16.346 2,12.819 2,11.008C2,9.155 2.496,6.63 4.467,6.63C6.764,6.63 6.895,10.24 9.767,10.047C12.625,9.842 12.677,6.081 14.531,4.476C16.149,3.09 20,4.585 20,10.912Z"
android:fillColor="#000000"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:pathData="M6.839,11.095C7.657,11.095 8.32,11.759 8.32,12.576V20.148C8.32,20.966 7.657,21.629 6.839,21.629H2.864C2.046,21.629 1.383,20.966 1.383,20.148V12.576C1.383,11.759 2.046,11.095 2.864,11.095H6.839ZM14.064,2.37C14.831,2.37 15.401,2.37 15.894,2.502C17.227,2.858 18.268,3.897 18.625,5.226C18.709,5.54 18.74,5.885 18.751,6.291C18.895,6.304 19.034,6.319 19.167,6.337C20.062,6.457 20.853,6.719 21.486,7.351C22.119,7.983 22.382,8.772 22.503,9.665C22.618,10.516 22.617,11.591 22.617,12.895V14.957C22.617,16.261 22.618,17.336 22.503,18.187C22.382,19.08 22.12,19.869 21.486,20.501C20.853,21.133 20.062,21.395 19.167,21.515C18.314,21.629 17.236,21.629 15.929,21.629H9.999C9.987,21.629 9.975,21.628 9.963,21.628C10.176,21.18 10.297,20.678 10.297,20.149V12.577C10.297,10.668 8.749,9.12 6.84,9.12H2.865C2.335,9.12 1.832,9.24 1.383,9.453V5.259C1.383,3.664 2.679,2.37 4.278,2.37H14.064ZM18.406,12.436C17.468,12.436 16.707,13.196 16.707,14.134C16.707,15.073 17.468,15.833 18.406,15.833C19.345,15.833 20.105,15.073 20.105,14.134C20.105,13.196 19.344,12.436 18.406,12.436ZM4.159,12.573C3.723,12.573 3.369,12.926 3.369,13.363C3.369,13.799 3.723,14.153 4.159,14.153H5.542C5.978,14.153 6.332,13.799 6.332,13.363C6.332,12.926 5.978,12.573 5.542,12.573H4.159ZM4.278,4.296C3.745,4.296 3.313,4.727 3.313,5.259C3.313,5.791 3.745,6.222 4.278,6.222H15.929C16.237,6.222 16.533,6.222 16.816,6.224C16.808,5.975 16.791,5.839 16.76,5.724C16.581,5.059 16.061,4.54 15.394,4.362C15.18,4.304 14.889,4.296 13.93,4.296H4.278Z">
<aapt:attr name="android:fillColor">
<gradient android:centerX="7.343" android:centerY="0.784" android:gradientRadius="33.786" android:type="radial">
<item android:color="#FF96999B" android:offset="0"/>
<item android:color="#FF4D4E53" android:offset="1"/>
</gradient>
</aapt:attr>
</path>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="33dp"
android:viewportWidth="32"
android:viewportHeight="33">
<path
android:pathData="M16,4.062C17.587,4.062 19.084,4.339 20.489,4.893C21.894,5.447 23.151,6.22 24.259,7.212C25.366,8.204 26.272,9.361 26.974,10.684C27.677,11.998 28.119,13.425 28.301,14.962H30.26C30.5,14.962 30.682,15.012 30.806,15.111C30.93,15.21 30.988,15.342 30.98,15.508C30.971,15.665 30.901,15.843 30.769,16.041L28.066,19.885C27.892,20.133 27.694,20.257 27.47,20.257C27.255,20.249 27.065,20.125 26.9,19.885L24.197,16.029C24.064,15.839 23.994,15.665 23.986,15.508C23.986,15.342 24.048,15.21 24.172,15.111C24.296,15.012 24.474,14.962 24.705,14.962H26.677C26.495,13.648 26.094,12.428 25.474,11.304C24.862,10.172 24.077,9.184 23.118,8.34C22.159,7.497 21.072,6.84 19.857,6.369C18.65,5.898 17.364,5.662 16,5.662C14.405,5.662 12.904,5.988 11.499,6.642C10.101,7.286 8.903,8.171 7.903,9.295C7.712,9.502 7.518,9.614 7.32,9.63C7.121,9.638 6.944,9.585 6.786,9.469C6.613,9.345 6.505,9.167 6.464,8.936C6.423,8.696 6.497,8.469 6.687,8.254C7.836,6.956 9.217,5.935 10.829,5.191C12.441,4.438 14.165,4.062 16,4.062ZM16,28.938C14.413,28.938 12.916,28.656 11.511,28.094C10.106,27.54 8.849,26.767 7.741,25.775C6.634,24.792 5.724,23.642 5.013,22.328C4.311,21.005 3.868,19.575 3.686,18.038H1.727C1.487,18.038 1.31,17.988 1.194,17.889C1.07,17.789 1.012,17.657 1.02,17.492C1.02,17.327 1.086,17.149 1.219,16.959L3.922,13.102C4.096,12.862 4.29,12.743 4.505,12.743C4.728,12.743 4.926,12.862 5.1,13.102L7.803,16.971C7.936,17.161 8.002,17.339 8.002,17.504C8.002,17.661 7.94,17.789 7.816,17.889C7.7,17.988 7.522,18.038 7.283,18.038H5.323C5.497,19.344 5.894,20.563 6.514,21.696C7.134,22.82 7.919,23.808 8.87,24.659C9.829,25.503 10.912,26.16 12.119,26.631C13.334,27.102 14.628,27.338 16,27.338C17.587,27.338 19.079,27.011 20.476,26.358C21.882,25.713 23.089,24.825 24.097,23.692C24.288,23.494 24.478,23.39 24.668,23.382C24.866,23.366 25.044,23.411 25.201,23.518C25.375,23.651 25.482,23.837 25.524,24.076C25.565,24.308 25.49,24.531 25.3,24.746C24.16,26.036 22.779,27.057 21.159,27.809C19.546,28.561 17.827,28.938 16,28.938Z"
android:fillColor="#0099FF"/>
<path
android:pathData="M16.153,10.844C17.725,10.924 18.975,12.223 18.975,13.815V15.295C19.876,15.317 20.6,16.055 20.6,16.961V20.492C20.6,21.413 19.853,22.159 18.933,22.159H13.069C12.148,22.159 11.402,21.413 11.402,20.492V16.961C11.402,16.055 12.125,15.318 13.026,15.295V13.815C13.026,12.172 14.357,10.84 16,10.84L16.153,10.844ZM16,12.23C15.125,12.23 14.415,12.939 14.415,13.815V15.294H17.586V13.815C17.586,12.939 16.876,12.23 16,12.23Z"
android:fillColor="#0099FF"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M49.875,26.906V20.475C49.875,17.902 49.875,16.616 49.377,15.633C48.939,14.769 48.24,14.066 47.381,13.626C46.403,13.125 45.124,13.125 42.566,13.125H13.434C10.876,13.125 9.597,13.125 8.619,13.626C7.76,14.066 7.061,14.769 6.623,15.633C6.125,16.616 6.125,17.902 6.125,20.475V28.744C6.125,31.316 6.125,32.603 6.623,33.585C7.061,34.45 7.76,35.153 8.619,35.593C9.597,36.094 10.876,36.094 13.434,36.094H25.716M28,24.609H28.011M38.546,24.609H38.557M17.454,24.609H17.466M28.571,24.609C28.571,24.927 28.315,25.184 28,25.184C27.685,25.184 27.429,24.927 27.429,24.609C27.429,24.292 27.685,24.035 28,24.035C28.315,24.035 28.571,24.292 28.571,24.609ZM39.117,24.609C39.117,24.927 38.861,25.184 38.546,25.184C38.23,25.184 37.975,24.927 37.975,24.609C37.975,24.292 38.23,24.035 38.546,24.035C38.861,24.035 39.117,24.292 39.117,24.609ZM18.025,24.609C18.025,24.927 17.77,25.184 17.454,25.184C17.139,25.184 16.883,24.927 16.883,24.609C16.883,24.292 17.139,24.035 17.454,24.035C17.77,24.035 18.025,24.292 18.025,24.609Z"
android:strokeLineJoin="round"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#008EFF"
android:strokeLineCap="round"/>
<path
android:pathData="M45.242,38.664V34.645C45.242,32.425 43.452,30.625 41.244,30.625C39.037,30.625 37.247,32.425 37.247,34.645V38.664M36.905,47.852H45.584C46.864,47.852 47.503,47.852 47.992,47.601C48.422,47.381 48.771,47.03 48.99,46.597C49.239,46.106 49.239,45.463 49.239,44.177V42.339C49.239,41.053 49.239,40.41 48.99,39.918C48.771,39.486 48.422,39.135 47.992,38.914C47.503,38.664 46.864,38.664 45.584,38.664H36.905C35.625,38.664 34.986,38.664 34.497,38.914C34.067,39.135 33.718,39.486 33.499,39.918C33.25,40.41 33.25,41.053 33.25,42.339V44.177C33.25,45.463 33.25,46.106 33.499,46.597C33.718,47.03 34.067,47.381 34.497,47.601C34.986,47.852 35.625,47.852 36.905,47.852Z"
android:strokeLineJoin="round"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#008EFF"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,16 @@
<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="M18.656,15.224C18.898,14.92 19.328,14.854 19.648,15.059L19.71,15.104L19.875,15.245C20.247,15.585 20.532,16.024 20.532,16.6C20.532,17.432 19.947,17.969 19.336,18.362C18.868,18.663 18.238,18.97 17.483,19.319L16.688,19.683L15.213,20.355C13.847,20.978 12.961,21.399 12,21.399C11.28,21.399 10.601,21.162 9.73,20.781L8.787,20.355L7.312,19.683C6.2,19.177 5.288,18.763 4.664,18.362C4.053,17.969 3.468,17.432 3.468,16.6C3.468,15.941 3.84,15.462 4.29,15.104L4.352,15.059C4.672,14.854 5.102,14.92 5.344,15.224C5.602,15.548 5.548,16.02 5.224,16.278L5.135,16.353C5.059,16.423 5.017,16.477 4.996,16.513C4.972,16.552 4.968,16.575 4.968,16.6C4.968,16.617 4.951,16.763 5.475,17.1C5.986,17.428 6.776,17.79 7.934,18.318L9.409,18.99L10.364,19.418C11.171,19.769 11.581,19.899 12,19.899C12.559,19.899 13.101,19.669 14.591,18.99L16.066,18.318L16.867,17.952C17.601,17.612 18.142,17.346 18.525,17.1C19.049,16.763 19.032,16.617 19.032,16.6C19.032,16.575 19.028,16.552 19.004,16.513C18.983,16.477 18.941,16.423 18.865,16.353L18.776,16.278L18.718,16.227C18.446,15.962 18.414,15.528 18.656,15.224Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M3.791,10.784C4.031,10.446 4.499,10.367 4.836,10.607C5.174,10.847 5.253,11.315 5.013,11.653C4.972,11.711 4.968,11.741 4.968,11.766C4.968,11.783 4.951,11.929 5.475,12.266C5.986,12.594 6.776,12.956 7.934,13.484L9.409,14.156L10.364,14.584C11.171,14.936 11.581,15.065 12,15.065C12.559,15.065 13.101,14.835 14.591,14.156L16.066,13.484L16.867,13.118C17.601,12.778 18.142,12.512 18.525,12.266C19.049,11.929 19.032,11.783 19.032,11.766C19.032,11.753 19.031,11.74 19.025,11.722L18.987,11.653L18.946,11.588C18.758,11.258 18.847,10.832 19.164,10.607C19.48,10.382 19.911,10.437 20.162,10.723L20.209,10.784L20.282,10.893C20.44,11.152 20.532,11.444 20.532,11.766C20.532,12.598 19.947,13.135 19.336,13.528C18.868,13.829 18.238,14.137 17.483,14.486L16.688,14.849L15.213,15.521C13.847,16.143 12.961,16.565 12,16.565C11.28,16.565 10.601,16.328 9.73,15.947L8.787,15.521L7.312,14.849C6.2,14.342 5.288,13.929 4.664,13.528C4.053,13.135 3.468,12.598 3.468,11.766C3.468,11.398 3.588,11.069 3.791,10.784Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M12,2.604C12.964,2.604 13.852,3.032 15.217,3.664L16.691,4.345L17.487,4.714C18.242,5.068 18.872,5.381 19.34,5.686C19.954,6.086 20.532,6.628 20.532,7.461C20.532,8.294 19.954,8.837 19.34,9.237C18.872,9.542 18.242,9.854 17.487,10.208L16.691,10.577L15.217,11.258C13.852,11.889 12.964,12.318 12,12.318C11.278,12.318 10.597,12.077 9.726,11.69L8.783,11.258L7.309,10.577C6.197,10.063 5.284,9.644 4.66,9.237C4.046,8.837 3.468,8.294 3.468,7.461C3.468,6.628 4.046,6.086 4.66,5.686C5.284,5.279 6.197,4.859 7.309,4.345L8.783,3.664L9.726,3.232C10.597,2.845 11.278,2.604 12,2.604ZM12,4.104C11.583,4.104 11.175,4.234 10.368,4.59L9.412,5.025L7.938,5.706C6.78,6.242 5.99,6.609 5.479,6.942C4.958,7.282 4.968,7.433 4.968,7.461C4.968,7.49 4.96,7.642 5.479,7.98C5.99,8.313 6.78,8.68 7.938,9.215L9.412,9.897L10.368,10.332C11.175,10.688 11.583,10.818 12,10.818C12.556,10.818 13.097,10.586 14.588,9.897L16.062,9.215L16.863,8.843C17.598,8.498 18.138,8.23 18.521,7.98C19.04,7.642 19.032,7.49 19.032,7.461C19.032,7.433 19.042,7.282 18.521,6.942C18.138,6.692 17.598,6.423 16.863,6.079L16.062,5.706L14.588,5.025C13.097,4.336 12.556,4.104 12,4.104Z"
android:fillColor="#1E1E1E"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="51dp"
android:height="11dp"
android:viewportWidth="74"
android:viewportHeight="16">
<path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M2.924,0H9.413C10.436,0 10.948,0 11.339,0.199C11.683,0.374 11.962,0.654 12.137,0.998C12.337,1.389 12.337,1.9 12.337,2.924V2.924V4.009H0V2.924C0,1.9 0,1.389 0.199,0.998C0.374,0.654 0.654,0.375 0.998,0.199C1.389,0 1.9,0 2.924,0ZM12.337,7.402H8.224L8.225,7.971V15.421H9.413H9.413C10.436,15.421 10.948,15.421 11.339,15.221C11.683,15.047 11.962,14.767 12.137,14.423C12.337,14.032 12.337,13.52 12.337,12.497V7.402ZM4.111,7.402H0V12.497C0,13.52 0,14.032 0.199,14.423C0.375,14.767 0.654,15.047 0.998,15.221C1.389,15.421 1.9,15.421 2.924,15.421H2.924H4.111V7.402ZM22.85,12.479C21.201,12.479 20.346,11.591 20.346,10.022V5.488H19.236V4.046H20.346V2.223H22.264V4.046H24.087V5.488H22.264V9.848C22.264,10.577 22.612,10.91 23.215,10.91C23.595,10.91 23.881,10.862 24.15,10.751V12.273C23.849,12.384 23.437,12.479 22.85,12.479ZM28.298,11.116C29.471,11.116 30.343,10.498 30.343,9.436V8.611H29.36C27.901,8.611 26.966,8.944 26.966,9.959C26.966,10.656 27.347,11.116 28.298,11.116ZM27.838,12.495C26.316,12.495 25.064,11.766 25.064,10.038C25.064,8.104 26.934,7.406 29.312,7.406H30.343V6.978C30.343,5.869 29.962,5.314 28.837,5.314C27.838,5.314 27.347,5.789 27.251,6.614H25.397C25.555,4.68 27.093,3.871 28.948,3.871C30.802,3.871 32.261,4.632 32.261,6.899V12.337H30.374V11.322C29.835,12.035 29.106,12.495 27.838,12.495ZM33.841,4.046V12.337H35.759V7.454C35.759,6.138 36.631,5.472 37.709,5.472C38.85,5.472 39.358,6.043 39.358,7.295V12.337H41.276V7.121C41.276,4.838 40.103,3.871 38.438,3.871C37.075,3.871 36.155,4.553 35.759,5.361V4.046H33.841ZM46.72,10.529C48.051,10.529 49.018,9.578 49.018,7.993V7.882C49.018,6.313 48.162,5.377 46.783,5.377C45.356,5.377 44.532,6.408 44.532,7.914V8.041C44.532,9.578 45.483,10.529 46.72,10.529ZM46.656,15.38C44.056,15.38 42.883,14.207 42.661,12.622H44.595C44.754,13.446 45.388,13.922 46.64,13.922C48.13,13.922 48.955,13.177 48.955,11.687V10.466C48.495,11.259 47.465,12.004 46.244,12.004C44.167,12.004 42.566,10.45 42.566,8.056V7.945C42.566,5.615 44.151,3.871 46.292,3.871C47.655,3.871 48.479,4.49 48.955,5.314V4.046H50.873V11.718C50.857,14.16 49.224,15.38 46.656,15.38ZM52.249,8.278C52.249,10.894 54.009,12.495 56.45,12.495C58.574,12.495 59.985,11.544 60.255,9.8H58.4C58.257,10.609 57.655,11.068 56.498,11.068C55.071,11.068 54.278,10.181 54.215,8.611H60.286V8.056C60.286,5.092 58.431,3.871 56.371,3.871C54.009,3.871 52.249,5.583 52.249,8.151V8.278ZM58.384,7.327H54.246C54.437,6.043 55.213,5.266 56.371,5.266C57.56,5.266 58.289,5.9 58.384,7.327ZM61.651,12.337V4.046H63.569V5.314C63.965,4.537 64.869,3.871 66.089,3.871C67.167,3.871 68.023,4.331 68.436,5.361C69.07,4.331 70.211,3.871 71.241,3.871C72.763,3.871 74,4.807 74,7.089V12.337H72.081V7.216C72.081,5.996 71.558,5.472 70.607,5.472C69.656,5.472 68.784,6.107 68.784,7.375V12.337H66.866V7.216C66.866,5.996 66.327,5.472 65.392,5.472C64.441,5.472 63.569,6.107 63.569,7.375V12.337H61.651Z" />
</vector>

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M8.003,9.316C7.646,9.316 7.447,9.11 7.44,8.746L7.35,5.007C7.344,4.642 7.612,4.381 7.997,4.381C8.368,4.381 8.656,4.649 8.65,5.014L8.546,8.746C8.54,9.117 8.34,9.316 8.003,9.316ZM8.003,11.619C7.591,11.619 7.234,11.289 7.234,10.883C7.234,10.471 7.584,10.141 8.003,10.141C8.416,10.141 8.766,10.464 8.766,10.883C8.766,11.296 8.409,11.619 8.003,11.619Z"
android:fillColor="#919191"
android:fillType="evenOdd"/>
<path
android:pathData="M8,1.164C4.225,1.164 1.164,4.225 1.164,8C1.164,11.775 4.225,14.836 8,14.836C11.775,14.836 14.836,11.775 14.836,8C14.836,4.225 11.775,1.164 8,1.164ZM2.164,8C2.164,4.777 4.777,2.164 8,2.164C11.223,2.164 13.836,4.777 13.836,8C13.836,11.223 11.223,13.836 8,13.836C4.777,13.836 2.164,11.223 2.164,8Z"
android:fillColor="#919191"
android:fillType="evenOdd"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Some files were not shown because too many files have changed in this diff Show more