Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-29 00:08:47 +05:00
parent 54cb670f22
commit 1bcc834e7e
7 changed files with 46 additions and 92 deletions

View file

@ -453,8 +453,6 @@
<string name="scan_card_settings_title">Приготовьте свою карту</string>
<string name="send_additional_field_already_included">Уже содержится в введенном адресе</string>
<string name="send_amount_label">Сумма</string>
<string name="send_amount_substract">Вычесть из суммы отправки</string>
<string name="send_amount_substract_footer">Сумма к получению %s</string>
<string name="send_alert_transaction_failed_title">Транзакция не выполнена</string>
<string name="send_alert_transaction_failed_text">Причина: %1$s\nКод: %2$s</string>
<string name="send_alert_fee_coverage_title">Недостаточно средств для покрытия комиссии сети. Вычесть комиссию из отправляемой сумму?</string>

View file

@ -455,8 +455,6 @@
<string name="scan_card_settings_title">Get your card ready!</string>
<string name="send_additional_field_already_included">Already included in the entered address</string>
<string name="send_amount_label">Amount</string>
<string name="send_amount_substract">Subtract from send amount</string>
<string name="send_amount_substract_footer">The recipient will receive %s</string>
<string name="send_alert_transaction_failed_title">The transaction is not completed</string>
<string name="send_alert_transaction_failed_text">Reason: %1$s\nCode: %2$s</string>
<string name="send_alert_fee_coverage_title">Not enough funds to cover the network commission. Subtract the commission from the amount sent?</string>

View file

@ -16,6 +16,11 @@ class JobHolder {
this.job?.cancel()
this.job = job
}
/** Cancel current [job] */
fun cancel() {
job?.cancel()
}
}
fun Job.saveIn(jobHolder: JobHolder) = jobHolder.update(job = this)

View file

