Updated on 2026-08-14
This commit is contained in:
parent
11f62a51bc
commit
9c925b309a
7 changed files with 65 additions and 82 deletions
|
|
@ -23,6 +23,7 @@ internal class DefaultSendRouter(
|
|||
}
|
||||
|
||||
override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
reduxNavController.popBackStack()
|
||||
reduxNavController.navigate(
|
||||
action = NavigationAction.NavigateTo(
|
||||
screen = AppScreen.WalletDetails,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ import com.tangem.blockchain.common.transaction.TransactionFee
|
|||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.features.send.impl.presentation.state.fee.*
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
import com.tangem.features.send.impl.presentation.state.fee.getFee
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -41,7 +42,7 @@ internal class SendEventStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getFeeUpdatedAlert(fee: TransactionFee, onConsume: () -> Unit): SendUiState {
|
||||
fun getFeeUpdatedAlert(fee: TransactionFee, onConsume: () -> Unit, onFeeNotIncreased: () -> Unit): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val feeSelector = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state
|
||||
val newFee = when (fee) {
|
||||
|
|
@ -67,6 +68,7 @@ internal class SendEventStateFactory(
|
|||
),
|
||||
)
|
||||
} else {
|
||||
onFeeNotIncreased()
|
||||
updateFeeState
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,22 +11,16 @@ internal class StateRouter(
|
|||
var currentState: MutableStateFlow<SendUiStateType> = MutableStateFlow(SendUiStateType.Recipient)
|
||||
private set
|
||||
|
||||
private var isFromSend: Boolean = false
|
||||
|
||||
fun popBackStack() {
|
||||
fragmentManager.get()?.popBackStack()
|
||||
}
|
||||
|
||||
fun onBackClick() {
|
||||
if (isFromSend) {
|
||||
showSend()
|
||||
} else {
|
||||
when (currentState.value) {
|
||||
SendUiStateType.Recipient -> popBackStack()
|
||||
SendUiStateType.Amount -> showRecipient()
|
||||
SendUiStateType.Fee -> showAmount()
|
||||
SendUiStateType.Send -> showFee()
|
||||
}
|
||||
when (currentState.value) {
|
||||
SendUiStateType.Recipient -> popBackStack()
|
||||
SendUiStateType.Amount -> showRecipient()
|
||||
SendUiStateType.Fee -> showAmount()
|
||||
SendUiStateType.Send -> showFee()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,18 +42,15 @@ internal class StateRouter(
|
|||
}
|
||||
}
|
||||
|
||||
fun showAmount(isFromSend: Boolean = false) {
|
||||
this.isFromSend = isFromSend
|
||||
fun showAmount() {
|
||||
currentState.update { SendUiStateType.Amount }
|
||||
}
|
||||
|
||||
fun showRecipient(isFromSend: Boolean = false) {
|
||||
this.isFromSend = isFromSend
|
||||
fun showRecipient() {
|
||||
currentState.update { SendUiStateType.Recipient }
|
||||
}
|
||||
|
||||
fun showFee(isFromSend: Boolean = false) {
|
||||
this.isFromSend = isFromSend
|
||||
fun showFee() {
|
||||
currentState.update { SendUiStateType.Fee }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import androidx.constraintlayout.compose.Visibility
|
|||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.R
|
||||
|
||||
|
|
@ -49,10 +50,11 @@ fun ListItemWithIcon(
|
|||
subtitleEndOffset: Int = 0,
|
||||
@DrawableRes subtitleIconRes: Int? = null,
|
||||
) {
|
||||
val hapticFeedback = rememberHapticFeedback(state = title, onAction = onClick)
|
||||
ConstraintLayout(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.clickable { hapticFeedback() }
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
val (iconRef, titleRef, subtitleRef, subtitleIconRef) = createRefs()
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ internal fun SendRecipientContent(
|
|||
placeholder = address.placeholder,
|
||||
onValueChange = address.onValueChange,
|
||||
onPasteClick = clickIntents::onRecipientAddressValueChange,
|
||||
singleLine = true,
|
||||
isError = isError,
|
||||
isLoading = isValidating,
|
||||
error = address.error,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.send.impl.presentation.ui.recipient
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -55,7 +56,7 @@ internal fun TextFieldWithPaste(
|
|||
placeholder = placeholder,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens.spacing6),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
|||
import com.tangem.features.send.impl.presentation.state.*
|
||||
import com.tangem.features.send.impl.presentation.state.fee.*
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -145,17 +147,11 @@ internal class SendViewModel @Inject constructor(
|
|||
private var feeNotificationsJobHolder = JobHolder()
|
||||
private var qrScannerJobHolder = JobHolder()
|
||||
|
||||
private var checkFeeUpdateScheduler = SingleTaskScheduler<Either<GetFeeError, TransactionFee>?>()
|
||||
private var sendIdleTimer = 0L
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
subscribeOnCurrencyStatusUpdates(owner)
|
||||
onFeeStateActive()
|
||||
onCheckFeeUpdate()
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
super.onStop(owner)
|
||||
checkFeeUpdateScheduler.cancelTask()
|
||||
onStateActive()
|
||||
}
|
||||
|
||||
fun setRouter(router: InnerSendRouter, stateRouter: StateRouter) {
|
||||
|
|
@ -316,10 +312,15 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onFeeStateActive() {
|
||||
private fun onStateActive() {
|
||||
uiState.currentState
|
||||
.filter { it == SendUiStateType.Fee }
|
||||
.onEach { loadFee() }
|
||||
.onEach {
|
||||
when (it) {
|
||||
SendUiStateType.Fee -> loadFee()
|
||||
SendUiStateType.Send -> sendIdleTimer = System.currentTimeMillis()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
|
|
@ -501,14 +502,19 @@ internal class SendViewModel @Inject constructor(
|
|||
if (sendState.isSuccess) popBackStack()
|
||||
|
||||
uiState = stateFactory.getSendingStateUpdate(isSending = true)
|
||||
verifyAndSendTransaction()
|
||||
if (System.currentTimeMillis() - sendIdleTimer < CHECK_FEE_UPDATE_DELAY) {
|
||||
verifyAndSendTransaction()
|
||||
} else {
|
||||
onCheckFeeUpdate()
|
||||
}
|
||||
sendIdleTimer = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
override fun showAmount() = stateRouter.showAmount(isFromSend = true)
|
||||
override fun showAmount() = stateRouter.showAmount()
|
||||
|
||||
override fun showRecipient() = stateRouter.showRecipient(isFromSend = true)
|
||||
override fun showRecipient() = stateRouter.showRecipient()
|
||||
|
||||
override fun showFee() = stateRouter.showFee(isFromSend = true)
|
||||
override fun showFee() = stateRouter.showFee()
|
||||
|
||||
override fun onExploreClick(txUrl: String) = innerRouter.openUrl(txUrl)
|
||||
|
||||
|
|
@ -591,63 +597,44 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onCheckFeeUpdate() {
|
||||
uiState.currentState
|
||||
.onEach { activeState ->
|
||||
val isSending = uiState.sendState.isSending
|
||||
val isSuccess = uiState.sendState.isSuccess
|
||||
val isNoNotifications = uiState.sendState.notifications.isEmpty()
|
||||
val isSendIdle = !isSending && !isSuccess && isNoNotifications
|
||||
if (activeState == SendUiStateType.Send && isSendIdle) {
|
||||
checkFeeUpdateScheduler.scheduleTask(
|
||||
scope = viewModelScope,
|
||||
task = getFeePeriodicTask(),
|
||||
)
|
||||
} else {
|
||||
checkFeeUpdateScheduler.cancelTask()
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
val isSending = uiState.sendState.isSending
|
||||
val isSuccess = uiState.sendState.isSuccess
|
||||
val noErrorNotifications = uiState.sendState.notifications.none { it is SendNotification.Error }
|
||||
|
||||
private fun getFeePeriodicTask() = PeriodicTask(
|
||||
delay = CHECK_FEE_UPDATE_DELAY,
|
||||
isDelayFirst = true,
|
||||
task = {
|
||||
runCatching(dispatchers.main) {
|
||||
if (uiState.sendState.isSuccess) {
|
||||
checkFeeUpdateScheduler.cancelTask()
|
||||
null
|
||||
} else {
|
||||
callFeeUseCase()
|
||||
}
|
||||
}
|
||||
},
|
||||
onSuccess = { maybeFee ->
|
||||
if (maybeFee == null) {
|
||||
checkFeeUpdateScheduler.cancelTask()
|
||||
uiState = eventStateFactory.getGenericErrorState(
|
||||
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
|
||||
)
|
||||
} else {
|
||||
maybeFee.fold(
|
||||
if (!isSending && !isSuccess && noErrorNotifications) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val feeUpdatedState = callFeeUseCase()?.fold(
|
||||
ifRight = {
|
||||
uiState = eventStateFactory.getFeeUpdatedAlert(
|
||||
uiState = stateFactory.getSendingStateUpdate(isSending = false)
|
||||
eventStateFactory.getFeeUpdatedAlert(
|
||||
fee = it,
|
||||
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
|
||||
onFeeNotIncreased = {
|
||||
uiState = stateFactory.getSendingStateUpdate(isSending = true)
|
||||
verifyAndSendTransaction()
|
||||
},
|
||||
)
|
||||
},
|
||||
ifLeft = {
|
||||
checkFeeUpdateScheduler.cancelTask()
|
||||
uiState = eventStateFactory.getGenericErrorState(
|
||||
uiState = stateFactory.getSendingStateUpdate(isSending = false)
|
||||
eventStateFactory.getGenericErrorState(
|
||||
error = (it as? GetFeeError.DataError)?.cause,
|
||||
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
uiState = if (feeUpdatedState != null) {
|
||||
feeUpdatedState
|
||||
} else {
|
||||
uiState = stateFactory.getSendingStateUpdate(isSending = false)
|
||||
eventStateFactory.getGenericErrorState(
|
||||
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onError = { /* no-op */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue