diff --git a/app/src/debug/res/xml/network_security_config.xml b/app/src/debug/res/xml/network_security_config.xml index 894912ac61..52c44ac992 100644 --- a/app/src/debug/res/xml/network_security_config.xml +++ b/app/src/debug/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - + - + diff --git a/app/src/external/res/xml/network_security_config.xml b/app/src/external/res/xml/network_security_config.xml index 894912ac61..d49c0f70ba 100644 --- a/app/src/external/res/xml/network_security_config.xml +++ b/app/src/external/res/xml/network_security_config.xml @@ -1,9 +1,10 @@ - + - + + diff --git a/app/src/internal/res/xml/network_security_config.xml b/app/src/internal/res/xml/network_security_config.xml index 894912ac61..52c44ac992 100644 --- a/app/src/internal/res/xml/network_security_config.xml +++ b/app/src/internal/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - + - + diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 22e9f8653e..683208ffda 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,11 +1,8 @@ - - - api.tangem.com + - - + diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt index 8ca86a2587..7a3279d188 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.reactive.asFlow +private const val PING_SERVER = "https://clients3.google.com/generate_204" private const val PING_INTERVAL = 5_000 internal class RealInternetConnectionManager : NetworkConnectionManager { @@ -24,7 +25,12 @@ internal class RealInternetConnectionManager : NetworkConnectionManager { private val initialNetworkResult: Boolean by lazy { ReactiveNetwork - .checkInternetConnectivity() + .checkInternetConnectivity( + InternetObservingSettings.builder() + .host(PING_SERVER) + .port(443) + .build(), + ) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .blockingGet() @@ -33,6 +39,8 @@ internal class RealInternetConnectionManager : NetworkConnectionManager { override val isOnlineFlow: StateFlow = ReactiveNetwork .observeInternetConnectivity( InternetObservingSettings.builder() + .host(PING_SERVER) + .port(443) .interval(PING_INTERVAL) .build(), ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt index 09b6158abb..c65bf02622 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt @@ -63,7 +63,8 @@ fun AppBarWithBackButtonAndIconContent( ) Column( verticalArrangement = Arrangement.Center, - modifier = Modifier.weight(1f), + modifier = Modifier.weight(1f) + .animateContentSize(), ) { AnimatedVisibility( visible = !text.isNullOrBlank(), @@ -89,7 +90,6 @@ fun AppBarWithBackButtonAndIconContent( color = TangemTheme.colors.text.secondary, maxLines = 1, style = TangemTheme.typography.caption2, - modifier = Modifier.animateContentSize(), ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIconState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIconState.kt index f51adb19b3..0f7ff350de 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIconState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIconState.kt @@ -60,24 +60,23 @@ sealed class TokenIconState { * @property background The background color to be used for the icon. * @property networkBadgeIconResId The drawable resource ID for the network badge. * @property isGrayscale Specifies whether to show the icon in grayscale. + * @property showCustomBadge Specifies whether to show the custom token badge. */ data class CustomTokenIcon( val tint: Color, val background: Color, @DrawableRes override val networkBadgeIconResId: Int, override val isGrayscale: Boolean, - ) : TokenIconState() { + override val showCustomBadge: Boolean = true, + ) : TokenIconState() - override val showCustomBadge: Boolean = true - } - - object Loading : TokenIconState() { + data object Loading : TokenIconState() { override val isGrayscale: Boolean = false override val showCustomBadge: Boolean = false override val networkBadgeIconResId: Int? = null } - object Locked : TokenIconState() { + data object Locked : TokenIconState() { override val isGrayscale: Boolean = false override val showCustomBadge: Boolean = false override val networkBadgeIconResId: Int? = null diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt index fbcc6e3caa..3e62e98992 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt @@ -30,6 +30,7 @@ class CryptoCurrencyToIconStateConverter : Converter getIconStateForToken( token = currency, @@ -50,13 +51,14 @@ class CryptoCurrencyToIconStateConverter : Converter textFieldValueState = newTextFieldValueState @@ -126,4 +138,14 @@ private fun SimpleTextPlaceholder( } textValue() } +} + +@Composable +private fun rememberSelectionChanged(textFieldValue: TextFieldValue, textFieldValueState: TextFieldValue) = remember { + derivedStateOf { + val isSelectionChanged = textFieldValue.selection != textFieldValueState.selection || + textFieldValue.composition != textFieldValueState.composition + val isTextNotChanged = textFieldValue.text == textFieldValueState.text + isSelectionChanged && isTextNotChanged + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt index bedec66060..b919363574 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt @@ -73,7 +73,7 @@ fun SelectorRowItem( color = TangemTheme.colors.text.primary1, modifier = Modifier.padding(start = TangemTheme.dimens.spacing8), ) - if (preDot != null && postDot != null) { + if (preDot != null) { SelectorValueContent( preDot = preDot, postDot = postDot, @@ -97,7 +97,7 @@ fun SelectorRowItem( @Composable private fun RowScope.SelectorValueContent( preDot: TextReference, - postDot: TextReference, + postDot: TextReference?, ellipsizeOffset: Int? = null, ) { val ellipsis = if (ellipsizeOffset == null) { @@ -115,18 +115,20 @@ private fun RowScope.SelectorValueContent( .weight(1f) .padding(start = TangemTheme.dimens.spacing4), ) - Text( - text = "•", - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing4), - ) - Text( - text = postDot.resolveReference(), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.tertiary, - ) + if (postDot != null) { + Text( + text = "•", + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing4), + ) + Text( + text = postDot.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) + } } @Preview diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 76b16b1409..3fe078eaad 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -71,7 +71,10 @@ internal class DefaultTransactionRepository( } Blockchain.Binance -> BinanceTransactionExtras(memo) Blockchain.XRP -> memo.toLongOrNull()?.let { XrpTransactionBuilder.XrpTransactionExtras(it) } - Blockchain.Cosmos -> CosmosTransactionExtras(memo) + Blockchain.Cosmos, + Blockchain.TerraV1, + Blockchain.TerraV2, + -> CosmosTransactionExtras(memo) Blockchain.TON -> TonTransactionExtras(memo) Blockchain.Hedera -> HederaTransactionBuilder.HederaTransactionExtras(memo) Blockchain.Algorand -> AlgorandTransactionExtras(memo) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index fac9f7fc95..d12ca4c9ad 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -264,10 +264,18 @@ internal class CurrenciesStatusesOperations( var quotesRetrievingFailed = false val networksStatuses = maybeNetworkStatuses?.bind()?.toNonEmptySetOrNull() - val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) { - quotesRetrievingFailed = true - null - } + val quotes: Set? = maybeQuotes?.fold( + ifLeft = { + quotesRetrievingFailed = true + null + }, + ifRight = { + it.ifEmpty { + quotesRetrievingFailed = true + null + } + }, + ) currencies.map { currency -> val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt index e99471d8de..feca8a0786 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt @@ -16,6 +16,20 @@ internal sealed class SendAnalyticEvents( params: Map = mapOf(), ) : AnalyticsEvent(category = "Token / Send", event = event, params = params) { + /** Close button clicked */ + data class CloseButtonClicked( + val source: SendScreenSource, + val isFromSummary: Boolean, + val isValid: Boolean, + ) : SendAnalyticEvents( + event = "Button - Close", + params = mapOf( + SOURCE to source.name, + "FromSummary" to if (isFromSummary) "Yes" else "No", + "isValid" to if (isValid) "Yes" else "No", + ), + ) + // region Address /** Recipient address screen opened */ data object AddressScreenOpened : SendAnalyticEvents(event = "Address Screen Opened") @@ -119,6 +133,7 @@ internal enum class SendScreenSource { Address, Amount, Fee, + Confirm, } internal enum class EnterAddressSource { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt similarity index 69% rename from features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt rename to features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt index 6fefbc87b8..27c93b9743 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt @@ -5,15 +5,17 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.features.send.impl.presentation.analytics.SelectedCurrencyType import com.tangem.features.send.impl.presentation.analytics.SelectedFeeType import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents -import com.tangem.features.send.impl.presentation.state.* +import com.tangem.features.send.impl.presentation.analytics.SendScreenSource import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.SendUiStateType import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.utils.Provider -internal class SendOnNextScreenAnalyticSender( +internal class SendScreenAnalyticSender( private val stateRouterProvider: Provider, + private val currentStateProvider: Provider, private val analyticsEventHandler: AnalyticsEventHandler, ) { fun send(prevScreen: SendUiStateType, state: SendUiState) { @@ -45,6 +47,32 @@ internal class SendOnNextScreenAnalyticSender( } } + fun sendOnClose() { + val routerState = stateRouterProvider().currentState.value + val state = currentStateProvider() + + val (source, isValid) = when (routerState.type) { + SendUiStateType.Recipient, + SendUiStateType.EditRecipient, + -> SendScreenSource.Address to (state.editRecipientState?.isPrimaryButtonEnabled ?: false) + SendUiStateType.Amount, + SendUiStateType.EditAmount, + -> SendScreenSource.Amount to (state.editAmountState?.isPrimaryButtonEnabled ?: false) + SendUiStateType.Fee, + SendUiStateType.EditFee, + -> SendScreenSource.Fee to (state.editFeeState?.isPrimaryButtonEnabled ?: false) + else -> SendScreenSource.Confirm to true + } + + analyticsEventHandler.send( + SendAnalyticEvents.CloseButtonClicked( + source = source, + isFromSummary = routerState.isFromConfirmation, + isValid = isValid, + ), + ) + } + private fun sendSelectedFeeAnalytics(feeSelectorState: FeeSelectorState.Content) { val type = when (feeSelectorState.fees) { is TransactionFee.Single -> SelectedFeeType.Fixed diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index a2b722b614..631cbdf100 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -18,6 +18,7 @@ import com.tangem.features.send.impl.presentation.state.common.SendSyncEditConve import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter +import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter @@ -28,8 +29,9 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import timber.log.Timber +import java.math.BigDecimal -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class SendStateFactory( private val clickIntents: SendClickIntents, private val stateRouterProvider: Provider, @@ -46,6 +48,7 @@ internal class SendStateFactory( private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountFieldConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, appCurrencyProvider = appCurrencyProvider, ) @@ -94,6 +97,7 @@ internal class SendStateFactory( isEditingDisabled = false, isBalanceHidden = false, cryptoCurrencyName = "", + isSubtracted = false, ) fun getReadyState(): SendUiState { @@ -279,12 +283,33 @@ internal class SendStateFactory( //endregion //region send + fun getIsAmountSubtractedState(isAmountSubtractAvailable: Boolean): SendUiState { + val state = currentStateProvider() + val balance = cryptoCurrencyStatusProvider().value.amount ?: return state + val amountState = state.getAmountState(stateRouterProvider().isEditState) ?: return state + val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return state + val amountValue = amountState.amountTextField.cryptoAmount.value ?: return state + val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO + return state.copy( + isSubtracted = checkFeeCoverage( + isSubtractAvailable = isAmountSubtractAvailable, + balance = balance, + amountValue = amountValue, + feeValue = feeValue, + ), + ) + } + fun getSendingStateUpdate(isSending: Boolean): SendUiState { val state = currentStateProvider() return state.copy( sendState = state.sendState?.copy( isSending = isSending, - isPrimaryButtonEnabled = isPrimaryButtonEnabled(state, state.sendState.notifications), + isPrimaryButtonEnabled = isPrimaryButtonEnabled( + state = state, + isSending = isSending, + notifications = state.sendState.notifications, + ), ), ) } @@ -308,7 +333,11 @@ internal class SendStateFactory( val sendState = state.sendState ?: return state return state.copy( sendState = sendState.copy( - isPrimaryButtonEnabled = isPrimaryButtonEnabled(state, notifications), + isPrimaryButtonEnabled = isPrimaryButtonEnabled( + state = state, + isSending = sendState.isSending, + notifications = notifications, + ), notifications = notifications, showTapHelp = sendState.showTapHelp && notifications.isEmpty(), ), @@ -323,11 +352,14 @@ internal class SendStateFactory( ) } - private fun isPrimaryButtonEnabled(state: SendUiState, notifications: ImmutableList): Boolean { - val sendState = state.sendState ?: return false + private fun isPrimaryButtonEnabled( + state: SendUiState, + isSending: Boolean, + notifications: ImmutableList, + ): Boolean { val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return false val hasErrorNotifications = notifications.any { it is SendNotification.Error } - return !hasErrorNotifications && !sendState.isSending && feeState.feeSelectorState is FeeSelectorState.Content + return !hasErrorNotifications && !isSending && feeState.feeSelectorState is FeeSelectorState.Content } //endregion } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index 23453c4b4d..c7aa7ddff0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -32,6 +32,7 @@ internal data class SendUiState( val editRecipientState: SendStates.RecipientState? = null, val editFeeState: SendStates.FeeState? = null, val isBalanceHidden: Boolean, + val isSubtracted: Boolean, val event: StateEvent, ) { @@ -97,6 +98,7 @@ internal sealed class SendStates { val walletBalance: TextReference, val tokenIconState: TokenIconState, val segmentedButtonConfig: PersistentList, + val selectedButton: Int, val isSegmentedButtonsEnabled: Boolean, val amountTextField: SendTextField.AmountField, val appCurrencyCode: String, @@ -139,6 +141,7 @@ internal sealed class SendStates { val transactionDate: Long, val txUrl: String, val ignoreAmountReduce: Boolean, + val reduceAmountBy: BigDecimal?, val isFromConfirmation: Boolean, val showTapHelp: Boolean, val notifications: ImmutableList, 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 5f2201052b..e09467fa9c 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 @@ -34,7 +34,7 @@ internal class StateRouter( when { isSuccess -> popBackStack() isEditingDisabled -> when (type) { - SendUiStateType.Send -> showFee() + SendUiStateType.EditFee -> showSend() else -> popBackStack() } else -> when (type) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index e91f30356f..6ff84cf35d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -6,6 +6,7 @@ import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter import com.tangem.utils.Provider +import java.math.BigDecimal /** * Factory to produce amount state for [SendUiState] @@ -44,9 +45,18 @@ internal class AmountStateFactory( currentStateProvider = currentStateProvider, ) } + private val amountReducedConverter by lazy { + SendAmountReducedConverter( + stateRouterProvider = stateRouterProvider, + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) + fun getOnAmountReducedState(reduceAmountBy: BigDecimal) = amountReducedConverter.convert(reduceAmountBy) + fun getOnMaxAmountClick(): SendUiState { return amountFieldMaxAmountConverter.convert(Unit) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountUtils.kt new file mode 100644 index 0000000000..3921429828 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountUtils.kt @@ -0,0 +1,59 @@ +package com.tangem.features.send.impl.presentation.state.amount + +import androidx.compose.ui.text.input.ImeAction +import com.tangem.common.extensions.isZero +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import java.math.BigDecimal +import java.math.RoundingMode + +internal fun String.getCryptoValue(fiatRate: BigDecimal?, isFiatValue: Boolean, decimals: Int): String { + return if (isFiatValue && fiatRate != null) { + parseToBigDecimal(decimals).divide(fiatRate, decimals, RoundingMode.DOWN) + .parseBigDecimal(decimals) + } else { + this + } +} + +internal fun String.getFiatValue( + fiatRate: BigDecimal?, + isFiatValue: Boolean, + decimals: Int, +): Pair { + return if (fiatRate != null) { + val fiatValue = if (!isFiatValue) { + parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals) + } else { + this + } + val decimalFiatValue = fiatValue.parseToBigDecimal(decimals) + fiatValue to decimalFiatValue + } else { + "" to null + } +} + +internal fun String.checkExceedBalance( + cryptoCurrencyStatus: CryptoCurrencyStatus, + amountTextField: SendTextField.AmountField, +): Boolean { + val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO + val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals) + val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals) + return if (amountTextField.isFiatValue) { + fiatDecimal > currencyFiatAmount + } else { + cryptoDecimal > currencyCryptoAmount + } +} + +internal fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) = + if (!isExceedBalance && !decimalCryptoValue.isZero()) { + ImeAction.Done + } else { + ImeAction.None + } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt index 4817557ac6..fa31b63ea0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt @@ -1,5 +1,8 @@ package com.tangem.features.send.impl.presentation.state.amount +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter @@ -20,6 +23,7 @@ internal class SendAmountCurrencyConverter( val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val isValidFiatRate = cryptoCurrencyStatus.value.fiatRate.isNullOrZero() + val isDoneActionEnabled = amountState.isPrimaryButtonEnabled return if (amountTextField.isFiatValue == value && !isValidFiatRate) { state } else { @@ -29,7 +33,12 @@ internal class SendAmountCurrencyConverter( amountTextField = amountTextField.copy( isFiatValue = value, isValuePasted = true, + keyboardOptions = KeyboardOptions( + imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, + keyboardType = KeyboardType.Number, + ), ), + selectedButton = amountState.segmentedButtonConfig.indexOfFirst { it.isFiat == value }, ), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt new file mode 100644 index 0000000000..d624608b14 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt @@ -0,0 +1,62 @@ +package com.tangem.features.send.impl.presentation.state.amount + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.extensions.isZero +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero +import java.math.BigDecimal + +internal class SendAmountReducedConverter( + private val stateRouterProvider: Provider, + private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, +) : Converter { + override fun convert(value: BigDecimal): SendUiState { + val state = currentStateProvider() + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val isEditState = stateRouterProvider().isEditState + val amountState = state.getAmountState(isEditState) ?: return state + val amountTextField = amountState.amountTextField + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + val amountValue = amountState.amountTextField.cryptoAmount.value ?: return state + + val decimalCryptoValue = amountValue.minus(value) + val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals) + val (fiatValue, decimalFiatValue) = cryptoValue.getFiatValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = false, + decimals = fiatDecimals, + ) + + val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue + val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else decimalCryptoValue.isZero() + return state.copyWrapped( + isEditState = isEditState, + sendState = state.sendState?.copy( + reduceAmountBy = value, + ), + amountState = amountState.copy( + isPrimaryButtonEnabled = !isExceedBalance && !isZero, + amountTextField = amountTextField.copy( + value = cryptoValue, + fiatValue = fiatValue, + isError = isExceedBalance, + cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + keyboardOptions = KeyboardOptions( + imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue), + keyboardType = KeyboardType.Number, + ), + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index 7e3199d1ff..6afdc1ada8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -57,6 +57,7 @@ internal class SendAmountStateConverter( ), ), isSegmentedButtonsEnabled = !noFeeRate, + selectedButton = 0, ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt index 7935da04c8..254a979807 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt @@ -16,6 +16,7 @@ internal class SendConfirmStateConverter( transactionDate = 0L, txUrl = "", ignoreAmountReduce = false, + reduceAmountBy = null, isFromConfirmation = true, showTapHelp = isTapHelpPreviewEnabledProvider(), notifications = persistentListOf(), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index cd1e28de72..c98336636c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -20,7 +20,6 @@ import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents -import com.tangem.lib.crypto.BlockchainUtils.isDogecoin import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList @@ -74,7 +73,6 @@ internal class SendNotificationFactory( addFeeUnreachableNotification(feeState.feeSelectorState) addExceedBalanceNotification(feeValue, sendingAmount) addExceedsBalanceNotification(feeState.fee) - addMinimumAmountErrorNotification(feeValue, sendingAmount) addDustWarningNotification(feeValue, sendingAmount) addTransactionLimitErrorNotification(feeValue, sendingAmount) // warnings @@ -95,6 +93,7 @@ internal class SendNotificationFactory( return state.copy( sendState = sendState.copy( ignoreAmountReduce = isIgnored, + reduceAmountBy = if (isIgnored) null else sendState.reduceAmountBy, notifications = updatedNotifications.toImmutableList(), ), ) @@ -227,8 +226,7 @@ internal class SendNotificationFactory( SendNotification.Warning.HighFeeError( amount = threshold.toPlainString(), onConfirmClick = { - val reduceTo = sendAmount.minus(threshold) - clickIntents.onAmountReduceClick(reduceTo, SendNotification.Warning.HighFeeError::class.java) + clickIntents.onAmountReduceClick(threshold, SendNotification.Warning.HighFeeError::class.java) }, onCloseClick = { clickIntents.onNotificationCancel(SendNotification.Warning.HighFeeError::class.java) @@ -238,21 +236,6 @@ internal class SendNotificationFactory( } } - // todo remove in [REDACTED_TASK_KEY] - private fun MutableList.addMinimumAmountErrorNotification( - feeAmount: BigDecimal, - receivedAmount: BigDecimal, - ) { - val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() - val minimum = BigDecimal(DOGECOIN_MINIMUM) - - val isDogecoin = isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value) - val isExceedDustLimit = checkDustLimits(feeAmount, receivedAmount, minimum) - if (isDogecoin && isExceedDustLimit) { - add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) - } - } - private suspend fun MutableList.addDustWarningNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, @@ -380,8 +363,4 @@ internal class SendNotificationFactory( val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO return receivedAmount < dustValue || isChangeLowerThanDust } - - companion object { - private const val DOGECOIN_MINIMUM = "0.01" - } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index 43e7da9e42..b06d42a23d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -1,12 +1,10 @@ package com.tangem.features.send.impl.presentation.state.fee -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.common.extensions.isZero import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal -import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.lib.crypto.BlockchainUtils import java.math.BigDecimal import java.math.RoundingMode @@ -18,6 +16,7 @@ internal fun checkAndCalculateSubtractedAmount( cryptoCurrencyStatus: CryptoCurrencyStatus, amountValue: BigDecimal, feeValue: BigDecimal, + reduceAmountBy: BigDecimal?, ): BigDecimal { val balance = cryptoCurrencyStatus.value.amount ?: return amountValue val isFeeCoverage = checkFeeCoverage( @@ -26,12 +25,17 @@ internal fun checkAndCalculateSubtractedAmount( amountValue = amountValue, feeValue = feeValue, ) - return calculateSubtractedAmount( + val subtractedAmount = calculateSubtractedAmount( isFeeCoverage = isFeeCoverage, cryptoCurrencyStatus = cryptoCurrencyStatus, amountValue = amountValue, feeValue = feeValue, ) + return if (reduceAmountBy != null) { + subtractedAmount.minus(reduceAmountBy) + } else { + subtractedAmount + } } /** @@ -44,7 +48,7 @@ internal fun checkFeeCoverage( feeValue: BigDecimal, ): Boolean { if (!isSubtractAvailable) return false - return balance < amountValue + feeValue && balance > feeValue + return balance < amountValue + feeValue && balance > feeValue && balance >= amountValue } /** @@ -58,12 +62,7 @@ internal fun calculateSubtractedAmount( ): BigDecimal { val balance = cryptoCurrencyStatus.value.amount ?: return amountValue return if (isFeeCoverage) { - var subtractedValue = minOf(amountValue, balance.minus(feeValue)) - if (BlockchainUtils.isTezos(cryptoCurrencyStatus.currency.network.id.value)) { - val threshold = Blockchain.Tezos.minimalAmount() - subtractedValue = -threshold - } - subtractedValue + minOf(amountValue, balance.minus(feeValue)) } else { amountValue } @@ -95,5 +94,12 @@ internal fun checkIfFeeTooHigh(feeSelectorState: FeeSelectorState.Content, onSho return isShow } +/** + * Checks if fee exceeds fee paid currency balance + */ +fun checkExceedBalance(feeBalance: BigDecimal?, feeAmount: BigDecimal?): Boolean { + return feeAmount == null || feeBalance == null || feeAmount.isZero() || feeAmount > feeBalance +} + private val FEE_MAX_DIFF = BigDecimal("5") private const val ZERO_DECIMALS = 0 \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt index 1fa99ee291..5109aa17b9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.custom.BitcoinCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents @@ -13,6 +14,7 @@ import com.tangem.utils.converter.Converter internal class FeeConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, ) : Converter { @@ -20,6 +22,7 @@ internal class FeeConverter( private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { EthereumCustomFeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) @@ -28,6 +31,7 @@ internal class FeeConverter( private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { BitcoinCustomFeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index e5683fcdc7..e933b89477 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -7,7 +7,10 @@ import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase -import com.tangem.features.send.impl.presentation.state.* +import com.tangem.features.send.impl.presentation.state.SendNotification +import com.tangem.features.send.impl.presentation.state.SendStates +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList @@ -27,6 +30,7 @@ internal class FeeStateFactory( private val customFeeFieldConverter by lazy(LazyThreadSafetyMode.NONE) { SendFeeCustomFieldConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) @@ -35,6 +39,7 @@ internal class FeeStateFactory( val feeConverter by lazy(LazyThreadSafetyMode.NONE) { FeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt index c9534ea97f..5fd2a4060d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt @@ -3,6 +3,7 @@ package com.tangem.features.send.impl.presentation.state.fee import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.custom.BitcoinCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fields.SendTextField @@ -14,6 +15,7 @@ import kotlinx.collections.immutable.persistentListOf internal class SendFeeCustomFieldConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, ) : Converter> { @@ -21,6 +23,7 @@ internal class SendFeeCustomFieldConverter( private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { EthereumCustomFeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) @@ -29,6 +32,7 @@ internal class SendFeeCustomFieldConverter( private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { BitcoinCustomFeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt index 8226a67504..a2a13e987a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt @@ -5,17 +5,16 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.blockchain.common.transaction.Fee -import com.tangem.common.extensions.isZero -import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.utils.getFiatReference import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isBitcoin import com.tangem.utils.Provider @@ -27,12 +26,14 @@ import java.math.RoundingMode internal class BitcoinCustomFeeConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, ) : CustomFeeConverter { override fun convert(value: Fee.Bitcoin): ImmutableList { val feeValue = value.amount.value + val feeCurrency = feeCryptoCurrencyStatusProvider()?.value val network = feeCryptoCurrencyStatusProvider()?.currency?.network?.id?.value return if (network != null && isBitcoin(network)) { persistentListOf( @@ -47,7 +48,11 @@ internal class BitcoinCustomFeeConverter( ), title = resourceReference(R.string.send_max_fee), footer = resourceReference(R.string.send_max_fee_footer), - label = getFeeFormatted(feeValue), + label = getFiatReference( + rate = feeCurrency?.fiatRate, + value = feeValue, + appCurrency = appCurrencyProvider(), + ), keyboardActions = KeyboardActions(), isReadonly = true, ), @@ -63,10 +68,20 @@ internal class BitcoinCustomFeeConverter( footer = resourceReference(R.string.send_satoshi_per_byte_text), onValueChange = { clickIntents.onCustomFeeValueChange(FEE_SATOSHI_INDEX, it) }, keyboardOptions = KeyboardOptions( - imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, + imeAction = if (checkExceedBalance( + feeBalance = feeCurrency?.amount, + feeAmount = feeValue, + ) + ) { + ImeAction.None + } else { + ImeAction.Done + }, keyboardType = KeyboardType.Number, ), - keyboardActions = KeyboardActions(), + keyboardActions = KeyboardActions( + onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, + ), ), ) } else { @@ -100,7 +115,11 @@ internal class BitcoinCustomFeeConverter( FEE_AMOUNT_INDEX, this[FEE_AMOUNT_INDEX].copy( value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT_INDEX].decimals), - label = getFeeFormatted(newFeeAmount), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), ), ) set(index, this[index].copy(value = value)) @@ -108,26 +127,6 @@ internal class BitcoinCustomFeeConverter( }.toImmutableList() } - private fun getFeeFormatted(fee: BigDecimal?): TextReference { - val appCurrency = appCurrencyProvider() - val rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate - val fiatFee = rate?.let { fee?.multiply(it) } - return stringReference( - BigDecimalFormatter.formatFiatAmount( - fiatAmount = fiatFee, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ), - ) - } - - private fun checkExceedBalance(feeAmount: BigDecimal?): Boolean { - val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider() - val currencyCryptoAmount = cryptoCurrencyStatus?.value?.amount ?: BigDecimal.ZERO - - return feeAmount == null || feeAmount.isZero() || feeAmount > currencyCryptoAmount - } - private fun toSatoshiPerByte(amount: BigDecimal?, decimals: Int, txSize: BigDecimal): BigDecimal? { val newFeeAmount = amount?.movePointRight(decimals) return newFeeAmount?.divide( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index 4851533b49..6bed843250 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -5,33 +5,33 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.blockchain.common.transaction.Fee -import com.tangem.common.extensions.isZero -import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.utils.getFiatReference import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import java.math.BigDecimal import java.math.RoundingMode internal class EthereumCustomFeeConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, ) : CustomFeeConverter { override fun convert(value: Fee.Ethereum): ImmutableList { val feeValue = value.amount.value + val feeCurrency = feeCryptoCurrencyStatusProvider()?.value return persistentListOf( SendTextField.CustomFee( value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), @@ -44,7 +44,11 @@ internal class EthereumCustomFeeConverter( ), title = resourceReference(R.string.send_max_fee), footer = resourceReference(R.string.send_max_fee_footer), - label = getFeeFormatted(feeValue), + label = getFiatReference( + rate = feeCurrency?.fiatRate, + value = feeValue, + appCurrency = appCurrencyProvider(), + ), keyboardActions = KeyboardActions(), ), SendTextField.CustomFee( @@ -68,10 +72,21 @@ internal class EthereumCustomFeeConverter( footer = resourceReference(R.string.send_gas_limit_footer), onValueChange = { clickIntents.onCustomFeeValueChange(GAS_LIMIT, it) }, keyboardOptions = KeyboardOptions( - imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, + imeAction = if ( + checkExceedBalance( + feeBalance = feeCurrency?.amount, + feeAmount = feeValue, + ) + ) { + ImeAction.None + } else { + ImeAction.Done + }, keyboardType = KeyboardType.Number, ), - keyboardActions = KeyboardActions(onDone = { clickIntents.onNextClick() }), + keyboardActions = KeyboardActions( + onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, + ), ), ) } @@ -102,26 +117,6 @@ internal class EthereumCustomFeeConverter( }.toImmutableList() } - private fun getFeeFormatted(fee: BigDecimal?): TextReference { - val appCurrency = appCurrencyProvider() - val rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate - val fiatFee = rate?.let { fee?.multiply(it) } - return stringReference( - BigDecimalFormatter.formatFiatAmount( - fiatAmount = fiatFee, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ), - ) - } - - private fun checkExceedBalance(feeAmount: BigDecimal?): Boolean { - val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider() - val currencyCryptoAmount = cryptoCurrencyStatus?.value?.amount ?: BigDecimal.ZERO - - return feeAmount == null || feeAmount.isZero() || feeAmount > currencyCryptoAmount - } - private fun MutableList.setEmpty(index: Int) { set(index, this[index].copy(value = "")) } @@ -140,7 +135,11 @@ internal class EthereumCustomFeeConverter( index, this[index].copy( value = value, - label = getFeeFormatted(newFeeAmountDecimal), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmountDecimal, + appCurrency = appCurrencyProvider(), + ), ), ) } @@ -159,7 +158,11 @@ internal class EthereumCustomFeeConverter( FEE_AMOUNT, this[FEE_AMOUNT].copy( value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFeeFormatted(newFeeAmount), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), ), ) set(index, this[index].copy(value = value)) @@ -179,15 +182,23 @@ internal class EthereumCustomFeeConverter( FEE_AMOUNT, this[FEE_AMOUNT].copy( value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFeeFormatted(newFeeAmount), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), ), ) + val isNotExceedBalance = checkExceedBalance( + feeBalance = feeCryptoCurrencyStatusProvider()?.value?.amount, + feeAmount = newFeeAmount, + ) set( index, this[index].copy( value = value, keyboardOptions = KeyboardOptions( - imeAction = if (!checkExceedBalance(newFeeAmount)) ImeAction.None else ImeAction.Done, + imeAction = if (!isNotExceedBalance) ImeAction.None else ImeAction.Done, keyboardType = KeyboardType.Number, ), ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index 04c375714e..3958e3b64a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -1,18 +1,20 @@ package com.tangem.features.send.impl.presentation.state.fields import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.common.extensions.isZero -import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.features.send.impl.presentation.state.amount.checkExceedBalance +import com.tangem.features.send.impl.presentation.state.amount.getCryptoValue +import com.tangem.features.send.impl.presentation.state.amount.getFiatValue +import com.tangem.features.send.impl.presentation.state.amount.getKeyboardAction import com.tangem.utils.Provider import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero import java.math.BigDecimal -import java.math.RoundingMode internal class SendAmountFieldChangeConverter( private val stateRouterProvider: Provider, @@ -21,6 +23,7 @@ internal class SendAmountFieldChangeConverter( ) : Converter { override fun convert(value: String): SendUiState { val state = currentStateProvider() + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val isEditState = stateRouterProvider().isEditState val amountState = state.getAmountState(isEditState) ?: return state val amountTextField = amountState.amountTextField @@ -30,14 +33,21 @@ internal class SendAmountFieldChangeConverter( val fiatDecimals = amountTextField.fiatAmount.decimals val trimmedValue = value.trim() - val cryptoValue = trimmedValue.getCryptoValue(amountTextField.isFiatValue, cryptoDecimals) - val fiatValue = trimmedValue.getFiatValue(amountTextField.isFiatValue, fiatDecimals) + val cryptoValue = trimmedValue.getCryptoValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = amountTextField.isFiatValue, + decimals = cryptoDecimals, + ) val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals) - val decimalFiatValue = fiatValue.parseToBigDecimal(fiatDecimals) + val (fiatValue, decimalFiatValue) = trimmedValue.getFiatValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = amountTextField.isFiatValue, + decimals = fiatDecimals, + ) val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue - val isExceedBalance = checkValue.checkExceedBalance(amountTextField) - val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isZero() else decimalCryptoValue.isZero() + val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else decimalCryptoValue.isZero() return state.copyWrapped( isEditState = isEditState, amountState = amountState.copy( @@ -57,27 +67,6 @@ internal class SendAmountFieldChangeConverter( ) } - private fun String.getCryptoValue(isFiatValue: Boolean, decimals: Int): String { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val fiatRate = cryptoCurrencyStatus.value.fiatRate - return if (isFiatValue && fiatRate != null) { - parseToBigDecimal(decimals).divide(fiatRate, decimals, RoundingMode.DOWN) - .parseBigDecimal(decimals) - } else { - this - } - } - - private fun String.getFiatValue(isFiatValue: Boolean, decimals: Int): String { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val fiatRate = cryptoCurrencyStatus.value.fiatRate - return if (!isFiatValue && fiatRate != null) { - parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals) - } else { - this - } - } - private fun SendUiState.emptyState(): SendUiState { val isEditState = stateRouterProvider().isEditState val amountState = getAmountState(isEditState) ?: return this @@ -96,24 +85,4 @@ internal class SendAmountFieldChangeConverter( ), ) } - - private fun String.checkExceedBalance(amountTextField: SendTextField.AmountField): Boolean { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO - val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals) - val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals) - return if (amountTextField.isFiatValue) { - fiatDecimal > currencyFiatAmount - } else { - cryptoDecimal > currencyCryptoAmount - } - } - - private fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) = - if (!isExceedBalance && !decimalCryptoValue.isZero()) { - ImeAction.Done - } else { - ImeAction.None - } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt index 2070b8c633..8bf30c0b57 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt @@ -14,15 +14,18 @@ import com.tangem.domain.tokens.model.AmountType import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.convertToAmount import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero import java.math.BigDecimal private const val FIAT_DECIMALS = 2 internal class SendAmountFieldConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, private val appCurrencyProvider: Provider, ) : Converter { @@ -32,12 +35,14 @@ internal class SendAmountFieldConverter( val cryptoDecimal = value.toBigDecimalOrDefault() val cryptoAmount = cryptoDecimal.convertToAmount(cryptoCurrencyStatus.currency) val fiatRate = cryptoCurrencyStatus.value.fiatRate - val (fiatValue, fiatDecimal) = if (value.isEmpty()) { - "" to BigDecimal.ZERO - } else { - val fiatDecimal = fiatRate?.multiply(cryptoDecimal) ?: BigDecimal.ZERO - val fiatValue = fiatDecimal.parseBigDecimal(FIAT_DECIMALS) - fiatValue to fiatDecimal + val (fiatValue, fiatDecimal) = when { + fiatRate.isNullOrZero() -> "" to null + value.isEmpty() -> "" to BigDecimal.ZERO + else -> { + val fiatDecimal = fiatRate?.multiply(cryptoDecimal) + val fiatValue = fiatDecimal?.parseBigDecimal(FIAT_DECIMALS).orEmpty() + fiatValue to fiatDecimal + } } val isDoneActionEnabled = !cryptoDecimal.isZero() return SendTextField.AmountField( @@ -48,7 +53,9 @@ internal class SendAmountFieldConverter( imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, keyboardType = KeyboardType.Number, ), - keyboardActions = KeyboardActions(onDone = { clickIntents.onNextClick() }), + keyboardActions = KeyboardActions( + onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, + ), isFiatValue = false, cryptoAmount = cryptoAmount, fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()), @@ -60,7 +67,7 @@ internal class SendAmountFieldConverter( ) } - private fun getAppCurrencyAmount(fiatValue: BigDecimal, appCurrency: AppCurrency) = Amount( + private fun getAppCurrencyAmount(fiatValue: BigDecimal?, appCurrency: AppCurrency) = Amount( currencySymbol = appCurrency.symbol, value = fiatValue, decimals = FIAT_DECIMALS, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt index d39cd3df01..e1685ad0cf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt @@ -49,6 +49,7 @@ internal object AmountStatePreviewData { onValuePastedTriggerDismiss = {}, ), isSegmentedButtonsEnabled = true, + selectedButton = 0, ) val fiatAmountState = amountState.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 0a113ea474..4f05dd6c30 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -14,6 +14,8 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onBackClick() {} + override fun onCloseClick() {} + override fun onNextClick(isFromEdit: Boolean) {} override fun onPrevClick() {} @@ -58,7 +60,7 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onShareClick() {} - override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) {} + override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) {} override fun onNotificationCancel(clazz: Class) {} } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index ace8e220f8..b0b37b764d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -165,7 +165,11 @@ private fun SendingText( val fiatRate = feeState?.rate val fiatAmount = amountState?.amountTextField?.fiatAmount val feeFiat = fiatRate?.let { feeState.fee?.amount?.value?.multiply(it) } - val sendingFiat = feeFiat?.let { fiatAmount?.value?.plus(it) } + val sendingFiat = if (uiState.isSubtracted) { + fiatAmount?.value + } else { + feeFiat?.let { fiatAmount?.value?.plus(it) } + } if (feeFiat != null && sendingFiat != null) { val sendingValue = BigDecimalFormatter.formatFiatAmount( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index b289867885..5c7eba689d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.withIndex @Composable internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) { val snackbarHostState = remember { SnackbarHostState() } - BackHandler { uiState.clickIntents.onBackClick() } + BackHandler(onBack = uiState.clickIntents::onBackClick) Column( modifier = Modifier .fillMaxSize() @@ -88,17 +88,17 @@ private fun SendAppBar(uiState: SendUiState, currentState: SendUiCurrentScreen) } else { null } - val (backIcon, backClick) = when (currentState.type) { + val backIcon = when (currentState.type) { SendUiStateType.EditAmount, SendUiStateType.EditFee, SendUiStateType.EditRecipient, - -> R.drawable.ic_back_24 to uiState.clickIntents::onBackClick - else -> R.drawable.ic_close_24 to uiState.clickIntents::popBackStack + -> R.drawable.ic_back_24 + else -> R.drawable.ic_close_24 } AppBarWithBackButtonAndIcon( text = titleRes?.resolveReference(), subtitle = subtitleRes, - onBackClick = backClick, + onBackClick = uiState.clickIntents::onCloseClick, onIconClick = uiState.clickIntents::onQrCodeScanClick, backIconRes = backIcon, iconRes = iconRes, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt index 340f8e20ad..1b6315e1ea 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt @@ -29,6 +29,7 @@ internal fun LazyListScope.buttons( segmentedButtonConfig: PersistentList, clickIntents: SendClickIntents, isSegmentedButtonsEnabled: Boolean, + selectedButton: Int, ) { item( key = AMOUNT_BUTTONS_KEY, @@ -48,6 +49,7 @@ internal fun LazyListScope.buttons( hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clickIntents.onCurrencyChangeClick(it.isFiat) }, + initialSelectedItem = segmentedButtonConfig.getOrNull(selectedButton), isEnabled = isSegmentedButtonsEnabled, ) { SendAmountCurrencyButton( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index 44b5a0244d..01d23d4edb 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -36,6 +36,7 @@ internal fun SendAmountContent( segmentedButtonConfig = amountState.segmentedButtonConfig, clickIntents = clickIntents, isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled, + selectedButton = amountState.selectedButton, ) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt index 0a2a65c93e..c42e7e152d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt @@ -54,7 +54,7 @@ internal fun SendSpeedSelectorItem( onSelect = onSelect, modifier = modifier, preDot = getCryptoReference(amount, state.isFeeApproximate), - postDot = getFiatReference(amount, state.rate, state.appCurrency), + postDot = getFiatReference(amount?.value, state.rate, state.appCurrency), ellipsizeOffset = amount?.currencySymbol?.length, isSelected = content?.selectedFee == feeType, showDivider = showDivider, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt index 85ff055ed6..ab68759908 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt @@ -31,8 +31,8 @@ internal fun AmountBlock( val amount = amountState.amountTextField val cryptoAmount = BigDecimalFormatter.formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol) - val fiatAmount = BigDecimalFormatter.formatFiatEditableAmount( - fiatAmount = amount.fiatValue, + val fiatAmount = BigDecimalFormatter.formatFiatAmount( + fiatAmount = amount.fiatAmount.value, fiatCurrencySymbol = amount.fiatAmount.currencySymbol, fiatCurrencyCode = amountState.appCurrencyCode, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index 11423e7c73..30814b802f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -60,7 +60,7 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick titleRes = title, iconRes = icon, preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate), - postDot = feeAmount?.let { getFiatReference(it, feeState.rate, feeState.appCurrency) }, + postDot = getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency), ellipsizeOffset = feeAmount?.currencySymbol?.length, isSelected = true, showDivider = false, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt index 022d0c5711..e21fe8c0c5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt @@ -22,11 +22,11 @@ internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): Tex ) } -internal fun getFiatReference(amount: Amount?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? { - if (amount == null) return null +internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? { + if (value == null || rate == null) return null return stringReference( BigDecimalFormatter.formatFiatAmount( - fiatAmount = rate?.let { amount.value?.multiply(it) }, + fiatAmount = value.multiply(rate), fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index f0715cd50a..61c33cb023 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -14,6 +14,8 @@ internal interface SendClickIntents { fun onBackClick() + fun onCloseClick() + fun onNextClick(isFromEdit: Boolean = false) fun onPrevClick() @@ -65,7 +67,7 @@ internal interface SendClickIntents { fun onShareClick() - fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) + fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) fun onNotificationCancel(clazz: Class) // endregion 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 1d678b777e..0273e12490 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 @@ -47,7 +47,7 @@ import com.tangem.features.send.impl.navigation.InnerSendRouter import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.analytics.SendScreenSource -import com.tangem.features.send.impl.presentation.analytics.utils.SendOnNextScreenAnalyticSender +import com.tangem.features.send.impl.presentation.analytics.utils.SendScreenAnalyticSender 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.amount.AmountStateFactory @@ -174,9 +174,10 @@ internal class SendViewModel @Inject constructor( getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase, ) - private val sendOnNextScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) { - SendOnNextScreenAnalyticSender( + private val sendScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) { + SendScreenAnalyticSender( stateRouterProvider = Provider { stateRouter }, + currentStateProvider = Provider { uiState }, analyticsEventHandler = analyticsEventHandler, ) } @@ -464,7 +465,10 @@ internal class SendViewModel @Inject constructor( SendUiStateType.Fee, SendUiStateType.EditFee, -> loadFee() - SendUiStateType.Send -> sendIdleTimer = SystemClock.elapsedRealtime() + SendUiStateType.Send -> { + uiState = stateFactory.getIsAmountSubtractedState(isAmountSubtractAvailable) + sendIdleTimer = SystemClock.elapsedRealtime() + } else -> Unit } } @@ -498,10 +502,21 @@ internal class SendViewModel @Inject constructor( stateRouter.onBackClick(isSuccess = uiState.sendState?.isSuccess == true) } + override fun onCloseClick() { + sendScreenAnalyticSender.sendOnClose() + when (stateRouter.currentState.value.type) { + SendUiStateType.EditAmount, + SendUiStateType.EditFee, + SendUiStateType.EditRecipient, + -> onBackClick() + else -> popBackStack() + } + } + override fun onNextClick(isFromEdit: Boolean) { val currentState = stateRouter.currentState.value uiState = stateFactory.syncEditStates(isFromEdit = isFromEdit) - sendOnNextScreenAnalyticSender.send(currentState.type, uiState) + sendScreenAnalyticSender.send(currentState.type, uiState) when (currentState.type) { SendUiStateType.Fee, SendUiStateType.EditFee, @@ -652,7 +667,7 @@ internal class SendViewModel @Inject constructor( private fun autoNextFromRecipient(type: EnterAddressSource?, isValidAddress: Boolean) { val isRecent = type == EnterAddressSource.RecentAddress - if (isRecent && isValidAddress) onNextClick() + if (isRecent && isValidAddress) onNextClick(stateRouter.isEditState) } // endregion @@ -687,6 +702,7 @@ internal class SendViewModel @Inject constructor( val isShowStatus = uiState.feeState?.fee == null if (isShowStatus) { uiState = feeStateFactory.onFeeOnLoadingState() + updateNotifications() } val result = callFeeUseCase()?.fold( ifRight = { @@ -722,14 +738,12 @@ internal class SendViewModel @Inject constructor( val recipientState = uiState.getRecipientState(isFromConfirmation) ?: return null val amount = amountState.amountTextField.cryptoAmount.value ?: return null - return feeCryptoCurrencyStatus?.let { feeCurrencyStatus -> - getFeeUseCase.invoke( - amount = amount, - destination = recipientState.addressTextField.value, - userWallet = userWallet, - cryptoCurrency = feeCurrencyStatus.currency, - ) - } + return getFeeUseCase.invoke( + amount = amount, + destination = recipientState.addressTextField.value, + userWallet = userWallet, + cryptoCurrency = cryptoCurrencyStatus.currency, + ) } // endregion @@ -783,8 +797,8 @@ internal class SendViewModel @Inject constructor( analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) } - override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) { - uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals)) + override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) { + uiState = amountStateFactory.getOnAmountReducedState(reduceAmountBy) uiState = sendNotificationFactory.dismissNotificationState(clazz) feeReload() } @@ -806,6 +820,7 @@ internal class SendViewModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, amountValue = amountValue, feeValue = feeValue, + reduceAmountBy = uiState.sendState?.reduceAmountBy, ) viewModelScope.launch(dispatchers.main) {