@ -14,23 +14,15 @@ internal class StateRouter(
private val analyticsEventsHandler: AnalyticsEventHandler,
private val isEditingDisabled: Boolean,
) {
private var mutableCurrentState: MutableStateFlow<SendUiCurrentScreen> = MutableStateFlow(
if (isEditingDisabled) {
SendUiCurrentScreen(
type = SendUiStateType.None,
isFromConfirmation = false,
)
} else {
SendUiCurrentScreen(
type = SendUiStateType.Recipient,
isFromConfirmation = false,
)
},
)
private var mutableCurrentState: MutableStateFlow<SendUiCurrentScreen> = MutableStateFlow(getInitState())
val currentState: StateFlow<SendUiCurrentScreen>
get() = mutableCurrentState
fun clear() {
mutableCurrentState.update { getInitState() }
}
fun popBackStack() {
fragmentManager.get()?.popBackStack()
}
@ -128,4 +120,16 @@ internal class StateRouter(
private fun continueToSend(show: () -> Unit) {
if (currentState.value.isFromConfirmation) showSend() else show()
}
private fun getInitState() = if (isEditingDisabled) {
SendUiCurrentScreen(
type = SendUiStateType.None,
isFromConfirmation = false,
)
} else {
SendUiCurrentScreen(
type = SendUiStateType.Recipient,
isFromConfirmation = false,
)
}
}

View file

@ -34,9 +34,10 @@ internal class SendAmountFieldChangeConverter(
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(amountTextField)
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isZero() else decimalCryptoValue.isZero()
return state.copy(
amountState = amountState.copy(
isPrimaryButtonEnabled = !isExceedBalance,
isPrimaryButtonEnabled = !isExceedBalance && !isZero,
amountTextField = amountTextField.copy(
value = cryptoValue,
fiatValue = fiatValue,
@ -93,9 +94,9 @@ internal class SendAmountFieldChangeConverter(
val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals)
val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals)
return if (amountTextField.isFiatValue) {
fiatDecimal > currencyFiatAmount || fiatDecimal.isZero()
fiatDecimal > currencyFiatAmount
} else {
cryptoDecimal > currencyCryptoAmount || cryptoDecimal.isZero()
cryptoDecimal > currencyCryptoAmount
}
}
}

View file

@ -1,61 +0,0 @@
package com.tangem.features.send.impl.presentation.ui.fee
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
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
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.components.TangemSwitch
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
@Composable
internal fun SendSpeedSubtract(
receivingAmount: String,
isSubtract: Boolean,
onSelectClick: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
val footerText = if (isSubtract) {
stringResource(R.string.send_amount_substract_footer, receivingAmount)
} else {
null
}
FooterContainer(
footer = footerText,
modifier = modifier,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.padding(
vertical = TangemTheme.dimens.spacing16,
horizontal = TangemTheme.dimens.spacing20,
),
) {
Text(
text = stringResource(R.string.send_amount_substract),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
modifier = Modifier
.padding(end = TangemTheme.dimens.spacing12),
)
TangemSwitch(
checked = isSubtract,
onCheckedChange = onSelectClick,
)
}
}
}

View file

@ -171,6 +171,7 @@ internal class SendViewModel @Inject constructor(
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var balanceJobHolder = JobHolder()
private var balanceHidingJobHolder = JobHolder()
private var recipientsJobHolder = JobHolder()
private var feeJobHolder = JobHolder()
private var addressValidationJobHolder = JobHolder()
@ -180,25 +181,35 @@ internal class SendViewModel @Inject constructor(
private var sendIdleTimer = 0L
init {
subscribeOnCurrencyStatusUpdates()
subscribeOnBalanceHidden()
}
override fun onCreate(owner: LifecycleOwner) {
subscribeOnCurrencyStatusUpdates(owner)
onStateActive()
subscribeOnBalanceHidden(owner)
analyticsEventHandler.send(SendAnalyticEvents.SendOpened)
}
override fun onCleared() {
super.onCleared()
balanceHidingJobHolder.cancel()
balanceJobHolder.cancel()
stateRouter.clear()
}
fun setRouter(router: InnerSendRouter, stateRouter: StateRouter) {
innerRouter = router
this.stateRouter = stateRouter
}
private fun subscribeOnCurrencyStatusUpdates(owner: LifecycleOwner) {
private fun subscribeOnCurrencyStatusUpdates() {
viewModelScope.launch(dispatchers.main) {
getUserWalletUseCase(userWalletId).fold(
ifRight = { wallet ->
userWallet = wallet
checkIfSubtractAvailable()
getCurrenciesStatusUpdates(owner, wallet)
getCurrenciesStatusUpdates(wallet)
},
ifLeft = {
uiState = eventStateFactory.getGenericErrorState(
@ -210,23 +221,22 @@ internal class SendViewModel @Inject constructor(
}
}
private fun subscribeOnBalanceHidden(owner: LifecycleOwner) {
private fun subscribeOnBalanceHidden() {
getBalanceHidingSettingsUseCase()
.flowWithLifecycle(owner.lifecycle)
.conflate()
.distinctUntilChanged()
.onEach {
uiState = stateFactory.getOnHideBalanceState(isBalanceHidden = it.isBalanceHidden)
}
.launchIn(viewModelScope)
.saveIn(balanceHidingJobHolder)
}
private fun getCurrenciesStatusUpdates(owner: LifecycleOwner, wallet: UserWallet) {
private fun getCurrenciesStatusUpdates(wallet: UserWallet) {
val isSingleWallet = wallet.scanResponse.walletData?.token != null && !wallet.isMultiCurrency
if (cryptoCurrency is CryptoCurrency.Coin) {
getCurrencyStatusUpdates(isSingleWallet = isSingleWallet)
.flowWithLifecycle(owner.lifecycle)
.onEach { currencyStatus ->
currencyStatus.onRight {
onDataLoaded(
@ -249,7 +259,7 @@ internal class SendViewModel @Inject constructor(
coinCurrencyStatus = coinStatus.getOrElse { error("Coin status is unreachable") },
)
}
}.flowWithLifecycle(owner.lifecycle)
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
.saveIn(balanceJobHolder)
@ -703,11 +713,10 @@ internal class SendViewModel @Inject constructor(
}
private fun onCheckFeeUpdate() {
val isSending = uiState.sendState.isSending
val isSuccess = uiState.sendState.isSuccess
val noErrorNotifications = uiState.sendState.notifications.none { it is SendNotification.Error }
if (!isSending && !isSuccess && noErrorNotifications) {
if (!isSuccess && noErrorNotifications) {
viewModelScope.launch(dispatchers.main) {
val feeUpdatedState = callFeeUseCase()?.fold(
ifRight = {