From c881e8d6dbd6fafe569e1c4fe12a29cc8643206d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 May 2024 17:36:26 +0500 Subject: [PATCH 01/30] Updated on 2026-08-14 --- .../ui/fee/SendSpeedSelectorItem.kt | 49 ++++++++++++++----- .../impl/presentation/ui/send/FeeBlock.kt | 2 +- 2 files changed, 37 insertions(+), 14 deletions(-) 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 c42e7e152d..d9c609a5ca 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 @@ -4,15 +4,14 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.animation.* import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.res.TangemTheme @@ -59,25 +58,50 @@ internal fun SendSpeedSelectorItem( isSelected = content?.selectedFee == feeType, showDivider = showDivider, ) - SendSpeedSelectorItemError(isError = feeSelectorState is FeeSelectorState.Error) + FeeLoading(feeSelectorState) + FeeError(feeSelectorState) } } } @Composable -private fun SendSpeedSelectorItemError(isError: Boolean) { +private fun FeeLoading(feeSelectorState: FeeSelectorState) { Row { SpacerWMax() AnimatedVisibility( - visible = isError, - label = "Error state indication animation", - enter = fadeIn(), - exit = fadeOut(), + visible = feeSelectorState == FeeSelectorState.Loading, + label = "Fee Loading State Change", + modifier = Modifier.align(Alignment.CenterVertically), + ) { + RectangleShimmer( + radius = TangemTheme.dimens.radius3, + modifier = Modifier + .padding( + vertical = TangemTheme.dimens.spacing18, + horizontal = TangemTheme.dimens.spacing12, + ) + .size( + height = TangemTheme.dimens.size12, + width = TangemTheme.dimens.size90, + ), + ) + } + } +} + +@Composable +private fun FeeError(feeSelectorState: FeeSelectorState) { + Row { + SpacerWMax() + AnimatedVisibility( + visible = feeSelectorState == FeeSelectorState.Error, + label = "Fee Error State Change", + modifier = Modifier.align(Alignment.CenterVertically), ) { Text( text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, - style = TangemTheme.typography.body2, color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body2, modifier = Modifier .padding( vertical = TangemTheme.dimens.spacing14, @@ -101,10 +125,9 @@ private fun FeeSelectorState.Content.getAmount(feeType: FeeType): Amount? { private fun FeeSelectorState.Content?.getDividerAndVisibility(feeType: FeeType): Pair { val hasCustomValues = !this?.customValues.isNullOrEmpty() val isNotSingle = this?.fees !is TransactionFee.Single - val isLoaded = this?.fees != null return when (feeType) { FeeType.Slow -> true to isNotSingle - FeeType.Market -> isNotSingle to isLoaded + FeeType.Market -> isNotSingle to true FeeType.Fast -> hasCustomValues to isNotSingle FeeType.Custom -> false to hasCustomValues } 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 30814b802f..54ef73296a 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 @@ -32,7 +32,7 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(TangemTheme.colors.background.action) - .clickable(enabled = !isSuccess && feeState.fee != null) { onClick() } + .clickable(enabled = !isSuccess, onClick = onClick) .padding(TangemTheme.dimens.spacing12), ) { Text( From 770b175ab6d9bc2fc7dbbaa6e38bbdbcd4b2001e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 May 2024 19:29:09 +0500 Subject: [PATCH 02/30] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 5bdbdc3921..75242285e6 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-612" +tangemBlockchainSdk = "release-app_5.9.1-615" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 1415297f58667ebb2c492063f4e834f3ccb75fb6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 11:18:10 +0300 Subject: [PATCH 03/30] Updated on 2026-08-14 --- .../presentation/ui/SendNavigationButtons.kt | 22 +++++++++---------- .../send/impl/presentation/ui/SendScreen.kt | 1 + .../presentation/ui/amount/AmountField.kt | 7 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) 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 b0b37b764d..e858d9b150 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 @@ -1,12 +1,10 @@ package com.tangem.features.send.impl.presentation.ui import androidx.compose.animation.* +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable -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.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -21,18 +19,19 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.tangem.core.ui.R +import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.SecondaryButtonIconStart import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.buttons.common.TangemButton import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults +import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.extensions.shareText import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType -import kotlinx.coroutines.delay @Composable internal fun SendNavigationButtons( @@ -144,11 +143,12 @@ private fun SendingText( modifier: Modifier = Modifier, ) { var isVisibleProxy by remember { mutableStateOf(isVisible) } + val keyboard by keyboardAsState() - // text appearance delay for smooth screen transitions - LaunchedEffect(key1 = isVisible) { - if (isVisible) { - delay(timeMillis = 400) + // the text should appear when the keyboard is closed + LaunchedEffect(isVisible, keyboard) { + if (isVisible && keyboard is Keyboard.Opened) { + return@LaunchedEffect } isVisibleProxy = isVisible } @@ -156,8 +156,8 @@ private fun SendingText( AnimatedVisibility( visible = isVisibleProxy, modifier = modifier, - enter = slideInVertically().plus(fadeIn()), - exit = slideOutVertically().plus(fadeOut()), + enter = slideInVertically() + fadeIn(), + exit = fadeOut(tween(durationMillis = 300)), label = "Animate show sending state text", ) { val amountState = uiState.getAmountState(isEditState) 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 5c7eba689d..d6529709b7 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 @@ -134,6 +134,7 @@ private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentS Box(modifier = modifier.fillMaxSize()) { AnimatedContent( targetState = currentStateProxy, + contentAlignment = Alignment.TopCenter, label = "Send Scree Navigation", transitionSpec = { val direction = if (initialState.type.ordinal < targetState.type.ordinal) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index 640189da93..8f7a69c8c1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -22,7 +22,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.rememberDecimalFormat import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import kotlinx.coroutines.job +import kotlinx.coroutines.delay @Composable internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: String) { @@ -66,9 +66,8 @@ internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: ) LaunchedEffect(key1 = Unit) { - this.coroutineContext.job.invokeOnCompletion { - requester.requestFocus() - } + delay(timeMillis = 200) + requester.requestFocus() } AmountSecondary(sendField, appCurrencyCode) From 2377322a3424f87b0adce9c45a390a1d5f9a4b9e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 12:56:05 +0500 Subject: [PATCH 04/30] Updated on 2026-08-14 --- .../impl/presentation/state/SendUiState.kt | 1 + .../state/amount/AmountStateFactory.kt | 14 ++++- ...rter.kt => SendAmountReduceByConverter.kt} | 2 +- .../amount/SendAmountReduceToConverter.kt | 60 +++++++++++++++++++ .../confirm/SendConfirmStateConverter.kt | 1 + .../state/confirm/SendNotificationFactory.kt | 9 ++- .../state/previewdata/SendClickIntentsStub.kt | 6 +- .../viewmodel/SendClickIntents.kt | 6 +- .../presentation/viewmodel/SendViewModel.kt | 13 +++- 9 files changed, 101 insertions(+), 11 deletions(-) rename features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/{SendAmountReducedConverter.kt => SendAmountReduceByConverter.kt} (98%) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt 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 c7aa7ddff0..84adf93793 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 @@ -142,6 +142,7 @@ internal sealed class SendStates { val txUrl: String, val ignoreAmountReduce: Boolean, val reduceAmountBy: BigDecimal?, + val reduceAmountTo: 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/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index 6ff84cf35d..715c696b71 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 @@ -45,8 +45,15 @@ internal class AmountStateFactory( currentStateProvider = currentStateProvider, ) } - private val amountReducedConverter by lazy { - SendAmountReducedConverter( + private val amountReduceByConverter by lazy { + SendAmountReduceByConverter( + stateRouterProvider = stateRouterProvider, + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + private val amountReduceToConverter by lazy { + SendAmountReduceToConverter( stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, @@ -55,7 +62,8 @@ internal class AmountStateFactory( fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) - fun getOnAmountReducedState(reduceAmountBy: BigDecimal) = amountReducedConverter.convert(reduceAmountBy) + fun getOnAmountReduceByState(reduceAmountBy: BigDecimal) = amountReduceByConverter.convert(reduceAmountBy) + fun getOnAmountReduceToState(reduceAmountTo: BigDecimal) = amountReduceToConverter.convert(reduceAmountTo) fun getOnMaxAmountClick(): SendUiState { return amountFieldMaxAmountConverter.convert(Unit) 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/SendAmountReduceByConverter.kt similarity index 98% rename from features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt rename to features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt index d624608b14..59dadf2844 100644 --- 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/SendAmountReduceByConverter.kt @@ -12,7 +12,7 @@ import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero import java.math.BigDecimal -internal class SendAmountReducedConverter( +internal class SendAmountReduceByConverter( private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt new file mode 100644 index 0000000000..5b40022bbb --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt @@ -0,0 +1,60 @@ +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 SendAmountReduceToConverter( + 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 cryptoValue = value.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 value.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 = value), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + keyboardOptions = KeyboardOptions( + imeAction = getKeyboardAction(isExceedBalance, value), + 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/confirm/SendConfirmStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt index 254a979807..6e6e67132b 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 @@ -17,6 +17,7 @@ internal class SendConfirmStateConverter( txUrl = "", ignoreAmountReduce = false, reduceAmountBy = null, + reduceAmountTo = 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 c98336636c..4f4766450f 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 @@ -166,8 +166,8 @@ internal class SendNotificationFactory( ), onConfirmClick = { clickIntents.onAmountReduceClick( - utxoLimit.maxAmount, - SendNotification.Error.TransactionLimitError::class.java, + reduceAmountTo = utxoLimit.maxAmount, + clazz = SendNotification.Error.TransactionLimitError::class.java, ) }, ), @@ -226,7 +226,10 @@ internal class SendNotificationFactory( SendNotification.Warning.HighFeeError( amount = threshold.toPlainString(), onConfirmClick = { - clickIntents.onAmountReduceClick(threshold, SendNotification.Warning.HighFeeError::class.java) + clickIntents.onAmountReduceClick( + reduceAmountBy = threshold, + clazz = SendNotification.Warning.HighFeeError::class.java, + ) }, onCloseClick = { clickIntents.onNotificationCancel(SendNotification.Warning.HighFeeError::class.java) 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 4f05dd6c30..0fedb031f2 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 @@ -60,7 +60,11 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onShareClick() {} - override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) {} + override fun onAmountReduceClick( + reduceAmountBy: BigDecimal?, + reduceAmountTo: 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/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 61c33cb023..32ba81ce73 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 @@ -67,7 +67,11 @@ internal interface SendClickIntents { fun onShareClick() - fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) + fun onAmountReduceClick( + reduceAmountBy: BigDecimal? = null, + reduceAmountTo: BigDecimal? = null, + 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 0273e12490..5a6260d448 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 @@ -797,8 +797,17 @@ internal class SendViewModel @Inject constructor( analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) } - override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) { - uiState = amountStateFactory.getOnAmountReducedState(reduceAmountBy) + override fun onAmountReduceClick( + reduceAmountBy: BigDecimal?, + reduceAmountTo: BigDecimal?, + clazz: Class, + ) { + uiState = when { + reduceAmountBy != null -> amountStateFactory.getOnAmountReduceByState(reduceAmountBy) + reduceAmountTo != null -> amountStateFactory.getOnAmountReduceToState(reduceAmountTo) + else -> return + } + uiState = sendNotificationFactory.dismissNotificationState(clazz) feeReload() } From eeca735cc0a4d5f8cbc47e94c5ef07507365932e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 13:07:32 +0100 Subject: [PATCH 05/30] Updated on 2026-08-14 --- .../com/tangem/core/analytics/Analytics.kt | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt index 3c12cf80a8..790de00ea3 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt @@ -4,6 +4,8 @@ import com.tangem.core.analytics.api.* import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import java.util.concurrent.Executors /** @@ -22,6 +24,7 @@ object Analytics : GlobalAnalyticsEventHandler { private val handlers = mutableMapOf() private val paramsInterceptors = mutableMapOf() private val analyticsFilters = mutableSetOf() + private val analyticsMutex = Mutex() private val analyticsHandlers: List get() = handlers.values.toList() @@ -55,23 +58,26 @@ object Analytics : GlobalAnalyticsEventHandler { event.params = applyParamsInterceptors(event) val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) } - when { - eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(event) } - eventFilter.canBeSent(event) -> { - analyticsHandlers - .filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) } - .forEach { handler -> handler.send(event) } + analyticsMutex.withLock { + when { + eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(event) } + eventFilter.canBeSent(event) -> { + analyticsHandlers + .filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) } + .forEach { handler -> handler.send(event) } + } } } } } - private fun applyParamsInterceptors(event: AnalyticsEvent): MutableMap { + private suspend fun applyParamsInterceptors(event: AnalyticsEvent): MutableMap { val interceptedParams = event.params.toMutableMap() - paramsInterceptors.values - .filter { it.canBeAppliedTo(event) } - .forEach { it.intercept(interceptedParams) } - + analyticsMutex.withLock { + paramsInterceptors.values + .filter { it.canBeAppliedTo(event) } + .forEach { it.intercept(interceptedParams) } + } return interceptedParams } From 5be919da589d6cba9e0f448adf62c28ca3453015 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:02:18 +0500 Subject: [PATCH 06/30] Updated on 2026-08-14 --- .../features/send/impl/presentation/ui/send/RecipientBlock.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt index 0801e63cd6..e3b92db56c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt @@ -35,6 +35,7 @@ internal fun RecipientBlock( Column( modifier = Modifier + .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(backgroundColor) .clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick) From bca687b217149e8ad4c00619c19416016677d881 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:02:45 +0500 Subject: [PATCH 07/30] Updated on 2026-08-14 --- .../impl/presentation/utils/FormatterUtils.kt | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) 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 e21fe8c0c5..73ef044814 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 @@ -5,8 +5,13 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.combinedReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.utils.BigDecimalFormatter.EMPTY_BALANCE_SIGN import com.tangem.domain.appcurrency.model.AppCurrency import java.math.BigDecimal +import java.math.RoundingMode + +private const val FIAT_DECIMALS = 2 +private const val FEE_MINIMUM_VALUE = 0.01 internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? { if (amount == null) return null @@ -24,11 +29,31 @@ internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): Tex internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? { if (value == null || rate == null) return null - return stringReference( + val formattedFiat = getFiatString(value = value, rate = rate, appCurrency = appCurrency) + return stringReference(formattedFiat) +} + +internal fun getFiatString(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): String { + if (value == null || rate == null) return EMPTY_BALANCE_SIGN + val feeValue = value.multiply(rate) + val scaled = feeValue.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO + val formattedValue = if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) { + buildString { + append(BigDecimalFormatter.CAN_BE_LOWER_SIGN) + append( + BigDecimalFormatter.formatFiatAmount( + fiatAmount = BigDecimal(FEE_MINIMUM_VALUE), + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ), + ) + } + } else { BigDecimalFormatter.formatFiatAmount( - fiatAmount = value.multiply(rate), + fiatAmount = feeValue, fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, - ), - ) + ) + } + return formattedValue } \ No newline at end of file From 467d62aa4fc50e767a919af5c50be32c4c02d9f6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:06:38 +0500 Subject: [PATCH 08/30] Updated on 2026-08-14 --- .../impl/presentation/state/SendNotification.kt | 6 +++++- .../state/confirm/SendNotificationFactory.kt | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index 3334f3faaa..2df3421a1a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -94,9 +94,13 @@ internal sealed class SendNotification(val config: NotificationConfig) { }, ) - data class ExistentialDeposit(val deposit: String) : Error( + data class ExistentialDeposit(val deposit: String, val onConfirmClick: () -> Unit) : Error( title = resourceReference(R.string.send_notification_existential_deposit_title), subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)), + buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = resourceReference(R.string.send_notification_existential_deposit_button, wrappedList(deposit)), + onClick = onConfirmClick, + ), ) } 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 4f4766450f..9ac1b7a5a7 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 @@ -7,6 +7,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase @@ -19,6 +20,8 @@ import com.tangem.features.send.impl.R 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.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.utils.getFiatString import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.Provider @@ -30,7 +33,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import java.math.BigDecimal -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class SendNotificationFactory( private val cryptoCurrencyStatusProvider: Provider, private val coinCryptoCurrencyStatusProvider: Provider, @@ -193,13 +196,19 @@ internal class SendNotificationFactory( cryptoCurrency.network, ) val diff = balance.minus(spendingAmount) - if (currencyDeposit != null && currencyDeposit > diff) { + if (currencyDeposit != null && diff >= BigDecimal.ZERO && currencyDeposit > diff) { add( SendNotification.Error.ExistentialDeposit( - BigDecimalFormatter.formatCryptoAmountUncapped( + deposit = BigDecimalFormatter.formatCryptoAmountUncapped( cryptoAmount = currencyDeposit, cryptoCurrency = cryptoCurrency, ), + onConfirmClick = { + clickIntents.onAmountReduceClick( + reduceAmountBy = currencyDeposit, + clazz = SendNotification.Error.ExistentialDeposit::class.java, + ) + }, ), ) } From a0c9cb20e5205a17c05a9d8707d6be9b0fa3a6f7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:07:07 +0500 Subject: [PATCH 09/30] Updated on 2026-08-14 --- .../presentation/state/SendNotification.kt | 7 ++-- .../state/confirm/SendNotificationFactory.kt | 35 ++++++++++++++++--- .../presentation/viewmodel/SendViewModel.kt | 1 + 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index 2df3421a1a..a22b3ee620 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -153,9 +153,12 @@ internal sealed class SendNotification(val config: NotificationConfig) { ), ) - data object FeeCoverageNotification : Warning( + data class FeeCoverageNotification(val cryptoAmount: String, val fiatAmount: String) : Warning( title = resourceReference(R.string.send_network_fee_warning_title), - subtitle = resourceReference(R.string.swapping_network_fee_warning_content), + subtitle = resourceReference( + R.string.send_network_fee_warning_content, + wrappedList(cryptoAmount, fiatAmount), + ), ) } } \ No newline at end of file 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 9ac1b7a5a7..4b30a9466f 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 @@ -42,6 +42,7 @@ internal class SendNotificationFactory( private val currencyChecksRepository: CurrencyChecksRepository, private val stateRouterProvider: Provider, private val isSubtractAvailableProvider: Provider, + private val appCurrencyProvider: Provider, private val clickIntents: SendClickIntents, private val analyticsEventHandler: AnalyticsEventHandler, private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, @@ -80,7 +81,11 @@ internal class SendNotificationFactory( addTransactionLimitErrorNotification(feeValue, sendingAmount) // warnings addExistentialWarningNotification(feeValue, amountValue) - addFeeCoverageNotification(isFeeCoverage) + addFeeCoverageNotification( + isFeeCoverage = isFeeCoverage, + amountField = amountState.amountTextField, + sendingValue = sendingAmount, + ) addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce) addTooHighNotification(feeState.feeSelectorState) addTooLowNotification(feeState) @@ -214,10 +219,32 @@ internal class SendNotificationFactory( } } - private fun MutableList.addFeeCoverageNotification(sendingAmount: Boolean) { - if (sendingAmount) { + private fun MutableList.addFeeCoverageNotification( + isFeeCoverage: Boolean, + amountField: SendTextField.AmountField, + sendingValue: BigDecimal, + ) { + if (isFeeCoverage) { analyticsEventHandler.send(SendAnalyticEvents.NoticeFeeCoverage) - add(SendNotification.Warning.FeeCoverageNotification) + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val cryptoCurrency = cryptoCurrencyStatus.currency + val fiatRate = cryptoCurrencyStatus.value.fiatRate + val amountValue = amountField.cryptoAmount.value ?: return + + val cryptoDiff = amountValue.minus(sendingValue) + add( + SendNotification.Warning.FeeCoverageNotification( + cryptoAmount = BigDecimalFormatter.formatCryptoAmountUncapped( + cryptoAmount = cryptoDiff, + cryptoCurrency = cryptoCurrency, + ), + fiatAmount = getFiatString( + value = cryptoDiff, + rate = fiatRate, + appCurrency = appCurrencyProvider(), + ), + ), + ) } } 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 5a6260d448..5410b54f17 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 @@ -168,6 +168,7 @@ internal class SendViewModel @Inject constructor( userWalletProvider = Provider { userWallet }, stateRouterProvider = Provider { stateRouter }, isSubtractAvailableProvider = Provider { isAmountSubtractAvailable }, + appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), currencyChecksRepository = currencyChecksRepository, clickIntents = this, analyticsEventHandler = analyticsEventHandler, From f7ed6f88df4a9bf043e0400493682e73a4a65efc Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:08:09 +0500 Subject: [PATCH 10/30] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 12 +++++++----- core/res/src/main/res/values/strings.xml | 13 +++++++------ gradle/dependencies.toml | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 298dffbcda..f12dc6ee9c 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -470,16 +470,17 @@ Комиссия не превысит Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. Допустим ввод только цифр - Сумма отправки будет уменьшена на %1$s для покрытия выбранного уровня комиссии. Получателю будет отправлено %2$s. + Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии Покрытие сетевой комиссии Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса Недостаточно средств - Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, убедитесь, что остаток после отправки будет не менее %s. + Оставить %s + Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. Экзистенциальный депозит Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Установлена высокая комиссия - Комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. - Комиссия увеличилась + Ввиду особенности сети Tezos комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. + Комиссия повышена Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению Недопустимая сумма Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. @@ -500,7 +501,8 @@ Отправить Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. Мои кошельки - Это способ измерения комиссии за отправку биткоин-транзакции. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый байт данных в транзакции. Чем выше это число, тем быстрее будет обработана транзакция сетью. + Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. + Сатоши / вбайт Отправка Нажмите на любое поле, чтобы изменить его Отправка %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 257e2e4adc..1b3c413136 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -467,16 +467,17 @@ Max fee The fee that will be charged for your transaction. You can set your own value. Numbers only for Destination Tag - Sending amount will be reduced by %1$s to cover the selected commission level. The recipient will get %2$s. + Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level Network fee coverage Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance Total exceeds balance - The account will be wiped from the blockchain if a balance goes below the existential deposit. Please ensure that the remaining balance after sending will not be less than %s. + Leave %s + The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. Existential deposit The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. Custom fee is high - The fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. - Fee is increased + Due to the peculiarities of the Tezos network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. + The fee is higher The included commission exceeds the transfer amount, leading to a negative value Invalid amount The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. @@ -499,8 +500,8 @@ Send to A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds My wallets - The fee for a Bitcoin transaction is measured by the number of the smallest Bitcoin unit (Satoshi) per byte of data. The higher this number, the faster the transaction will be processed. - Satoshi per vbyte + A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. + Satoshi / vByte Sending... Tap any field to change it Send %s diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 75242285e6..80bda90466 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9.1-615" +tangemBlockchainSdk = "release-app_5.10-618" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From f5c3f4fbbf05a3541c463d3662675960384528bc Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 6 May 2024 23:02:37 +0800 Subject: [PATCH 11/30] Updated on 2026-08-14 --- .../tangem/tap/di/domain/CardDomainModule.kt | 8 +++++++ .../DefaultDeleteSavedAccessCodesUseCase.kt | 22 +++++++++++++++++++ .../card/DeleteSavedAccessCodesUseCase.kt | 8 +++++++ .../intents/WalletCardClickIntents.kt | 19 ++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 084134986d..7a05bef47d 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -8,6 +8,8 @@ import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -77,4 +79,10 @@ internal object CardDomainModule { ): GetExtendedPublicKeyForCurrencyUseCase { return GetExtendedPublicKeyForCurrencyUseCase(derivationsRepository) } + + @Provides + @ViewModelScoped + fun provideDeleteSavedAccessCodesUseCase(tangemSdkManager: TangemSdkManager): DeleteSavedAccessCodesUseCase { + return DefaultDeleteSavedAccessCodesUseCase(tangemSdkManager) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt new file mode 100644 index 0000000000..b0fadb5bcf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt @@ -0,0 +1,22 @@ +package com.tangem.tap.domain.card + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.domain.card.DeleteSavedAccessCodesUseCase +import com.tangem.tap.domain.TangemSdkManager + +internal class DefaultDeleteSavedAccessCodesUseCase( + private val tangemSdkManager: TangemSdkManager, +) : DeleteSavedAccessCodesUseCase { + + override suspend fun invoke(cardId: String): Either { + tangemSdkManager.deleteSavedUserCodes(setOf(cardId)) + .doOnFailure { return it.left() } + .doOnSuccess { return Unit.right() } + + return Unit.right() + } +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt new file mode 100644 index 0000000000..dc118e8f15 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.card + +import arrow.core.Either + +interface DeleteSavedAccessCodesUseCase { + + suspend operator fun invoke(cardId: String): Either +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index 7e47a2a490..10d52a0047 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -1,8 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.card.DeleteSavedAccessCodesUseCase +import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase +import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.UpdateWalletUseCase import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader @@ -32,9 +36,13 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, private val walletScreenContentLoader: WalletScreenContentLoader, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val updateWalletUseCase: UpdateWalletUseCase, private val deleteWalletUseCase: DeleteWalletUseCase, + private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val reduxStateHolder: ReduxStateHolder, private val dispatchers: CoroutineDispatcherProvider, ) : BaseWalletClickIntents(), WalletCardClickIntents { @@ -72,7 +80,18 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) { viewModelScope.launch(dispatchers.main) { walletScreenContentLoader.cancel(userWalletId) + + val deletedUserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch + + deleteSavedAccessCodesUseCase(cardId = deletedUserWallet.cardId) + .onLeft { Timber.e(it.toString()) } + deleteWalletUseCase(userWalletId) + .onRight { + getSelectedWalletSyncUseCase().getOrNull()?.let { + reduxStateHolder.onUserWalletSelected(it) + } + } .onLeft { Timber.e(it.toString()) } } } From 12f396efefcdf9085f86bea10113023492d45e68 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 7 May 2024 18:00:00 +0500 Subject: [PATCH 12/30] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 3 +- core/res/src/main/res/values/strings.xml | 3 +- .../presentation/state/SendNotification.kt | 5 +- .../state/confirm/SendNotificationFactory.kt | 1 + .../presentation/ui/SendNavigationButtons.kt | 23 +++++---- .../impl/presentation/utils/FormatterUtils.kt | 19 ++++---- .../tangem/feature/swap/ui/StateBuilder.kt | 47 +++++++++++++++---- 7 files changed, 69 insertions(+), 32 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index f12dc6ee9c..e79cca7b1b 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -102,6 +102,7 @@ Заблокирован Основная сеть Сетевая комиссия + Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии Далее Нет Нет адреса @@ -479,7 +480,7 @@ Экзистенциальный депозит Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Установлена высокая комиссия - Ввиду особенности сети Tezos комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. + Ввиду особенности сети %1$s комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %2$s. Комиссия повышена Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению Недопустимая сумма diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 1b3c413136..a01c0c63ef 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -101,6 +101,7 @@ Locked Main network Network fee + Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level Next No No address @@ -476,7 +477,7 @@ Existential deposit The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. Custom fee is high - Due to the peculiarities of the Tezos network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. + Due to the peculiarities of the %1$s network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %2$s. The fee is higher The included commission exceeds the transfer amount, leading to a negative value Invalid amount diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index a22b3ee620..c59c030f46 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -119,12 +119,13 @@ internal sealed class SendNotification(val config: NotificationConfig) { ), ) { data class HighFeeError( + val currencyName: String, val amount: String, val onConfirmClick: () -> Unit, val onCloseClick: () -> Unit, ) : Warning( title = resourceReference(R.string.send_notification_high_fee_title), - subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(amount)), + subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(currencyName, amount)), buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( text = resourceReference(R.string.send_notification_reduce_by, wrappedList(amount)), onClick = onConfirmClick, @@ -156,7 +157,7 @@ internal sealed class SendNotification(val config: NotificationConfig) { data class FeeCoverageNotification(val cryptoAmount: String, val fiatAmount: String) : Warning( title = resourceReference(R.string.send_network_fee_warning_title), subtitle = resourceReference( - R.string.send_network_fee_warning_content, + R.string.common_network_fee_warning_content, wrappedList(cryptoAmount, fiatAmount), ), ) 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 4b30a9466f..916388ae26 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 @@ -260,6 +260,7 @@ internal class SendNotificationFactory( if (!ignoreAmountReduce && isTotalBalance && isTezos) { add( SendNotification.Warning.HighFeeError( + currencyName = cryptoCurrencyStatus.currency.name, amount = threshold.toPlainString(), onConfirmClick = { clickIntents.onAmountReduceClick( 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 e858d9b150..c4f1b95bdf 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 @@ -4,7 +4,10 @@ import androidx.compose.animation.* import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +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.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -28,10 +31,10 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.extensions.shareText import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen 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.utils.getFiatFormatted @Composable internal fun SendNavigationButtons( @@ -172,15 +175,15 @@ private fun SendingText( } if (feeFiat != null && sendingFiat != null) { - val sendingValue = BigDecimalFormatter.formatFiatAmount( - fiatAmount = sendingFiat, - fiatCurrencyCode = feeState.appCurrency.code, - fiatCurrencySymbol = feeState.appCurrency.symbol, + val sendingValue = getFiatFormatted( + value = sendingFiat, + currencySymbol = feeState.appCurrency.symbol, + currencyCode = feeState.appCurrency.code, ) - val feeValue = BigDecimalFormatter.formatFiatAmount( - fiatAmount = feeFiat, - fiatCurrencyCode = feeState.appCurrency.code, - fiatCurrencySymbol = feeState.appCurrency.symbol, + val feeValue = getFiatFormatted( + value = feeState.fee?.amount?.value, + currencySymbol = feeState.appCurrency.symbol, + currencyCode = feeState.appCurrency.code, ) Text( text = stringResource(id = R.string.send_summary_transaction_description, sendingValue, feeValue), 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 73ef044814..8854274b17 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 @@ -36,24 +36,27 @@ internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency internal fun getFiatString(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): String { if (value == null || rate == null) return EMPTY_BALANCE_SIGN val feeValue = value.multiply(rate) - val scaled = feeValue.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO - val formattedValue = if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) { + return getFiatFormatted(feeValue, appCurrency.code, appCurrency.symbol) +} + +internal fun getFiatFormatted(value: BigDecimal?, currencyCode: String, currencySymbol: String): String { + val scaled = value?.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO + return if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) { buildString { append(BigDecimalFormatter.CAN_BE_LOWER_SIGN) append( BigDecimalFormatter.formatFiatAmount( fiatAmount = BigDecimal(FEE_MINIMUM_VALUE), - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, + fiatCurrencyCode = currencyCode, + fiatCurrencySymbol = currencySymbol, ), ) } } else { BigDecimalFormatter.formatFiatAmount( - fiatAmount = feeValue, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, + fiatAmount = value, + fiatCurrencyCode = currencyCode, + fiatCurrencySymbol = currencySymbol, ) } - return formattedValue } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index c3fd4f4fd5..1147346e65 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -218,6 +218,7 @@ internal class StateBuilder( quoteModel = quoteModel, fromToken = fromToken, ignoreAmountReduce = uiStateHolder.reduceAmountIgnore, + selectedFeeType = selectedFeeType, ) val feeState = createFeeState(quoteModel.txFee, selectedFeeType) val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus @@ -317,12 +318,13 @@ internal class StateBuilder( quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, ignoreAmountReduce: Boolean, + selectedFeeType: FeeType, ): List { val warnings = mutableListOf() maybeAddDomainWarnings(quoteModel, warnings, ignoreAmountReduce) maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings) maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken) - maybeAddNetworkFeeCoverageWarning(quoteModel, warnings) + maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType) maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings) maybeAddInsufficientFundsWarning(quoteModel, warnings) maybeAddTransactionInProgressWarning(quoteModel, warnings) @@ -461,18 +463,37 @@ internal class StateBuilder( private fun maybeAddNetworkFeeCoverageWarning( quoteModel: SwapState.QuotesLoadedState, warnings: MutableList, + selectedFeeType: FeeType, ) { when (quoteModel.preparedSwapConfigState.includeFeeInAmount) { - is IncludeFeeInAmount.Included -> + is IncludeFeeInAmount.Included -> { + val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return warnings.add( SwapWarning.GeneralWarning( - createNetworkFeeCoverageNotificationConfig(), + createNetworkFeeCoverageNotificationConfig( + quoteModel.fromTokenInfo.tokenAmount.getFormattedCryptoAmount( + quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency, + ), + fee.feeFiatFormatted, + ), ), ) + } else -> Unit } } + private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? { + return when (txFeeState) { + TxFeeState.Empty -> null + is TxFeeState.SingleFeeState -> txFeeState.fee + is TxFeeState.MultipleFeeState -> when (feeType) { + FeeType.NORMAL -> txFeeState.normalFee + FeeType.PRIORITY -> txFeeState.priorityFee + } + } + } + private fun maybeAddUnableCoverFeeWarning( quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, @@ -548,12 +569,12 @@ internal class StateBuilder( if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder val warnings = mutableListOf() warnings.add(getWarningForError(dataError, fromToken.cryptoCurrencyStatus.currency)) - if (includeFeeInAmount is IncludeFeeInAmount.Included) { - warnings.add( - SwapWarning.GeneralWarning( - createNetworkFeeCoverageNotificationConfig(), - ), + if (includeFeeInAmount is IncludeFeeInAmount.Included && uiStateHolder.fee is FeeItemState.Content) { + val feeCoverageNotification = createNetworkFeeCoverageNotificationConfig( + fromToken.tokenAmount.getFormattedCryptoAmount(fromToken.cryptoCurrencyStatus.currency), + uiStateHolder.fee.amountFiatFormatted, ) + warnings.add(SwapWarning.GeneralWarning(feeCoverageNotification)) } val providerState = getProviderStateForError( swapProvider = swapProvider, @@ -1397,10 +1418,16 @@ internal class StateBuilder( ) } - private fun createNetworkFeeCoverageNotificationConfig(): NotificationConfig { + private fun createNetworkFeeCoverageNotificationConfig( + cryptoAmount: String, + fiatAmount: String, + ): NotificationConfig { return NotificationConfig( title = resourceReference(R.string.send_network_fee_warning_title), - subtitle = resourceReference(R.string.swapping_network_fee_warning_content), + subtitle = resourceReference( + R.string.common_network_fee_warning_content, + wrappedList(cryptoAmount, fiatAmount), + ), iconResId = R.drawable.img_attention_20, ) } From 5e274d04824bdaf2b1865bcea68f8619ef937712 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 7 May 2024 19:55:00 +0400 Subject: [PATCH 13/30] Updated on 2026-08-14 --- .../repository/DelegatedKeystoreManager.kt | 11 ++++++++--- gradle/dependencies.toml | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt index 7e7a781189..3db56b3dcb 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt @@ -8,15 +8,20 @@ internal class DelegatedKeystoreManager( private val keystoreManagerProvider: Provider, ) : KeystoreManager { - override suspend fun get(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String): SecretKey? { - return keystoreManagerProvider().get(masterKeyConfig, keyAlias) + override suspend fun get( + masterKeyConfig: KeystoreManager.MasterKeyConfig, + keyAlias: String, + forceAuthentication: Boolean, + ): SecretKey? { + return keystoreManagerProvider().get(masterKeyConfig, keyAlias, forceAuthentication) } override suspend fun get( masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAliases: Set, + forceAuthentication: Boolean, ): Map { - return keystoreManagerProvider().get(masterKeyConfig, keyAliases) + return keystoreManagerProvider().get(masterKeyConfig, keyAliases, forceAuthentication) } override suspend fun store(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String, key: SecretKey) { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 80bda90466..303ac01722 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ leakcanary = "2.13" # region Tangem tangemBlockchainSdk = "release-app_5.10-618" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.9-343" +tangemCardSdk = "release-app_5.10-353" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From d16355e67ee5b6213e838638f79312147a2aea79 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 8 May 2024 20:06:14 +0500 Subject: [PATCH 14/30] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 2 ++ core/res/src/main/res/values/strings.xml | 2 ++ .../presentation/state/fee/custom/BitcoinCustomFeeConverter.kt | 2 +- .../presentation/state/fee/custom/EthereumCustomFeeConverter.kt | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index e79cca7b1b..533cc30161 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -446,11 +446,13 @@ Причина: %1$s\nКод: %2$s Транзакция не выполнена Сумма + Вы можете установить комиссию за транзакцию, изменив значение в поле Satoshi per vByte. %1$s, %2$s Адрес Код назначения Введите адрес Адрес совпадает с адресом кошелька + Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. Недопустимый Tag. Он не будет добавлен в транзакцию. Недопустимый Memo. Он не будет добавлен в транзакцию. Tag diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index a01c0c63ef..53b1c0d5b8 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -443,11 +443,13 @@ Amount Base fee Represents the part of the transaction fee that goes to the miner + You can set your transaction fee by adjusting the value in the Satoshi per vByte field. %1$s, %2$s Address Destination Tag Enter address Address is the same as wallet address + The fee that will be charged for your transaction. You can set your own value. Invalid Tag. It won\'t be added to the transaction. Invalid Memo. It won\'t be added to the transaction. Tag 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 a2a13e987a..62bf1460ad 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 @@ -47,7 +47,7 @@ internal class BitcoinCustomFeeConverter( keyboardType = KeyboardType.Number, ), title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_max_fee_footer), + footer = resourceReference(R.string.send_bitcoin_custom_fee_footer), label = getFiatReference( rate = feeCurrency?.fiatRate, value = feeValue, 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 6bed843250..d64f1c3d47 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 @@ -43,7 +43,7 @@ internal class EthereumCustomFeeConverter( keyboardType = KeyboardType.Number, ), title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_max_fee_footer), + footer = resourceReference(R.string.send_evm_custom_fee_footer), label = getFiatReference( rate = feeCurrency?.fiatRate, value = feeValue, From 9bdfaf096ee8bd235cb1e0782644a6cca7de3b06 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 May 2024 10:59:50 +0500 Subject: [PATCH 15/30] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/fields/SimpleTextField.kt | 5 ++++- .../impl/presentation/ui/recipient/TextFieldWithPaste.kt | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index ff2bf968ae..59128a93a9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -38,6 +38,7 @@ fun SimpleTextField( keyboardActions: KeyboardActions = KeyboardActions.Default, color: Color = TangemTheme.colors.text.primary1, textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color), + placeholderColor: Color = TangemTheme.colors.text.disabled, readOnly: Boolean = false, isValuePasted: Boolean = false, onValuePastedTriggerDismiss: () -> Unit = {}, @@ -108,6 +109,7 @@ fun SimpleTextField( value = value, textStyle = textStyle, textValue = textValue, + color = placeholderColor, ) }, modifier = modifier @@ -122,6 +124,7 @@ private fun SimpleTextPlaceholder( value: String, textStyle: TextStyle, textValue: @Composable () -> Unit, + color: Color = TangemTheme.colors.text.disabled, ) { Box { if (value.isBlank() && placeholder != null) { @@ -132,7 +135,7 @@ private fun SimpleTextPlaceholder( Text( text = it.resolveReference(), style = textStyle, - color = TangemTheme.colors.text.disabled, + color = color, ) } } 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 5c0ca33a84..c59617aac0 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 @@ -33,9 +33,10 @@ internal fun TextFieldWithPaste( ) { val (title, color) = when { isError && error != null -> error to TangemTheme.colors.text.warning - isReadOnly -> label to TangemTheme.colors.text.disabled + isReadOnly -> label to TangemTheme.colors.text.tertiary else -> label to TangemTheme.colors.text.secondary } + val placeholderColor = if (isReadOnly) TangemTheme.colors.text.tertiary else TangemTheme.colors.text.disabled FooterContainer(modifier, footer) { Box( modifier = Modifier @@ -59,6 +60,7 @@ internal fun TextFieldWithPaste( SimpleTextField( value = value, placeholder = placeholder, + placeholderColor = placeholderColor, onValueChange = onValueChange, readOnly = isReadOnly, modifier = Modifier From 250a539140242194e25429a9818ed156b8ccbdd3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 May 2024 15:35:46 +0500 Subject: [PATCH 16/30] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 8 ++ .../tokens/GetNetworkAddressesUseCase.kt | 24 ++++++ .../wallets/usecase/GetWalletsUseCase.kt | 9 +- .../presentation/viewmodel/SendViewModel.kt | 85 ++++++++----------- 4 files changed, 72 insertions(+), 54 deletions(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 81d84db2d2..662e68f7bb 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -351,4 +351,12 @@ internal object TokensDomainModule { ): RunPolkadotAccountHealthCheckUseCase { return RunPolkadotAccountHealthCheckUseCase(repository) } + + @Provides + @ViewModelScoped + fun provideGetNetworkStatusesUseCase(networksRepository: NetworksRepository): GetNetworkAddressesUseCase { + return GetNetworkAddressesUseCase( + networksRepository = networksRepository, + ) + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt new file mode 100644 index 0000000000..0cc768a333 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class GetNetworkAddressesUseCase( + internal val networksRepository: NetworksRepository, +) { + + operator fun invoke(userWalletId: UserWalletId, network: Network): Flow = + networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network)) + .map { networkStatuses -> + when (val networkStatus = networkStatuses.singleOrNull { it.network.id == network.id }?.value) { + is NetworkStatus.NoAccount -> networkStatus.address.defaultAddress.value + is NetworkStatus.Unreachable -> networkStatus.address?.defaultAddress?.value.orEmpty() + is NetworkStatus.Verified -> networkStatus.address.defaultAddress.value + else -> "" + } + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt index 3c589f1bcd..e40e284270 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.wallets.usecase import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull /** * Use case for getting list of user wallets @@ -14,7 +15,9 @@ import kotlinx.coroutines.flow.Flow class GetWalletsUseCase(private val walletsStateHolder: WalletsStateHolder) { @Throws(IllegalArgumentException::class) - operator fun invoke(): Flow> { - return requireNotNull(walletsStateHolder.userWalletsListManager).userWallets - } + operator fun invoke(): Flow> = + requireNotNull(walletsStateHolder.userWalletsListManager).userWallets + + @Throws(IllegalArgumentException::class) + suspend fun invokeSync(): List? = walletsStateHolder.userWalletsListManager?.userWallets?.firstOrNull() } \ No newline at end of file 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 5410b54f17..9f3fc4ecca 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 @@ -26,7 +26,6 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.GetFeeError @@ -59,8 +58,9 @@ 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.delay import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import timber.log.Timber import java.math.BigDecimal import java.util.Locale @@ -79,8 +79,8 @@ internal class SendViewModel @Inject constructor( private val getWalletsUseCase: GetWalletsUseCase, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, - private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, - private val getCryptoCurrencyStatusesSyncUseCase: GetCryptoCurrencyStatusesSyncUseCase, + private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, + private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase, private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase, private val getFeeUseCase: GetFeeUseCase, private val sendTransactionUseCase: SendTransactionUseCase, @@ -400,57 +400,40 @@ internal class SendViewModel @Inject constructor( } private fun getUserWallets() { - getWalletsUseCase() - .conflate() - .distinctUntilChanged() - .onEach { userWallets -> - coroutineScope { - runCatching { - userWallets - .filterNot { it.walletId == userWalletId || it.isLocked } - .map { wallet -> - async(dispatchers.io) { wallet.toAvailableWallet() } - }.awaitAll() - }.onSuccess { result -> - uiState = stateFactory.onLoadedWalletsList(wallets = result) - }.onFailure { - uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) - } - } - } - .flowOn(dispatchers.main) - .launchIn(viewModelScope) - } - - private suspend fun UserWallet.toAvailableWallet(): AvailableWallet? { - return if (!isMultiCurrency) { - val status = getCryptoCurrencyStatusSyncUseCase(walletId).getOrNull() - val address = status?.value?.networkAddress.takeIf { - status?.currency?.network?.id == cryptoCurrency.network.id && - status.currency.network.derivationPath !is Network.DerivationPath.Custom - } - address?.let { - AvailableWallet( - name = name, - address = it.defaultAddress.value, - ) - } - } else { - val statuses = getCryptoCurrencyStatusesSyncUseCase(walletId).getOrNull() - val walletCurrency = statuses?.firstOrNull { - it.currency.network.id == cryptoCurrency.network.id && - it.currency.network.derivationPath !is Network.DerivationPath.Custom - } - val address = walletCurrency?.value?.networkAddress - address?.let { - AvailableWallet( - name = name, - address = it.defaultAddress.value, - ) + viewModelScope.launch(dispatchers.main) { + runCatching { + getWalletsUseCase.invokeSync() + ?.toAvailableWallets() + .orEmpty() + }.onSuccess { result -> + combine(*result.toTypedArray()) { it } + .onEach { uiState = stateFactory.onLoadedWalletsList(wallets = it.toList()) } + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + }.onFailure { + uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) } } } + private suspend fun List.toAvailableWallets(): List> = + filterNot { it.walletId == userWalletId || it.isLocked } + .mapNotNull { wallet -> + val status = if (!wallet.isMultiCurrency) { + getCryptoCurrencyUseCase(wallet.walletId).getOrNull()?.let { + getNetworkAddressesUseCase(wallet.walletId, it.network) + } + } else { + getNetworkAddressesUseCase(wallet.walletId, cryptoCurrency.network) + } + status?.map { address -> + AvailableWallet( + wallet.name, + address = address, + ) + } + } + private suspend fun getTxHistory() { val txHistoryList = getFixedTxHistoryItemsUseCase.getSync( userWalletId = userWalletId, From fd6733f834c25c2ca243673ea3389844f0322009 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 May 2024 16:38:38 +0500 Subject: [PATCH 17/30] Updated on 2026-08-14 --- .../core/analytics/models/AnalyticsParam.kt | 9 +-- .../analytics/SendAnalyticEvents.kt | 25 +++---- .../utils/SendScreenAnalyticSender.kt | 65 ++++++++++++++++--- .../presentation/viewmodel/SendViewModel.kt | 3 +- 4 files changed, 76 insertions(+), 26 deletions(-) diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index a20223d2cb..80517d7175 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -97,10 +97,11 @@ sealed class AnalyticsParam { } sealed class FeeType(val value: String) { - object Fixed : FeeType("Fixed") - object Min : FeeType("Min") - object Normal : FeeType("Normal") - object Max : FeeType("Max") + data object Fixed : FeeType("Fixed") + data object Min : FeeType("Min") + data object Normal : FeeType("Normal") + data object Max : FeeType("Max") + data object Custom : FeeType("Custom") companion object { fun fromString(feeType: String): FeeType { 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 feca8a0786..c4ce202599 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 @@ -1,7 +1,9 @@ package com.tangem.features.send.impl.presentation.analytics import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN +import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE @@ -66,9 +68,9 @@ internal sealed class SendAnalyticEvents( data object FeeScreenOpened : SendAnalyticEvents(event = "Fee Screen Opened") /** Selected fee (send after next screen opened) */ - data class SelectedFee(val feeType: SelectedFeeType) : SendAnalyticEvents( + data class SelectedFee(val feeType: AnalyticsParam.FeeType) : SendAnalyticEvents( event = "Fee Selected", - params = mapOf("Fee Type" to feeType.name), + params = mapOf("Fee Type" to feeType.value), ) /** Custom fee selected */ @@ -97,7 +99,16 @@ internal sealed class SendAnalyticEvents( // region Transaction Result /** Transaction send screen opened */ - data object TransactionScreenOpened : SendAnalyticEvents(event = "Transaction Sent Screen Opened") + data class TransactionScreenOpened( + val token: String, + val feeType: AnalyticsParam.FeeType, + ) : SendAnalyticEvents( + event = "Transaction Sent Screen Opened", + params = mapOf( + TOKEN to token, + FEE_TYPE to feeType.value, + ), + ) /** Share button clicked */ data object ShareButtonClicked : SendAnalyticEvents(event = "Button - Share") @@ -145,12 +156,4 @@ internal enum class EnterAddressSource { internal enum class SelectedCurrencyType(val value: String) { Token("Token"), AppCurrency("App Currency"), -} - -internal enum class SelectedFeeType { - Min, - Max, - Fixed, - Normal, - Custom, } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt index 27c93b9743..ac6485fc8a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt @@ -2,8 +2,10 @@ package com.tangem.features.send.impl.presentation.analytics.utils import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic +import com.tangem.domain.tokens.model.CryptoCurrency 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.analytics.SendScreenSource import com.tangem.features.send.impl.presentation.state.SendUiState @@ -11,11 +13,13 @@ 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.features.send.impl.presentation.state.fields.SendTextField import com.tangem.utils.Provider internal class SendScreenAnalyticSender( private val stateRouterProvider: Provider, private val currentStateProvider: Provider, + private val cryptoCurrencyProvider: Provider, private val analyticsEventHandler: AnalyticsEventHandler, ) { fun send(prevScreen: SendUiStateType, state: SendUiState) { @@ -73,16 +77,57 @@ internal class SendScreenAnalyticSender( ) } + fun sendTransaction() { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val cryptoCurrency = cryptoCurrencyProvider() + val feeState = state.getFeeState(isEditState) ?: return + val recipientState = state.getRecipientState(isEditState) ?: return + + val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return + val feeType = getSendTransactionFeeType(feeSelectorState) + analyticsEventHandler.send( + SendAnalyticEvents.TransactionScreenOpened( + token = cryptoCurrency.symbol, + feeType = feeType, + ), + ) + analyticsEventHandler.send( + Basic.TransactionSent( + sentFrom = AnalyticsParam.TxSentFrom.Send( + blockchain = cryptoCurrency.network.name, + token = cryptoCurrency.symbol, + feeType = feeType, + ), + memoType = getSendTransactionMemoType(recipientState.memoTextField), + ), + ) + } + private fun sendSelectedFeeAnalytics(feeSelectorState: FeeSelectorState.Content) { - val type = when (feeSelectorState.fees) { - is TransactionFee.Single -> SelectedFeeType.Fixed - is TransactionFee.Choosable -> when (feeSelectorState.selectedFee) { - FeeType.Slow -> SelectedFeeType.Min - FeeType.Market -> SelectedFeeType.Normal - FeeType.Fast -> SelectedFeeType.Max - FeeType.Custom -> SelectedFeeType.Custom - } - } + val type = getSendTransactionFeeType(feeSelectorState) analyticsEventHandler.send(SendAnalyticEvents.SelectedFee(type)) } + + private fun getSendTransactionFeeType(feeSelectorState: FeeSelectorState.Content): AnalyticsParam.FeeType = + when (feeSelectorState.fees) { + is TransactionFee.Single -> AnalyticsParam.FeeType.Fixed + is TransactionFee.Choosable -> when (feeSelectorState.selectedFee) { + FeeType.Slow -> AnalyticsParam.FeeType.Min + FeeType.Market -> AnalyticsParam.FeeType.Normal + FeeType.Fast -> AnalyticsParam.FeeType.Max + FeeType.Custom -> AnalyticsParam.FeeType.Custom + } + } + + private fun getSendTransactionMemoType( + recipientMemo: SendTextField.RecipientMemo?, + ): Basic.TransactionSent.MemoType { + val memo = recipientMemo?.value + return when { + memo?.isBlank() == true -> Basic.TransactionSent.MemoType.Empty + memo?.isNotBlank() == true -> Basic.TransactionSent.MemoType.Full + else -> Basic.TransactionSent.MemoType.Null + } + } } \ No newline at end of file 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 5410b54f17..1a26222648 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 @@ -180,6 +180,7 @@ internal class SendViewModel @Inject constructor( stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState }, analyticsEventHandler = analyticsEventHandler, + cryptoCurrencyProvider = Provider { cryptoCurrency }, ) } @@ -875,7 +876,7 @@ internal class SendViewModel @Inject constructor( uiState = stateFactory.getSendingStateUpdate(isSending = false) updateTransactionStatus(txData) scheduleBalanceUpdate() - analyticsEventHandler.send(SendAnalyticEvents.TransactionScreenOpened) + sendScreenAnalyticSender.sendTransaction() }, ) } From 5797d2691076a8cd55ab12173c806e1f59355f77 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 May 2024 18:55:59 +0500 Subject: [PATCH 18/30] Updated on 2026-08-14 --- .../send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 d9c609a5ca..d29fea2394 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 @@ -16,6 +16,7 @@ import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType @@ -114,11 +115,14 @@ private fun FeeError(feeSelectorState: FeeSelectorState) { private fun FeeSelectorState.Content.getAmount(feeType: FeeType): Amount? { val choosableFees = fees as? TransactionFee.Choosable + val decimals = fees.normal.amount.decimals + val customValue = this.customValues.firstOrNull()?.value?.parseToBigDecimal(decimals) + val customAmount = fees.normal.amount.copy(value = customValue) return when (feeType) { FeeType.Slow -> choosableFees?.minimum?.amount FeeType.Market -> fees.normal.amount FeeType.Fast -> choosableFees?.priority?.amount - FeeType.Custom -> null + FeeType.Custom -> customAmount } } From 0beda4cc817202af60985562823fd13410e47643 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 May 2024 18:56:38 +0500 Subject: [PATCH 19/30] Updated on 2026-08-14 --- .../features/send/impl/presentation/utils/FormatterUtils.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 8854274b17..f2be7e6ed2 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 @@ -11,6 +11,7 @@ import java.math.BigDecimal import java.math.RoundingMode private const val FIAT_DECIMALS = 2 +private const val CRYPTO_FEE_DECIMALS = 6 private const val FEE_MINIMUM_VALUE = 0.01 internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? { @@ -21,7 +22,7 @@ internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): Tex BigDecimalFormatter.formatCryptoAmount( cryptoAmount = amount.value, cryptoCurrency = amount.currencySymbol, - decimals = amount.decimals, + decimals = amount.decimals.coerceAtMost(CRYPTO_FEE_DECIMALS), ), ), ) From 8ffaa3ba7d78a1123999a18e0e8c8a69a8f98af3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 May 2024 18:57:00 +0500 Subject: [PATCH 20/30] Updated on 2026-08-14 --- .../ui/components/fields/AmountTextField.kt | 43 ++++++++++++++++--- .../core/ui/utils/DecimalFormatterExt.kt | 4 +- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt index c095c2b8fd..521245b940 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt @@ -94,10 +94,14 @@ fun AmountTextField( SimpleTextField( value = value, onValueChange = { newText -> - if (decimalFormat.isValidSymbols(newText)) { - val trimmed = decimalFormat.getValidatedNumberWithFixedDecimals(newText, decimals) - onValueChange(trimmed) - } + onValueChange( + prepareEnter( + oldValue = value, + newValue = newText, + decimalFormat = decimalFormat, + decimals = decimals, + ), + ) }, textStyle = textStyle.copy( fontSize = fontSize, @@ -116,8 +120,37 @@ fun AmountTextField( } } +private fun prepareEnter(oldValue: String, newValue: String, decimalFormat: DecimalFormat, decimals: Int): String { + val decimalSymbol = decimalFormat.decimalFormatSymbols.decimalSeparator + return if (decimalFormat.isValidSymbols(newValue)) { + val parsedValue = newValue.parseBigDecimalOrNull()?.toPlainString() + ?: if (newValue.isBlank()) "" else oldValue + val replacedWithSymbol = if (parsedValue.findLast { it != decimalSymbol } != null) { + when { + parsedValue.findLast { it == COMMA_SEPARATOR } != null -> { + parsedValue.replace(COMMA_SEPARATOR, decimalSymbol) + } + parsedValue.findLast { it == POINT_SEPARATOR } != null -> { + parsedValue.replace(POINT_SEPARATOR, decimalSymbol) + } + else -> parsedValue + } + } else { + parsedValue + } + val joinedSymbol = if (newValue.endsWith(COMMA_SEPARATOR) || newValue.endsWith(POINT_SEPARATOR)) { + replacedWithSymbol.plus(decimalSymbol) + } else { + replacedWithSymbol + } + decimalFormat.getValidatedNumberWithFixedDecimals(joinedSymbol, decimals) + } else { + oldValue + } +} + private fun DecimalFormat.isValidSymbols(text: String): Boolean { - return checkDecimalSeparatorDuplicate(text) && checkGroupingSeparator(text) + return checkDecimalSeparatorDuplicate(text) } // region preview diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt index 5006df1d7a..2d685763e3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt @@ -10,9 +10,9 @@ import java.text.DecimalFormatSymbols import java.util.Locale private const val TEXT_CHUNK_THOUSAND = 3 -private const val POINT_SEPARATOR = '.' -private const val COMMA_SEPARATOR = ',' private const val SCIENTIFIC_NOTATION = 'e' +const val POINT_SEPARATOR = '.' +const val COMMA_SEPARATOR = ',' const val DECIMAL_SEPARATOR_LIMIT = 1 @Composable From 3b313ada7c9dfd25bcf921409b11dd4ec53026aa Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 May 2024 18:57:27 +0500 Subject: [PATCH 21/30] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 533cc30161..bf38e2ad14 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -500,7 +500,7 @@ Последние Получатель Неверный адрес - Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов + Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов. Отправить Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. Мои кошельки diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 53b1c0d5b8..ca86e6df9c 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -499,7 +499,7 @@ Recent Recipient Not a valid address - Ensure that you are sending funds to an %s wallet address. Errors may result in the loss of your tokens + Ensure the receiving wallet address is on the %s network to avoid losing your tokens Send to A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds My wallets From 4ef5b06701b06413edfdfee2191274387ff8a3ff Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 May 2024 18:58:14 +0500 Subject: [PATCH 22/30] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 2 +- core/ui/build.gradle.kts | 1 + .../core/ui/extensions/MarkdownExtension.kt | 56 +++++++++++++++++++ .../core/ui/extensions/TextReference.kt | 36 ++++++++++++ .../presentation/ui/SendNavigationButtons.kt | 15 ++++- gradle/dependencies.toml | 2 + 7 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index bf38e2ad14..8be4d56fef 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -509,7 +509,7 @@ Отправка Нажмите на любое поле, чтобы изменить его Отправка %s - Вы отправляете %1$s, включая комиссию сети %2$s + Вы отправляете **%1$s**, включая комиссию сети %2$s Отправка %s Всего %1$s и %2$s будет отправлено diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ca86e6df9c..50421fa1b7 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -508,7 +508,7 @@ Sending... Tap any field to change it Send %s - You are sending %1$s including a network fee of %2$s + You are sending **%1$s** including a network fee of %2$s Sending %s Total %1$s and %2$s will be sent diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index e2abfa0f4b..3129c8917b 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -45,4 +45,5 @@ dependencies { implementation(deps.zxing.qrCore) implementation(deps.jodatime) implementation(deps.timber) + implementation(deps.markdown) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt new file mode 100644 index 0000000000..408e9130ee --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt @@ -0,0 +1,56 @@ +package com.tangem.core.ui.extensions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import org.intellij.markdown.MarkdownElementTypes +import org.intellij.markdown.ast.ASTNode +import org.intellij.markdown.ast.getTextInNode +import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor +import org.intellij.markdown.parser.MarkdownParser + +/** Markdown parser */ +@Composable +fun rememberMarkdownParser() = remember { + MarkdownParser(CommonMarkFlavourDescriptor()) +} + +/** + * Styling markdown tree recursively + * + * @param markdownText original text + * @param node current processed node + */ +@Composable +fun AnnotatedString.Builder.appendMarkdown(markdownText: String, node: ASTNode): AnnotatedString.Builder { + when (node.type) { + MarkdownElementTypes.MARKDOWN_FILE, MarkdownElementTypes.PARAGRAPH -> { + node.children.forEach { childNode -> + appendMarkdown( + markdownText = markdownText, + node = childNode, + ) + } + } + MarkdownElementTypes.STRONG -> { + withStyle(SpanStyle(fontWeight = FontWeight.Medium)) { + node.children + .drop(2) + .dropLast(2) + .forEach { childNode -> + appendMarkdown( + markdownText = markdownText, + node = childNode, + ) + } + } + } + else -> { + append(node.getTextInNode(markdownText).toString()) + } + } + return this +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt index 766815d328..2f22994f64 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt @@ -8,6 +8,9 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.buildAnnotatedString +import org.intellij.markdown.MarkdownElementTypes /** * Utility class for creating text as [String] or [StringRes]. @@ -160,6 +163,29 @@ fun TextReference.resolveReference(resources: Resources): String { } } +/** Resolve [TextReference] as [AnnotatedString] */ +@Composable +fun TextReference.resolveAnnotatedReference(): AnnotatedString { + return when (this) { + is TextReference.Res -> { + val args = formatArgs + .map { if (it is TextReference) it.resolveReference() else it } + .toTypedArray() + + formatAnnotated(stringResource(id = id, *args)) + } + is TextReference.PluralRes -> formatAnnotated( + pluralStringResource(id, count, *formatArgs.toTypedArray()), + ) + is TextReference.Str -> formatAnnotated(value) + is TextReference.Combined -> buildAnnotatedString { + refs.forEach { + append(formatAnnotated(it.resolveReference())) + } + } + } +} + /** Concatenate [this] reference with [ref] */ operator fun TextReference.plus(ref: TextReference): TextReference { return when (this) { @@ -169,4 +195,14 @@ operator fun TextReference.plus(ref: TextReference): TextReference { is TextReference.Str, -> TextReference.Combined(refs = wrappedList(this, ref)) } +} + +@Composable +private fun formatAnnotated(rawString: String): AnnotatedString { + val markdownDescriptor = rememberMarkdownParser() + val parsedTree = markdownDescriptor.parse(MarkdownElementTypes.MARKDOWN_FILE, rawString, true) + + return buildAnnotatedString { + appendMarkdown(markdownText = rawString, node = parsedTree) + } } \ 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 c4f1b95bdf..c3167929ad 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 @@ -29,7 +29,10 @@ import com.tangem.core.ui.components.buttons.common.TangemButton import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.components.keyboardAsState +import com.tangem.core.ui.extensions.resolveAnnotatedReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.shareText +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen import com.tangem.features.send.impl.presentation.state.SendUiState @@ -185,11 +188,17 @@ private fun SendingText( currencySymbol = feeState.appCurrency.symbol, currencyCode = feeState.appCurrency.code, ) + val textResource = remember(sendingValue, feeValue) { + resourceReference( + id = R.string.send_summary_transaction_description, + formatArgs = wrappedList(sendingValue, feeValue), + ) + } Text( - text = stringResource(id = R.string.send_summary_transaction_description, sendingValue, feeValue), + text = textResource.resolveAnnotatedReference(), textAlign = TextAlign.Center, - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.primary1, modifier = Modifier .fillMaxWidth() .padding(TangemTheme.dimens.spacing12), diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 303ac01722..af0d8874b0 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -82,6 +82,7 @@ swipeRefreshLayout = "1.1.0" spr-client = "3.6.2" web3j = "4.10.1" leakcanary = "2.13" +markdown = "0.7.2" # endregion Other libraries # region Tangem @@ -249,4 +250,5 @@ camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = camera-view = { module = "androidx.camera:camera-view", version.ref = "androidXCamera" } web3j-core = { module = "org.web3j:core", version.ref = "web3j" } leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } +markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" } # endregion Other From ca12e1cdff01d6e0f89125af60d60bc98dffbd51 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 May 2024 13:44:30 +0500 Subject: [PATCH 23/30] Updated on 2026-08-14 --- .../components/notifications/Notification.kt | 35 +++++++++++++------ .../send/impl/presentation/ui/SendScreen.kt | 5 ++- .../presentation/ui/common/Notifications.kt | 2 ++ .../impl/presentation/ui/send/AmountBlock.kt | 8 ++--- .../impl/presentation/ui/send/FeeBlock.kt | 8 ++--- .../presentation/ui/send/RecipientBlock.kt | 8 ++--- .../impl/presentation/ui/send/SendContent.kt | 10 +++--- .../presentation/viewmodel/SendViewModel.kt | 8 +++-- 8 files changed, 54 insertions(+), 30 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index c352cf41eb..4a71c9c99c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -49,12 +49,14 @@ fun Notification( modifier: Modifier = Modifier, containerColor: Color? = null, iconTint: Color? = null, + isEnabled: Boolean = true, ) { BaseContainer( buttonsState = config.buttonsState, onClick = config.onClick, modifier = modifier, containerColor = containerColor, + isEnabled = isEnabled, ) { Column( modifier = Modifier.padding(all = TangemTheme.dimens.spacing12), @@ -65,15 +67,16 @@ fun Notification( iconTint = iconTint, title = config.title, subtitle = config.subtitle, - isClickableComponent = config.onClick != null, + isClickableComponent = isEnabled && config.onClick != null, ) - Buttons(state = config.buttonsState) + Buttons(state = config.buttonsState, isEnabled = isEnabled) } CloseableIconButton( onClick = config.onCloseClick, modifier = Modifier.align(alignment = Alignment.TopEnd), + isEnabled = isEnabled, ) } } @@ -83,6 +86,7 @@ private fun BaseContainer( buttonsState: NotificationConfig.ButtonsState?, onClick: (() -> Unit)?, modifier: Modifier = Modifier, + isEnabled: Boolean = true, containerColor: Color? = null, content: @Composable BoxScope.() -> Unit, ) { @@ -99,7 +103,7 @@ private fun BaseContainer( modifier = modifier .defaultMinSize(minHeight = TangemTheme.dimens.size62) .fillMaxWidth(), - enabled = onClick != null, + enabled = onClick != null && isEnabled, shape = TangemTheme.shapes.roundedCornersXMedium, color = containerColor ?: tempContainerColor, ) { @@ -177,27 +181,31 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) { } @Composable -private fun Buttons(state: NotificationButtonsState?) { +private fun Buttons(state: NotificationButtonsState?, isEnabled: Boolean = true) { when (state) { - is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(config = state) - is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state) - is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state) + is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton( + config = state, + isEnabled = isEnabled, + ) + is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state, isEnabled = isEnabled) + is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state, isEnabled = isEnabled) null -> Unit } } @Composable -private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig) { +private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig, isEnabled: Boolean = true) { SecondaryButton( text = config.text.resolveReference(), onClick = config.onClick, modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } @Composable -private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig) { +private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig, isEnabled: Boolean = true) { if (config.iconResId != null) { PrimaryButtonIconEnd( text = config.text.resolveReference(), @@ -205,6 +213,7 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo onClick = config.onClick, modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } else { PrimaryButton( @@ -212,18 +221,20 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo onClick = config.onClick, modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } } @Composable -private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) { +private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig, isEnabled: Boolean = true) { Row(horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8)) { SecondaryButton( text = config.secondaryText.resolveReference(), onClick = config.onSecondaryClick, modifier = Modifier.weight(weight = 1f), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) PrimaryButton( @@ -231,12 +242,13 @@ private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) { onClick = config.onPrimaryClick, modifier = Modifier.weight(weight = 1f), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } } @Composable -private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) { +private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier, isEnabled: Boolean = true) { AnimatedVisibility(visible = onClick != null, modifier = modifier) { onClick ?: return@AnimatedVisibility @@ -253,6 +265,7 @@ private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Mod interactionSource = remember { MutableInteractionSource() }, indication = LocalIndication.current, role = Role.Button, + enabled = isEnabled, onClick = onClick, ), ) { 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 d6529709b7..819655152a 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,10 @@ import kotlinx.coroutines.flow.withIndex @Composable internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) { val snackbarHostState = remember { SnackbarHostState() } - BackHandler(onBack = uiState.clickIntents::onBackClick) + val onBackClick = uiState.clickIntents::onBackClick.takeIf { + uiState.sendState?.isSending != true + } ?: {} + BackHandler(onBack = onBackClick) Column( modifier = Modifier .fillMaxSize() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt index fcff795b70..929cfdf3b2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt @@ -15,6 +15,7 @@ internal fun LazyListScope.notifications( notifications: ImmutableList, modifier: Modifier = Modifier, hasPaddingAbove: Boolean = false, + isClickDisabled: Boolean = false, ) { itemsIndexed( items = notifications, @@ -44,6 +45,7 @@ internal fun LazyListScope.notifications( -> null is SendNotification.Error -> TangemTheme.colors.icon.warning }, + isEnabled = !isClickDisabled, ) }, ) 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 ab68759908..22d8835ec6 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 @@ -24,7 +24,7 @@ import com.tangem.features.send.impl.presentation.state.previewdata.AmountStateP @Composable internal fun AmountBlock( amountState: SendStates.AmountState, - isSuccess: Boolean, + isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit, ) { @@ -53,7 +53,7 @@ internal fun AmountBlock( modifier = Modifier .clip(TangemTheme.shapes.roundedCornersXMedium) .background(backgroundColor) - .clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick) + .clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick) .padding( vertical = TangemTheme.dimens.spacing14, horizontal = TangemTheme.dimens.spacing16, @@ -91,7 +91,7 @@ private fun AmountBlockPreview_Light( TangemTheme { AmountBlock( amountState = value, - isSuccess = false, + isClickDisabled = false, isEditingDisabled = false, onClick = {}, ) @@ -106,7 +106,7 @@ private fun AmountBlockPreview_Dark( TangemTheme(isDark = true) { AmountBlock( amountState = value, - isSuccess = true, + isClickDisabled = true, isEditingDisabled = false, onClick = {}, ) 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 54ef73296a..3af4e1c827 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 @@ -26,13 +26,13 @@ import com.tangem.features.send.impl.presentation.utils.getCryptoReference import com.tangem.features.send.impl.presentation.utils.getFiatReference @Composable -internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) { +internal fun FeeBlock(feeState: SendStates.FeeState, isClickDisabled: Boolean, onClick: () -> Unit) { Column( modifier = Modifier .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(TangemTheme.colors.background.action) - .clickable(enabled = !isSuccess, onClick = onClick) + .clickable(enabled = !isClickDisabled, onClick = onClick) .padding(TangemTheme.dimens.spacing12), ) { Text( @@ -115,7 +115,7 @@ private fun FeeBlockPreview_Light(@PreviewParameter(FeeBlockPreviewProvider::cla TangemTheme { FeeBlock( feeState = value, - isSuccess = true, + isClickDisabled = true, onClick = {}, ) } @@ -127,7 +127,7 @@ private fun FeeBlockPreview_Dark(@PreviewParameter(FeeBlockPreviewProvider::clas TangemTheme(isDark = true) { FeeBlock( feeState = value, - isSuccess = true, + isClickDisabled = true, onClick = {}, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt index e3b92db56c..3936e4985b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt @@ -23,7 +23,7 @@ import com.tangem.features.send.impl.presentation.state.previewdata.RecipientSta @Composable internal fun RecipientBlock( recipientState: SendStates.RecipientState, - isSuccess: Boolean, + isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit, ) { @@ -38,7 +38,7 @@ internal fun RecipientBlock( .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(backgroundColor) - .clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick) + .clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick) .padding(TangemTheme.dimens.spacing12), ) { AddressBlock(recipientState.addressTextField) @@ -104,7 +104,7 @@ private fun RecipientBlockPreview_Light( TangemTheme { RecipientBlock( recipientState = value, - isSuccess = true, + isClickDisabled = true, isEditingDisabled = false, onClick = {}, ) @@ -119,7 +119,7 @@ private fun RecipientBlockPreview_Dark( TangemTheme(isDark = true) { RecipientBlock( recipientState = value, - isSuccess = true, + isClickDisabled = true, isEditingDisabled = false, onClick = {}, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index 6a6cc0550d..6c6de6e979 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -35,12 +35,13 @@ private const val TAP_HELP_ANIMATION_DELAY = 500L @Composable internal fun SendContent(uiState: SendUiState) { val sendState = uiState.sendState ?: return + val isClickDisabled = sendState.isSending || sendState.isSuccess LazyColumn( modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { blocks(uiState) tapHelp(isDisplay = sendState.showTapHelp) - notifications(sendState.notifications) + notifications(notifications = sendState.notifications, isClickDisabled = isClickDisabled) } } @@ -50,6 +51,7 @@ private fun LazyListScope.blocks(uiState: SendUiState) { val feeState = uiState.feeState ?: return val sendState = uiState.sendState ?: return val isSuccess = sendState.isSuccess + val isClickDisabled = sendState.isSending || isSuccess val timestamp = sendState.transactionDate item(key = BLOCKS_KEY) { @@ -65,19 +67,19 @@ private fun LazyListScope.blocks(uiState: SendUiState) { } RecipientBlock( recipientState = recipientState, - isSuccess = isSuccess, + isClickDisabled = isClickDisabled, isEditingDisabled = uiState.isEditingDisabled, onClick = uiState.clickIntents::showRecipient, ) AmountBlock( amountState = amountState, - isSuccess = isSuccess, + isClickDisabled = isClickDisabled, isEditingDisabled = uiState.isEditingDisabled, onClick = uiState.clickIntents::showAmount, ) FeeBlock( feeState = feeState, - isSuccess = isSuccess, + isClickDisabled = isClickDisabled, onClick = uiState.clickIntents::showFee, ) } 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 a231ac9226..1a42abca89 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 @@ -422,14 +422,18 @@ internal class SendViewModel @Inject constructor( .mapNotNull { wallet -> val status = if (!wallet.isMultiCurrency) { getCryptoCurrencyUseCase(wallet.walletId).getOrNull()?.let { - getNetworkAddressesUseCase(wallet.walletId, it.network) + if (it.network.id == cryptoCurrency.network.id) { + getNetworkAddressesUseCase(wallet.walletId, it.network) + } else { + null + } } } else { getNetworkAddressesUseCase(wallet.walletId, cryptoCurrency.network) } status?.map { address -> AvailableWallet( - wallet.name, + name = wallet.name, address = address, ) } From 1b9867e30c1e595d28b0cd4bcb2292cb7c3c3294 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 May 2024 13:45:14 +0500 Subject: [PATCH 24/30] Updated on 2026-08-14 --- .../state/fields/SendAmountFieldMaxAmountConverter.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt index 7d4dfe3b45..8f3d03dbbc 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -10,6 +10,7 @@ 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.RoundingMode internal class SendAmountFieldMaxAmountConverter( private val stateRouterProvider: Provider, @@ -33,7 +34,7 @@ internal class SendAmountFieldMaxAmountConverter( val isDoneActionEnabled = !decimalCryptoValue.isNullOrZero() val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty() - val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals).orEmpty() + val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals, roundingMode = RoundingMode.HALF_UP).orEmpty() return state.copyWrapped( isEditState = isEditState, amountState = amountState.copy( From a76b405d2c557a9020965bb2bf191fe7b534b1d8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 May 2024 18:33:31 +0500 Subject: [PATCH 25/30] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 3 +-- core/res/src/main/res/values/strings.xml | 3 +-- .../send/impl/presentation/state/SendNotification.kt | 4 ++-- .../presentation/state/confirm/SendNotificationFactory.kt | 7 ++++--- .../send/impl/presentation/state/fee/FeeCalculation.kt | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 8be4d56fef..7b010115ba 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -477,7 +477,7 @@ Покрытие сетевой комиссии Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса Недостаточно средств - Оставить %s + Оставить %s Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. Экзистенциальный депозит Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. @@ -490,7 +490,6 @@ Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. Сумма отправки не может быть менее %s Уменьшить на %s - Уменьшить до %s Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции Возможны задержки по транзакции Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 50421fa1b7..2e840caeb5 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -474,7 +474,6 @@ Network fee coverage Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance Total exceeds balance - Leave %s The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. Existential deposit The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. @@ -486,8 +485,8 @@ The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. Target account is not created. Please change the amount to send. The amount to send must be at least %s + Leave %s Reduce by %s - Reduce to %s Kindly be aware that your transaction may experience delays under specific fee settings Transaction delays are possible Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index c59c030f46..fcd33d986f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -54,7 +54,7 @@ internal sealed class SendNotification(val config: NotificationConfig) { wrappedList(cryptoCurrency, utxoLimit, amountLimit), ), buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = resourceReference(R.string.send_notification_reduce_to, wrappedList(amountLimit)), + text = resourceReference(R.string.send_notification_leave_button, wrappedList(amountLimit)), onClick = onConfirmClick, ), ) @@ -98,7 +98,7 @@ internal sealed class SendNotification(val config: NotificationConfig) { title = resourceReference(R.string.send_notification_existential_deposit_title), subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)), buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = resourceReference(R.string.send_notification_existential_deposit_button, wrappedList(deposit)), + text = resourceReference(R.string.send_notification_leave_button, wrappedList(deposit)), onClick = onConfirmClick, ), ) 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 916388ae26..ad764dbb71 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 @@ -66,11 +66,12 @@ internal class SendNotificationFactory( amountValue = amountValue, feeValue = feeValue, ) - val sendingAmount = calculateSubtractedAmount( - isFeeCoverage = isFeeCoverage, + val sendingAmount = checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isFeeCoverage, cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), amountValue = amountValue, feeValue = feeValue, + reduceAmountBy = sendState.reduceAmountBy, ) buildList { // errors @@ -194,7 +195,7 @@ internal class SendNotificationFactory( val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) { feeAmount } else { - feeAmount + receivedAmount + receivedAmount } val currencyDeposit = currencyChecksRepository.getExistentialDeposit( userWalletId, 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 b06d42a23d..a8a93bcd32 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 @@ -54,7 +54,7 @@ internal fun checkFeeCoverage( /** * Calculates subtracted amount */ -internal fun calculateSubtractedAmount( +private fun calculateSubtractedAmount( isFeeCoverage: Boolean, cryptoCurrencyStatus: CryptoCurrencyStatus, amountValue: BigDecimal, From e106417f40c2e3ba06598429dfa1e16f4a8379f3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 May 2024 18:34:06 +0500 Subject: [PATCH 26/30] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- gradle/dependencies.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index cf1fb0dc1d..644ced90b9 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -17,7 +17,7 @@ }, { "name": "LOCAL_USER_LOGS_ENABLED", - "version": "5.10.0" + "version": "5.11.0" }, { "name": "GENERATE_XPUB_ENABLED", diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index af0d8874b0..cfa112dcc2 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -86,7 +86,7 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.10-618" +tangemBlockchainSdk = "release-app_5.10-629" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.10-353" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 3736148531e3e376cc0b796dce1566b4dded37e3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 May 2024 18:34:40 +0500 Subject: [PATCH 27/30] Updated on 2026-08-14 --- .../tangem/tap/features/main/MainViewModel.kt | 9 +++++++ .../api/tangemTech/TangemTechApi.kt | 3 +++ .../api/tangemTech/models/FeaturesResponse.kt | 7 +++++ .../api/featuretoggles/SendFeatureToggles.kt | 3 +++ features/send/impl/build.gradle.kts | 1 + .../send/impl/di/SendFeatureTogglesModule.kt | 14 ++++++++-- .../DefaultSendFeatureToggles.kt | 27 ++++++++++++++++++- gradle/dependencies.toml | 2 +- 8 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 6f5d289dd9..d61c7ffebf 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -11,6 +11,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase +import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.tap.features.main.model.MainScreenState import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel @@ -26,6 +27,7 @@ internal class MainViewModel @Inject constructor( private val reduxNavController: ReduxNavController, private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase, private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase, + private val sendFeatureToggles: SendFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel(), MainIntents { @@ -41,6 +43,7 @@ internal class MainViewModel @Inject constructor( init { updateAppCurrencies() + updateSendFeatureToggle() observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() @@ -56,6 +59,12 @@ internal class MainViewModel @Inject constructor( } } + private fun updateSendFeatureToggle() { + viewModelScope.launch(dispatchers.main) { + sendFeatureToggles.fetchNewSendEnabled() + } + } + private fun observeFlips() { listenToFlipsUseCase().launchIn(viewModelScope) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 63e632438e..38e872886c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -82,4 +82,7 @@ interface TangemTechApi { @Header("card_id") cardId: String, @Body body: CreateUserNetworkAccountBody, ): ApiResponse + + @GET("features") + suspend fun getFeatures(): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt new file mode 100644 index 0000000000..2d72021cd0 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt @@ -0,0 +1,7 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class FeaturesResponse( + @Json(name = "send") val isNewSendEnabled: Boolean, +) \ No newline at end of file diff --git a/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt b/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt index d80f08cf56..453a79b60c 100644 --- a/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt +++ b/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt @@ -7,4 +7,7 @@ interface SendFeatureToggles { /** Availability of redesigned send screen */ val isRedesignedSendEnabled: Boolean + + /** Updates remote toggle */ + suspend fun fetchNewSendEnabled() } \ No newline at end of file diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts index 587cb6d67c..4d0d072624 100644 --- a/features/send/impl/build.gradle.kts +++ b/features/send/impl/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(projects.core.navigation) implementation(projects.core.analytics) implementation(projects.core.analytics.models) + implementation(projects.core.datasource) /** Common */ implementation(projects.common) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt index 6b1465762c..60c8b2822c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt @@ -1,8 +1,10 @@ package com.tangem.features.send.impl.di import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.features.send.impl.featuretoggles.DefaultSendFeatureToggles +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -18,7 +20,15 @@ internal object SendFeatureTogglesModule { @Provides @Singleton - fun provideSendFeatureToggles(featureTogglesManager: FeatureTogglesManager): SendFeatureToggles { - return DefaultSendFeatureToggles(featureTogglesManager = featureTogglesManager) + fun provideSendFeatureToggles( + featureTogglesManager: FeatureTogglesManager, + tangemTechApi: TangemTechApi, + dispatchers: CoroutineDispatcherProvider, + ): SendFeatureToggles { + return DefaultSendFeatureToggles( + featureTogglesManager = featureTogglesManager, + tangemTechApi = tangemTechApi, + dispatchers = dispatchers, + ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt index 71f11c480d..b36cba4cbd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt @@ -1,16 +1,41 @@ package com.tangem.features.send.impl.featuretoggles import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.features.send.api.featuretoggles.SendFeatureToggles +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import timber.log.Timber /** * Default implementation of Send feature toggles * * @property featureTogglesManager manager for getting information about the availability of feature toggles + * @property tangemTechApi api to get remote feature toggle for send + * @property dispatchers coroutine dispatchers */ internal class DefaultSendFeatureToggles( private val featureTogglesManager: FeatureTogglesManager, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) : SendFeatureToggles { + + private val remoteSendEnabled: MutableStateFlow = MutableStateFlow(true) + override val isRedesignedSendEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_SEND_SCREEN_ENABLED") + get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_SEND_SCREEN_ENABLED") && + remoteSendEnabled.value + + override suspend fun fetchNewSendEnabled() { + runCatching(dispatchers.io) { + tangemTechApi.getFeatures().getOrThrow() + }.onSuccess { response -> + remoteSendEnabled.update { response.isNewSendEnabled } + }.onFailure { + Timber.e(it.localizedMessage, "Unable to fetch new send toggle") + } + } } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index cfa112dcc2..954d918b05 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -86,7 +86,7 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.10-629" +tangemBlockchainSdk = "release-app_5.10-630" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.10-353" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From f9e147f1659563041a88263a6b16452eb1dc541a Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 May 2024 19:23:49 +0500 Subject: [PATCH 28/30] Updated on 2026-08-14 --- .../presentation/domain/AvailableWallet.kt | 3 +++ .../presentation/viewmodel/SendViewModel.kt | 24 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt index 69e56bfc62..8a7d5e7f3a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt @@ -1,15 +1,18 @@ package com.tangem.features.send.impl.presentation.domain import androidx.compose.runtime.Immutable +import com.tangem.domain.wallets.models.UserWalletId /** * Available wallet to send * * @property name wallet name + * @property userWalletId wallet id * @property address blockchain address */ @Immutable data class AvailableWallet( val name: String, + val userWalletId: UserWalletId, val address: String, ) \ No newline at end of file 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 1a42abca89..540968febf 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 @@ -95,6 +95,7 @@ internal class SendViewModel @Inject constructor( private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, + private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, validateWalletMemoUseCase: ValidateWalletMemoUseCase, @@ -189,6 +190,7 @@ internal class SendViewModel @Inject constructor( private set private var userWallet: UserWallet by Delegates.notNull() + private var userWallets: List = emptyList() private var isAmountSubtractAvailable: Boolean = false private var isTapHelpPreviewEnabled: Boolean = false private var coinCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() @@ -408,7 +410,10 @@ internal class SendViewModel @Inject constructor( .orEmpty() }.onSuccess { result -> combine(*result.toTypedArray()) { it } - .onEach { uiState = stateFactory.onLoadedWalletsList(wallets = it.toList()) } + .onEach { + userWallets = it.filterNotNull().toList() + uiState = stateFactory.onLoadedWalletsList(wallets = userWallets) + } .flowOn(dispatchers.main) .launchIn(viewModelScope) }.onFailure { @@ -417,7 +422,7 @@ internal class SendViewModel @Inject constructor( } } - private suspend fun List.toAvailableWallets(): List> = + private suspend fun List.toAvailableWallets(): List> = filterNot { it.walletId == userWalletId || it.isLocked } .mapNotNull { wallet -> val status = if (!wallet.isMultiCurrency) { @@ -435,6 +440,7 @@ internal class SendViewModel @Inject constructor( AvailableWallet( name = wallet.name, address = address, + userWalletId = wallet.walletId, ) } } @@ -863,11 +869,25 @@ internal class SendViewModel @Inject constructor( uiState = stateFactory.getSendingStateUpdate(isSending = false) updateTransactionStatus(txData) scheduleBalanceUpdate() + addTokenToWalletIfNeeded() sendScreenAnalyticSender.sendTransaction() }, ) } + private fun addTokenToWalletIfNeeded() { + if (cryptoCurrency !is CryptoCurrency.Token) return + + val recipientState = uiState.getRecipientState(stateRouter.isEditState) ?: return + val destinationAddress = recipientState.addressTextField.value + + val maybeUserWallet = userWallets.firstOrNull { it.address == destinationAddress } ?: return + + viewModelScope.launch(dispatchers.io) { + addCryptoCurrenciesUseCase(userWalletId = maybeUserWallet.userWalletId, currency = cryptoCurrency) + } + } + private suspend fun updateTransactionStatus(txData: TransactionData) { val txUrl = getExplorerTransactionUrlUseCase( userWalletId = userWalletId, From ad05b94b557b28f4ece4092f600168b5cc92d652 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 13:44:57 +0500 Subject: [PATCH 29/30] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 2 ++ core/res/src/main/res/values/strings.xml | 2 ++ domain/tokens/build.gradle.kts | 1 + .../tokens/model/warnings/CryptoCurrencyWarning.kt | 2 ++ .../domain/tokens/GetCurrencyWarningsUseCase.kt | 6 ++++++ .../TokenDetailsNotificationsAnalyticsSender.kt | 1 + .../state/components/TokenDetailsNotification.kt | 11 ++++++++++- .../factory/TokenDetailsNotificationConverter.kt | 6 ++++++ .../java/com/tangem/lib/crypto/BlockchainUtils.kt | 6 ++++++ 9 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 7b010115ba..453301a20d 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -642,6 +642,8 @@ Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. Ошибка активации + По решению разработчиков сети BNB, стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять свои активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении чтобы перевести их в cеть BNB Smart Chain. + Отключение сети BNB Beacon Chain Можно лучше Нравится Понятно! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 2e840caeb5..34e609005e 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -642,6 +642,8 @@ Use %s or scan a card to unlock access to your wallet Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. Activation error + According to BNB network developers, support for the BEP-2 standard will end in June 2024. To avoid losing assets with this standard, please convert them to the BEP-20 standard. Use our swap service to transfer them to the BNB Smart Chain network. + BNB Beacon Chain will shut down Could be better Like it Ok, Got it! diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index 5d91ab4124..3e0bb9743c 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { /** Project - Other */ implementation(projects.core.utils) + implementation(projects.libs.crypto) /** Android - Other */ implementation(deps.androidx.paging.runtime) diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index 6e477d8a10..36d186b41d 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -47,4 +47,6 @@ sealed class CryptoCurrencyWarning { val startDateTime: DateTime, val endDateTime: DateTime, ) : CryptoCurrencyWarning() + + data object BeaconChainShutdown : CryptoCurrencyWarning() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 91430442e1..5b3bf0649d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -12,6 +12,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching import com.tangem.utils.isNullOrZero @@ -74,6 +75,7 @@ class GetCurrencyWarningsUseCase( *coinRelatedWarnings.toTypedArray(), getNetworkUnavailableWarning(currencyStatus), getNetworkNoAccountWarning(currencyStatus), + getBeaconChainShutdownWarning(currency.network.id), ) }.flowOn(dispatchers.io) } @@ -264,6 +266,10 @@ class GetCurrencyWarningsUseCase( } } + private fun getBeaconChainShutdownWarning(networkId: Network.ID): CryptoCurrencyWarning.BeaconChainShutdown? { + return if (BlockchainUtils.isBeaconChain(networkId.value)) CryptoCurrencyWarning.BeaconChainShutdown else null + } + private fun BigDecimal?.isZero(): Boolean { return this?.signum() == 0 } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index 3a470f1a30..cf4d420f32 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -42,6 +42,7 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.TopUpWithoutReserve, is TokenDetailsNotification.RentInfo, is TokenDetailsNotification.SwapPromo, + is TokenDetailsNotification.NetworkShutdown, -> null } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index a33cc576e3..874efc22b9 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -155,7 +155,11 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { ), ) - class NetworksNoAccount(val network: String, val symbol: String, val amount: String) : Informational( + data class NetworksNoAccount( + private val network: String, + private val symbol: String, + private val amount: String, + ) : Informational( title = resourceReference(R.string.warning_no_account_title), subtitle = resourceReference( id = R.string.no_account_generic, @@ -175,4 +179,9 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { formatArgs = wrappedList(coinSymbol), ), ) + + data class NetworkShutdown(private val title: TextReference, private val subtitle: TextReference) : Warning( + title = title, + subtitle = subtitle, + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index eaeb7f760a..23080d5d8e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -1,6 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning @@ -8,6 +9,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification.* import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.features.tokendetails.impl.R import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.removeBy import kotlinx.collections.immutable.ImmutableList @@ -78,6 +80,10 @@ internal class TokenDetailsNotificationConverter( onSwapClick = clickIntents::onSwapPromoClick, onCloseClick = clickIntents::onSwapPromoDismiss, ) + is CryptoCurrencyWarning.BeaconChainShutdown -> NetworkShutdown( + title = resourceReference(R.string.warning_beacon_chain_retirement_title), + subtitle = resourceReference(R.string.warning_beacon_chain_retirement_content), + ) } } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index a4492852b0..85ef064f98 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -44,4 +44,10 @@ object BlockchainUtils { val blockchain = Blockchain.fromId(networkId) return blockchain == Blockchain.Tezos } + + /** If current [networkId] is BeaconChain */ + fun isBeaconChain(networkId: String): Boolean { + val blockchain = Blockchain.fromId(networkId) + return blockchain == Blockchain.Binance || blockchain == Blockchain.BinanceTestnet + } } \ No newline at end of file From a3db0b52bd91716e036c91fe4630061659992208 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 17:47:35 +0500 Subject: [PATCH 30/30] Updated on 2026-08-14 --- .../send/impl/presentation/viewmodel/SendViewModel.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 540968febf..fc9c9af998 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 @@ -208,7 +208,6 @@ internal class SendViewModel @Inject constructor( private var sendIdleTimer = 0L init { - subscribeOnQRScannerResult() subscribeOnCurrencyStatusUpdates() subscribeOnBalanceHidden() getTapHelpPreviewAvailability() @@ -375,7 +374,7 @@ internal class SendViewModel @Inject constructor( cryptoCurrencyStatus = currencyStatus coinCryptoCurrencyStatus = coinCurrencyStatus feeCryptoCurrencyStatus = feeCurrencyStatus - + subscribeOnQRScannerResult() when { uiState.sendState?.isSuccess == true -> { stateRouter.showSend() @@ -631,7 +630,7 @@ internal class SendViewModel @Inject constructor( }.saveIn(memoValidationJobHolder) } - private suspend fun validateAddress(value: String): Boolean { + private suspend fun validateAddress(value: String): Boolean = runCatching { val isValidAddress = validateWalletAddressUseCase( userWalletId = userWalletId, network = cryptoCurrency.network, @@ -641,7 +640,7 @@ internal class SendViewModel @Inject constructor( ?.any { it.value == value } ?: true onEnteredValidAddress(isValidAddress, isAddressInWallet) return isValidAddress - } + }.getOrElse { false } private suspend fun checkIfXrpAddressValue(value: String): Boolean { return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress ->