Updated on 2026-08-14

This commit is contained in:
Tangem 2024-01-31 13:17:57 +03:00
parent 11f62a51bc
commit 9c925b309a
7 changed files with 65 additions and 82 deletions

View file

@ -23,6 +23,7 @@ internal class DefaultSendRouter(
}
override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) {
reduxNavController.popBackStack()
reduxNavController.navigate(
action = NavigationAction.NavigateTo(
screen = AppScreen.WalletDetails,

View file

@ -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
}
}

View file

@ -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 }
}

View file

@ -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()

View file

@ -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,

View file

@ -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),
)
}

View file

@ -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 {