Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-07 17:00:36 +04:00
commit 507169a2c3
492 changed files with 5734 additions and 12510 deletions

View file

@ -1,6 +1,6 @@
package com.tangem.features.staking.impl.presentation.state.events
import com.tangem.common.ui.alerts.SendTransactionAlertConverter
import com.tangem.common.ui.alerts.TransactionErrorAlertConverter
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.features.staking.impl.presentation.state.StakingStateController
@ -22,7 +22,7 @@ internal class StakingEventFactory(
fun createSendTransactionErrorAlert(error: SendTransactionError?) {
val alert = error?.let {
SendTransactionAlertConverter(
TransactionErrorAlertConverter(
popBackStack = popBackStack,
onFailedTxEmailClick = onFailedTxEmailClick,
).convert(error)

View file

@ -16,7 +16,7 @@ import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionSt
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.usecase.SendMultipleTransactionUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents
@ -40,7 +40,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
private val stakingBalanceUpdater: StakingBalanceUpdater.Factory,
private val getStakingTransactionUseCase: GetStakingTransactionUseCase,
private val getConstructedStakingTransactionUseCase: GetConstructedStakingTransactionUseCase,
private val sendMultipleTransactionUseCase: SendMultipleTransactionUseCase,
private val sendTransactionUseCase: SendTransactionUseCase,
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val submitHashUseCase: SubmitHashUseCase,
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
@ -210,7 +210,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
onSendSuccess: (txUrl: String) -> Unit,
onSendError: (SendTransactionError?) -> Unit,
) {
sendMultipleTransactionUseCase(
sendTransactionUseCase(
txsData = fullTransactionsData.map { it.tangemTransaction },
userWallet = userWallet,
network = cryptoCurrencyStatus.currency.network,

View file

@ -136,13 +136,11 @@ internal class AddStakingNotificationsTransformer(
val cryptoCurrency = cryptoCurrencyStatus.currency
val network = cryptoCurrency.network
if (feeError != null) {
addFeeUnreachableNotification(
feeError = feeError,
tokenName = cryptoCurrencyStatusProvider().currency.name,
onReload = onReload,
)
}
addFeeUnreachableNotification(
feeError = feeError,
tokenName = cryptoCurrencyStatusProvider().currency.name,
onReload = onReload,
)
addStakeExceedBalanceNotification(
feeAmount = feeValue,
sendingAmount = sendingAmount,

View file

@ -8,15 +8,17 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.extensions.orZero
import com.tangem.features.staking.impl.presentation.state.utils.getRewardSchedule
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
internal class SetConfirmationStateLoadingTransformer(
private val yield: Yield,
private val appCurrency: AppCurrency,
private val cryptoCurrency: CryptoCurrency,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
@ -38,7 +40,7 @@ internal class SetConfirmationStateLoadingTransformer(
availableValidators = filteredValidators,
),
notifications = persistentListOf(),
footerText = getFooter(prevState, chosenValidator),
footerText = getFooter(prevState),
transactionDoneState = TransactionDoneState.Empty,
pendingAction = possibleConfirmationState?.pendingAction,
pendingActions = possibleConfirmationState?.pendingActions,
@ -49,30 +51,25 @@ internal class SetConfirmationStateLoadingTransformer(
)
}
private fun getFooter(state: StakingUiState, validator: Yield.Validator): TextReference {
private fun getFooter(state: StakingUiState): TextReference {
val amountState = state.amountState as? AmountState.Data
val isEnterAction = state.actionType == StakingActionCommonType.ENTER
val apr = validator.apr.orZero()
val amountDecimal = amountState?.amountTextField?.fiatAmount?.value
val potentialReward = amountDecimal?.multiply(apr)
val amountValue = BigDecimalFormatter.formatFiatAmount(
fiatAmount = amountDecimal,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
val potentialRewardValue = BigDecimalFormatter.formatFiatAmount(
fiatAmount = potentialReward,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
withApproximateSign = true,
val rewardSchedule = getRewardSchedule(
yield.metadata.rewardSchedule,
cryptoCurrency.network.id.value,
)
return if (isEnterAction && amountDecimal != null && potentialReward != null) {
return if (isEnterAction && amountDecimal != null && rewardSchedule != null) {
resourceReference(
id = R.string.staking_summary_description_text,
formatArgs = wrappedList(amountValue, potentialRewardValue),
formatArgs = wrappedList(amountValue, rewardSchedule),
)
} else {
TextReference.EMPTY

View file

@ -9,7 +9,6 @@ import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.serialization.SerializedBigDecimal
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -19,6 +18,7 @@ import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.state.converters.RewardsValidatorStateConverter
import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter
import com.tangem.features.staking.impl.presentation.state.utils.getRewardSchedule
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.lib.crypto.BlockchainUtils.isPolkadot
import com.tangem.utils.Provider
@ -102,20 +102,18 @@ internal class SetInitialDataStateTransformer(
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
return listOfNotNull(
createAnnualPercentageRateItem(yield.validators),
createAnnualPercentageRateItem(),
createAvailableItem(cryptoCurrencyStatus),
createUnbondingPeriodItem(yield.metadata.cooldownPeriod?.days),
createMinimumRequirementItem(
cryptoCurrencyStatus = cryptoCurrencyStatus,
minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum,
),
createRewardClaimingItem(yield.metadata.rewardClaiming),
createWarmupPeriodItem(yield.metadata.warmupPeriod.days),
createRewardScheduleItem(yield.metadata.rewardSchedule),
createUnbondingPeriodItem(),
createMinimumRequirementItem(cryptoCurrencyStatus),
createRewardClaimingItem(),
createWarmupPeriodItem(),
createRewardScheduleItem(),
).toPersistentList()
}
private fun createAnnualPercentageRateItem(validators: List<Yield.Validator>): RoundedListWithDividersItemData {
private fun createAnnualPercentageRateItem(): RoundedListWithDividersItemData {
val validators = yield.validators
return RoundedListWithDividersItemData(
id = R.string.staking_details_annual_percentage_rate,
startText = TextReference.Res(R.string.staking_details_annual_percentage_rate),
@ -140,8 +138,8 @@ internal class SetInitialDataStateTransformer(
)
}
private fun createUnbondingPeriodItem(cooldownPeriodDays: Int?): RoundedListWithDividersItemData? {
cooldownPeriodDays ?: return null
private fun createUnbondingPeriodItem(): RoundedListWithDividersItemData? {
val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days ?: return null
return RoundedListWithDividersItemData(
id = R.string.staking_details_unbonding_period,
startText = TextReference.Res(R.string.staking_details_unbonding_period),
@ -156,9 +154,8 @@ internal class SetInitialDataStateTransformer(
private fun createMinimumRequirementItem(
cryptoCurrencyStatus: CryptoCurrencyStatus,
minimumCryptoAmount: SerializedBigDecimal?,
): RoundedListWithDividersItemData? {
if (minimumCryptoAmount == null) return null
val minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum ?: return null
if (!isPolkadot(cryptoCurrencyStatus.currency.network.id.value)) return null
val formattedAmount = BigDecimalFormatter.formatCryptoAmount(
@ -174,9 +171,8 @@ internal class SetInitialDataStateTransformer(
)
}
private fun createRewardClaimingItem(
rewardClaiming: Yield.Metadata.RewardClaiming,
): RoundedListWithDividersItemData? {
private fun createRewardClaimingItem(): RoundedListWithDividersItemData? {
val rewardClaiming = yield.metadata.rewardClaiming
val endTextId = rewardClaimingResources[rewardClaiming] ?: return null
return RoundedListWithDividersItemData(
@ -187,7 +183,8 @@ internal class SetInitialDataStateTransformer(
)
}
private fun createWarmupPeriodItem(warmupPeriodDays: Int): RoundedListWithDividersItemData? {
private fun createWarmupPeriodItem(): RoundedListWithDividersItemData? {
val warmupPeriodDays = yield.metadata.warmupPeriod.days
if (warmupPeriodDays == 0) return null
return RoundedListWithDividersItemData(
@ -202,10 +199,8 @@ internal class SetInitialDataStateTransformer(
)
}
private fun createRewardScheduleItem(
rewardSchedule: Yield.Metadata.RewardSchedule,
): RoundedListWithDividersItemData? {
val endTextReference = getRewardScheduleText(rewardSchedule) ?: return null
private fun createRewardScheduleItem(): RoundedListWithDividersItemData? {
val endTextReference = getRewardScheduleText() ?: return null
return RoundedListWithDividersItemData(
id = R.string.staking_details_reward_schedule,
@ -257,17 +252,16 @@ internal class SetInitialDataStateTransformer(
return resourceReference(R.string.common_range, wrappedList(formattedMinApr, formattedMaxApr))
}
private fun getRewardScheduleText(rewardSchedule: Yield.Metadata.RewardSchedule): TextReference? {
return when (rewardSchedule) {
Yield.Metadata.RewardSchedule.BLOCK -> resourceReference(R.string.staking_reward_schedule_block)
Yield.Metadata.RewardSchedule.WEEK -> resourceReference(R.string.staking_reward_schedule_week)
Yield.Metadata.RewardSchedule.HOUR -> resourceReference(R.string.staking_reward_schedule_hour)
Yield.Metadata.RewardSchedule.DAY -> resourceReference(R.string.staking_reward_schedule_each_day)
Yield.Metadata.RewardSchedule.MONTH -> resourceReference(R.string.staking_reward_schedule_month)
Yield.Metadata.RewardSchedule.ERA -> resourceReference(R.string.staking_reward_schedule_era)
Yield.Metadata.RewardSchedule.EPOCH -> resourceReference(R.string.staking_reward_schedule_epoch)
Yield.Metadata.RewardSchedule.UNKNOWN -> null
}
private fun getRewardScheduleText(): TextReference? {
val rewardSchedule = getRewardSchedule(
yield.metadata.rewardSchedule,
cryptoCurrencyStatusProvider().currency.network.id.value,
) ?: return null
return resourceReference(
R.string.staking_reward_schedule_each,
wrappedList(rewardSchedule),
)
}
private companion object {

View file

@ -0,0 +1,52 @@
package com.tangem.features.staking.impl.presentation.state.utils
import com.tangem.core.ui.extensions.*
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.utils.StakingRewardSchedule.COSMOS_SCHEDULE
import com.tangem.features.staking.impl.presentation.state.utils.StakingRewardSchedule.SOLANA_SCHEDULE
import com.tangem.lib.crypto.BlockchainUtils.isCosmos
import com.tangem.lib.crypto.BlockchainUtils.isSolana
import com.tangem.lib.crypto.BlockchainUtils.isTron
import com.tangem.utils.StringsSigns.MINUS
import com.tangem.utils.StringsSigns.NON_BREAKING_SPACE
private data object StakingRewardSchedule {
val COSMOS_SCHEDULE = 5 to 12
val SOLANA_SCHEDULE = 2 to 3
}
internal fun getRewardSchedule(schedule: Yield.Metadata.RewardSchedule, networkId: String): TextReference? =
when (schedule) {
Yield.Metadata.RewardSchedule.BLOCK,
Yield.Metadata.RewardSchedule.EPOCH,
Yield.Metadata.RewardSchedule.ERA,
-> getCustomRewardSchedule(networkId)
Yield.Metadata.RewardSchedule.WEEK -> resourceReference(R.string.common_week)
Yield.Metadata.RewardSchedule.HOUR -> resourceReference(R.string.common_hour)
Yield.Metadata.RewardSchedule.DAY -> pluralReference(R.plurals.common_days_no_param, count = 1)
Yield.Metadata.RewardSchedule.MONTH -> resourceReference(R.string.common_month)
Yield.Metadata.RewardSchedule.UNKNOWN -> null
}
private fun getCustomRewardSchedule(networkId: String): TextReference? {
return when {
isSolana(networkId) -> {
combinedReference(
stringReference("${SOLANA_SCHEDULE.first}$MINUS${SOLANA_SCHEDULE.second}$NON_BREAKING_SPACE"),
pluralReference(
id = R.plurals.common_days_no_param,
count = SOLANA_SCHEDULE.second,
),
)
}
isCosmos(networkId) -> {
combinedReference(
stringReference("${COSMOS_SCHEDULE.first}$MINUS${COSMOS_SCHEDULE.second}$NON_BREAKING_SPACE"),
resourceReference(R.string.common_second_no_param),
)
}
isTron(networkId) -> pluralReference(R.plurals.common_days_no_param, count = 1)
else -> null
}
}

View file

@ -50,29 +50,17 @@ internal fun StakingEventEffect(event: StateEvent<StakingEvent>, snackbarHostSta
@Composable
internal fun StakingAlert(state: AlertUM, onDismiss: () -> Unit) {
val confirmButton: DialogButtonUM
val dismissButton: DialogButtonUM?
val onActionClick = state.onConfirmClick
if (onActionClick != null) {
confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
onClick = {
onActionClick()
onDismiss()
},
)
dismissButton = DialogButtonUM(
title = stringResource(id = R.string.common_cancel),
onClick = onDismiss,
)
} else {
confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
onClick = onDismiss,
)
dismissButton = null
}
val confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
onClick = {
state.onConfirmClick()
onDismiss()
},
)
val dismissButton = DialogButtonUM(
title = stringResource(id = R.string.common_cancel),
onClick = onDismiss,
)
BasicDialog(
message = state.message.resolveReference(),

View file

@ -18,9 +18,9 @@ import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.feedback.FeedbackManager
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase
@ -92,7 +92,6 @@ internal class StakingViewModel @Inject constructor(
private val getAllowanceUseCase: GetAllowanceUseCase,
private val isApproveNeededUseCase: IsApproveNeededUseCase,
private val vibratorHapticManager: VibratorHapticManager,
private val feedbackManager: FeedbackManager,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
@ -105,6 +104,7 @@ internal class StakingViewModel @Inject constructor(
private val stakingFeeTransactionLoader: StakingFeeTransactionLoader.Factory,
private val stakingBalanceUpdater: StakingBalanceUpdater.Factory,
private val analyticsEventHandler: AnalyticsEventHandler,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
@DelayedWork private val coroutineScope: CoroutineScope,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
@ -222,7 +222,13 @@ internal class StakingViewModel @Inject constructor(
when {
isInitState() -> {
stateController.update(SetConfirmationStateResetAssentTransformer)
stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency))
stateController.update(
SetConfirmationStateLoadingTransformer(
yield = yield,
appCurrency = appCurrency,
cryptoCurrency = cryptoCurrencyStatus.currency,
),
)
if (balanceState != null) {
stateController.update(SetPossiblePendingTransactionTransformer(balanceState, cryptoCurrencyStatus))
}
@ -263,7 +269,13 @@ internal class StakingViewModel @Inject constructor(
}
override fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList<PendingAction>?) {
stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency))
stateController.update(
SetConfirmationStateLoadingTransformer(
yield = yield,
appCurrency = appCurrency,
cryptoCurrency = cryptoCurrencyStatus.currency,
),
)
viewModelScope.launch {
feeLoader.getFee(
pendingAction = pendingAction,
@ -766,7 +778,7 @@ internal class StakingViewModel @Inject constructor(
unsignedTransactions = transactionsInProgress.map { it.unsignedTransaction },
)
feedbackManager.sendEmail(email)
sendFeedbackEmailUseCase(email)
}
}