Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-27 18:50:39 +03:00
parent bbcdc778fa
commit 8c6372e398
13 changed files with 136 additions and 82 deletions

View file

@ -1,7 +1,7 @@
package com.tangem.features.send.impl.presentation.state
import androidx.paging.PagingData
import com.tangem.blockchain.common.address.Address
import arrow.core.getOrElse
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.resourceReference
@ -10,33 +10,32 @@ 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.fee.*
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter
import com.tangem.features.send.impl.presentation.state.fee.calculateReceiveAmount
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter
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.utils.Provider
import com.tangem.utils.isNullOrZero
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import timber.log.Timber
import java.math.BigDecimal
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass")
internal class SendStateFactory(
private val clickIntents: SendClickIntents,
private val currentStateProvider: Provider<SendUiState>,
private val userWalletProvider: Provider<UserWallet>,
private val walletAddressesProvider: Provider<Set<Address>>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
coinCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
) {
@ -138,29 +137,32 @@ internal class SendStateFactory(
}
fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val state = currentStateProvider()
val recipientState = state.recipientState ?: return state
val isValidMemo = validateMemo(
memo = recipientState.addressTextField.value,
cryptoCurrency = cryptoCurrencyStatusProvider().currency,
)
val isAddressInWallet = isNotAddressInWallet(
address = value,
walletAddresses = walletAddressesProvider(),
)
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.copy(
recipientState = recipientState.copy(
isPrimaryButtonEnabled = isValidMemo && isValidAddress && isAddressInWallet,
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
isValidating = false,
addressTextField = recipientState.addressTextField.copy(
error = when {
!isValidAddress || !isAddressInWallet -> resourceReference(
!isValidAddress || isAddressInWallet -> resourceReference(
R.string.send_recipient_address_error,
)
else -> null
},
isError = value.isNotEmpty() && !isValidAddress || !isAddressInWallet,
isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet,
),
),
)
@ -185,23 +187,26 @@ internal class SendStateFactory(
}
fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val state = currentStateProvider()
val recipientState = state.recipientState ?: return state
val isValidMemo = validateMemo(
val isValidMemo = validateWalletMemoUseCase(
memo = value,
cryptoCurrency = cryptoCurrencyStatusProvider().currency,
)
val isAddressInWallet = isNotAddressInWallet(
walletAddresses = walletAddressesProvider(),
address = recipientState.addressTextField.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.copy(
recipientState = recipientState.copy(
isPrimaryButtonEnabled = isValidMemo && isValidAddress && isAddressInWallet,
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
isValidating = false,
memoTextField = recipientState.memoTextField?.copy(
isError = value.isNotEmpty() || isValidMemo && isValidAddress && isAddressInWallet,
isError = value.isNotEmpty() && !isValidMemo,
),
),
)

View file

@ -85,12 +85,10 @@ private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier
)
}
val isButtonEnabled = remember {
isButtonEnabled(
currentState = currentState,
uiState = uiState,
)
}
val isButtonEnabled = isButtonEnabled(
currentState = currentState,
uiState = uiState,
)
AnimatedContent(
targetState = buttonTextId,

View file

@ -1,18 +0,0 @@
package com.tangem.features.send.impl.presentation.viewmodel
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.address.Address
import com.tangem.domain.tokens.model.CryptoCurrency
internal fun verifyAddress(address: String, cryptoCurrency: CryptoCurrency?): Boolean {
if (address.isEmpty()) return true
val blockchain = cryptoCurrency?.let {
Blockchain.fromId(cryptoCurrency.id.rawNetworkId)
} ?: return false
return blockchain.validateAddress(address)
}
internal fun isNotAddressInWallet(walletAddresses: Set<Address>, address: String): Boolean {
return walletAddresses.all { it.value != address }
}

View file

@ -15,7 +15,6 @@ import com.tangem.blockchain.blockchains.xrp.XrpAddressService
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionExtras
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -38,6 +37,7 @@ 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.domain.wallets.usecase.ValidateWalletMemoUseCase
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
@ -78,6 +78,7 @@ internal class SendViewModel @Inject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val validateWalletAddressUseCase: ValidateWalletAddressUseCase,
private val walletManagersFacade: WalletManagersFacade,
validateWalletMemoUseCase: ValidateWalletMemoUseCase,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, SendClickIntents {
@ -97,10 +98,10 @@ internal class SendViewModel @Inject constructor(
clickIntents = this,
currentStateProvider = Provider { uiState },
userWalletProvider = Provider { userWallet },
walletAddressesProvider = Provider { walletAddresses },
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
validateWalletMemoUseCase = validateWalletMemoUseCase,
)
var uiState: SendUiState by mutableStateOf(stateFactory.getInitialState())
@ -109,16 +110,13 @@ internal class SendViewModel @Inject constructor(
private var userWallet: UserWallet by Delegates.notNull()
private var coinCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var walletAddresses = emptySet<Address>()
private var balanceJobHolder = JobHolder()
private var recipientsJobHolder = JobHolder()
private var walletAddressesJobHolder = JobHolder()
private var feeJobHolder = JobHolder()
private var addressValidationJobHolder = JobHolder()
override fun onCreate(owner: LifecycleOwner) {
getWalletAddresses()
subscribeOnCurrencyStatusUpdates(owner)
getFee()
}
@ -310,15 +308,6 @@ internal class SendViewModel @Inject constructor(
}.saveIn(feeJobHolder)
}
private fun getWalletAddresses() {
viewModelScope.launch(dispatchers.io) {
walletAddresses = walletManagersFacade.getAddresses(
userWalletId = userWalletId,
network = cryptoCurrency.network,
)
}.saveIn(walletAddressesJobHolder)
}
// region screen state navigation
override fun popBackStack() = stateRouter.popBackStack()
override fun onBackClick() = stateRouter.onBackClick()
@ -370,7 +359,7 @@ internal class SendViewModel @Inject constructor(
viewModelScope.launch(dispatchers.main) {
uiState = stateFactory.getOnRecipientAddressValidationStarted()
if (!checkIfXrpAddressValue(value)) {
val isValidAddress = validateAddress(value)
val isValidAddress = validateAddress(uiState.recipientState?.addressTextField?.value.orEmpty())
uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress)
}
}.saveIn(addressValidationJobHolder)
@ -508,7 +497,7 @@ internal class SendViewModel @Inject constructor(
)
}
}
// endregion
// endregion
private fun getMemoExtras(networkId: String, memo: String?): TransactionExtras? {
val blockchain = Blockchain.fromId(networkId)