Updated on 2026-08-14
This commit is contained in:
parent
bc1e6acd0f
commit
b9b41e9d26
24 changed files with 570 additions and 815 deletions
|
|
@ -69,4 +69,8 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
fun showPrimaryClickAlert()
|
||||
|
||||
fun onOpenLearnMoreAboutApproveClick()
|
||||
|
||||
fun onActivateTonAccountNotificationClick()
|
||||
|
||||
fun onActivateTonAccountClick()
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.staking.impl.presentation.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
|
||||
|
|
@ -48,8 +49,11 @@ import com.tangem.domain.staking.utils.getValidatorsCount
|
|||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.staking.impl.analytics.StakingParamsInterceptor
|
||||
|
|
@ -69,10 +73,15 @@ import com.tangem.features.staking.impl.presentation.state.transformers.approval
|
|||
import com.tangem.features.staking.impl.presentation.state.transformers.confirmation.SetUpdatedAllowanceTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.notifications.AddStakingNotificationsTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.notifications.DismissStakingNotificationsStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.ton.CompleteInitializeBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.ton.SetFeeToTonInitializeBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.ton.ShowTonInitializeBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.ton.SetFeeErrorToTonInitializeBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTon
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.TangemBlogUrlBuilder.RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP
|
||||
import com.tangem.utils.coroutines.*
|
||||
|
|
@ -122,6 +131,9 @@ internal class StakingModel @Inject constructor(
|
|||
private val getActionsUseCase: GetActionsUseCase,
|
||||
private val getYieldUseCase: GetYieldUseCase,
|
||||
private val checkAccountInitializedUseCase: CheckAccountInitializedUseCase,
|
||||
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
|
||||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase,
|
||||
private val getActionRequirementAmountUseCase: GetActionRequirementAmountUseCase,
|
||||
private val paramsInterceptorHolder: ParamsInterceptorHolder,
|
||||
private val shareManager: ShareManager,
|
||||
|
|
@ -150,11 +162,14 @@ internal class StakingModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private var isAccountInitialized: Boolean = true
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
private var stakingActions: List<StakingAction> = emptyList()
|
||||
private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
private var minimumTransactionAmount: EnterAmountBoundary? = null
|
||||
|
||||
private var tonAccountInitializeTransaction: TransactionData.Uncompiled? = null
|
||||
|
||||
private val userWallet by lazy {
|
||||
requireNotNull(
|
||||
getUserWalletUseCase(userWalletId).getOrNull(),
|
||||
|
|
@ -250,26 +265,12 @@ internal class StakingModel @Inject constructor(
|
|||
val isInitialInfoStep = value.currentStep == StakingStep.InitialInfo
|
||||
val noBalanceState = balanceState == null
|
||||
val noYieldBalanceData = cryptoCurrencyStatus.value.yieldBalance !is YieldBalance.Data
|
||||
val isAccountInitialized = checkAccountInitializedUseCase.invoke(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e(it)
|
||||
false
|
||||
}
|
||||
|
||||
when {
|
||||
isInitialInfoStep && noBalanceState && yield.allValidatorsFull && noYieldBalanceData -> {
|
||||
stakingEventFactory.createStakingValidatorsUnavailableAlert()
|
||||
return@launch
|
||||
}
|
||||
isInitialInfoStep && noBalanceState && !isAccountInitialized -> {
|
||||
analyticsEventHandler.send(StakingAnalyticsEvent.UnitializedAddress(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
))
|
||||
stakingEventFactory.createInitializeAccountAlert()
|
||||
return@launch
|
||||
}
|
||||
isInitialInfoStep && noBalanceState -> {
|
||||
val list = buildList {
|
||||
SetConfirmationStateInitTransformer(
|
||||
|
|
@ -763,6 +764,7 @@ internal class StakingModel @Inject constructor(
|
|||
AddStakingNotificationsTransformer(
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
isAccountInitializedProvider = Provider { isAccountInitialized },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
currencyWarning = currencyWarning,
|
||||
currencyCheck = currencyStatus,
|
||||
|
|
@ -934,6 +936,85 @@ internal class StakingModel @Inject constructor(
|
|||
urlOpener.openUrl(RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP)
|
||||
}
|
||||
|
||||
override fun onActivateTonAccountNotificationClick() {
|
||||
modelScope.launch {
|
||||
stateController.update(
|
||||
ShowTonInitializeBottomSheetTransformer(
|
||||
onDismiss = { stateController.update(DismissBottomSheetStateTransformer) },
|
||||
),
|
||||
)
|
||||
|
||||
val tonAmount = BigDecimal(1)
|
||||
val destination = getNetworkAddressesUseCase.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrNull(0)?.address ?: return@launch
|
||||
|
||||
val transaction = createTransferTransactionUseCase(
|
||||
amount = tonAmount.convertToSdkAmount(cryptoCurrencyStatus),
|
||||
destination = destination,
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
memo = null,
|
||||
)
|
||||
tonAccountInitializeTransaction = transaction.getOrElse {
|
||||
stateController.update(
|
||||
SetFeeErrorToTonInitializeBottomSheetTransformer(),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val transactionFee = getFeeUseCase(
|
||||
userWallet = userWallet,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
transactionData = tonAccountInitializeTransaction!!,
|
||||
)
|
||||
|
||||
transactionFee.fold(
|
||||
ifLeft = {
|
||||
stateController.update(
|
||||
SetFeeErrorToTonInitializeBottomSheetTransformer(),
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
stateController.update(
|
||||
SetFeeToTonInitializeBottomSheetTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = it.normal,
|
||||
isFeeApproximate = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivateTonAccountClick() {
|
||||
modelScope.launch {
|
||||
tonAccountInitializeTransaction?.let { transaction ->
|
||||
sendTransactionUseCase.invoke(
|
||||
txData = transaction,
|
||||
userWallet = userWallet,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).fold(
|
||||
ifLeft = {
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
},
|
||||
ifRight = {
|
||||
stateController.update(CompleteInitializeBottomSheetTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
))
|
||||
|
||||
balanceUpdater.partialUpdateWithDelay()
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setupApprovalNeeded() {
|
||||
val approval = StakingIntegrationID.create(currencyId = cryptoCurrencyStatus.currency.id)?.approval
|
||||
?: StakingApproval.Empty
|
||||
|
|
@ -961,7 +1042,9 @@ internal class StakingModel @Inject constructor(
|
|||
)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.filter { value.currentStep == StakingStep.InitialInfo }
|
||||
.filter {
|
||||
value.currentStep == StakingStep.InitialInfo || isCaseWithUnitializedTonAccount()
|
||||
}
|
||||
.onEach { maybeStatus ->
|
||||
maybeStatus.fold(
|
||||
ifRight = { status ->
|
||||
|
|
@ -989,6 +1072,19 @@ internal class StakingModel @Inject constructor(
|
|||
}
|
||||
cryptoCurrencyStatus = status
|
||||
|
||||
val isAccountInitializedNew = checkAccountInitializedUseCase.invoke(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e(it)
|
||||
false
|
||||
}
|
||||
if (isAccountInitializedNew && !isAccountInitialized) {
|
||||
isAccountInitialized = true
|
||||
updateNotifications()
|
||||
}
|
||||
isAccountInitialized = isAccountInitializedNew
|
||||
|
||||
setupApprovalNeeded()
|
||||
setupIsAnyTokenStaked()
|
||||
checkIfSubtractAvailable()
|
||||
|
|
@ -1150,6 +1246,12 @@ internal class StakingModel @Inject constructor(
|
|||
.getOrElse { false }
|
||||
}
|
||||
|
||||
private fun isCaseWithUnitializedTonAccount(): Boolean {
|
||||
return value.currentStep == StakingStep.Confirmation &&
|
||||
isTon(cryptoCurrencyStatus.currency.network.rawId) &&
|
||||
!isAccountInitialized
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -60,6 +60,15 @@ internal object StakingNotification {
|
|||
title = resourceReference(R.string.staking_notification_low_staked_balance_title),
|
||||
subtitle = resourceReference(R.string.staking_notification_low_staked_balance_text),
|
||||
)
|
||||
|
||||
data class InitializeTonAccount(private val onInitializeClick: () -> Unit) : Info(
|
||||
title = resourceReference(R.string.staking_notification_ton_account_initialization_title),
|
||||
subtitle = resourceReference(R.string.staking_notification_ton_account_initialization_message),
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.common_activate),
|
||||
onClick = onInitializeClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Info(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.bottomsheet
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
|
||||
internal data class TonInitializeAccountBottomSheetConfig(
|
||||
val title: TextReference,
|
||||
val message: TextReference,
|
||||
val footer: TextReference,
|
||||
val onButtonClick: () -> Unit,
|
||||
val isButtonEnabled: Boolean,
|
||||
val isButtonLoading: Boolean,
|
||||
val feeState: FeeState,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -62,13 +62,6 @@ internal sealed class StakingAlertUM : AlertUM {
|
|||
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
|
||||
}
|
||||
|
||||
data object InitializeAccount : StakingAlertUM() {
|
||||
override val onConfirmClick: (() -> Unit)? = null
|
||||
override val title: TextReference = resourceReference(id = R.string.staking_error_no_validators_title)
|
||||
override val message: TextReference = resourceReference(id = R.string.staking_notification_ton_activate_account)
|
||||
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
|
||||
}
|
||||
|
||||
data class RewardsMinimumRequirementsError(
|
||||
val cryptoCurrencyName: String,
|
||||
val cryptoAmountValue: String,
|
||||
|
|
|
|||
|
|
@ -47,11 +47,6 @@ internal class StakingEventFactory(
|
|||
stateController.updateEvent(alert)
|
||||
}
|
||||
|
||||
fun createInitializeAccountAlert() {
|
||||
val alert = StakingEvent.ShowAlert(alert = StakingAlertUM.InitializeAccount)
|
||||
stateController.updateEvent(alert)
|
||||
}
|
||||
|
||||
fun createStakingRewardsMinimumRequirementsErrorAlert(cryptoCurrencyName: String, cryptoAmountValue: String) {
|
||||
stateController.updateEvent(
|
||||
StakingEvent.ShowAlert(
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun partialUpdateWithDelay() {
|
||||
delay(BALANCE_UPDATE_DELAY)
|
||||
partialUpdate()
|
||||
}
|
||||
|
||||
private suspend fun fetchCurrencyStatus(delayMillis: Long = 0L) {
|
||||
delay(delayMillis)
|
||||
fetchCurrencyStatusUseCase(
|
||||
|
|
|
|||
|
|
@ -78,4 +78,8 @@ internal object StakingClickIntentsStub : StakingClickIntents {
|
|||
override fun showPrimaryClickAlert() {}
|
||||
|
||||
override fun onOpenLearnMoreAboutApproveClick() {}
|
||||
|
||||
override fun onActivateTonAccountNotificationClick() {}
|
||||
|
||||
override fun onActivateTonAccountClick() {}
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ import java.math.BigDecimal
|
|||
internal class AddStakingNotificationsTransformer(
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val isAccountInitializedProvider: Provider<Boolean>,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
private val currencyWarning: CryptoCurrencyWarning?,
|
||||
private val feeError: GetFeeError?,
|
||||
|
|
@ -90,34 +91,40 @@ internal class AddStakingNotificationsTransformer(
|
|||
BigDecimal.ZERO
|
||||
}
|
||||
|
||||
val notifications = buildList {
|
||||
// errors
|
||||
addErrorNotifications(
|
||||
prevState = prevState,
|
||||
feeError = feeError,
|
||||
sendingAmount = sendingAmount,
|
||||
onReload = prevState.clickIntents::getFee,
|
||||
feeValue = feeValue,
|
||||
)
|
||||
addStakingErrorNotifications(stakingError = stakingError, onReload = prevState.clickIntents::getFee)
|
||||
// warnings
|
||||
addWarningNotifications(
|
||||
prevState = prevState,
|
||||
amountState = amountState,
|
||||
feeState = feeState,
|
||||
sendingAmount = sendingAmount,
|
||||
isFeeCoverage = isFeeCoverage && isEnterAction && !sendingAmount.equals(minimumRequirement),
|
||||
)
|
||||
val notifications = if (isAccountInitializedProvider.invoke()) {
|
||||
buildList {
|
||||
// errors
|
||||
addErrorNotifications(
|
||||
prevState = prevState,
|
||||
feeError = feeError,
|
||||
sendingAmount = sendingAmount,
|
||||
onReload = prevState.clickIntents::getFee,
|
||||
feeValue = feeValue,
|
||||
)
|
||||
addStakingErrorNotifications(stakingError = stakingError, onReload = prevState.clickIntents::getFee)
|
||||
// warnings
|
||||
addWarningNotifications(
|
||||
prevState = prevState,
|
||||
amountState = amountState,
|
||||
feeState = feeState,
|
||||
sendingAmount = sendingAmount,
|
||||
isFeeCoverage = isFeeCoverage && isEnterAction && !sendingAmount.equals(minimumRequirement),
|
||||
)
|
||||
|
||||
stakingInfoNotificationsFactory.addInfoNotifications(
|
||||
notifications = this,
|
||||
prevState = prevState,
|
||||
sendingAmount = sendingAmount,
|
||||
actionAmount = amountValue,
|
||||
feeValue = feeValue,
|
||||
tonBalanceExtraFeeThreshold = TON_BALANCE_EXTRA_FEE_THRESHOLD,
|
||||
)
|
||||
}.toImmutableList()
|
||||
stakingInfoNotificationsFactory.addInfoNotifications(
|
||||
notifications = this,
|
||||
prevState = prevState,
|
||||
sendingAmount = sendingAmount,
|
||||
actionAmount = amountValue,
|
||||
feeValue = feeValue,
|
||||
tonBalanceExtraFeeThreshold = TON_BALANCE_EXTRA_FEE_THRESHOLD,
|
||||
)
|
||||
}.toImmutableList()
|
||||
} else {
|
||||
buildList {
|
||||
addTonInitializeAccountNotification(prevState)
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
val isActualSources = with(cryptoCurrencyStatus.value) {
|
||||
sources.yieldBalanceSource.isActual() && sources.networkSource.isActual()
|
||||
|
|
@ -130,7 +137,8 @@ internal class AddStakingNotificationsTransformer(
|
|||
it is StakingNotification.Error ||
|
||||
it is NotificationUM.Error ||
|
||||
it is NotificationUM.Warning.NetworkFeeUnreachable ||
|
||||
it is StakingNotification.Warning.TransactionInProgress
|
||||
it is StakingNotification.Warning.TransactionInProgress ||
|
||||
it is StakingNotification.Warning.InitializeTonAccount
|
||||
} && isActualSources,
|
||||
),
|
||||
)
|
||||
|
|
@ -219,7 +227,6 @@ internal class AddStakingNotificationsTransformer(
|
|||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val appCurrency = appCurrencyProvider()
|
||||
cryptoCurrencyStatus.currency
|
||||
|
||||
addRentExemptionNotification(
|
||||
rentWarning = currencyCheck.rentWarning,
|
||||
|
|
@ -282,6 +289,19 @@ internal class AddStakingNotificationsTransformer(
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addTonInitializeAccountNotification(prevState: StakingUiState) {
|
||||
val isAccountInitialized = isAccountInitializedProvider.invoke()
|
||||
val cryptoCurrencyNetworkIdValue = cryptoCurrencyStatusProvider().currency.network.rawId
|
||||
|
||||
if (isTon(cryptoCurrencyNetworkIdValue) && !isAccountInitialized) {
|
||||
add(
|
||||
StakingNotification.Warning.InitializeTonAccount(
|
||||
onInitializeClick = prevState.clickIntents::onActivateTonAccountNotificationClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val TON_BALANCE_EXTRA_FEE_THRESHOLD = BigDecimal(0.2)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.ton
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class CompleteInitializeBottomSheetTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val minimumTransactionAmount: EnterAmountBoundary?,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val prevBottomSheetConfig = prevState.bottomSheetConfig ?: return prevState
|
||||
val prevBottomSheetConfigContent =
|
||||
prevBottomSheetConfig.content as? TonInitializeAccountBottomSheetConfig ?: return prevState
|
||||
|
||||
val feeAmount = (prevBottomSheetConfigContent.feeState as? FeeState.Content)
|
||||
?.fee?.amount?.value?.multiply(BigDecimal(FEE_MULTIPLIER)) ?: return prevState
|
||||
|
||||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
|
||||
|
||||
return prevState.copy(
|
||||
amountState = AmountReduceByTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
value = ReduceByData(
|
||||
feeAmount,
|
||||
feeAmount,
|
||||
),
|
||||
).transform(prevState.amountState),
|
||||
confirmationState = confirmationState.copy(
|
||||
reduceAmountBy = feeAmount,
|
||||
),
|
||||
bottomSheetConfig = prevBottomSheetConfig.copy(
|
||||
content = prevBottomSheetConfigContent.copy(
|
||||
isButtonLoading = true,
|
||||
isButtonEnabled = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val FEE_MULTIPLIER = 1.1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.ton
|
||||
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SetFeeErrorToTonInitializeBottomSheetTransformer : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val prevBottomSheetConfig = prevState.bottomSheetConfig ?: return prevState
|
||||
val prevBottomSheetConfigContent =
|
||||
prevBottomSheetConfig.content as? TonInitializeAccountBottomSheetConfig ?: return prevState
|
||||
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = prevBottomSheetConfig.copy(
|
||||
content = prevBottomSheetConfigContent.copy(
|
||||
isButtonEnabled = false,
|
||||
feeState = FeeState.Error,
|
||||
isButtonLoading = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.ton
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SetFeeToTonInitializeBottomSheetTransformer(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
private val fee: Fee,
|
||||
private val isFeeApproximate: Boolean,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val prevBottomSheetConfig = prevState.bottomSheetConfig ?: return prevState
|
||||
val prevBottomSheetConfigContent =
|
||||
prevBottomSheetConfig.content as? TonInitializeAccountBottomSheetConfig ?: return prevState
|
||||
|
||||
val isFeeConvertibleToFiat = feeCryptoCurrencyStatus?.currency?.network?.hasFiatFeeRate == true
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = prevBottomSheetConfig.copy(
|
||||
content = prevBottomSheetConfigContent.copy(
|
||||
isButtonEnabled = true,
|
||||
isButtonLoading = false,
|
||||
feeState = FeeState.Content(
|
||||
fee = fee,
|
||||
rate = feeCryptoCurrencyStatus?.value?.fiatRate,
|
||||
isFeeConvertibleToFiat = isFeeConvertibleToFiat,
|
||||
appCurrency = appCurrencyProvider(),
|
||||
isFeeApproximate = isFeeApproximate,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.ton
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class ShowTonInitializeBottomSheetTransformer(
|
||||
private val onDismiss: () -> Unit,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = TonInitializeAccountBottomSheetConfig(
|
||||
title = resourceReference(R.string.staking_account_initialization_title),
|
||||
message = resourceReference(R.string.staking_account_initialization_message),
|
||||
footer = resourceReference(R.string.staking_account_initialization_footer),
|
||||
onButtonClick = prevState.clickIntents::onActivateTonAccountClick,
|
||||
isButtonEnabled = false,
|
||||
feeState = FeeState.Loading,
|
||||
isButtonLoading = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -28,8 +28,10 @@ import com.tangem.features.staking.impl.presentation.state.StakingStep
|
|||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingActionSelectionBottomSheetConfig
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingInfoBottomSheetConfig
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig
|
||||
import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingActionSelectorBottomSheet
|
||||
import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingInfoBottomSheet
|
||||
import com.tangem.features.staking.impl.presentation.ui.bottomsheet.TonInitializeAccountBottomSheet
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -83,6 +85,7 @@ fun StakingBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
|||
is StakingInfoBottomSheetConfig -> StakingInfoBottomSheet(bottomSheetConfig)
|
||||
is GiveTxPermissionBottomSheetConfig -> GiveTxPermissionBottomSheet(bottomSheetConfig)
|
||||
is StakingActionSelectionBottomSheetConfig -> StakingActionSelectorBottomSheet(bottomSheetConfig)
|
||||
is TonInitializeAccountBottomSheetConfig -> TonInitializeAccountBottomSheet(bottomSheetConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
package com.tangem.features.staking.impl.presentation.ui.bottomsheet
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
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.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig
|
||||
import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Composable
|
||||
internal fun TonInitializeAccountBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet<TonInitializeAccountBottomSheetConfig>(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
) { content ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SpacerH(height = 16.dp)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.img_ton_22),
|
||||
modifier = Modifier.size(56.dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
SpacerH(height = 24.dp)
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = content.title.resolveReference(),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
SpacerH(height = 16.dp)
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = content.message.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
SpacerH(height = 24.dp)
|
||||
|
||||
StakingFeeBlock(content.feeState)
|
||||
|
||||
SpacerH(height = 12.dp)
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = content.footer.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
SpacerH(height = 20.dp)
|
||||
|
||||
TangemButton(
|
||||
text = stringResourceSafe(R.string.common_activate),
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
|
||||
onClick = content.onButtonClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
showProgress = content.feeState is FeeState.Loading || content.isButtonLoading,
|
||||
enabled = content.isButtonEnabled,
|
||||
size = TangemButtonSize.WideAction,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
SpacerH(height = 8.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview_TonInitializeAccountBottomSheetContent() {
|
||||
TangemThemePreview {
|
||||
TonInitializeAccountBottomSheet(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = {},
|
||||
content = TonInitializeAccountBottomSheetConfig(
|
||||
title = resourceReference(R.string.staking_account_initialization_title),
|
||||
message = resourceReference(R.string.staking_account_initialization_message),
|
||||
footer = resourceReference(R.string.staking_account_initialization_footer),
|
||||
onButtonClick = {},
|
||||
isButtonEnabled = true,
|
||||
isButtonLoading = false,
|
||||
feeState = FeeState.Content(
|
||||
fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "MATIC",
|
||||
value = BigDecimal(0.159806),
|
||||
decimals = 18,
|
||||
type = AmountType.Coin,
|
||||
),
|
||||
),
|
||||
rate = BigDecimal.ONE,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = true,
|
||||
),
|
||||
),
|
||||
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
Loading…
Add table
Add a link
Reference in a new issue