From cec4c2d637436a2fc02a251a12384a6594fa6e18 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 16:28:48 +0100 Subject: [PATCH 1/9] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../wallet/presentation/wallet/domain/BackupValidator.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 980cc4cb2f..367aa6465e 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 980cc4cb2fb9ba44cd6798191ae7d208e4f7f74c +Subproject commit 367aa6465ec6ee7fdccce6cc7d7abeed5aad1111 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt index caa4c9fd87..4153d69e68 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt @@ -37,6 +37,7 @@ class BackupValidator @Inject constructor() { private fun validateBackupStatus(cardDTO: CardDTO): Boolean { val backupStatus = cardDTO.backupStatus - return backupStatus != null && backupStatus !is CardDTO.BackupStatus.CardLinked + backupStatus ?: return true // for card with null backup status, validation should always returns true + return backupStatus !is CardDTO.BackupStatus.CardLinked } } \ No newline at end of file From dca4c954b4e3172590c0f778e8b0b11c3dbf5104 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 19:44:27 +0500 Subject: [PATCH 2/9] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- .../presentation/state/confirm/SendNotificationFactory.kt | 5 ++--- .../presentation/state/previewdata/SendClickIntentsStub.kt | 3 ++- .../send/impl/presentation/viewmodel/SendClickIntents.kt | 3 ++- .../send/impl/presentation/viewmodel/SendViewModel.kt | 6 ++++-- gradle/dependencies.toml | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 7a8e690d21..84088baede 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -9,7 +9,7 @@ }, { "name": "REDESIGNED_SEND_SCREEN_ENABLED", - "version": "undefined" + "version": "5.9.0" }, { "name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED", diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 68d334e0ae..0d147eb6fd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -167,9 +167,8 @@ internal class SendNotificationFactory( cryptoCurrency = cryptoCurrency, ), onConfirmClick = { - val reduceTo = utxoLimit.maxAmount.toPlainString() clickIntents.onAmountReduceClick( - reduceTo, + utxoLimit.maxAmount, SendNotification.Error.TransactionLimitError::class.java, ) }, @@ -223,7 +222,7 @@ internal class SendNotificationFactory( SendNotification.Warning.HighFeeError( amount = threshold.toPlainString(), onConfirmClick = { - val reduceTo = sendAmount.minus(threshold).toPlainString() + val reduceTo = sendAmount.minus(threshold) clickIntents.onAmountReduceClick(reduceTo, SendNotification.Warning.HighFeeError::class.java) }, onCloseClick = { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 67d5b07642..92be46efa2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -6,6 +6,7 @@ import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import java.math.BigDecimal @Suppress("TooManyFunctions") internal object SendClickIntentsStub : SendClickIntents { @@ -57,7 +58,7 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onShareClick() {} - override fun onAmountReduceClick(reducedAmount: String, clazz: Class) {} + override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) {} override fun onNotificationCancel(clazz: Class) {} } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 14ed1562af..9db5547b20 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -5,6 +5,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.fee.FeeType +import java.math.BigDecimal @Suppress("TooManyFunctions") internal interface SendClickIntents { @@ -64,7 +65,7 @@ internal interface SendClickIntents { fun onShareClick() - fun onAmountReduceClick(reducedAmount: String, clazz: Class) + fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) fun onNotificationCancel(clazz: Class) // endregion diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 2a3112371e..2d397422cf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -10,6 +10,7 @@ import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -58,6 +59,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import timber.log.Timber +import java.math.BigDecimal import java.util.Locale import javax.inject.Inject import kotlin.properties.Delegates @@ -785,8 +787,8 @@ internal class SendViewModel @Inject constructor( analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) } - override fun onAmountReduceClick(reducedAmount: String, clazz: Class) { - uiState = amountStateFactory.getOnAmountValueChange(reducedAmount) + override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) { + uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals)) uiState = sendNotificationFactory.dismissNotificationState(clazz) } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 702d001217..48a3bcb3df 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-596" +tangemBlockchainSdk = "release-app_5.9-599" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From f8b66b963707baf8d8fbebcb46a0038786e7f1f8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 18:25:31 +0100 Subject: [PATCH 3/9] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 48a3bcb3df..56d6351be9 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-599" +tangemBlockchainSdk = "release-app_5.9-600" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From f117e5a2469fa46b74ebf882e947e16acf0a2c06 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 10:42:01 +0300 Subject: [PATCH 4/9] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 5 ++-- core/res/src/main/res/values/strings.xml | 3 ++- .../tokendetails/TokenDetailsPreviewData.kt | 15 ++++++++++++ .../tokendetails/state/TokenInfoBlockState.kt | 2 +- .../TokenDetailsSkeletonStateConverter.kt | 6 ++++- .../ui/components/TokenInfoBlock.kt | 24 +++++++++++++------ tangem-android-tools | 2 +- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index b4bd309592..5d25d41f8e 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -427,7 +427,7 @@ Уже содержится в введенном адресе Вычесть Недостаточно средств для покрытия комиссии сети. Вычесть недостающую сумму для покрытия комиссии из отправляемой суммы? - Сумма комиссии в %@ раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? Причина: %1$s\nКод: %2$s Транзакция не выполнена @@ -537,9 +537,9 @@ Балансы скрыты Балансы показаны Отменить + Выбранная операция в данный момент недоступна. Попробуйте позже. В данный момент покупка монеты %s недоступна. Но мы работаем над её добавлением. У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - Выбранная операция в данный момент недоступна. Попробуйте позже. Обмен %s не доступен. Но мы работаем над его добавлением. В данный момент продажа монеты %s недоступна. Но мы работаем над её добавлением. Сгенерировать XPUB @@ -548,6 +548,7 @@ Скрыть %s Скрыть токен %1$s токен в сети %%image%% %2$s + Токен в сети %%image%% %1$s Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. Невозможно скрыть %s Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 9310a0153a..a099c577d4 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -534,9 +534,9 @@ Balances hidden Balances shown Undo + This operation is currently unavailable. Please try again later. The purchase of the %s is currently unavailable. But we are working on adding it. You do not have funds to send. Top up your account to be able to send funds from it. - This operation is currently unavailable. Please try again later. %s swap is not available. But we are working on adding it. Sell of the %s coin is currently unavailable. But we are working on adding it. Generate XPUB @@ -545,6 +545,7 @@ Hide %s Hide token %1$s token in %%image%% %2$s network + Token in %%image%% %1$s network The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list. Unable to hide %s Exchange this token for another at %1$s service fees from February %2$s-%3$s. diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 77f27a4cac..69d878168f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -57,6 +57,21 @@ internal object TokenDetailsPreviewData { ), ) + val tokenInfoBlockStateWithLongNameNoStandard = TokenInfoBlockState( + name = "Tether (USDT) with long name test", + iconState = TokenInfoBlockState.IconState.TokenIcon( + url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png", + fallbackTint = Color.Cyan, + fallbackBackground = Color.Blue, + isGrayscale = false, + ), + currency = TokenInfoBlockState.Currency.Token( + standardName = null, + networkIcon = R.drawable.img_shibarium_22, + networkName = "Shibarium", + ), + ) + val tokenInfoBlockState = TokenInfoBlockState( name = "Tether USDT", iconState = TokenInfoBlockState.IconState.CustomTokenIcon( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt index 7c1e1eda2b..f0ec9e8d43 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt @@ -19,7 +19,7 @@ internal data class TokenInfoBlockState( * @param networkIcon - token's network icon. */ data class Token( - val standardName: String, + val standardName: String?, val networkName: String, @DrawableRes val networkIcon: Int, ) : Currency() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index dbc7ff632b..163a1516ae 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Network import com.tangem.feature.tokendetails.presentation.tokendetails.state.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsPullToRefreshConfig @@ -40,7 +41,7 @@ internal class TokenDetailsSkeletonStateConverter( currency = when (value) { is CryptoCurrency.Coin -> TokenInfoBlockState.Currency.Native is CryptoCurrency.Token -> TokenInfoBlockState.Currency.Token( - standardName = value.network.standardType.name, + standardName = value.network.standardType.getSpecifiedNameOrNull(), networkName = value.network.name, networkIcon = value.networkIconResId, ) @@ -65,6 +66,9 @@ internal class TokenDetailsSkeletonStateConverter( ) } + private fun Network.StandardType.getSpecifiedNameOrNull(): String? = + name.takeIf { this !is Network.StandardType.Unspecified } + private fun createMenu(cryptoCurrency: CryptoCurrency): TokenDetailsAppBarMenuConfig = TokenDetailsAppBarMenuConfig( items = buildList { if (featureToggles.isGenerateXPubEnabled() && isBitcoin(cryptoCurrency.network.id.value)) { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt index 3254286ed2..3982a8dc21 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt @@ -105,13 +105,22 @@ private const val SEPARATOR = " %image% " @Composable private fun extractNetwork(tokenCurrency: TokenInfoBlockState.Currency.Token): ExtractedTokenNetworkText { - val splitString = stringResource( - id = R.string.token_details_token_type_subtitle, - formatArgs = arrayOf( - tokenCurrency.standardName, - tokenCurrency.networkName, - ), - ).split(SEPARATOR) + val splitString = if (tokenCurrency.standardName != null) { + stringResource( + id = R.string.token_details_token_type_subtitle, + formatArgs = arrayOf( + tokenCurrency.standardName, + tokenCurrency.networkName, + ), + ).split(SEPARATOR) + } else { + stringResource( + id = R.string.token_details_token_type_subtitle_no_standard, + formatArgs = arrayOf( + tokenCurrency.networkName, + ), + ).split(SEPARATOR) + } return remember(splitString) { ExtractedTokenNetworkText( @@ -153,5 +162,6 @@ private class TokenInfoStateProvider : CollectionPreviewParameterProvider Date: Wed, 17 Apr 2024 13:33:57 +0500 Subject: [PATCH 5/9] Updated on 2026-08-14 --- .../DefaultQrScanningEventsRepository.kt | 19 ++++++++++--------- .../DefaultQrScanningEventsRepositoryTest.kt | 10 ++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt index 87729e8bc4..994539366b 100644 --- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt +++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt @@ -52,16 +52,17 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { result.memo = URLDecoder.decode(it.value, "UTF-8") } Parameter.Address -> { + // If 'address' parameter is exists, then currency must be TOKEN. + val tokenCurrency = cryptoCurrency as? CryptoCurrency.Token ?: return QrResult() + // Overrides destination address for token transfers (ERC-681) - if (cryptoCurrency is CryptoCurrency.Token) { - // `address` parameter is used only if the contract address, encoded in the QR, - // matches the contract address of the token. - // Otherwise, the scanned string is likely malformed, and we stop the entire parsing routine - if (cryptoCurrency.contractAddress.equals(address, ignoreCase = true)) { - result.address = it.value - } else { - return QrResult() - } + // `address` parameter is used only if the contract address, encoded in the QR, + // matches the contract address of the token. + // Otherwise, the scanned string is likely malformed, and we stop the entire parsing routin + if (tokenCurrency.contractAddress.equals(address, ignoreCase = true)) { + result.address = it.value + } else { + return QrResult() } } Parameter.Value, diff --git a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt index ad1f6244a7..13d49ce302 100644 --- a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt +++ b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt @@ -232,11 +232,21 @@ internal class DefaultQrScanningEventsRepositoryTest { QrResult(address = address2, amount = BigDecimal("2300")), tokenCryptoCurrency, ) + positiveCase( + "$address4?$addressParam=$addressParamValue", + QrResult(address = addressParamValue), + tokenCryptoCurrency, + ) negativeCase( "$address2?$someParam=$someParamValue&$amountParam=$amountParamValue", QrResult(address = address2, amount = BigDecimal("123.123"), memo = memoParamValueUtf8), tokenCryptoCurrency, ) + negativeCase( + "$address4?$addressParam=$addressParamValue", + QrResult(address = addressParamValue), + cryptoCurrency, + ) } private fun positiveCase(input: String, expected: QrResult, cryptoCurrency: CryptoCurrency) { From 741b6702cee048a9285e4ca9df207973bfcbe24e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 12:04:24 +0500 Subject: [PATCH 6/9] Updated on 2026-08-14 --- .../tangem/features/send/impl/presentation/SendFragment.kt | 4 ++-- .../send/impl/presentation/viewmodel/SendViewModel.kt | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt index cebe88712f..64e8f1e992 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt @@ -92,9 +92,9 @@ internal class SendFragment : ComposeFragment() { delay(QR_SCAN_DELAY) // Delayed launch is needed in order for the UI to be drawn and to process the sent events. - // If do not use the delay, then etAmount error field is not displayed when + // If do not use the delay, then error field is not displayed when // inserting an incorrect amount by shareUri - viewModel.onRecipientAddressScanned(it) + viewModel.onQrCodeScanned(it) } } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 2d397422cf..ffad3f3633 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -569,12 +569,14 @@ internal class SendViewModel @Inject constructor( // endregion // region recipient state clicks - fun onRecipientAddressScanned(address: String) { + fun onQrCodeScanned(address: String) { viewModelScope.launch(dispatchers.main) { parseQrCodeUseCase(address, cryptoCurrency).fold( ifRight = { parsedCode -> onRecipientAddressValueChange(parsedCode.address, EnterAddressSource.QRCode) - parsedCode.amount?.let { onAmountValueChange(it.toPlainString()) } + parsedCode.amount?.let { + onAmountValueChange(it.parseBigDecimal(decimals = cryptoCurrency.decimals)) + } parsedCode.memo?.let { onRecipientMemoValueChange(it) } }, ifLeft = { From 626e2764d308dc91f388f6ec82fa28d7b5ac404a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 23:08:32 +0500 Subject: [PATCH 7/9] Updated on 2026-08-14 --- .../core/ui/utils/DecimalFormatterExt.kt | 33 +++++++++++++++++++ .../DefaultQrScanningEventsRepository.kt | 5 +-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt index 7db27cf7f4..b5a21b53f7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt @@ -11,6 +11,8 @@ import java.util.Locale private const val TEXT_CHUNK_THOUSAND = 3 private const val POINT_SEPARATOR = '.' +private const val COMMA_SEPARATOR = ',' +const val DECIMAL_SEPARATOR_LIMIT = 1 @Composable fun rememberDecimalFormat(): DecimalFormat { @@ -152,6 +154,37 @@ fun BigDecimal.parseBigDecimal(decimals: Int, roundingMode: RoundingMode = Round } } +/** + * Universal amount string parser to [BigDecimal] + * Able to parse values with only ONE separator, assuming separator is COMMA. + * Otherwise returns null. + */ +fun String.parseBigDecimalOrNull() = runCatching { + // Filtering value containing more than one either grouping or decimal separator. + // We assume there will be only decimal separator, otherwise parsing will fail. + if (count { !it.isDigit() } > DECIMAL_SEPARATOR_LIMIT) return null + + // An attempt to parse value with POINT decimal separator + val parsed = this.toBigDecimalOrNull() + + if (parsed == null) { + // If parsing with POINT separator fails trying to parse with COMMA separator + val decimalFormatSymbol = DecimalFormatSymbols().apply { + decimalSeparator = COMMA_SEPARATOR + } + val decimalFormat = DecimalFormat().apply { + decimalFormatSymbols = decimalFormatSymbol + isParseBigDecimal = true + } + + // Return either number or null if fails + decimalFormat.parse(this) as? BigDecimal + } else { + // If parsing with POINT separator succeeds return number + parsed + } +}.getOrNull() + private fun Int.getWithIntegerDecimals(before: String, separator: Char, after: String): String = if (this == 0) { before } else { diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt index 87729e8bc4..213044a170 100644 --- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt +++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt @@ -1,6 +1,7 @@ package com.tangem.data.qrscanning.repository import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.utils.parseBigDecimalOrNull import com.tangem.domain.qrscanning.models.QrResult import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.repository.QrScanningEventsRepository @@ -44,7 +45,7 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { when (it.key) { Parameter.Amount -> { // According to BIP-0021, the value is specified in decimals. No conversion needed - result.amount = it.value.toBigDecimalOrNull() + result.amount = it.value.parseBigDecimalOrNull() } Parameter.Message, Parameter.Memo, @@ -69,7 +70,7 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { -> { // Extra convert parses scientific notation to decimal // This is necessary to be able comparing BigDecimal values - result.amount = it.value.toBigDecimalOrNull() + result.amount = it.value.parseBigDecimalOrNull() ?.toPlainString()?.toBigDecimalOrNull() ?.divide(BigDecimal.TEN.pow(cryptoCurrency.decimals)) } From a6c54fbaae7ab31056b3ea443e683d9328018ca2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 12:21:37 +0300 Subject: [PATCH 8/9] Updated on 2026-08-14 --- .../impl/presentation/ui/BriefNetworksList.kt | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt index d5328878eb..e122327ff8 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt @@ -4,20 +4,20 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.key +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState import kotlinx.collections.immutable.ImmutableCollection @@ -41,10 +41,7 @@ internal fun BriefNetworksList( exit = fadeOut(), ) { Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { - val iterator = networks.iterator() - var index = 0 - while (iterator.hasNext()) { - val network = iterator.next() + for ((index, network) in networks.withIndex()) { if (index < MAX_VISIBLE_BRIEF_ICONS) { key(network.name + network.protocolName) { BriefNetworkItem(model = network) @@ -59,7 +56,6 @@ internal fun BriefNetworksList( break } } - index++ } } } @@ -109,8 +105,16 @@ internal fun BriefNetworkItem(model: NetworkItemState, modifier: Modifier = Modi } } +@Suppress("MagicNumber") @Composable internal fun HasMoreItem(moreCount: Int) { + val count = if (moreCount > 99) 99 else moreCount + val themeTextStyle = TangemTheme.typography.overline.copy( + letterSpacing = TextUnit(value = 0f, type = TextUnitType.Sp), + ) + var textStyle by remember(themeTextStyle) { mutableStateOf(themeTextStyle) } + var readyToDraw by remember(themeTextStyle) { mutableStateOf(false) } + Box( modifier = Modifier .size(size = TangemTheme.dimens.size20) @@ -118,10 +122,20 @@ internal fun HasMoreItem(moreCount: Int) { .background(TangemTheme.colors.control.unchecked), ) { Text( - modifier = Modifier.align(Alignment.Center), - text = "+$moreCount", - style = TangemTheme.typography.overline, - color = TangemTheme.colors.text.tertiary, + modifier = Modifier + .padding(TangemTheme.dimens.spacing4) + .align(Alignment.Center) + .drawWithContent { if (readyToDraw) drawContent() }, + text = "+$count", + style = textStyle, + overflow = TextOverflow.Clip, + onTextLayout = { textLayoutResult -> + if (textLayoutResult.didOverflowHeight) { + textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9) + } else { + readyToDraw = true + } + }, ) } } \ No newline at end of file From ec1b9093ece71431287cec5efbfcac6028a8c55c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 15:49:31 +0500 Subject: [PATCH 9/9] Updated on 2026-08-14 --- .../com/tangem/core/ui/utils/DecimalFormatterExt.kt | 13 ++++++++++++- .../DefaultQrScanningEventsRepositoryTest.kt | 10 +++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt index b5a21b53f7..5006df1d7a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt @@ -12,6 +12,7 @@ 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 DECIMAL_SEPARATOR_LIMIT = 1 @Composable @@ -162,7 +163,17 @@ fun BigDecimal.parseBigDecimal(decimals: Int, roundingMode: RoundingMode = Round fun String.parseBigDecimalOrNull() = runCatching { // Filtering value containing more than one either grouping or decimal separator. // We assume there will be only decimal separator, otherwise parsing will fail. - if (count { !it.isDigit() } > DECIMAL_SEPARATOR_LIMIT) return null + + // Step 1. Exclude formatted (100,000.0) except scientific notation (100.000e10) + val excludeFormatted = this.count { + !it.isDigit() && !it.equals(SCIENTIFIC_NOTATION, ignoreCase = true) + } > DECIMAL_SEPARATOR_LIMIT + + // Step 2. Exclude wrong scientific notation (100e100e100) + val excludeWrongScientific = this.count { + it.equals(SCIENTIFIC_NOTATION, ignoreCase = true) + } > DECIMAL_SEPARATOR_LIMIT + if (excludeFormatted || excludeWrongScientific) return null // An attempt to parse value with POINT decimal separator val parsed = this.toBigDecimalOrNull() diff --git a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt index 13d49ce302..3b3cd6df38 100644 --- a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt +++ b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt @@ -152,11 +152,6 @@ internal class DefaultQrScanningEventsRepositoryTest { QrResult(address = address2), cryptoCurrency, ) - positiveCase( - "$garbage$schema2:$address2$function?$addressParam=$addressParamValue", - QrResult(address = address2), - cryptoCurrency, - ) positiveCase( "$garbage$schema2:$address2?$someParam=$someParamValue&$valueParam=$someAmountParamValue", QrResult(address = address2), @@ -177,6 +172,11 @@ internal class DefaultQrScanningEventsRepositoryTest { QrResult(address = address2, amount = BigDecimal("0.000000023")), cryptoCurrency, ) + negativeCase( + "$garbage$schema2:$address2$function?$addressParam=$addressParamValue", + QrResult(address = address2), + cryptoCurrency, + ) } @Test