From 42b90de54daab00b8c95a997ae7d21ed3cea38bc Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 May 2025 18:48:43 +0500 Subject: [PATCH 1/4] Updated on 2026-08-14 --- .../send/v2/common/CommonSendRoute.kt | 2 +- .../send/v2/common/utils/SendRouteUtils.kt | 28 +++++++++ .../send/v2/send/DefaultSendComponent.kt | 59 ++++++++++--------- .../v2/sendnft/DefaultNFTSendComponent.kt | 48 ++++++++------- 4 files changed, 87 insertions(+), 50 deletions(-) create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/utils/SendRouteUtils.kt diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/CommonSendRoute.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/CommonSendRoute.kt index 7493c4d073..e0889d71a4 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/CommonSendRoute.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/CommonSendRoute.kt @@ -14,7 +14,7 @@ internal sealed class CommonSendRoute : Route { @Serializable data object Confirm : CommonSendRoute() { - override val isEditMode: Boolean = false + override val isEditMode: Boolean = true } @Serializable diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/utils/SendRouteUtils.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/utils/SendRouteUtils.kt new file mode 100644 index 0000000000..eba835865e --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/utils/SendRouteUtils.kt @@ -0,0 +1,28 @@ +package com.tangem.features.send.v2.common.utils + +import com.arkivanov.decompose.router.stack.ChildStack +import com.arkivanov.decompose.value.Value +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.send.v2.common.CommonSendRoute + +/** + * Workaround to try fix duplicate route crash + */ +internal fun Router.safeNextClick( + currentRoute: CommonSendRoute, + nextRoute: CommonSendRoute, + childStack: Value>, + popBack: () -> Unit, +) { + if (currentRoute.isEditMode) { + popBack() + } else { + val isAlreadyInStack = childStack.value.items.any { it.configuration == nextRoute } + if (isAlreadyInStack) { + popTo(nextRoute) + } else { + push(nextRoute) + } + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt index 895ac9ac94..3f2eba602f 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt @@ -26,6 +26,7 @@ import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.SendScreenSource import com.tangem.features.send.v2.common.ui.SendContent import com.tangem.features.send.v2.common.ui.state.ConfirmUM +import com.tangem.features.send.v2.common.utils.safeNextClick import com.tangem.features.send.v2.impl.R import com.tangem.features.send.v2.send.confirm.SendConfirmComponent import com.tangem.features.send.v2.send.model.SendModel @@ -139,28 +140,31 @@ internal class DefaultSendComponent @AssistedInject constructor( CommonSendRoute.Confirm -> getConfirmComponent(factoryContext) } - private fun getDestinationComponent(factoryContext: AppComponentContext, route: CommonSendRoute) = - SendDestinationComponent( - appComponentContext = factoryContext, - params = SendDestinationComponentParams.DestinationParams( - state = model.uiState.value.destinationUM, - currentRoute = currentRoute.filterIsInstance(), - isBalanceHidingFlow = model.isBalanceHiddenFlow, - analyticsCategoryName = analyticCategoryName, - title = resourceReference(R.string.send_recipient_label), - userWalletId = params.userWalletId, - cryptoCurrency = params.currency, - callback = model, - onBackClick = ::onChildBack, - onNextClick = { - if (route.isEditMode) { - onChildBack() - } else { - innerRouter.push(CommonSendRoute.Amount(isEditMode = false)) - } - }, - ), - ) + private fun getDestinationComponent( + factoryContext: AppComponentContext, + route: CommonSendRoute, + ): SendDestinationComponent = SendDestinationComponent( + appComponentContext = factoryContext, + params = SendDestinationComponentParams.DestinationParams( + state = model.uiState.value.destinationUM, + currentRoute = currentRoute.filterIsInstance(), + isBalanceHidingFlow = model.isBalanceHiddenFlow, + analyticsCategoryName = analyticCategoryName, + title = resourceReference(R.string.send_recipient_label), + userWalletId = params.userWalletId, + cryptoCurrency = params.currency, + callback = model, + onBackClick = ::onChildBack, + onNextClick = { + innerRouter.safeNextClick( + currentRoute = route, + nextRoute = CommonSendRoute.Amount(isEditMode = false), + popBack = ::onChildBack, + childStack = childStack, + ) + }, + ), + ) private fun getAmountComponent( factoryContext: AppComponentContext, @@ -196,11 +200,12 @@ internal class DefaultSendComponent @AssistedInject constructor( } }, onNextClick = { - if (route.isEditMode) { - onChildBack() - } else { - innerRouter.push(CommonSendRoute.Confirm) - } + innerRouter.safeNextClick( + currentRoute = route, + nextRoute = CommonSendRoute.Confirm, + popBack = ::onChildBack, + childStack = childStack, + ) }, ), ) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt index 7fc4f402c2..49bcae92d6 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt @@ -24,6 +24,7 @@ import com.tangem.features.send.v2.common.CommonSendRoute import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents import com.tangem.features.send.v2.common.ui.SendContent import com.tangem.features.send.v2.common.ui.state.ConfirmUM +import com.tangem.features.send.v2.common.utils.safeNextClick import com.tangem.features.send.v2.impl.R import com.tangem.features.send.v2.sendnft.confirm.NFTSendConfirmComponent import com.tangem.features.send.v2.sendnft.model.NFTSendModel @@ -126,28 +127,31 @@ internal class DefaultNFTSendComponent @AssistedInject constructor( else -> getStubComponent() } - private fun getDestinationComponent(factoryContext: AppComponentContext, route: CommonSendRoute) = - SendDestinationComponent( - appComponentContext = factoryContext, - params = SendDestinationComponentParams.DestinationParams( - state = model.uiState.value.destinationUM, - currentRoute = currentRouteFlow.filterIsInstance(), - isBalanceHidingFlow = model.isBalanceHiddenFlow, - title = resourceReference(R.string.nft_send), - analyticsCategoryName = analyticsCategoryName, - userWalletId = params.userWalletId, - cryptoCurrency = model.cryptoCurrency, - callback = model, - onBackClick = ::onChildBack, - onNextClick = { - if (route.isEditMode) { - onChildBack() - } else { - innerRouter.push(CommonSendRoute.Confirm) - } - }, - ), - ) + private fun getDestinationComponent( + factoryContext: AppComponentContext, + route: CommonSendRoute, + ): SendDestinationComponent = SendDestinationComponent( + appComponentContext = factoryContext, + params = SendDestinationComponentParams.DestinationParams( + state = model.uiState.value.destinationUM, + currentRoute = currentRouteFlow.filterIsInstance(), + isBalanceHidingFlow = model.isBalanceHiddenFlow, + title = resourceReference(R.string.nft_send), + analyticsCategoryName = analyticsCategoryName, + userWalletId = params.userWalletId, + cryptoCurrency = model.cryptoCurrency, + callback = model, + onBackClick = ::onChildBack, + onNextClick = { + innerRouter.safeNextClick( + currentRoute = route, + nextRoute = CommonSendRoute.Confirm, + popBack = ::onChildBack, + childStack = childStack, + ) + }, + ), + ) private fun getFeeComponent(factoryContext: AppComponentContext): ComposableContentComponent { val state = model.uiState.value From 88b622053d64a0fbcc6f08aa3f4b52e1673d9c09 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 May 2025 18:49:05 +0500 Subject: [PATCH 2/4] Updated on 2026-08-14 --- .../features/send/v2/common/PredefinedValues.kt | 6 +++--- .../tangem/features/send/v2/send/model/SendModel.kt | 9 ++++----- .../v2/subcomponents/amount/SendAmountComponent.kt | 2 +- .../subcomponents/amount/model/SendAmountModel.kt | 13 +++++++++---- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/PredefinedValues.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/PredefinedValues.kt index adbfd84dfc..ef94394fcf 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/PredefinedValues.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/PredefinedValues.kt @@ -4,19 +4,19 @@ sealed class PredefinedValues { data object Empty : PredefinedValues() sealed class Content : PredefinedValues() { - abstract val amount: String abstract val address: String + abstract val amount: String? abstract val memo: String? data class Deeplink( - override val amount: String, + override val amount: String?, override val address: String, override val memo: String?, val transactionId: String, ) : Content() data class QrCode( - override val amount: String, + override val amount: String?, override val address: String, override val memo: String?, ) : Content() diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt index 250e790576..ff273f2b85 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt @@ -121,8 +121,8 @@ internal class SendModel @Inject constructor( _uiState.update { it.copy(destinationUM = destinationUM) } } - override fun onAmountResult(amountUM: AmountState) { - resetPredefinedAmount() + override fun onAmountResult(amountUM: AmountState, isResetPredefined: Boolean) { + if (isResetPredefined) resetPredefinedAmount() _uiState.update { it.copy(amountUM = amountUM) } } @@ -131,7 +131,6 @@ internal class SendModel @Inject constructor( } override fun onResult(sendUM: SendUM) { - resetPredefinedAmount() _uiState.update { sendUM } } @@ -163,8 +162,8 @@ internal class SendModel @Inject constructor( // reset predefined amount val internalPredefinedValues = predefinedValues predefinedValues = when (internalPredefinedValues) { - is PredefinedValues.Content.Deeplink -> internalPredefinedValues.copy(amount = "") - is PredefinedValues.Content.QrCode -> internalPredefinedValues.copy(amount = "") + is PredefinedValues.Content.Deeplink -> internalPredefinedValues.copy(amount = null) + is PredefinedValues.Content.QrCode -> internalPredefinedValues.copy(amount = null) PredefinedValues.Empty -> internalPredefinedValues } } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt index f79c417846..b2a1c6a264 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt @@ -38,6 +38,6 @@ internal class SendAmountComponent( } interface ModelCallback : SendNavigationModelCallback { - fun onAmountResult(amountUM: AmountState) + fun onAmountResult(amountUM: AmountState, isResetPredefined: Boolean) } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt index 11974ef160..691bd074ca 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt @@ -103,9 +103,9 @@ internal class SendAmountModel @Inject constructor( ) } } - val predefinedValues = params.predefinedValues as? PredefinedValues.Content - if (predefinedValues?.amount != null) { - onAmountValueChange(predefinedValues.amount) + val predefinedAmount = (params.predefinedValues as? PredefinedValues.Content)?.amount + if (predefinedAmount != null) { + onAmountValueChange(predefinedAmount) } } @@ -220,7 +220,12 @@ internal class SendAmountModel @Inject constructor( private fun saveResult() { val params = params as? SendAmountComponentParams.AmountParams ?: return - params.callback.onAmountResult(uiState.value) + val predefinedAmount = (params.predefinedValues as? PredefinedValues.Content.QrCode)?.amount + val enteredAmount = (uiState.value as? AmountState.Data)?.amountTextField?.value + params.callback.onAmountResult( + amountUM = uiState.value, + isResetPredefined = predefinedAmount != enteredAmount, + ) } private fun configAmountNavigation() { From 8b47cd5aba7f58e7512bc9fc3a544ba365c1b606 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 May 2025 19:25:43 +0500 Subject: [PATCH 3/4] Updated on 2026-08-14 --- .../tangem/core/ui/extensions/ModifierExt.kt | 30 +++++++++++++++++++ .../core/ui/utils/MultipleClickPreventer.kt | 10 ++++++- .../v2/common/ui/SendNavigationButtons.kt | 19 ++++++++---- 3 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt new file mode 100644 index 0000000000..469e048635 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/ModifierExt.kt @@ -0,0 +1,30 @@ +package com.tangem.core.ui.extensions + +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.semantics.Role +import com.tangem.core.ui.utils.MultipleClickPreventer + +/** + * Clickable modifier with debounce multiple click in a short period of time + */ +fun Modifier.clickableSingle( + enabled: Boolean = true, + onClickLabel: String? = null, + role: Role? = null, + onClick: () -> Unit, +) = composed { + val multipleEventsCutter = remember { MultipleClickPreventer.get() } + Modifier.clickable( + enabled = enabled, + onClickLabel = onClickLabel, + onClick = { multipleEventsCutter.processEvent { onClick() } }, + role = role, + indication = LocalIndication.current, + interactionSource = remember { MutableInteractionSource() }, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/MultipleClickPreventer.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/MultipleClickPreventer.kt index 15ede204ae..767e1ada4d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/MultipleClickPreventer.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/MultipleClickPreventer.kt @@ -2,6 +2,9 @@ package com.tangem.core.ui.utils import android.os.SystemClock +/** + * Debounce multiple events (e.i. clicks, etc) in a short period of time + */ interface MultipleClickPreventer { fun processEvent(event: () -> Unit) @@ -24,4 +27,9 @@ private class DefaultMultipleClickPreventer : MultipleClickPreventer { companion object { private const val TIME_BETWEEN_CLICK_IN_MS = 500L } -} \ No newline at end of file +} + +/** + * Debounce multiple events (e.i. clicks, etc) in a short period of time + */ +fun singleEvent(event: () -> Unit) = MultipleClickPreventer.get().processEvent(event) \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/ui/SendNavigationButtons.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/ui/SendNavigationButtons.kt index 581e14ba92..70d852dff8 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/ui/SendNavigationButtons.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/ui/SendNavigationButtons.kt @@ -2,7 +2,6 @@ package com.tangem.features.send.v2.common.ui import androidx.compose.animation.* 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 @@ -26,10 +25,12 @@ 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.features.send.v2.send.ui.state.ButtonsUM +import com.tangem.core.ui.extensions.clickableSingle import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.singleEvent import com.tangem.features.send.v2.common.ui.state.NavigationUM +import com.tangem.features.send.v2.send.ui.state.ButtonsUM @Composable internal fun SendNavigationButtons(navigationUM: NavigationUM, modifier: Modifier = Modifier) { @@ -70,7 +71,7 @@ private fun SendNavigationButton(navigationUM: NavigationUM, modifier: Modifier modifier = Modifier .clip(RoundedCornerShape(16.dp)) .background(TangemTheme.colors.button.secondary) - .clickable(onClick = wrappedNavigationUM.onClick) + .clickableSingle(onClick = wrappedNavigationUM.onClick) .padding(12.dp), ) SpacerW12() @@ -110,7 +111,11 @@ private fun SendDoneButtons(pairButtonsUM: ButtonsUM.SecondaryPairButtonsUM?, mo SecondaryButtonIconStart( text = wrappedPairButtonsUM.leftText.resolveReference(), iconResId = wrappedPairButtonsUM.leftIconResId!!, - onClick = wrappedPairButtonsUM.onLeftClick, + onClick = { + singleEvent { + wrappedPairButtonsUM.onLeftClick + } + }, modifier = Modifier.weight(1f), ) SpacerW12() @@ -118,8 +123,10 @@ private fun SendDoneButtons(pairButtonsUM: ButtonsUM.SecondaryPairButtonsUM?, mo text = wrappedPairButtonsUM.rightText.resolveReference(), iconResId = wrappedPairButtonsUM.rightIconResId!!, onClick = { - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - wrappedPairButtonsUM.onRightClick() + singleEvent { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + wrappedPairButtonsUM.onRightClick() + } }, modifier = Modifier.weight(1f), ) From 597ff6b82e58033c9cb2e11cf07c9cb4973e3e03 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 May 2025 15:26:21 +0000 Subject: [PATCH 4/4] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index ef3a5a7696..ccc406b5ff 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,9 +5,9 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.24.0-1062" +tangemBlockchainSdk = "develop-1063" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.24.0-467" +tangemCardSdk = "develop-468" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^