Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-05 10:18:17 +04:00
parent 910cea0e31
commit 676ed0f5f7
3 changed files with 96 additions and 10 deletions

View file

@ -81,6 +81,10 @@ dependencies {
/** Feature modules */
implementation(projects.features.staking.api)
implementation(projects.features.txhistory.api)
implementation(projects.features.approval.api)
/** Decompose */
implementation(deps.decompose.ext.compose)
/** DI */
implementation(deps.hilt.android)

View file

@ -4,8 +4,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.approval.api.GiveApprovalComponent
import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.staking.impl.presentation.model.StakingModel
import com.tangem.features.staking.impl.presentation.ui.StakingScreen
@ -16,14 +20,34 @@ import dagger.assisted.AssistedInject
internal class DefaultStakingComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: StakingComponent.Params,
private val giveApprovalComponentFactory: GiveApprovalComponent.Factory,
) : StakingComponent, AppComponentContext by appComponentContext {
private val model: StakingModel = getOrCreateModel(params)
private val approvalSlot = childSlot(
key = "stakingApprovalSlot",
source = model.approvalSlotNavigation,
serializer = null,
handleBackButton = true,
childFactory = { _, factoryContext ->
val approvalParams = model.getApprovalParams()
?: error("Approval params are not available")
giveApprovalComponentFactory.create(
context = childByContext(factoryContext),
params = approvalParams,
)
},
)
@Composable
override fun Content(modifier: Modifier) {
val currentState by model.uiState.collectAsStateWithLifecycle()
val approvalSlotState by approvalSlot.subscribeAsState()
StakingScreen(currentState)
approvalSlotState.child?.instance?.BottomSheet()
}
@AssistedFactory

View file

@ -10,8 +10,13 @@ import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
import com.tangem.features.approval.api.GiveApprovalComponent
import com.tangem.features.approval.api.GiveApprovalFeatureToggles
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.ParamsInterceptorHolder
@ -22,6 +27,7 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.haptic.TangemHapticEffect
@ -146,12 +152,15 @@ internal class StakingModel @Inject constructor(
@DelayedWork private val coroutineScope: CoroutineScope,
private val innerRouter: InnerStakingRouter,
private val messageSender: UiMessageSender,
private val giveApprovalFeatureToggles: GiveApprovalFeatureToggles,
appRouter: AppRouter,
) : Model(), StakingClickIntents {
val uiState: StateFlow<StakingUiState> = stateController.uiState
val value: StakingUiState get() = uiState.value
val approvalSlotNavigation = SlotNavigation<Unit>()
private val params = paramsContainer.require<StakingComponent.Params>()
private val stakingStateRouter: StakingStateRouter = StakingStateRouter(
@ -254,6 +263,31 @@ internal class StakingModel @Inject constructor(
analyticsEventHandler = analyticsEventHandler,
)
private val approvalCallback = object : GiveApprovalComponent.Callback {
override fun onApproveClick() {
}
override fun onApproveDone() {
approvalSlotNavigation.dismiss()
stakingAnalyticSender.sendTransactionApprovalAnalytics(
cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return,
)
stateController.update(SetApprovalInProgressTransformer)
awaitForAllowance()
}
override fun onApproveFailed() {
approvalSlotNavigation.dismiss()
stateController.update(
SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus),
)
}
override fun onCancelClick() {
approvalSlotNavigation.dismiss()
}
}
private var stakingApproval: StakingApproval = StakingApproval.Empty
private var stakingAllowance: BigDecimal = BigDecimal.ZERO
private var isAmountSubtractAvailable: Boolean = false
@ -748,16 +782,20 @@ internal class StakingModel @Inject constructor(
}
override fun showApprovalBottomSheet() {
stateController.update(
ShowApprovalBottomSheetTransformer(
userWallet = userWallet,
appCurrencyProvider = Provider { appCurrency },
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
) {
stateController.update(DismissBottomSheetStateTransformer)
},
)
if (giveApprovalFeatureToggles.isGaslessApprovalEnabled) {
approvalSlotNavigation.activate(Unit)
} else {
stateController.update(
ShowApprovalBottomSheetTransformer(
userWallet = userWallet,
appCurrencyProvider = Provider { appCurrency },
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
) {
stateController.update(DismissBottomSheetStateTransformer)
},
)
}
}
override fun onApproveTypeChange(approveType: ApproveType) {
@ -1439,6 +1477,26 @@ internal class StakingModel @Inject constructor(
isTonHeatupCase
}
fun getApprovalParams(): GiveApprovalComponent.Params? {
val amountState = value.amountState as? AmountState.Data ?: return null
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data ?: return null
val targetAddress = validatorState.chosenTarget.address
val feeCurrencyStatus = feeCryptoCurrencyStatus ?: return null
return GiveApprovalComponent.Params(
userWalletId = userWallet.walletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
feeCryptoCurrencyStatus = feeCurrencyStatus,
amount = amountState.amountTextField.value,
spenderAddress = targetAddress,
subtitle = resourceReference(
id = R.string.give_permission_staking_subtitle,
formatArgs = wrappedList(cryptoCurrencyStatus.currency.symbol),
),
callback = approvalCallback,
)
}
private companion object {
const val WHAT_IS_STAKING_ARTICLE_URL = "https://tangem.com/en/blog/post/how-to-stake-cryptocurrency/"
const val ALLOWANCE_UPDATE_DELAY = 10_000L