Updated on 2026-08-14
This commit is contained in:
commit
62419af039
11 changed files with 158 additions and 16 deletions
|
|
@ -500,7 +500,7 @@
|
|||
<string name="send_recent_transactions">Последние</string>
|
||||
<string name="send_recipient">Получатель</string>
|
||||
<string name="send_recipient_address_error">Неверный адрес</string>
|
||||
<string name="send_recipient_address_footer">Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов</string>
|
||||
<string name="send_recipient_address_footer">Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов.</string>
|
||||
<string name="send_recipient_label">Отправить</string>
|
||||
<string name="send_recipient_memo_footer">Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств.</string>
|
||||
<string name="send_recipient_wallets_title">Мои кошельки</string>
|
||||
|
|
@ -509,7 +509,7 @@
|
|||
<string name="send_sending">Отправка</string>
|
||||
<string name="send_summary_tap_hint">Нажмите на любое поле, чтобы изменить его</string>
|
||||
<string name="send_summary_title">Отправка %s</string>
|
||||
<string name="send_summary_transaction_description">Вы отправляете %1$s, включая комиссию сети %2$s</string>
|
||||
<string name="send_summary_transaction_description">Вы отправляете **%1$s**, включая комиссию сети %2$s</string>
|
||||
<string name="send_title_currency_format">Отправка %s</string>
|
||||
<string name="send_total_label">Всего</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s и %2$s будет отправлено</string>
|
||||
|
|
|
|||
|
|
@ -499,7 +499,7 @@
|
|||
<string name="send_recent_transactions">Recent</string>
|
||||
<string name="send_recipient">Recipient</string>
|
||||
<string name="send_recipient_address_error">Not a valid address</string>
|
||||
<string name="send_recipient_address_footer">Ensure that you are sending funds to an %s wallet address. Errors may result in the loss of your tokens</string>
|
||||
<string name="send_recipient_address_footer">Ensure the receiving wallet address is on the %s network to avoid losing your tokens</string>
|
||||
<string name="send_recipient_label">Send to</string>
|
||||
<string name="send_recipient_memo_footer">A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds</string>
|
||||
<string name="send_recipient_wallets_title">My wallets</string>
|
||||
|
|
@ -508,7 +508,7 @@
|
|||
<string name="send_sending">Sending...</string>
|
||||
<string name="send_summary_tap_hint">Tap any field to change it</string>
|
||||
<string name="send_summary_title">Send %s</string>
|
||||
<string name="send_summary_transaction_description">You are sending %1$s including a network fee of %2$s</string>
|
||||
<string name="send_summary_transaction_description">You are sending **%1$s** including a network fee of %2$s</string>
|
||||
<string name="send_title_currency_format">Sending %s</string>
|
||||
<string name="send_total_label">Total</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s and %2$s will be sent</string>
|
||||
|
|
|
|||
|
|
@ -45,4 +45,5 @@ dependencies {
|
|||
implementation(deps.zxing.qrCore)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.markdown)
|
||||
}
|
||||
|
|
@ -94,10 +94,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,
|
||||
|
|
@ -116,8 +120,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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ import com.tangem.core.ui.components.buttons.common.TangemButton
|
|||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
|
|
@ -185,11 +188,17 @@ private fun SendingText(
|
|||
currencySymbol = feeState.appCurrency.symbol,
|
||||
currencyCode = feeState.appCurrency.code,
|
||||
)
|
||||
val textResource = remember(sendingValue, feeValue) {
|
||||
resourceReference(
|
||||
id = R.string.send_summary_transaction_description,
|
||||
formatArgs = wrappedList(sendingValue, feeValue),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = R.string.send_summary_transaction_description, sendingValue, feeValue),
|
||||
text = textResource.resolveAnnotatedReference(),
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.core.ui.components.SpacerWMax
|
|||
import com.tangem.core.ui.components.rows.SelectorRowItem
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
|
|
@ -114,11 +115,14 @@ private fun FeeError(feeSelectorState: FeeSelectorState) {
|
|||
|
||||
private fun FeeSelectorState.Content.getAmount(feeType: FeeType): Amount? {
|
||||
val choosableFees = fees as? TransactionFee.Choosable
|
||||
val decimals = fees.normal.amount.decimals
|
||||
val customValue = this.customValues.firstOrNull()?.value?.parseToBigDecimal(decimals)
|
||||
val customAmount = fees.normal.amount.copy(value = customValue)
|
||||
return when (feeType) {
|
||||
FeeType.Slow -> choosableFees?.minimum?.amount
|
||||
FeeType.Market -> fees.normal.amount
|
||||
FeeType.Fast -> choosableFees?.priority?.amount
|
||||
FeeType.Custom -> null
|
||||
FeeType.Custom -> customAmount
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import java.math.BigDecimal
|
|||
import java.math.RoundingMode
|
||||
|
||||
private const val FIAT_DECIMALS = 2
|
||||
private const val CRYPTO_FEE_DECIMALS = 6
|
||||
private const val FEE_MINIMUM_VALUE = 0.01
|
||||
|
||||
internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? {
|
||||
|
|
@ -21,7 +22,7 @@ internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): Tex
|
|||
BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = amount.value,
|
||||
cryptoCurrency = amount.currencySymbol,
|
||||
decimals = amount.decimals,
|
||||
decimals = amount.decimals.coerceAtMost(CRYPTO_FEE_DECIMALS),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ swipeRefreshLayout = "1.1.0"
|
|||
spr-client = "3.6.2"
|
||||
web3j = "4.10.1"
|
||||
leakcanary = "2.13"
|
||||
markdown = "0.7.2"
|
||||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
|
|
@ -249,4 +250,5 @@ camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref =
|
|||
camera-view = { module = "androidx.camera:camera-view", version.ref = "androidXCamera" }
|
||||
web3j-core = { module = "org.web3j:core", version.ref = "web3j" }
|
||||
leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" }
|
||||
markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" }
|
||||
# endregion Other
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue