diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt index 360165f228..db243390ea 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt @@ -23,6 +23,7 @@ internal class DefaultSendRouter( } override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) { + reduxNavController.popBackStack() reduxNavController.navigate( action = NavigationAction.NavigateTo( screen = AppScreen.WalletDetails, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index 794f00706a..5ecb7dc55c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -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 } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt index 97ad5f2dfc..4edb445226 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt @@ -11,22 +11,16 @@ internal class StateRouter( var currentState: MutableStateFlow = 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 } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt index e83ba769bd..d16846ae48 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt @@ -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() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index 57187dffc1..d2741dffcc 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -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, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt index b01980e9e9..758fef6f57 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt @@ -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), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index cbdd0688e0..32629e642a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -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?>() + 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 {