Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-15 19:24:38 +03:00
parent b2129017a6
commit d8ce00bd6c
19 changed files with 278 additions and 121 deletions

View file

@ -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,
)
}
}

View file

@ -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<InputRowRecipientPreviewData> {
@ -161,6 +180,7 @@ private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider<In
),
InputRowRecipientPreviewData(
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
isLoading = true,
isError = true,
),
)

View file

@ -11,14 +11,27 @@ android {
}
dependencies {
/** Tangem libraries */
implementation(deps.tangem.blockchain) // android-library
/** Core */
implementation(projects.core.datasource)
implementation(projects.core.utils)
/** Domain */
implementation(projects.domain.wallets)
/** Domain models */
implementation(projects.domain.wallets.models)
implementation(projects.domain.tokens.models)
/** DI */
implementation(deps.hilt.android)
implementation(project(":domain:legacy"))
kapt(deps.hilt.kapt)
/** Local storages */
/** Other deps */
implementation(deps.androidx.datastore)
implementation(deps.arrow.core)
}

View file

@ -0,0 +1,31 @@
package com.tangem.data.wallets
import com.tangem.blockchain.blockchains.near.NearWalletManager
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
class DefaultWalletAddressServiceRepository(
private val walletManagersFacade: WalletManagersFacade,
) : WalletAddressServiceRepository {
override suspend fun validate(userWalletId: UserWalletId, network: Network, address: String): Boolean {
val blockchain = Blockchain.fromId(network.id.value)
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return false
return if (blockchain.isNear()) {
(walletManager as? NearWalletManager)?.validateAddress(address) ?: false
} else {
blockchain.validateAddress(address)
}
}
private fun Blockchain.isNear(): Boolean {
return this == Blockchain.Near || this == Blockchain.NearTestnet
}
}

View file

@ -1,7 +1,10 @@
package com.tangem.data.wallets.di
import com.tangem.data.wallets.DefaultWalletAddressServiceRepository
import com.tangem.data.wallets.DefaultWalletsRepository
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import dagger.Module
import dagger.Provides
@ -18,4 +21,12 @@ internal object WalletsDataModule {
fun providesWalletsRepository(appPreferencesStore: AppPreferencesStore): WalletsRepository {
return DefaultWalletsRepository(appPreferencesStore = appPreferencesStore)
}
@Provides
@Singleton
fun providesWalletAddressServiceRepository(
walletManagersFacade: WalletManagersFacade,
): WalletAddressServiceRepository {
return DefaultWalletAddressServiceRepository(walletManagersFacade)
}
}

View file

@ -12,6 +12,7 @@ dependencies {
// region Core modules
implementation(projects.core.res)
implementation(projects.core.utils)
// endregion
// region Domain modules

View file

@ -0,0 +1,12 @@
package com.tangem.domain.wallets.repository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
/**
* Wallet address service repository.
*/
interface WalletAddressServiceRepository {
suspend fun validate(userWalletId: UserWalletId, network: Network, address: String): Boolean
}

View file

@ -0,0 +1,27 @@
package com.tangem.domain.wallets.usecase
import arrow.core.Either
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
/**
* Use case for validating wallet address.
*/
class ValidateWalletAddressUseCase(
private val walletAddressServiceRepository: WalletAddressServiceRepository,
private val dispatchers: CoroutineDispatcherProvider,
) {
suspend operator fun invoke(
userWalletId: UserWalletId,
network: Network,
address: String,
): Either<Throwable, Boolean> = withContext(dispatchers.io) {
Either.catch {
walletAddressServiceRepository.validate(userWalletId, network, address)
}
}
}

View file

@ -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,
),
),
)
}

View file

@ -43,18 +43,19 @@ internal sealed class SendStates {
val tokenIconState: TokenIconState,
val isFiatValue: Boolean,
val segmentedButtonConfig: PersistentList<SendAmountSegmentedButtonsConfig>,
val amountTextField: MutableStateFlow<SendTextField.Amount>,
val amountTextField: SendTextField.Amount,
val isPrimaryButtonEnabled: Boolean,
) : SendStates()
/** Recipient state */
data class RecipientState(
override val type: SendUiStateType = SendUiStateType.Recipient,
val addressTextField: MutableStateFlow<SendTextField.RecipientAddress>,
val memoTextField: MutableStateFlow<SendTextField.RecipientMemo>?,
val addressTextField: SendTextField.RecipientAddress,
val memoTextField: SendTextField.RecipientMemo?,
val recipients: MutableStateFlow<PagingData<SendRecipientListContent>> = MutableStateFlow(PagingData.empty()),
val network: String,
val isPrimaryButtonEnabled: Boolean,
val isValidating: Boolean = false,
) : SendStates()
/** Fee and speed state */

View file

@ -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<AppCurrency>,
@ -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(

View file

@ -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,
),
),
)
}

View file

@ -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<Unit, MutableStateFlow<SendTextField.RecipientAddress>> {
) : Converter<Unit, SendTextField.RecipientAddress> {
override fun convert(value: Unit): MutableStateFlow<SendTextField.RecipientAddress> {
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),
)
}
}

View file

@ -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<CryptoCurrencyStatus>,
) : Converter<Int, MutableStateFlow<SendTextField.RecipientMemo>> {
) : Converter<Int, SendTextField.RecipientMemo> {
fun convertOrNull(): MutableStateFlow<SendTextField.RecipientMemo>? {
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<SendTextField.RecipientMemo> {
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),
)
}
}

View file

@ -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,

View file

@ -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<SendRecipientListContent>,
) {
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,

View file

@ -114,14 +114,14 @@ private fun FromWallet(walletName: String, walletBalance: String) {
@Composable
private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: State<Boolean>, 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<Bo
@Composable
private fun RecipientBlock(recipientState: SendStates.RecipientState, isSuccess: State<Boolean>, 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()),
)
}
}

View file

@ -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,

View file

@ -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 ^