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 7900acf6a3..3d038ef6a6 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 @@ -1,18 +1,11 @@ package com.tangem.features.send.impl.presentation.state -import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.event.consumedEvent -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 import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter import com.tangem.features.send.impl.presentation.state.common.SendSyncEditConverter import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter @@ -20,18 +13,14 @@ import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter -import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter -import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientWalletListConverter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList -import timber.log.Timber import java.math.BigDecimal -@Suppress("LongParameterList", "LargeClass") +@Suppress("LongParameterList") internal class SendStateFactory( private val clickIntents: SendClickIntents, private val stateRouterProvider: Provider, @@ -41,7 +30,6 @@ internal class SendStateFactory( private val cryptoCurrencyStatusProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, private val isTapHelpPreviewEnabledProvider: Provider, - private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, ) { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) @@ -79,14 +67,6 @@ internal class SendStateFactory( isTapHelpPreviewEnabledProvider = isTapHelpPreviewEnabledProvider, ) } - private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendRecipientWalletListConverter() - } - private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendRecipientHistoryListConverter( - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } private val sendSyncEditConverter by lazy(LazyThreadSafetyMode.NONE) { SendSyncEditConverter(currentStateProvider = currentStateProvider) } @@ -132,159 +112,6 @@ internal class SendStateFactory( } //endregion - //region recipient - fun onLoadedWalletsList(wallets: List): SendUiState { - val state = currentStateProvider() - return state.copy( - recipientState = state.recipientState?.copy( - wallets = recipientWalletListStateConverter.convert(wallets), - ), - ) - } - - fun onLoadedHistoryList(txHistory: List): SendUiState { - val state = currentStateProvider() - return state.copy( - recipientState = state.recipientState?.copy( - recent = recipientHistoryListStateConverter.convert(txHistory), - ), - ) - } - - fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false, isValuePasted: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - addressTextField = recipientState.addressTextField.copy(value = value, isValuePasted = isValuePasted), - memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress), - ), - ) - } - - fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - - val isValidMemo = validateWalletMemoUseCase( - memo = recipientState.memoTextField?.value.orEmpty(), - network = cryptoCurrencyStatus.currency.network, - ).getOrElse { - Timber.e("Failed to validateWalletMemoUseCase: $it") - false - } - val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses - ?.any { it.value == value } ?: true - - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, - isValidating = false, - addressTextField = recipientState.addressTextField.copy( - error = when { - !isValidAddress -> resourceReference(R.string.send_recipient_address_error) - isAddressInWallet -> resourceReference(R.string.send_error_address_same_as_wallet) - else -> null - }, - isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet, - ), - ), - ) - } - - fun getOnRecipientAddressValidationStarted(): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy(isValidating = true), - ) - } - - fun getOnRecipientMemoValueChange(value: String, isValuePasted: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - memoTextField = recipientState.memoTextField?.copy( - value = value, - isValuePasted = isValuePasted, - ), - ), - ) - } - - fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - - val isValidMemo = validateWalletMemoUseCase( - memo = value, - network = cryptoCurrencyStatus.currency.network, - ).getOrElse { - Timber.e("Failed to validateWalletMemoUseCase: $it") - false - } - val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses - ?.any { it.value == value } ?: true - - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, - isValidating = false, - memoTextField = recipientState.memoTextField?.copy( - isError = value.isNotEmpty() && !isValidMemo, - isEnabled = true, - ), - ), - ) - } - - fun getOnXAddressMemoState(): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - memoTextField = recipientState.memoTextField?.copy( - value = "", - isEnabled = false, - ), - ), - ) - } - - fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - val isNotValid = isAddressInWallet || !isValidAddress - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - recent = recipientState.recent.map { recent -> - recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY)) - }.toPersistentList(), - wallets = recipientState.wallets.map { wallet -> - wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY)) - }.toPersistentList(), - ), - ) - } - //endregion - //region send fun getIsAmountSubtractedState(isAmountSubtractAvailable: Boolean): SendUiState { val state = currentStateProvider() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt new file mode 100644 index 0000000000..5fcb985783 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt @@ -0,0 +1,182 @@ +package com.tangem.features.send.impl.presentation.state.recipient + +import arrow.core.getOrElse +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase +import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.domain.AvailableWallet +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.utils.Provider +import kotlinx.collections.immutable.toPersistentList +import timber.log.Timber + +internal class RecipientSendFactory( + private val stateRouterProvider: Provider, + private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, + private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, +) { + private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientWalletListConverter() + } + private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientHistoryListConverter( + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + + fun onLoadedWalletsList(wallets: List): SendUiState { + val state = currentStateProvider() + return state.copy( + recipientState = state.recipientState?.copy( + wallets = recipientWalletListStateConverter.convert(wallets), + ), + ) + } + + fun onLoadedHistoryList(txHistory: List): SendUiState { + val state = currentStateProvider() + return state.copy( + recipientState = state.recipientState?.copy( + recent = recipientHistoryListStateConverter.convert(txHistory), + ), + ) + } + + fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false, isValuePasted: Boolean): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + addressTextField = recipientState.addressTextField.copy(value = value, isValuePasted = isValuePasted), + memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress), + ), + ) + } + + fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + + val isValidMemo = validateWalletMemoUseCase( + memo = recipientState.memoTextField?.value.orEmpty(), + network = cryptoCurrencyStatus.currency.network, + ).getOrElse { + Timber.e("Failed to validateWalletMemoUseCase: $it") + false + } + val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses + ?.any { it.value == value } ?: true + + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, + isValidating = false, + addressTextField = recipientState.addressTextField.copy( + error = when { + !isValidAddress -> resourceReference(R.string.send_recipient_address_error) + isAddressInWallet -> resourceReference(R.string.send_error_address_same_as_wallet) + else -> null + }, + isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet, + ), + ), + ) + } + + fun getOnRecipientAddressValidationStarted(): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy(isValidating = true), + ) + } + + fun getOnRecipientMemoValueChange(value: String, isValuePasted: Boolean): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + memoTextField = recipientState.memoTextField?.copy( + value = value, + isValuePasted = isValuePasted, + ), + ), + ) + } + + fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + + val isValidMemo = validateWalletMemoUseCase( + memo = value, + network = cryptoCurrencyStatus.currency.network, + ).getOrElse { + Timber.e("Failed to validateWalletMemoUseCase: $it") + false + } + val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses + ?.any { it.value == value } ?: true + + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, + isValidating = false, + memoTextField = recipientState.memoTextField?.copy( + isError = value.isNotEmpty() && !isValidMemo, + isEnabled = true, + ), + ), + ) + } + + fun getOnXAddressMemoState(): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + memoTextField = recipientState.memoTextField?.copy( + value = "", + isEnabled = false, + ), + ), + ) + } + + fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + val isNotValid = isAddressInWallet || !isValidAddress + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + recent = recipientState.recent.map { recent -> + recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY)) + }.toPersistentList(), + wallets = recipientState.wallets.map { wallet -> + wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY)) + }.toPersistentList(), + ), + ) + } +} \ 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 40737d0156..5b9d5f336f 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 @@ -50,9 +50,13 @@ import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory import com.tangem.features.send.impl.presentation.state.fee.* +import com.tangem.features.send.impl.presentation.state.recipient.RecipientSendFactory import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider -import com.tangem.utils.coroutines.* +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.DelayedWork +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* @@ -128,10 +132,16 @@ internal class SendViewModel @Inject constructor( appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, - validateWalletMemoUseCase = validateWalletMemoUseCase, isTapHelpPreviewEnabledProvider = Provider { isTapHelpPreviewEnabled }, ) + private val recipientStateFactory = RecipientSendFactory( + stateRouterProvider = Provider { stateRouter }, + currentStateProvider = Provider { uiState }, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + validateWalletMemoUseCase = validateWalletMemoUseCase, + ) + private val amountStateFactory = AmountStateFactory( stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState }, @@ -407,9 +417,9 @@ internal class SendViewModel @Inject constructor( .orEmpty() }.onSuccess { result -> userWallets = result - uiState = stateFactory.onLoadedWalletsList(wallets = userWallets) + uiState = recipientStateFactory.onLoadedWalletsList(wallets = userWallets) }.onFailure { - uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) + uiState = recipientStateFactory.onLoadedWalletsList(wallets = emptyList()) } } } @@ -449,7 +459,7 @@ internal class SendViewModel @Inject constructor( userWalletId = userWalletId, currency = cryptoCurrency, ).getOrElse { emptyList() } - uiState = stateFactory.onLoadedHistoryList(txHistory = txHistoryList) + uiState = recipientStateFactory.onLoadedHistoryList(txHistory = txHistoryList) } private fun onStateActive() { @@ -609,10 +619,10 @@ internal class SendViewModel @Inject constructor( override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) { viewModelScope.launch { if (!checkIfXrpAddressValue(value)) { - uiState = stateFactory.onRecipientAddressValueChange(value, isValuePasted = type != null) - uiState = stateFactory.getOnRecipientAddressValidationStarted() + uiState = recipientStateFactory.onRecipientAddressValueChange(value, isValuePasted = type != null) + uiState = recipientStateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(value) - uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress) + uiState = recipientStateFactory.getOnRecipientAddressValidState(value, isValidAddress) type?.let { analyticsEventHandler.send(SendAnalyticEvents.AddressEntered(it, isValidAddress)) } autoNextFromRecipient(type, isValidAddress) } @@ -622,10 +632,11 @@ internal class SendViewModel @Inject constructor( override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) { viewModelScope.launch { if (!checkIfXrpAddressValue(value)) { - uiState = stateFactory.getOnRecipientMemoValueChange(value, isValuePasted) - uiState = stateFactory.getOnRecipientAddressValidationStarted() - val isValidAddress = validateAddress(uiState.recipientState?.addressTextField?.value.orEmpty()) - uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress) + uiState = recipientStateFactory.getOnRecipientMemoValueChange(value, isValuePasted) + uiState = recipientStateFactory.getOnRecipientAddressValidationStarted() + val recipientState = uiState.getRecipientState(stateRouter.isEditState) + val isValidAddress = validateAddress(recipientState?.addressTextField?.value.orEmpty()) + uiState = recipientStateFactory.getOnRecipientMemoValidState(value, isValidAddress) } }.saveIn(memoValidationJobHolder) } @@ -644,16 +655,17 @@ internal class SendViewModel @Inject constructor( private suspend fun checkIfXrpAddressValue(value: String): Boolean { return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress -> - uiState = stateFactory.onRecipientAddressValueChange(value, isXAddress = true, isValuePasted = true) - uiState = stateFactory.getOnXAddressMemoState() + uiState = + recipientStateFactory.onRecipientAddressValueChange(value, isXAddress = true, isValuePasted = true) + uiState = recipientStateFactory.getOnXAddressMemoState() val isValidAddress = validateAddress(decodedAddress.address) - uiState = stateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress) + uiState = recipientStateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress) true } ?: false } private fun onEnteredValidAddress(isValidAddress: Boolean, isAddressInWallet: Boolean) { - uiState = stateFactory.getHiddenRecentListState( + uiState = recipientStateFactory.getHiddenRecentListState( isAddressInWallet = isAddressInWallet, isValidAddress = isValidAddress, )