Updated on 2026-08-14
This commit is contained in:
parent
bbcdc778fa
commit
8c6372e398
13 changed files with 136 additions and 82 deletions
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue