Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-13 11:33:35 +03:00
parent bc1e6acd0f
commit b9b41e9d26
24 changed files with 570 additions and 815 deletions

View file

@ -69,4 +69,8 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
fun showPrimaryClickAlert()
fun onOpenLearnMoreAboutApproveClick()
fun onActivateTonAccountNotificationClick()
fun onActivateTonAccountClick()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -78,4 +78,8 @@ internal object StakingClickIntentsStub : StakingClickIntents {
override fun showPrimaryClickAlert() {}
override fun onOpenLearnMoreAboutApproveClick() {}
override fun onActivateTonAccountNotificationClick() {}
override fun onActivateTonAccountClick() {}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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