Updated on 2026-08-14
This commit is contained in:
commit
81bd9875e0
87 changed files with 1128 additions and 225 deletions
|
|
@ -95,10 +95,14 @@ fun AmountTextField(
|
|||
SimpleTextField(
|
||||
value = value,
|
||||
onValueChange = { newText ->
|
||||
if (decimalFormat.isValidSymbols(newText)) {
|
||||
val trimmed = decimalFormat.getValidatedNumberWithFixedDecimals(newText, decimals)
|
||||
onValueChange(trimmed)
|
||||
}
|
||||
onValueChange(
|
||||
prepareEnter(
|
||||
oldValue = value,
|
||||
newValue = newText,
|
||||
decimalFormat = decimalFormat,
|
||||
decimals = decimals,
|
||||
),
|
||||
)
|
||||
},
|
||||
textStyle = textStyle.copy(
|
||||
fontSize = fontSize,
|
||||
|
|
@ -117,8 +121,37 @@ fun AmountTextField(
|
|||
}
|
||||
}
|
||||
|
||||
private fun prepareEnter(oldValue: String, newValue: String, decimalFormat: DecimalFormat, decimals: Int): String {
|
||||
val decimalSymbol = decimalFormat.decimalFormatSymbols.decimalSeparator
|
||||
return if (decimalFormat.isValidSymbols(newValue)) {
|
||||
val parsedValue = newValue.parseBigDecimalOrNull()?.toPlainString()
|
||||
?: if (newValue.isBlank()) "" else oldValue
|
||||
val replacedWithSymbol = if (parsedValue.findLast { it != decimalSymbol } != null) {
|
||||
when {
|
||||
parsedValue.findLast { it == COMMA_SEPARATOR } != null -> {
|
||||
parsedValue.replace(COMMA_SEPARATOR, decimalSymbol)
|
||||
}
|
||||
parsedValue.findLast { it == POINT_SEPARATOR } != null -> {
|
||||
parsedValue.replace(POINT_SEPARATOR, decimalSymbol)
|
||||
}
|
||||
else -> parsedValue
|
||||
}
|
||||
} else {
|
||||
parsedValue
|
||||
}
|
||||
val joinedSymbol = if (newValue.endsWith(COMMA_SEPARATOR) || newValue.endsWith(POINT_SEPARATOR)) {
|
||||
replacedWithSymbol.plus(decimalSymbol)
|
||||
} else {
|
||||
replacedWithSymbol
|
||||
}
|
||||
decimalFormat.getValidatedNumberWithFixedDecimals(joinedSymbol, decimals)
|
||||
} else {
|
||||
oldValue
|
||||
}
|
||||
}
|
||||
|
||||
private fun DecimalFormat.isValidSymbols(text: String): Boolean {
|
||||
return checkDecimalSeparatorDuplicate(text) && checkGroupingSeparator(text)
|
||||
return checkDecimalSeparatorDuplicate(text)
|
||||
}
|
||||
|
||||
// region preview
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ fun SimpleTextField(
|
|||
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,
|
||||
isValuePasted: Boolean = false,
|
||||
onValuePastedTriggerDismiss: () -> Unit = {},
|
||||
|
|
@ -108,6 +109,7 @@ fun SimpleTextField(
|
|||
value = value,
|
||||
textStyle = textStyle,
|
||||
textValue = textValue,
|
||||
color = placeholderColor,
|
||||
)
|
||||
},
|
||||
modifier = modifier
|
||||
|
|
@ -122,6 +124,7 @@ private fun SimpleTextPlaceholder(
|
|||
value: String,
|
||||
textStyle: TextStyle,
|
||||
textValue: @Composable () -> Unit,
|
||||
color: Color = TangemTheme.colors.text.disabled,
|
||||
) {
|
||||
Box {
|
||||
if (value.isBlank() && placeholder != null) {
|
||||
|
|
@ -132,7 +135,7 @@ private fun SimpleTextPlaceholder(
|
|||
Text(
|
||||
text = it.resolveReference(),
|
||||
style = textStyle,
|
||||
color = TangemTheme.colors.text.disabled,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,12 +51,14 @@ fun Notification(
|
|||
modifier: Modifier = Modifier,
|
||||
containerColor: Color? = null,
|
||||
iconTint: Color? = null,
|
||||
isEnabled: Boolean = true,
|
||||
) {
|
||||
BaseContainer(
|
||||
buttonsState = config.buttonsState,
|
||||
onClick = config.onClick,
|
||||
modifier = modifier,
|
||||
containerColor = containerColor,
|
||||
isEnabled = isEnabled,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
|
||||
|
|
@ -67,15 +69,16 @@ fun Notification(
|
|||
iconTint = iconTint,
|
||||
title = config.title,
|
||||
subtitle = config.subtitle,
|
||||
isClickableComponent = config.onClick != null,
|
||||
isClickableComponent = isEnabled && config.onClick != null,
|
||||
)
|
||||
|
||||
Buttons(state = config.buttonsState)
|
||||
Buttons(state = config.buttonsState, isEnabled = isEnabled)
|
||||
}
|
||||
|
||||
CloseableIconButton(
|
||||
onClick = config.onCloseClick,
|
||||
modifier = Modifier.align(alignment = Alignment.TopEnd),
|
||||
isEnabled = isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +88,7 @@ private fun BaseContainer(
|
|||
buttonsState: NotificationConfig.ButtonsState?,
|
||||
onClick: (() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean = true,
|
||||
containerColor: Color? = null,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
|
|
@ -101,7 +105,7 @@ private fun BaseContainer(
|
|||
modifier = modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
|
||||
.fillMaxWidth(),
|
||||
enabled = onClick != null,
|
||||
enabled = onClick != null && isEnabled,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
color = containerColor ?: tempContainerColor,
|
||||
) {
|
||||
|
|
@ -179,27 +183,31 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Buttons(state: NotificationButtonsState?) {
|
||||
private fun Buttons(state: NotificationButtonsState?, isEnabled: Boolean = true) {
|
||||
when (state) {
|
||||
is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(config = state)
|
||||
is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state)
|
||||
is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state)
|
||||
is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(
|
||||
config = state,
|
||||
isEnabled = isEnabled,
|
||||
)
|
||||
is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state, isEnabled = isEnabled)
|
||||
is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state, isEnabled = isEnabled)
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig) {
|
||||
private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig, isEnabled: Boolean = true) {
|
||||
SecondaryButton(
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig) {
|
||||
private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig, isEnabled: Boolean = true) {
|
||||
if (config.iconResId != null) {
|
||||
PrimaryButtonIconEnd(
|
||||
text = config.text.resolveReference(),
|
||||
|
|
@ -207,6 +215,7 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo
|
|||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
} else {
|
||||
PrimaryButton(
|
||||
|
|
@ -214,18 +223,20 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo
|
|||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) {
|
||||
private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig, isEnabled: Boolean = true) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8)) {
|
||||
SecondaryButton(
|
||||
text = config.secondaryText.resolveReference(),
|
||||
onClick = config.onSecondaryClick,
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
|
||||
PrimaryButton(
|
||||
|
|
@ -233,12 +244,13 @@ private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) {
|
|||
onClick = config.onPrimaryClick,
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) {
|
||||
private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier, isEnabled: Boolean = true) {
|
||||
AnimatedVisibility(visible = onClick != null, modifier = modifier) {
|
||||
onClick ?: return@AnimatedVisibility
|
||||
|
||||
|
|
@ -255,6 +267,7 @@ private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Mod
|
|||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = LocalIndication.current,
|
||||
role = Role.Button,
|
||||
enabled = isEnabled,
|
||||
onClick = onClick,
|
||||
),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,11 @@ fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier =
|
|||
modifier = modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium),
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.clickable(
|
||||
enabled = config.onClick != null,
|
||||
onClick = config.onClick ?: {},
|
||||
),
|
||||
) {
|
||||
val (iconRef, titleRef, subtitleRef, closeIconRef, buttonRef, backgroundRef) = createRefs()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
package com.tangem.core.ui.components.notifications
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.ScaleFactor
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.LineBreak
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonColors
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemColorPalette.White
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Travala notification with image background
|
||||
* @see <a href="https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=11690-12057&mode=design&t=eFnsA9sNytcQIoQ4-4">Travala Promo</a>
|
||||
*/
|
||||
@Suppress("LongMethod", "DestructuringDeclarationWithTooManyEntries")
|
||||
@Composable
|
||||
fun TravalaNotificationWithBackground(config: NotificationConfig, modifier: Modifier = Modifier) {
|
||||
val button = config.buttonsState as? NotificationConfig.ButtonsState.SecondaryButtonConfig
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(Color.Black)
|
||||
.clickable(
|
||||
enabled = config.onClick != null,
|
||||
onClick = config.onClick ?: {},
|
||||
),
|
||||
propagateMinConstraints = true,
|
||||
contentAlignment = Alignment.TopStart,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_travala_banner_promo_background),
|
||||
contentDescription = null,
|
||||
contentScale = TravalaBackgroundScale(density),
|
||||
alignment = Alignment.TopStart,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.wrapContentSize(unbounded = true, align = Alignment.TopStart)
|
||||
.align(Alignment.TopStart),
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_travala_banner_promo_background_2),
|
||||
contentDescription = null,
|
||||
contentScale = TravalaBackgroundScale(density),
|
||||
alignment = Alignment.TopStart,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.wrapContentSize(unbounded = true, align = Alignment.TopEnd)
|
||||
.align(Alignment.TopEnd),
|
||||
)
|
||||
Column {
|
||||
Row {
|
||||
Box(modifier = Modifier.size(87.dp))
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(top = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = config.title.resolveReference(),
|
||||
style = TangemTheme.typography.button.copy(
|
||||
lineBreak = LineBreak.Heading,
|
||||
),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
SpacerH8()
|
||||
Text(
|
||||
text = formatSubtitle(config.subtitle.resolveReference()),
|
||||
style = TangemTheme.typography.caption2.copy(
|
||||
lineBreak = LineBreak.Heading,
|
||||
),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.text.constantWhite,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
start = TangemTheme.dimens.spacing2,
|
||||
)
|
||||
.size(TangemTheme.dimens.size16)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
) {
|
||||
config.onCloseClick?.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
TangemButton(
|
||||
text = button?.text?.resolveReference().orEmpty(),
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = button?.onClick ?: {},
|
||||
colors = TangemButtonColors(
|
||||
backgroundColor = White.copy(alpha = 0.3f),
|
||||
contentColor = White,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
),
|
||||
enabled = true,
|
||||
showProgress = false,
|
||||
modifier = Modifier
|
||||
.padding(TangemTheme.dimens.spacing12)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TRAVALA_BACKGROUND_SRC_IMG_SCALE = 4
|
||||
|
||||
private class TravalaBackgroundScale(
|
||||
val density: Density,
|
||||
) : ContentScale {
|
||||
override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor {
|
||||
with(density) {
|
||||
val originalWidth = (srcSize.width / TRAVALA_BACKGROUND_SRC_IMG_SCALE).dp.toPx()
|
||||
val widthScale = originalWidth / srcSize.width
|
||||
val originalHeight = (srcSize.height / TRAVALA_BACKGROUND_SRC_IMG_SCALE).dp.toPx()
|
||||
val heightScale = originalHeight / srcSize.height
|
||||
return ScaleFactor(widthScale, heightScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun formatSubtitle(subtitle: String): AnnotatedString {
|
||||
val pattern = Regex("\\*\\*(.*?)\\*\\*")
|
||||
var startIndex = 0
|
||||
val annotatedString = buildAnnotatedString {
|
||||
pattern.findAll(subtitle).forEach { matchResult ->
|
||||
val index = matchResult.range.first
|
||||
val matchedValue = matchResult.groups[1]?.value ?: ""
|
||||
|
||||
// appends unformatted part
|
||||
append(subtitle.substring(startIndex, index))
|
||||
|
||||
// applies style on ^^-wrapped parts
|
||||
withStyle(SpanStyle(fontWeight = TangemTheme.typography.caption1.fontWeight)) {
|
||||
append(matchedValue)
|
||||
}
|
||||
|
||||
// goes to next part
|
||||
startIndex = matchResult.range.last + 1
|
||||
}
|
||||
|
||||
// appends remaining ending if exists
|
||||
append(subtitle.substring(startIndex))
|
||||
}
|
||||
|
||||
return annotatedString
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TravalaNotificationWithBackgroundPreview() {
|
||||
TangemTheme {
|
||||
TravalaNotificationWithBackground(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.main_travala_promotion_title,
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.main_travala_promotion_description,
|
||||
formatArgs = wrappedList("May 13", "June 12"),
|
||||
),
|
||||
iconResId = R.drawable.img_swap_promo,
|
||||
backgroundResId = R.drawable.img_travala_banner_promo_background,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(id = R.string.token_swap_promotion_button),
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.core.ui.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
import org.intellij.markdown.ast.ASTNode
|
||||
import org.intellij.markdown.ast.getTextInNode
|
||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
||||
import org.intellij.markdown.parser.MarkdownParser
|
||||
|
||||
/** Markdown parser */
|
||||
@Composable
|
||||
fun rememberMarkdownParser() = remember {
|
||||
MarkdownParser(CommonMarkFlavourDescriptor())
|
||||
}
|
||||
|
||||
/**
|
||||
* Styling markdown tree recursively
|
||||
*
|
||||
* @param markdownText original text
|
||||
* @param node current processed node
|
||||
*/
|
||||
@Composable
|
||||
fun AnnotatedString.Builder.appendMarkdown(markdownText: String, node: ASTNode): AnnotatedString.Builder {
|
||||
when (node.type) {
|
||||
MarkdownElementTypes.MARKDOWN_FILE, MarkdownElementTypes.PARAGRAPH -> {
|
||||
node.children.forEach { childNode ->
|
||||
appendMarkdown(
|
||||
markdownText = markdownText,
|
||||
node = childNode,
|
||||
)
|
||||
}
|
||||
}
|
||||
MarkdownElementTypes.STRONG -> {
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Medium)) {
|
||||
node.children
|
||||
.drop(2)
|
||||
.dropLast(2)
|
||||
.forEach { childNode ->
|
||||
appendMarkdown(
|
||||
markdownText = markdownText,
|
||||
node = childNode,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
append(node.getTextInNode(markdownText).toString())
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
|
@ -8,6 +8,9 @@ import androidx.compose.runtime.Immutable
|
|||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
|
||||
/**
|
||||
* Utility class for creating text as [String] or [StringRes].
|
||||
|
|
@ -160,6 +163,29 @@ fun TextReference.resolveReference(resources: Resources): String {
|
|||
}
|
||||
}
|
||||
|
||||
/** Resolve [TextReference] as [AnnotatedString] */
|
||||
@Composable
|
||||
fun TextReference.resolveAnnotatedReference(): AnnotatedString {
|
||||
return when (this) {
|
||||
is TextReference.Res -> {
|
||||
val args = formatArgs
|
||||
.map { if (it is TextReference) it.resolveReference() else it }
|
||||
.toTypedArray()
|
||||
|
||||
formatAnnotated(stringResource(id = id, *args))
|
||||
}
|
||||
is TextReference.PluralRes -> formatAnnotated(
|
||||
pluralStringResource(id, count, *formatArgs.toTypedArray()),
|
||||
)
|
||||
is TextReference.Str -> formatAnnotated(value)
|
||||
is TextReference.Combined -> buildAnnotatedString {
|
||||
refs.forEach {
|
||||
append(formatAnnotated(it.resolveReference()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Concatenate [this] reference with [ref] */
|
||||
operator fun TextReference.plus(ref: TextReference): TextReference {
|
||||
return when (this) {
|
||||
|
|
@ -169,4 +195,14 @@ operator fun TextReference.plus(ref: TextReference): TextReference {
|
|||
is TextReference.Str,
|
||||
-> TextReference.Combined(refs = wrappedList(this, ref))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun formatAnnotated(rawString: String): AnnotatedString {
|
||||
val markdownDescriptor = rememberMarkdownParser()
|
||||
val parsedTree = markdownDescriptor.parse(MarkdownElementTypes.MARKDOWN_FILE, rawString, true)
|
||||
|
||||
return buildAnnotatedString {
|
||||
appendMarkdown(markdownText = rawString, node = parsedTree)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.format.DateTimeFormat
|
||||
import org.joda.time.format.DateTimeFormatter
|
||||
|
|
@ -55,6 +56,16 @@ object DateTimeFormatters {
|
|||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
/**
|
||||
* In API version < 24, there may be some problems with getting the best date and time format pattern.
|
||||
*/
|
||||
val dateMMMMd: DateTimeFormatter by lazy {
|
||||
DateTimeFormatterBuilder()
|
||||
.appendPattern(DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMMM d"))
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
val dateTimeFormatter: DateTimeFormatter by lazy {
|
||||
DateTimeFormat.forPattern("dd.MM.yyyy HH:mm")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import java.text.DecimalFormatSymbols
|
|||
import java.util.Locale
|
||||
|
||||
private const val TEXT_CHUNK_THOUSAND = 3
|
||||
private const val POINT_SEPARATOR = '.'
|
||||
private const val COMMA_SEPARATOR = ','
|
||||
private const val SCIENTIFIC_NOTATION = 'e'
|
||||
const val POINT_SEPARATOR = '.'
|
||||
const val COMMA_SEPARATOR = ','
|
||||
const val DECIMAL_SEPARATOR_LIMIT = 1
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 441 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 409 KiB |
Loading…
Add table
Add a link
Reference in a new issue