Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-12 18:29:42 +03:00
commit 4152281fea
11 changed files with 156 additions and 70 deletions

View file

@ -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

View file

@ -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()

View file

@ -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),
)

View file

@ -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<ChildStack<CommonSendRoute, ComposableContentComponent>>,
popBack: () -> Unit,
) {
if (currentRoute.isEditMode) {
popBack()
} else {
val isAlreadyInStack = childStack.value.items.any { it.configuration == nextRoute }
if (isAlreadyInStack) {
popTo(nextRoute)
} else {
push(nextRoute)
}
}
}

View file

@ -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<CommonSendRoute.Destination>(),
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<CommonSendRoute.Destination>(),
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,
)
},
),
)

View file

@ -119,8 +119,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) }
}
@ -129,7 +129,6 @@ internal class SendModel @Inject constructor(
}
override fun onResult(sendUM: SendUM) {
resetPredefinedAmount()
_uiState.update { sendUM }
}
@ -161,8 +160,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
}
}

View file

@ -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<CommonSendRoute.Destination>(),
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<CommonSendRoute.Destination>(),
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

View file

@ -38,6 +38,6 @@ internal class SendAmountComponent(
}
interface ModelCallback : SendNavigationModelCallback {
fun onAmountResult(amountUM: AmountState)
fun onAmountResult(amountUM: AmountState, isResetPredefined: Boolean)
}
}

View file

@ -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() {