From d8ce00bd6cb3aadc969548f23913bab3545c9e18 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Dec 2023 19:24:38 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/WalletsDomainModule.kt | 14 ++++ .../components/inputrow/InputRowRecipient.kt | 36 +++++++-- data/wallets/build.gradle.kts | 15 +++- .../DefaultWalletAddressServiceRepository.kt | 31 ++++++++ .../data/wallets/di/WalletsDataModule.kt | 11 +++ domain/wallets/build.gradle.kts | 1 + .../WalletAddressServiceRepository.kt | 12 +++ .../usecase/ValidateWalletAddressUseCase.kt | 27 +++++++ .../presentation/state/SendStateFactory.kt | 79 +++++++++++-------- .../impl/presentation/state/SendUiState.kt | 7 +- .../state/amount/SendAmountStateConverter.kt | 3 +- .../fields/SendAmountFieldChangeConverter.kt | 25 +++--- .../SendRecipientAddressFieldConverter.kt | 26 +++--- .../SendRecipientMemoFieldConverter.kt | 29 +++---- .../ui/amount/AmountFieldContainer.kt | 4 +- .../ui/recipient/SendRecipientContent.kt | 14 ++-- .../impl/presentation/ui/send/SendContent.kt | 16 ++-- .../presentation/viewmodel/SendViewModel.kt | 47 ++++++++--- gradle/dependencies.toml | 2 +- 19 files changed, 278 insertions(+), 121 deletions(-) create mode 100644 data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletAddressServiceRepository.kt create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletAddressServiceRepository.kt create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ValidateWalletAddressUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 236f6e9b8f..9efdcc16d8 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -3,8 +3,10 @@ package com.tangem.tap.di.domain import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.repository.WalletAddressServiceRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.* +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -91,4 +93,16 @@ internal object WalletsDomainModule { fun providesShouldSaveUserWalletsUseCase(walletsRepository: WalletsRepository): ShouldSaveUserWalletsUseCase { return ShouldSaveUserWalletsUseCase(walletsRepository = walletsRepository) } + + @Provides + @ViewModelScoped + fun providesValidateWalletAddressUseCase( + walletAddressServiceRepository: WalletAddressServiceRepository, + dispatchers: CoroutineDispatcherProvider, + ): ValidateWalletAddressUseCase { + return ValidateWalletAddressUseCase( + walletAddressServiceRepository = walletAddressServiceRepository, + dispatchers = dispatchers, + ) + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt index 312b0c0db1..e58a5bd0a8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt @@ -1,8 +1,10 @@ package com.tangem.core.ui.components.inputrow +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment.Companion.CenterVertically @@ -48,6 +50,7 @@ fun InputRowRecipient( error: TextReference? = null, isError: Boolean = false, showDivider: Boolean = false, + isLoading: Boolean = false, ) { val (titleText, color) = if (isError && error != null) { error to TangemTheme.colors.text.warning @@ -63,23 +66,36 @@ fun InputRowRecipient( .fillMaxWidth() .padding(TangemTheme.dimens.spacing12), ) { - Text( - text = titleText.resolveReference(), - style = TangemTheme.typography.caption2, - color = color, - ) + AnimatedContent(targetState = titleText, label = "Title Change") { + Text( + text = it.resolveReference(), + style = TangemTheme.typography.caption2, + color = color, + ) + } Row( modifier = Modifier .padding(top = TangemTheme.dimens.spacing8), ) { - IdentIcon( - address = value, + AnimatedContent( + targetState = isLoading, + label = "Indicator Show Change", modifier = Modifier .align(CenterVertically) .clip(RoundedCornerShape(TangemTheme.dimens.radius18)) .size(TangemTheme.dimens.size36) .background(TangemTheme.colors.background.tertiary), - ) + ) { showIndicator -> + if (showIndicator) { + CircularProgressIndicator( + color = TangemTheme.colors.icon.informative, + modifier = Modifier + .padding(TangemTheme.dimens.spacing8), + ) + } else { + IdentIcon(address = value) + } + } SimpleTextField( value = value, placeholder = placeholder, @@ -115,6 +131,7 @@ private fun InputRowRecipientPreview_Light( placeholder = TextReference.Res(R.string.send_optional_field), error = TextReference.Str("Error"), isError = value.isError, + isLoading = value.isLoading, showDivider = true, onValueChange = {}, onPasteClick = {}, @@ -134,6 +151,7 @@ private fun InputRowRecipientPreview_Dark( title = TextReference.Res(R.string.send_recipient), placeholder = TextReference.Res(R.string.send_optional_field), error = TextReference.Str("Error"), + isLoading = value.isLoading, isError = value.isError, showDivider = true, onValueChange = {}, @@ -146,6 +164,7 @@ private fun InputRowRecipientPreview_Dark( private data class InputRowRecipientPreviewData( val value: String, val isError: Boolean, + val isLoading: Boolean = false, ) private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider { @@ -161,6 +180,7 @@ private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider = withContext(dispatchers.io) { + Either.catch { + walletAddressServiceRepository.validate(userWalletId, network, address) + } + } +} \ No newline at end of file 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 9daf7513a6..0cdd57f8a2 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 @@ -4,7 +4,7 @@ import androidx.paging.PagingData import com.tangem.blockchain.common.address.Address import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter -import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.txhistory.models.TxHistoryItem @@ -23,7 +23,6 @@ import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientS import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.features.send.impl.presentation.viewmodel.isNotAddressInWallet import com.tangem.features.send.impl.presentation.viewmodel.validateMemo -import com.tangem.features.send.impl.presentation.viewmodel.verifyAddress import com.tangem.utils.Provider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -118,41 +117,64 @@ internal class SendStateFactory( ) } - fun getOnRecipientAddressValueChangeState(value: String): SendUiState { + fun onRecipientAddressValueChange(value: String): SendUiState { + val state = currentStateProvider() + val recipientState = state.recipientState ?: return state + return state.copy( + recipientState = recipientState.copy( + addressTextField = recipientState.addressTextField.copy(value = value), + ), + ) + } + + fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState { val state = currentStateProvider() val recipientState = state.recipientState ?: return state val isValidMemo = validateMemo( - memo = recipientState.addressTextField.value.value, + memo = recipientState.addressTextField.value, cryptoCurrency = cryptoCurrencyStatusProvider().currency, ) val isAddressInWallet = isNotAddressInWallet( address = value, walletAddresses = walletAddressesProvider(), ) - val isValidAddress = verifyAddress( - address = value, - cryptoCurrency = cryptoCurrencyStatusProvider().currency, - ) - - recipientState.addressTextField.update { - it.copy( - value = value, - error = when { - !isValidAddress || !isAddressInWallet -> TextReference.Res(R.string.send_recipient_address_error) - else -> null - }, - isError = !isValidAddress || !isAddressInWallet, - ) - } return state.copy( recipientState = recipientState.copy( isPrimaryButtonEnabled = isValidMemo && isValidAddress && isAddressInWallet, + isValidating = false, + addressTextField = recipientState.addressTextField.copy( + error = when { + !isValidAddress || !isAddressInWallet -> resourceReference( + R.string.send_recipient_address_error, + ) + else -> null + }, + isError = value.isNotEmpty() && !isValidAddress || !isAddressInWallet, + ), ), ) } - fun getOnRecipientMemoValueChangeState(value: String): SendUiState { + fun getOnRecipientAddressValidationStarted(): SendUiState { + val state = currentStateProvider() + val recipientState = state.recipientState ?: return state + return state.copy( + recipientState = recipientState.copy(isValidating = true), + ) + } + + fun getOnRecipientMemoValueChange(value: String): SendUiState { + val state = currentStateProvider() + val recipientState = state.recipientState ?: return state + return state.copy( + recipientState = recipientState.copy( + memoTextField = recipientState.memoTextField?.copy(value = value), + ), + ) + } + + fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState { val state = currentStateProvider() val recipientState = state.recipientState ?: return state @@ -162,22 +184,15 @@ internal class SendStateFactory( ) val isAddressInWallet = isNotAddressInWallet( walletAddresses = walletAddressesProvider(), - address = recipientState.addressTextField.value.value, + address = recipientState.addressTextField.value, ) - val isValidAddress = verifyAddress( - address = recipientState.addressTextField.value.value, - cryptoCurrency = cryptoCurrencyStatusProvider().currency, - ) - - recipientState.memoTextField?.update { - it.copy( - value = value, - isError = !isValidMemo, - ) - } return state.copy( recipientState = recipientState.copy( isPrimaryButtonEnabled = isValidMemo && isValidAddress && isAddressInWallet, + isValidating = false, + memoTextField = recipientState.memoTextField?.copy( + isError = value.isNotEmpty() || isValidMemo && isValidAddress && isAddressInWallet, + ), ), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index 1d7474d6b4..b7eb3da888 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -43,18 +43,19 @@ internal sealed class SendStates { val tokenIconState: TokenIconState, val isFiatValue: Boolean, val segmentedButtonConfig: PersistentList, - val amountTextField: MutableStateFlow, + val amountTextField: SendTextField.Amount, val isPrimaryButtonEnabled: Boolean, ) : SendStates() /** Recipient state */ data class RecipientState( override val type: SendUiStateType = SendUiStateType.Recipient, - val addressTextField: MutableStateFlow, - val memoTextField: MutableStateFlow?, + val addressTextField: SendTextField.RecipientAddress, + val memoTextField: SendTextField.RecipientMemo?, val recipients: MutableStateFlow> = MutableStateFlow(PagingData.empty()), val network: String, val isPrimaryButtonEnabled: Boolean, + val isValidating: Boolean = false, ) : SendStates() /** Fee and speed state */ diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index aca08b4b07..c80815c345 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -12,7 +12,6 @@ import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldCo import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.flow.MutableStateFlow internal class SendAmountStateConverter( private val appCurrencyProvider: Provider, @@ -35,7 +34,7 @@ internal class SendAmountStateConverter( walletName = userWallet.name, walletBalance = "$crypto ($fiat)", tokenIconState = iconStateConverter.convert(status), - amountTextField = MutableStateFlow(sendAmountFieldConverter.convert(Unit)), + amountTextField = sendAmountFieldConverter.convert(Unit), isFiatValue = false, isPrimaryButtonEnabled = false, segmentedButtonConfig = persistentListOf( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index a174b181ef..279e2355b9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -5,7 +5,6 @@ import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.utils.Provider import com.tangem.utils.converter.Converter -import kotlinx.coroutines.flow.update import java.text.DecimalFormatSymbols import java.text.NumberFormat @@ -42,31 +41,27 @@ internal class SendAmountFieldChangeConverter( } val isExceedBalance = value.checkExceedBalance(amountState.cryptoCurrencyStatus, amountState) - amountState.amountTextField.update { - it.copy( - value = cryptoValue, - fiatValue = fiatValue, - isError = isExceedBalance, - ) - } return state.copy( amountState = amountState.copy( isPrimaryButtonEnabled = !isExceedBalance, + amountTextField = amountState.amountTextField.copy( + value = cryptoValue, + fiatValue = fiatValue, + isError = isExceedBalance, + ), ), ) } private fun SendUiState.emptyState(): SendUiState { - amountState?.amountTextField?.update { - it.copy( - value = if (!amountState.isFiatValue) "" else DEFAULT_VALUE, - fiatValue = if (amountState.isFiatValue) "" else DEFAULT_VALUE, - isError = false, - ) - } return copy( amountState = amountState?.copy( isPrimaryButtonEnabled = false, + amountTextField = amountState.amountTextField.copy( + value = if (!amountState.isFiatValue) "" else DEFAULT_VALUE, + fiatValue = if (amountState.isFiatValue) "" else DEFAULT_VALUE, + isError = false, + ), ), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt index 75cd69aa6a..bc5cf7a463 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt @@ -3,29 +3,27 @@ package com.tangem.features.send.impl.presentation.state.recipient import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.converter.Converter -import kotlinx.coroutines.flow.MutableStateFlow internal class SendRecipientAddressFieldConverter( private val clickIntents: SendClickIntents, -) : Converter> { +) : Converter { - override fun convert(value: Unit): MutableStateFlow { - return MutableStateFlow( - SendTextField.RecipientAddress( - value = "", - onValueChange = clickIntents::onRecipientAddressValueChange, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Text, - ), - placeholder = TextReference.Res(R.string.send_enter_address_field), - label = TextReference.Res(R.string.send_recipient), + override fun convert(value: Unit): SendTextField.RecipientAddress { + return SendTextField.RecipientAddress( + value = "", + onValueChange = clickIntents::onRecipientAddressValueChange, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Next, + keyboardType = KeyboardType.Text, ), + error = resourceReference(R.string.send_recipient_address_error), + placeholder = resourceReference(R.string.send_enter_address_field), + label = resourceReference(R.string.send_recipient), ) } } \ No newline at end of file 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 f069ca1318..45e2f47771 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 @@ -4,21 +4,20 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.blockchain.common.Blockchain -import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter -import kotlinx.coroutines.flow.MutableStateFlow internal class SendRecipientMemoFieldConverter( private val clickIntents: SendClickIntents, private val cryptoCurrencyStatus: Provider, -) : Converter> { +) : Converter { - fun convertOrNull(): MutableStateFlow? { + fun convertOrNull(): SendTextField.RecipientMemo? { val cryptoCurrency = cryptoCurrencyStatus().currency return when (cryptoCurrency.network.id.value) { @@ -34,19 +33,17 @@ internal class SendRecipientMemoFieldConverter( } } - override fun convert(value: Int): MutableStateFlow { - return MutableStateFlow( - SendTextField.RecipientMemo( - value = "", - onValueChange = clickIntents::onRecipientMemoValueChange, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - keyboardType = KeyboardType.Text, - ), - placeholder = TextReference.Res(R.string.send_optional_field), - label = TextReference.Res(value), - error = TextReference.Res(R.string.send_memo_destination_tag_error), + override fun convert(value: Int): SendTextField.RecipientMemo { + return SendTextField.RecipientMemo( + value = "", + onValueChange = clickIntents::onRecipientMemoValueChange, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + keyboardType = KeyboardType.Text, ), + placeholder = resourceReference(R.string.send_optional_field), + label = resourceReference(value), + error = resourceReference(R.string.send_memo_destination_tag_error), ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt index 363392865b..4f95280719 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt @@ -11,14 +11,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.ui.components.currency.tokenicon.TokenIcon import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendStates @Composable internal fun AmountFieldContainer(amountState: SendStates.AmountState, modifier: Modifier = Modifier) { - val amountTextField = amountState.amountTextField.collectAsStateWithLifecycle() Column( modifier = modifier .fillMaxWidth() @@ -54,7 +52,7 @@ internal fun AmountFieldContainer(amountState: SendStates.AmountState, modifier: .align(Alignment.CenterHorizontally), ) AmountField( - sendField = amountTextField.value, + sendField = amountState.amountTextField, isFiat = amountState.isFiatValue, cryptoSymbol = amountState.cryptoCurrencyStatus.currency.symbol, fiatSymbol = amountState.appCurrency.symbol, 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 5e4316a1e4..57187dffc1 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 @@ -11,7 +11,9 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource @@ -38,8 +40,9 @@ internal fun SendRecipientContent( recipientList: LazyPagingItems, ) { if (uiState == null) return - val address = uiState.addressTextField.collectAsState().value - val memo = uiState.memoTextField?.collectAsState()?.value + val address = uiState.addressTextField + val isValidating by remember(uiState.isValidating) { derivedStateOf { uiState.isValidating } } + val isError by remember(address.isError) { derivedStateOf { address.isError } } LazyColumn( modifier = Modifier .fillMaxSize() @@ -57,7 +60,8 @@ internal fun SendRecipientContent( onValueChange = address.onValueChange, onPasteClick = clickIntents::onRecipientAddressValueChange, singleLine = true, - isError = address.isError, + isError = isError, + isLoading = isValidating, error = address.error, modifier = Modifier .padding(top = TangemTheme.dimens.spacing4) @@ -68,7 +72,7 @@ internal fun SendRecipientContent( ) } } - memo?.let { memoField -> + uiState.memoTextField?.let { memoField -> item(key = MEMO_FIELD_KEY) { TextFieldWithPaste( value = memoField.value, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index 037913ef69..9726adff7c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -114,14 +114,14 @@ private fun FromWallet(walletName: String, walletBalance: String) { @Composable private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: State, onClick: () -> Unit) { - val amount = amountState.amountTextField.collectAsStateWithLifecycle() + val amount = amountState.amountTextField val cryptoAmount = formatCryptoAmount( cryptoCurrency = amountState.cryptoCurrencyStatus.currency, - cryptoAmount = amount.value.value.toBigDecimalOrDefault(), + cryptoAmount = amount.value.toBigDecimalOrDefault(), ) val fiatAmount = BigDecimalFormatter.formatFiatAmount( - fiatAmount = amount.value.fiatValue.toBigDecimalOrDefault(), + fiatAmount = amount.fiatValue.toBigDecimalOrDefault(), fiatCurrencyCode = amountState.appCurrency.code, fiatCurrencySymbol = amountState.appCurrency.symbol, ) @@ -140,8 +140,8 @@ private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: State, onClick: () -> Unit) { - val address = recipientState.addressTextField.collectAsStateWithLifecycle() - val memo = recipientState.memoTextField?.collectAsStateWithLifecycle() + val address = recipientState.addressTextField + val memo = recipientState.memoTextField Column( modifier = Modifier @@ -149,16 +149,16 @@ private fun RecipientBlock(recipientState: SendStates.RecipientState, isSuccess: .background(TangemTheme.colors.background.action) .clickable(enabled = !isSuccess.value) { onClick() }, ) { - val showMemo = memo != null && memo.value.value.isNotBlank() + val showMemo = memo != null && memo.value.isNotBlank() InputRowRecipientDefault( title = TextReference.Res(R.string.send_recipient), - value = address.value.value, + value = address.value, showDivider = showMemo, ) if (showMemo) { InputRowDefault( title = TextReference.Res(R.string.send_extras_hint_memo), - text = TextReference.Str(memo?.value?.value.orEmpty()), + text = TextReference.Str(memo?.value.orEmpty()), ) } } 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 a14e2999c8..59a21072b0 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 @@ -37,6 +37,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.domain.wallets.usecase.ValidateWalletAddressUseCase import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.send.impl.navigation.InnerSendRouter import com.tangem.features.send.impl.presentation.domain.AvailableWallet @@ -74,6 +75,7 @@ internal class SendViewModel @Inject constructor( private val getFeeUseCase: GetFeeUseCase, private val sendTransactionUseCase: SendTransactionUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, + private val validateWalletAddressUseCase: ValidateWalletAddressUseCase, private val walletManagersFacade: WalletManagersFacade, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver, SendClickIntents { @@ -110,6 +112,7 @@ internal class SendViewModel @Inject constructor( private var recipientsJobHolder = JobHolder() private var walletAddressesJobHolder = JobHolder() private var feeJobHolder = JobHolder() + private var addressValidationJobHolder = JobHolder() override fun onCreate(owner: LifecycleOwner) { getWalletAddresses() @@ -255,8 +258,8 @@ internal class SendViewModel @Inject constructor( stateFactory.onFeeOnLoadingState() getFeeUseCase.invoke( - amount = amountState.amountTextField.value.value.toBigDecimal(), - destination = recipientState.addressTextField.value.value, + amount = amountState.amountTextField.value.toBigDecimal(), + destination = recipientState.addressTextField.value, userWalletId = userWalletId, cryptoCurrency = cryptoCurrency, ) @@ -319,15 +322,33 @@ internal class SendViewModel @Inject constructor( // region recipient state clicks override fun onRecipientAddressValueChange(value: String) { - if (!checkIfXrpAddressValue(value)) { - uiState = stateFactory.getOnRecipientAddressValueChangeState(value) - } + uiState = stateFactory.onRecipientAddressValueChange(value) + viewModelScope.launch(dispatchers.main) { + uiState = stateFactory.getOnRecipientAddressValidationStarted() + if (!checkIfXrpAddressValue(value)) { + val isValidAddress = validateAddress(value) + uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress) + } + }.saveIn(addressValidationJobHolder) } override fun onRecipientMemoValueChange(value: String) { - if (!checkIfXrpAddressValue(value)) { - uiState = stateFactory.getOnRecipientMemoValueChangeState(value) - } + uiState = stateFactory.getOnRecipientMemoValueChange(value) + viewModelScope.launch(dispatchers.main) { + uiState = stateFactory.getOnRecipientAddressValidationStarted() + if (!checkIfXrpAddressValue(value)) { + val isValidAddress = validateAddress(value) + uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress) + } + }.saveIn(addressValidationJobHolder) + } + + private suspend fun validateAddress(value: String): Boolean { + return validateWalletAddressUseCase( + userWalletId = userWalletId, + network = cryptoCurrency.network, + address = value, + ).getOrElse { false } } private fun checkIfXrpAddressValue(value: String): Boolean { @@ -395,7 +416,7 @@ internal class SendViewModel @Inject constructor( is TransactionFee.Single -> selectedFee.normal.amount.value } ?: BigDecimal.ZERO - return BigDecimal(amount.value).minus(fee) + return BigDecimal(amount).minus(fee) } //endregion @@ -418,7 +439,7 @@ internal class SendViewModel @Inject constructor( val memo = uiState.recipientState?.memoTextField?.value val fee = getFee(feeState) ?: return - val amountToSend = amount.value.toBigDecimal().convertToAmount(cryptoCurrency) + val amountToSend = amount.toBigDecimal().convertToAmount(cryptoCurrency) // todo add notifications [[REDACTED_JIRA]] // val transactionErrors = walletManagersFacade.validateTransaction( @@ -431,11 +452,11 @@ internal class SendViewModel @Inject constructor( val txData = walletManagersFacade.createTransaction( amount = amountToSend, fee = fee, - memo = memo?.value, - destination = recipient.value, + memo = memo, + destination = recipient, userWalletId = userWalletId, network = cryptoCurrency.network, - )?.copy(extras = getMemoExtras(cryptoCurrency.network.id.value, memo?.value)) ?: return + )?.copy(extras = getMemoExtras(cryptoCurrency.network.id.value, memo)) ?: return sendTransactionUseCase( txData = txData, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 2cdeed84a6..9aee74781c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -86,7 +86,7 @@ spr-client = "3.6.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-411" +tangemBlockchainSdk = "develop-418" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-312" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^