Updated on 2026-08-14
This commit is contained in:
parent
946d60c1c6
commit
9c11182ca6
14 changed files with 127 additions and 40 deletions
|
|
@ -449,6 +449,7 @@
|
|||
<string name="scan_card_settings_button">Сканировать</string>
|
||||
<string name="scan_card_settings_message">Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку.</string>
|
||||
<string name="scan_card_settings_title">Приготовьте свою карту</string>
|
||||
<string name="send_additional_field_already_included">Уже содержится в введенном адресе</string>
|
||||
<string name="send_amount_label">Сумма</string>
|
||||
<string name="send_amount_substract">Вычесть из суммы отправки</string>
|
||||
<string name="send_amount_substract_footer">Сумма к получению %s</string>
|
||||
|
|
|
|||
|
|
@ -448,6 +448,7 @@
|
|||
<string name="scan_card_settings_button">Scan Card</string>
|
||||
<string name="scan_card_settings_message">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.</string>
|
||||
<string name="scan_card_settings_title">Get your card ready!</string>
|
||||
<string name="send_additional_field_already_included">Already included in the entered address</string>
|
||||
<string name="send_amount_label">Amount</string>
|
||||
<string name="send_amount_substract">Subtract from send amount</string>
|
||||
<string name="send_amount_substract_footer">The recipient will receive %s</string>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,4 +13,7 @@ dependencies {
|
|||
|
||||
/** SDK */
|
||||
implementation(deps.tangem.blockchain)
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.utils)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BlockchainXrpTaggedAddress, XrpTaggedAddress> {
|
||||
|
||||
override fun convert(value: BlockchainXrpTaggedAddress): XrpTaggedAddress {
|
||||
return XrpTaggedAddress(
|
||||
address = value.address,
|
||||
destinationTag = value.destinationTag,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
data class XrpTaggedAddress(
|
||||
val address: String,
|
||||
val destinationTag: Long?,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue