Updated on 2026-08-14
This commit is contained in:
parent
dab5fb51b6
commit
967d5520a1
37 changed files with 452 additions and 408 deletions
|
|
@ -9,7 +9,6 @@ import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
|
|||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.domain.staking.model.PendingTransaction
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
|
|
@ -120,7 +119,6 @@ internal sealed class StakingStates {
|
|||
val transactionDoneState: TransactionDoneState,
|
||||
val isApprovalNeeded: Boolean,
|
||||
val reduceAmountBy: BigDecimal?,
|
||||
val possiblePendingTransaction: PendingTransaction?,
|
||||
) : ConfirmationState()
|
||||
|
||||
data class Empty(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.helpers
|
||||
|
||||
import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase
|
||||
import com.tangem.domain.staking.FetchActionsUseCase
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
|
||||
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -20,16 +23,21 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
|||
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
|
||||
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val fetchActionsUseCase: FetchActionsUseCase,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
@Assisted private val userWallet: UserWallet,
|
||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
@Assisted private val yield: Yield,
|
||||
) {
|
||||
fun scheduleUpdates() {
|
||||
coroutineScope.launch {
|
||||
listOf(
|
||||
// we should update network to find pending tx after 1 sec
|
||||
async {
|
||||
fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrencyStatus.currency.network))
|
||||
fetchPendingTransactionsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
networks = setOf(cryptoCurrencyStatus.currency.network),
|
||||
)
|
||||
},
|
||||
// we should update tx history and network for new balances
|
||||
async {
|
||||
|
|
@ -41,6 +49,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
|||
async {
|
||||
updateNetworkStatuses()
|
||||
},
|
||||
async {
|
||||
updateProcessingActions()
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +65,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
|||
async {
|
||||
updateNetworkStatuses(delay = 0)
|
||||
},
|
||||
async {
|
||||
updateProcessingActions()
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
|
@ -91,9 +105,22 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun updateProcessingActions() {
|
||||
fetchActionsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
networkType = yield.token.network,
|
||||
stakingActionStatus = StakingActionStatus.PROCESSING,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet): StakingBalanceUpdater
|
||||
fun create(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
userWallet: UserWallet,
|
||||
yield: Yield,
|
||||
): StakingBalanceUpdater
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.blockchain.common.transaction.Fee
|
|||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.model.PendingTransaction
|
||||
import com.tangem.domain.staking.model.SubmitHashData
|
||||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
|
|
@ -36,7 +35,6 @@ import kotlinx.coroutines.awaitAll
|
|||
import kotlinx.coroutines.coroutineScope
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class StakingTransactionSender @AssistedInject constructor(
|
||||
|
|
@ -48,7 +46,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val submitHashUseCase: SubmitHashUseCase,
|
||||
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
|
||||
private val savePendingTransactionUseCase: SavePendingTransactionUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
@Assisted private val userWallet: UserWallet,
|
||||
|
|
@ -57,7 +54,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
) {
|
||||
|
||||
private val balanceUpdater: StakingBalanceUpdater
|
||||
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet)
|
||||
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, yield)
|
||||
|
||||
suspend fun constructAndSendTransactions(
|
||||
onConstructSuccess: (List<StakingTransaction>) -> Unit,
|
||||
|
|
@ -95,16 +92,11 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
|
||||
sendStakingTransaction(
|
||||
fullTransactionsData = fullTransactionsData,
|
||||
possiblePendingTransaction = composePendingTransaction(confirmationState),
|
||||
onSendSuccess = onSendSuccess,
|
||||
onSendError = onSendError,
|
||||
)
|
||||
}
|
||||
|
||||
private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
|
||||
return confirmationState.possiblePendingTransaction ?: composeStakeTransaction()
|
||||
}
|
||||
|
||||
private suspend fun getStakingTransactions(
|
||||
state: StakingUiState,
|
||||
confirmationState: StakingStates.ConfirmationState.Data,
|
||||
|
|
@ -214,7 +206,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
|
||||
private suspend fun sendStakingTransaction(
|
||||
fullTransactionsData: List<FullTransactionData>,
|
||||
possiblePendingTransaction: PendingTransaction,
|
||||
onSendSuccess: (txUrl: String) -> Unit,
|
||||
onSendError: (SendTransactionError?) -> Unit,
|
||||
) {
|
||||
|
|
@ -230,7 +221,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
submitHash(
|
||||
transactions = fullTransactionsData.map { it.stakeKitTransaction },
|
||||
transactionHashes = transactionHashes,
|
||||
pendingTransaction = possiblePendingTransaction,
|
||||
)
|
||||
val txUrl = getExplorerTransactionUrlUseCase(
|
||||
txHash = transactionHashes.last(),
|
||||
|
|
@ -243,11 +233,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun submitHash(
|
||||
transactions: List<StakingTransaction>,
|
||||
transactionHashes: List<String>,
|
||||
pendingTransaction: PendingTransaction,
|
||||
) {
|
||||
private suspend fun submitHash(transactions: List<StakingTransaction>, transactionHashes: List<String>) {
|
||||
transactions
|
||||
.zip(transactionHashes)
|
||||
.forEach { (transaction, transactionHash) ->
|
||||
|
|
@ -255,7 +241,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
SubmitHashData(
|
||||
transactionId = transaction.id,
|
||||
transactionHash = transactionHash,
|
||||
pendingTransaction = pendingTransaction,
|
||||
),
|
||||
)
|
||||
.onLeft { error ->
|
||||
|
|
@ -271,9 +256,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
)
|
||||
}.onRight {
|
||||
Timber.d("Successful hash submission")
|
||||
if (transaction.type != StakingTransactionType.FREEZE_ENERGY) {
|
||||
savePendingTransactionUseCase.invoke(userWallet.walletId, pendingTransaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -292,23 +274,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun composeStakeTransaction(): PendingTransaction {
|
||||
val state = stateController.value
|
||||
|
||||
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
|
||||
val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId)
|
||||
|
||||
return PendingTransaction(
|
||||
groupId = UUID.randomUUID().toString(),
|
||||
token = token,
|
||||
type = BalanceType.STAKED,
|
||||
amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO,
|
||||
rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId,
|
||||
validator = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator,
|
||||
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
private data class FullTransactionData(
|
||||
val stakeKitTransaction: StakingTransaction,
|
||||
val tangemTransaction: TransactionData.Compiled,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,5 @@ internal object ConfirmationStatePreviewData {
|
|||
isApprovalNeeded = false,
|
||||
reduceAmountBy = null,
|
||||
pendingActions = null,
|
||||
possiblePendingTransaction = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -63,7 +63,6 @@ internal class SetConfirmationStateInitTransformer(
|
|||
reduceAmountBy = null,
|
||||
pendingAction = pendingAction,
|
||||
pendingActions = pendingActions.takeIf { isComposePendingActions },
|
||||
possiblePendingTransaction = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.domain.staking.model.PendingTransaction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SetPossiblePendingTransactionTransformer(
|
||||
private val yield: Yield,
|
||||
private val balanceState: BalanceState,
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val possibleConfirmationState =
|
||||
prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
|
||||
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
|
||||
|
||||
val balancesId = yieldBalance?.getBalancesUniqueId() ?: 0
|
||||
val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId)
|
||||
|
||||
return prevState.copy(
|
||||
confirmationState = possibleConfirmationState.copy(
|
||||
possiblePendingTransaction = PendingTransaction(
|
||||
groupId = balanceState.groupId,
|
||||
token = token,
|
||||
type = balanceState.type,
|
||||
amount = balanceState.cryptoDecimal,
|
||||
rawCurrencyId = balanceState.rawCurrencyId,
|
||||
validator = balanceState.validator,
|
||||
balancesId = balancesId,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,14 +25,13 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
|||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase
|
||||
import com.tangem.domain.staking.IsAnyTokenStakedUseCase
|
||||
import com.tangem.domain.staking.IsApproveNeededUseCase
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.model.StakingApproval
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceItem
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.domain.tokens.*
|
||||
|
|
@ -74,7 +73,6 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import javax.inject.Inject
|
||||
|
|
@ -108,6 +106,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val stakingBalanceUpdater: StakingBalanceUpdater.Factory,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val getActionsUseCase: GetActionsUseCase,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
|
||||
|
|
@ -131,6 +130,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
?: error("This screen can't be opened without `Yield`")
|
||||
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
private var processingActions: List<StakingAction> = emptyList()
|
||||
private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
|
||||
private var innerRouter: InnerStakingRouter by Delegates.notNull()
|
||||
|
|
@ -141,9 +141,8 @@ internal class StakingViewModel @Inject constructor(
|
|||
get() {
|
||||
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
|
||||
return invalidatePendingTransactionsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
balanceItems = yieldBalance?.balance?.items ?: emptyList(),
|
||||
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
|
||||
processingActions = processingActions,
|
||||
).getOrElse { emptyList() }
|
||||
}
|
||||
|
||||
|
|
@ -153,6 +152,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
stakingBalanceUpdater.create(
|
||||
cryptoCurrencyStatus,
|
||||
userWallet,
|
||||
yield,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -215,11 +215,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
|
||||
override fun onNextClick(balanceState: BalanceState?) {
|
||||
stakingStateRouter.onNextClick()
|
||||
if (balanceState != null && (isInitState() || isAssentState())) {
|
||||
stateController.update(
|
||||
SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFee() {
|
||||
|
|
@ -281,7 +276,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
transactionsInProgress.addAll(constructedTransactions)
|
||||
},
|
||||
onConstructError = { error ->
|
||||
Timber.e(error.toString())
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvents.StakekitError(
|
||||
token = value.cryptoCurrencySymbol,
|
||||
|
|
@ -297,7 +291,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
stateController.update(SetConfirmationStateCompletedTransformer(txUrl))
|
||||
},
|
||||
onSendError = { error ->
|
||||
Timber.e(error.toString())
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
|
||||
stakingEventFactory.createSendTransactionErrorAlert(error)
|
||||
stateController.update(SetConfirmationStateResetAssentTransformer)
|
||||
|
|
@ -527,7 +520,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
userWalletId = userWalletId,
|
||||
).fold(
|
||||
ifLeft = { error ->
|
||||
Timber.e(error.toString())
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentApprovalTransformer(
|
||||
|
|
@ -549,7 +541,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
network = tokenCryptoCurrency.network,
|
||||
).fold(
|
||||
ifLeft = { error ->
|
||||
Timber.e(error.toString())
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentApprovalTransformer(
|
||||
|
|
@ -760,7 +751,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
userWallet = wallet
|
||||
},
|
||||
ifLeft = {
|
||||
Timber.e(it.toString())
|
||||
stakingEventFactory.createGenericErrorAlert(it.toString())
|
||||
stateController.update(SetConfirmationStateResetAssentTransformer)
|
||||
},
|
||||
|
|
@ -790,10 +780,10 @@ internal class StakingViewModel @Inject constructor(
|
|||
setupApprovalNeeded()
|
||||
setupIsAnyTokenStaked()
|
||||
checkIfSubtractAvailable()
|
||||
subscribeOnActionsUpdates()
|
||||
subscribeOnStepChanges()
|
||||
},
|
||||
ifLeft = { error ->
|
||||
Timber.e(error.toString())
|
||||
stakingEventFactory.createGenericErrorAlert(error.toString())
|
||||
stateController.update(SetConfirmationStateResetAssentTransformer)
|
||||
},
|
||||
|
|
@ -831,18 +821,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
.onEach {
|
||||
when {
|
||||
isInitState() -> {
|
||||
stateController.updateAll(
|
||||
SetInitialDataStateTransformer(
|
||||
clickIntents = this@StakingViewModel,
|
||||
yield = yield,
|
||||
isAnyTokenStaked = isAnyTokenStaked,
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
userWalletProvider = Provider { userWallet },
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
balancesToShowProvider = Provider { balancesToShow },
|
||||
),
|
||||
SetConfirmationStateEmptyTransformer,
|
||||
)
|
||||
updateInitialData()
|
||||
onRefreshSwipe(isRefreshing = false)
|
||||
}
|
||||
isAssentState() -> {
|
||||
|
|
@ -862,6 +841,40 @@ internal class StakingViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnActionsUpdates() {
|
||||
getActionsUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrencyId,
|
||||
)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach { result ->
|
||||
result.getOrNull()?.let { actions ->
|
||||
processingActions = actions
|
||||
if (isInitState()) {
|
||||
updateInitialData()
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun updateInitialData() {
|
||||
stateController.updateAll(
|
||||
SetInitialDataStateTransformer(
|
||||
clickIntents = this@StakingViewModel,
|
||||
yield = yield,
|
||||
isAnyTokenStaked = isAnyTokenStaked,
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
userWalletProvider = Provider { userWallet },
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
balancesToShowProvider = Provider { balancesToShow },
|
||||
),
|
||||
SetConfirmationStateEmptyTransformer,
|
||||
)
|
||||
}
|
||||
|
||||
private fun prepareForConfirmation(
|
||||
pendingActions: ImmutableList<PendingAction> = persistentListOf(),
|
||||
pendingAction: PendingAction? = pendingActions.firstOrNull(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue