From 9c11182ca623d030c8f66cab611f22ee2ab72924 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 5 Feb 2024 21:00:06 +0300 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 1 + core/res/src/main/res/values/strings.xml | 1 + .../ui/components/fields/SimpleTextField.kt | 16 ++++--- features/send/impl/build.gradle.kts | 3 ++ .../presentation/state/SendStateFactory.kt | 17 +++++++- .../state/fields/SendTextField.kt | 2 + .../SendRecipientMemoFieldConverter.kt | 2 + .../ui/recipient/SendRecipientContent.kt | 4 +- .../ui/recipient/TextFieldWithPaste.kt | 26 +++++++----- .../presentation/viewmodel/SendViewModel.kt | 42 +++++++++---------- libs/crypto/build.gradle.kts | 3 ++ .../com/tangem/lib/crypto/BlockchainUtils.kt | 29 +++++++++++++ .../converter/XrpTaggedAddressConverter.kt | 15 +++++++ .../lib/crypto/models/XrpTaggedAddress.kt | 6 +++ 14 files changed, 127 insertions(+), 40 deletions(-) create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/converter/XrpTaggedAddressConverter.kt create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/XrpTaggedAddress.kt diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index e257132463..8c7c16f2b5 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -449,6 +449,7 @@ Сканировать Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. Приготовьте свою карту + Уже содержится в введенном адресе Сумма Вычесть из суммы отправки Сумма к получению %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index dd42088e6b..d384d8f946 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -448,6 +448,7 @@ Scan Card Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Get your card ready! + Already included in the entered address Amount Subtract from send amount The recipient will receive %s diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index 1e29006979..db725acbc8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.fields +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions @@ -89,11 +90,16 @@ fun SimpleTextField( decorationBox = decorationBox ?: { textValue -> Box { if (value.isBlank() && placeholder != null) { - Text( - text = placeholder.resolveReference(), - style = textStyle, - color = TangemTheme.colors.text.disabled, - ) + AnimatedContent( + targetState = placeholder, + label = "Placeholder Change Animation", + ) { + Text( + text = it.resolveReference(), + style = textStyle, + color = TangemTheme.colors.text.disabled, + ) + } } textValue() } diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts index 46295abadb..95074550ff 100644 --- a/features/send/impl/build.gradle.kts +++ b/features/send/impl/build.gradle.kts @@ -46,6 +46,9 @@ dependencies { implementation(projects.core.utils) implementation(projects.core.navigation) + /** Libs */ + implementation(projects.libs.crypto) + /** Domain modules */ implementation(projects.domain.models) implementation(projects.domain.legacy) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index d53fbaab70..7f8e97b93e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -136,12 +136,13 @@ internal class SendStateFactory( ) } - fun onRecipientAddressValueChange(value: String): SendUiState { + fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState { val state = currentStateProvider() val recipientState = state.recipientState ?: return state return state.copy( recipientState = recipientState.copy( addressTextField = recipientState.addressTextField.copy(value = value), + memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress), ), ) } @@ -216,6 +217,20 @@ internal class SendStateFactory( isValidating = false, memoTextField = recipientState.memoTextField?.copy( isError = value.isNotEmpty() && !isValidMemo, + isEnabled = true, + ), + ), + ) + } + + fun getOnXAddressMemoState(): SendUiState { + val state = currentStateProvider() + val recipientState = state.recipientState ?: return state + return state.copy( + recipientState = recipientState.copy( + memoTextField = recipientState.memoTextField?.copy( + value = "", + isEnabled = false, ), ), ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt index 42b2ac5b0d..d89b75c9b1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt @@ -47,6 +47,8 @@ internal sealed class SendTextField { val label: TextReference, val isError: Boolean = false, val error: TextReference? = null, + val disabledText: TextReference, + val isEnabled: Boolean, ) : SendTextField() data class CustomFee( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt index 45e2f47771..91321769f0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt @@ -44,6 +44,8 @@ internal class SendRecipientMemoFieldConverter( placeholder = resourceReference(R.string.send_optional_field), label = resourceReference(value), error = resourceReference(R.string.send_memo_destination_tag_error), + disabledText = resourceReference(R.string.send_additional_field_already_included), + isEnabled = true, ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index d2741dffcc..3fa19bdf16 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -73,16 +73,18 @@ internal fun SendRecipientContent( } uiState.memoTextField?.let { memoField -> item(key = MEMO_FIELD_KEY) { + val placeholder = if (memoField.isEnabled) memoField.placeholder else memoField.disabledText TextFieldWithPaste( value = memoField.value, label = memoField.label, - placeholder = memoField.placeholder, + placeholder = placeholder, footer = stringResource(R.string.send_recipient_memo_footer), onValueChange = memoField.onValueChange, onPasteClick = clickIntents::onRecipientMemoValueChange, modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), isError = memoField.isError, error = memoField.error, + isReadOnly = !memoField.isEnabled, ) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt index 758fef6f57..c3a49b8da6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt @@ -27,11 +27,12 @@ internal fun TextFieldWithPaste( footer: String? = null, error: TextReference? = null, isError: Boolean = false, + isReadOnly: Boolean = false, ) { - val (title, color) = if (isError && error != null) { - error to TangemTheme.colors.text.warning - } else { - label to TangemTheme.colors.text.secondary + val (title, color) = when { + isError && error != null -> error to TangemTheme.colors.text.warning + isReadOnly -> label to TangemTheme.colors.text.disabled + else -> label to TangemTheme.colors.text.secondary } FooterContainer(modifier, footer) { Row( @@ -55,18 +56,21 @@ internal fun TextFieldWithPaste( value = value, placeholder = placeholder, onValueChange = onValueChange, + readOnly = isReadOnly, modifier = Modifier .fillMaxWidth() .padding(top = TangemTheme.dimens.spacing6), ) } - PasteButton( - isPasteButtonVisible = value.isBlank(), - onClick = onPasteClick, - modifier = Modifier - .align(CenterVertically) - .padding(end = TangemTheme.dimens.spacing16), - ) + if (!isReadOnly) { + PasteButton( + isPasteButtonVisible = value.isBlank(), + onClick = onPasteClick, + modifier = Modifier + .align(CenterVertically) + .padding(end = TangemTheme.dimens.spacing16), + ) + } } } } \ No newline at end of file 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 98e7341fc8..b7bc549c8e 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 @@ -7,8 +7,6 @@ import androidx.lifecycle.* import androidx.paging.PagingData import arrow.core.Either import arrow.core.getOrElse -import com.tangem.blockchain.blockchains.xrp.XrpAddressService -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.extensions.isZero @@ -20,8 +18,8 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.* import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.tokens.repository.CurrencyChecksRepository +import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.usecase.CreateTransactionUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase @@ -43,6 +41,7 @@ import com.tangem.features.send.impl.presentation.state.fee.FeeNotificationFacto import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory import com.tangem.features.send.impl.presentation.state.fee.FeeType +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder @@ -301,10 +300,12 @@ internal class SendViewModel @Inject constructor( userWalletId = wallet.walletId, network = walletCurrency.network, ) - return@fold AvailableWallet( - name = wallet.name, - address = addresses.first().value, - ) + return@fold addresses.firstOrNull()?.let { + AvailableWallet( + name = wallet.name, + address = it.value, + ) + } }, ifLeft = { null }, ) @@ -426,10 +427,10 @@ internal class SendViewModel @Inject constructor( } override fun onRecipientAddressValueChange(value: String) { - uiState = stateFactory.onRecipientAddressValueChange(value) viewModelScope.launch(dispatchers.main) { - uiState = stateFactory.getOnRecipientAddressValidationStarted() if (!checkIfXrpAddressValue(value)) { + uiState = stateFactory.onRecipientAddressValueChange(value) + uiState = stateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(value) uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress) } @@ -437,10 +438,10 @@ internal class SendViewModel @Inject constructor( } override fun onRecipientMemoValueChange(value: String) { - uiState = stateFactory.getOnRecipientMemoValueChange(value) viewModelScope.launch(dispatchers.main) { - uiState = stateFactory.getOnRecipientAddressValidationStarted() if (!checkIfXrpAddressValue(value)) { + uiState = stateFactory.getOnRecipientMemoValueChange(value) + uiState = stateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(uiState.recipientState?.addressTextField?.value.orEmpty()) uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress) } @@ -455,16 +456,14 @@ internal class SendViewModel @Inject constructor( ).getOrElse { false } } - private fun checkIfXrpAddressValue(value: String): Boolean { - if (cryptoCurrency.network.id.value == Blockchain.XRP.id && value.firstOrNull() == XRP_X_ADDRESS) { - viewModelScope.launch(dispatchers.io) { - val result = XrpAddressService.decodeXAddress(value) - onRecipientAddressValueChange(result?.address.orEmpty()) - onRecipientMemoValueChange(result?.destinationTag.toString()) - } - return true - } - return false + private suspend fun checkIfXrpAddressValue(value: String): Boolean { + return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress -> + uiState = stateFactory.onRecipientAddressValueChange(value, isXAddress = true) + uiState = stateFactory.getOnXAddressMemoState() + val isValidAddress = validateAddress(decodedAddress.address) + uiState = stateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress) + true + } ?: false } // endregion @@ -665,7 +664,6 @@ internal class SendViewModel @Inject constructor( // endregion companion object { - private const val XRP_X_ADDRESS = 'X' private const val CHECK_FEE_UPDATE_DELAY = 60_000L private const val BALANCE_UPDATE_DELAY = 10_000L } diff --git a/libs/crypto/build.gradle.kts b/libs/crypto/build.gradle.kts index 60b1f6f10e..917e9c310d 100644 --- a/libs/crypto/build.gradle.kts +++ b/libs/crypto/build.gradle.kts @@ -13,4 +13,7 @@ dependencies { /** SDK */ implementation(deps.tangem.blockchain) + + /** Core */ + implementation(projects.core.utils) } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt new file mode 100644 index 0000000000..1e7a2e6a6d --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -0,0 +1,29 @@ +package com.tangem.lib.crypto + +import com.tangem.blockchain.blockchains.xrp.XrpAddressService +import com.tangem.blockchain.common.Blockchain +import com.tangem.lib.crypto.converter.XrpTaggedAddressConverter +import com.tangem.lib.crypto.models.XrpTaggedAddress + +/** + * !!!IMPORTANT!!! + * Methods for working with different blockchains + * All methods are depend on specific blockchain or check for specific blockchain + * + * Temporary solution for domain specific logic for Blockchain. + * Instead of creating repositories and unnecessary and overkill use cases + */ +object BlockchainUtils { + + private const val XRP_X_ADDRESS = 'X' + + /** Decodes XRP Blockchain address */ + fun decodeRippleXAddress(xAddress: String, networkId: String): XrpTaggedAddress? { + return if (networkId == Blockchain.XRP.id && xAddress.firstOrNull() == XRP_X_ADDRESS) { + val decodedAddress = XrpAddressService.decodeXAddress(xAddress) + return decodedAddress?.let(XrpTaggedAddressConverter()::convert) + } else { + null + } + } +} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/converter/XrpTaggedAddressConverter.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/converter/XrpTaggedAddressConverter.kt new file mode 100644 index 0000000000..9d42e0cbc4 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/converter/XrpTaggedAddressConverter.kt @@ -0,0 +1,15 @@ +package com.tangem.lib.crypto.converter + +import com.tangem.lib.crypto.models.XrpTaggedAddress +import com.tangem.utils.converter.Converter +import com.tangem.blockchain.blockchains.xrp.XrpTaggedAddress as BlockchainXrpTaggedAddress + +internal class XrpTaggedAddressConverter : Converter { + + override fun convert(value: BlockchainXrpTaggedAddress): XrpTaggedAddress { + return XrpTaggedAddress( + address = value.address, + destinationTag = value.destinationTag, + ) + } +} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/XrpTaggedAddress.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/XrpTaggedAddress.kt new file mode 100644 index 0000000000..d9d0946326 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/XrpTaggedAddress.kt @@ -0,0 +1,6 @@ +package com.tangem.lib.crypto.models + +data class XrpTaggedAddress( + val address: String, + val destinationTag: Long?, +) \ No newline at end of file