From 91ca2a23ebb6f4bddd861a771917fb031c1f610c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 May 2024 17:36:26 +0500 Subject: [PATCH 1/9] 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 0bbe6b8a4a72efad1221cc7a0887e2dfb3411868 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 11:18:10 +0300 Subject: [PATCH 2/9] 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 7362f95be4c90acc99013d6f79f529873e486dea Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 12:56:05 +0500 Subject: [PATCH 3/9] 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 1ad3cabf3ccb7300dda73fcfe5ae90063b58c385 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 13:07:32 +0100 Subject: [PATCH 4/9] 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 c9afaa5eafd45998e1795446885bcaf119869672 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:02:18 +0500 Subject: [PATCH 5/9] 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 e082f0fb320a26018b3fd79708f0b6ee65a058f6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:02:45 +0500 Subject: [PATCH 6/9] 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 ef83fdd921d6f84b0507bdfded5d8aa6b5de50a3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:06:38 +0500 Subject: [PATCH 7/9] 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 2884fe802bc906a8ba84a0dacaefb1d19381a487 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:07:07 +0500 Subject: [PATCH 8/9] 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 045311fea2216253c3837661013180fa52b9a1ab Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:08:09 +0500 Subject: [PATCH 9/9] 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 ^