Updated on 2026-08-14
This commit is contained in:
parent
faa58cf142
commit
ea8c9079b7
24 changed files with 311 additions and 69 deletions
|
|
@ -156,4 +156,16 @@ internal object StakingDomainModule {
|
|||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsApproveNeededUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): IsApproveNeededUseCase {
|
||||
return IsApproveNeededUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ class AmountStateConverter(
|
|||
|
||||
return AmountState.Data(
|
||||
walletName = userWallet.name,
|
||||
walletBalance = resourceReference(R.string.send_wallet_balance_format, wrappedList(crypto, fiat)),
|
||||
walletBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
|
||||
tokenIconState = iconStateConverter.convert(status),
|
||||
amountTextField = amountFieldConverter.convert(value),
|
||||
isPrimaryButtonEnabled = false,
|
||||
|
|
|
|||
|
|
@ -93,10 +93,12 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh
|
|||
PrimaryButtonIconEnd(
|
||||
text = stringResource(id = R.string.common_approve),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
showProgress = data.approveButton.loading,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
onClick = data.approveButton.onClick,
|
||||
enabled = data.approveButton.enabled,
|
||||
)
|
||||
|
||||
SpacerH12()
|
||||
|
|
@ -107,6 +109,7 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh
|
|||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
onClick = content.onCancel,
|
||||
enabled = data.cancelButton.enabled,
|
||||
)
|
||||
|
||||
SpacerH16()
|
||||
|
|
@ -151,7 +154,7 @@ private fun AmountItem(
|
|||
currency: String,
|
||||
approveType: ApproveType,
|
||||
approveItems: ImmutableList<ApproveType>,
|
||||
onChangeApproveType: (ApproveType) -> Unit,
|
||||
onChangeApproveType: ((ApproveType) -> Unit)?,
|
||||
) {
|
||||
var isExpandSelector by remember { mutableStateOf(false) }
|
||||
var amountSize by remember { mutableStateOf(IntSize.Zero) }
|
||||
|
|
@ -208,8 +211,10 @@ private fun AmountItem(
|
|||
isExpanded = isExpandSelector,
|
||||
onDismiss = { isExpandSelector = false },
|
||||
onItemClick = { approveType ->
|
||||
isExpandSelector = false
|
||||
onChangeApproveType.invoke(approveType)
|
||||
onChangeApproveType?.let {
|
||||
isExpandSelector = false
|
||||
onChangeApproveType.invoke(approveType)
|
||||
}
|
||||
},
|
||||
items = approveItems,
|
||||
selectedType = approveType,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ sealed class GiveTxPermissionState {
|
|||
val approveItems: ImmutableList<ApproveType> = ApproveType.entries.toImmutableList(),
|
||||
val approveButton: ApprovePermissionButton,
|
||||
val cancelButton: CancelPermissionButton,
|
||||
val onChangeApproveType: (ApproveType) -> Unit,
|
||||
val onChangeApproveType: ((ApproveType) -> Unit)? = null,
|
||||
) : GiveTxPermissionState()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@
|
|||
<string name="common_copy">Kopieren</string>
|
||||
<string name="common_copy_address">Adresse kopieren</string>
|
||||
<string name="common_create">Erstellen</string>
|
||||
<string name="common_crypto_fiat_format">%1$s (%2$s)</string>
|
||||
<string name="common_custom">Eigen</string>
|
||||
<plurals name="common_days">
|
||||
<item quantity="one">%d tag</item>
|
||||
|
|
@ -639,7 +640,6 @@
|
|||
<string name="send_transaction_success">Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert</string>
|
||||
<string name="send_tron_account_activation_error">%1$s ist ein Vermögenswert im Tron-Netzwerk. Um die Gebühr zu berechnen und eine Transaktion durchzuführen, musst du etwas Tron (TRX) auf deinem Konto einzahlen.</string>
|
||||
<string name="send_validation_invalid_address">Ungültige Adresse</string>
|
||||
<string name="send_wallet_balance_format">%1$s (%2$s)</string>
|
||||
<string name="sent_transaction_sent_title">Transaktion gesendet</string>
|
||||
<string name="settings_card_settings_footer">Bereite das Scannen der Karte vor, die du einrichten möchtest.</string>
|
||||
<string name="settings_forget_wallet">Entferne diese Wallet</string>
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@
|
|||
<string name="common_copy">Copier</string>
|
||||
<string name="common_copy_address">Copier l\'adresse</string>
|
||||
<string name="common_create">Créer</string>
|
||||
<string name="common_crypto_fiat_format">%1$s (%2$s)</string>
|
||||
<string name="common_custom">Personnalisé</string>
|
||||
<plurals name="common_days">
|
||||
<item quantity="one">%d jour</item>
|
||||
|
|
@ -567,7 +568,6 @@
|
|||
<string name="send_transaction_success">La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps</string>
|
||||
<string name="send_tron_account_activation_error">%1$s est un actif du réseau Tron. Pour calculer les frais et effectuer une transaction, déposez du Tron (TRX) sur votre compte.</string>
|
||||
<string name="send_validation_invalid_address">Adresse incorrecte</string>
|
||||
<string name="send_wallet_balance_format">%1$s (%2$s)</string>
|
||||
<string name="sent_transaction_sent_title">Transaction envoyée</string>
|
||||
<string name="settings_card_settings_footer">Préparez-vous à numériser la carte que vous souhaitez configurer.</string>
|
||||
<string name="settings_forget_wallet">Oublier le portefeuille</string>
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@
|
|||
<string name="common_copy">コピー</string>
|
||||
<string name="common_copy_address">アドレスをコピー</string>
|
||||
<string name="common_create">作成</string>
|
||||
<string name="common_crypto_fiat_format">%1$s ( %2$s )</string>
|
||||
<string name="common_custom">設定</string>
|
||||
<plurals name="common_days">
|
||||
<item quantity="other">%d 日</item>
|
||||
|
|
@ -627,7 +628,6 @@
|
|||
<string name="send_transaction_success">取引は正常に署名され、ブロックチェーンノードに送信されました。ウォレットの残高はしばらくして更新されます。</string>
|
||||
<string name="send_tron_account_activation_error">%1$sはTronネットワークのアセットです。手数料を計算して取引を行うには、アカウントにTron(TRX)を入金する必要があります。</string>
|
||||
<string name="send_validation_invalid_address">無効なアドレス</string>
|
||||
<string name="send_wallet_balance_format">%1$s ( %2$s )</string>
|
||||
<string name="sent_transaction_sent_title">取引が送信されました</string>
|
||||
<string name="settings_card_settings_footer">セットアップしたいカードをスキャンするために準備してください。</string>
|
||||
<string name="settings_forget_wallet">ウォレット削除</string>
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@
|
|||
<string name="common_copy">Копіювати</string>
|
||||
<string name="common_copy_address">Скопіювати адресу</string>
|
||||
<string name="common_create">Створити</string>
|
||||
<string name="common_crypto_fiat_format">%1$s (%2$s)</string>
|
||||
<string name="common_custom">Власна</string>
|
||||
<plurals name="common_days">
|
||||
<item quantity="one">%d день</item>
|
||||
|
|
@ -651,7 +652,6 @@
|
|||
<string name="send_transaction_success">Транзакція успішно підписана і відправлена до блокчейну. Баланс гаманця буде оновлено через деякий час</string>
|
||||
<string name="send_tron_account_activation_error">%1$s — це монета у мережі Tron. Щоб розрахувати комісію та здійснити транзакцію, вам необхідно внести певну кількість Tron (TRX) на свій рахунок.</string>
|
||||
<string name="send_validation_invalid_address">Недійсна адреса</string>
|
||||
<string name="send_wallet_balance_format">%1$s (%2$s)</string>
|
||||
<string name="sent_transaction_sent_title">Трансакцію надіслано</string>
|
||||
<string name="settings_card_settings_footer">Підготуйтеся до сканування картку, яку потрібно налаштувати.</string>
|
||||
<string name="settings_forget_wallet">Забути гаманець</string>
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@
|
|||
<string name="common_copy">Copy</string>
|
||||
<string name="common_copy_address">Copy address</string>
|
||||
<string name="common_create">Create</string>
|
||||
<string name="common_crypto_fiat_format">%1$s (%2$s)</string>
|
||||
<string name="common_custom">Custom</string>
|
||||
<plurals name="common_days">
|
||||
<item quantity="one">%d day</item>
|
||||
|
|
@ -635,7 +636,6 @@
|
|||
<string name="send_transaction_success">Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while</string>
|
||||
<string name="send_tron_account_activation_error">%1$s is an asset in the Tron network. To calculate the fee and make a transaction you need to deposit some Tron (TRX) in your account.</string>
|
||||
<string name="send_validation_invalid_address">Invalid address</string>
|
||||
<string name="send_wallet_balance_format">%1$s (%2$s)</string>
|
||||
<string name="sent_transaction_sent_title">Transaction sent</string>
|
||||
<string name="settings_card_settings_footer">Prepare to scan card you want to setup.</string>
|
||||
<string name="settings_forget_wallet">Forget wallet</string>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ import kotlinx.coroutines.flow.*
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
internal class DefaultStakingRepository(
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
|
|
@ -99,8 +99,8 @@ internal class DefaultStakingRepository(
|
|||
value = emptyMap<UserWalletId, Boolean>(),
|
||||
)
|
||||
|
||||
override fun isStakingSupported(currencyId: String): Boolean {
|
||||
return integrationIdMap.containsKey(currencyId)
|
||||
override fun isStakingSupported(integrationKey: String): Boolean {
|
||||
return integrationIdMap.containsKey(integrationKey)
|
||||
}
|
||||
|
||||
override suspend fun fetchEnabledYields(refresh: Boolean) {
|
||||
|
|
@ -153,7 +153,7 @@ internal class DefaultStakingRepository(
|
|||
val yields = getEnabledYields() ?: return@withContext StakingAvailability.Unavailable
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(yields, rawCurrencyId, symbol)
|
||||
val isSupported = isStakingSupported(rawCurrencyId)
|
||||
val isSupported = isStakingSupported(cryptoCurrencyId.getIntegrationKey())
|
||||
|
||||
when {
|
||||
prefetchedYield != null && isSupported -> {
|
||||
|
|
@ -250,10 +250,7 @@ internal class DefaultStakingRepository(
|
|||
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
|
||||
|
||||
val cryptoCurrency = address.cryptoCurrency
|
||||
val rawCurrencyId =
|
||||
cryptoCurrency.id.rawCurrencyId ?: error("Staking custom tokens is not available")
|
||||
|
||||
val integrationId = integrationIdMap[rawCurrencyId] ?: return@withContext
|
||||
val integrationId = integrationIdMap[cryptoCurrency.id.getIntegrationKey()] ?: return@withContext
|
||||
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
|
|
@ -284,7 +281,7 @@ internal class DefaultStakingRepository(
|
|||
send(YieldBalance.Empty)
|
||||
} else {
|
||||
launch(dispatchers.io) {
|
||||
val integrationId = integrationIdMap[address.cryptoCurrency.id.rawCurrencyId]
|
||||
val integrationId = integrationIdMap[address.cryptoCurrency.id.getIntegrationKey()]
|
||||
?: error("Could not get integrationId")
|
||||
stakingBalanceStore.get(integrationId)
|
||||
.collectLatest {
|
||||
|
|
@ -317,7 +314,7 @@ internal class DefaultStakingRepository(
|
|||
} else {
|
||||
fetchSingleYieldBalance(userWalletId, address)
|
||||
|
||||
val integrationId = integrationIdMap[address.cryptoCurrency.id.rawCurrencyId]
|
||||
val integrationId = integrationIdMap[address.cryptoCurrency.id.getIntegrationKey()]
|
||||
?: error("Could not get integrationId")
|
||||
val result = stakingBalanceStore.getSyncOrNull(integrationId) ?: return@withContext YieldBalance.Error
|
||||
yieldBalanceConverter.convert(
|
||||
|
|
@ -347,8 +344,7 @@ internal class DefaultStakingRepository(
|
|||
addresses
|
||||
.mapNotNull { networkAddress ->
|
||||
val cryptoCurrency = networkAddress.cryptoCurrency
|
||||
val rawCurrencyId = cryptoCurrency.id.rawCurrencyId ?: error("Currency raw id is null")
|
||||
val integrationId = integrationIdMap[rawCurrencyId]
|
||||
val integrationId = integrationIdMap[cryptoCurrency.id.getIntegrationKey()]
|
||||
|
||||
if (integrationId != null) {
|
||||
networkAddress.address to integrationId
|
||||
|
|
@ -534,6 +530,13 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override fun isApproveNeeded(cryptoCurrency: CryptoCurrency): Boolean {
|
||||
return when (cryptoCurrency.id.getIntegrationKey()) {
|
||||
Blockchain.Ethereum.id.plus(Blockchain.Polygon.toCoinId()) -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun findPrefetchedYield(yields: List<Yield>, currencyId: String, symbol: String): Yield? {
|
||||
return yields.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol }
|
||||
}
|
||||
|
|
@ -559,13 +562,15 @@ internal class DefaultStakingRepository(
|
|||
|
||||
private fun getYieldBalancesKey(userWalletId: UserWalletId) = "yield_balance_${userWalletId.stringValue}"
|
||||
|
||||
private fun CryptoCurrency.ID.getIntegrationKey(): String = rawNetworkId.plus(rawCurrencyId)
|
||||
|
||||
private companion object {
|
||||
const val YIELDS_STORE_KEY = "yields"
|
||||
|
||||
const val SOLANA_INTEGRATION_ID = "solana-sol-native-multivalidator-staking"
|
||||
const val COSMOS_INTEGRATION_ID = "cosmos-atom-native-staking"
|
||||
const val ETHEREUM_POLYGON_INTEGRATION_ID = "ethereum-matic-native-staking"
|
||||
const val POLKADOT_INTEGRATION_ID = "polkadot-dot-validator-staking"
|
||||
const val ETHEREUM_INTEGRATION_ID = "ethereum-matic-native-staking"
|
||||
const val AVALANCHE_INTEGRATION_ID = "avalanche-avax-native-staking"
|
||||
const val TRON_INTEGRATION_ID = "tron-trx-native-staking"
|
||||
const val CRONOS_INTEGRATION_ID = "cronos-cro-native-staking"
|
||||
|
|
@ -576,17 +581,17 @@ internal class DefaultStakingRepository(
|
|||
|
||||
// uncomment items as implementation is ready
|
||||
val integrationIdMap = mapOf(
|
||||
Blockchain.Solana.toCoinId() to SOLANA_INTEGRATION_ID,
|
||||
Blockchain.Cosmos.toCoinId() to COSMOS_INTEGRATION_ID,
|
||||
// Blockchain.Polkadot.toCoinId() to POLKADOT_INTEGRATION_ID,
|
||||
// Blockchain.Polygon.toCoinId() to ETHEREUM_INTEGRATION_ID,
|
||||
// Blockchain.Avalanche.toCoinId() to AVALANCHE_INTEGRATION_ID,
|
||||
// Blockchain.Tron.toCoinId() to TRON_INTEGRATION_ID,
|
||||
// Blockchain.Cronos.toCoinId() to CRONOS_INTEGRATION_ID,
|
||||
// Blockchain.BSC.toCoinId() to BINANCE_INTEGRATION_ID,
|
||||
// Blockchain.Kava.toCoinId() to KAVA_INTEGRATION_ID,
|
||||
// Blockchain.Near.toCoinId() to NEAR_INTEGRATION_ID,
|
||||
// Blockchain.Tezos.toCoinId() to TEZOS_INTEGRATION_ID,
|
||||
Blockchain.Solana.run { id + toCoinId() } to SOLANA_INTEGRATION_ID,
|
||||
Blockchain.Cosmos.run { id + toCoinId() } to COSMOS_INTEGRATION_ID,
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId() to ETHEREUM_POLYGON_INTEGRATION_ID,
|
||||
// Blockchain.Polkadot.run { id + toCoinId() } to POLKADOT_INTEGRATION_ID,
|
||||
// Blockchain.Avalanche.run { id + toCoinId() } to AVALANCHE_INTEGRATION_ID,
|
||||
// Blockchain.Tron.run { id + toCoinId() } to TRON_INTEGRATION_ID,
|
||||
// Blockchain.Cronos.run { id + toCoinId() } to CRONOS_INTEGRATION_ID,
|
||||
// Blockchain.BSC.run { id + toCoinId() } to BINANCE_INTEGRATION_ID,
|
||||
// Blockchain.Kava.run { id + toCoinId() } to KAVA_INTEGRATION_ID,
|
||||
// Blockchain.Near.run { id + toCoinId() } to NEAR_INTEGRATION_ID,
|
||||
// Blockchain.Tezos.run { id + toCoinId() } to TEZOS_INTEGRATION_ID,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.staking
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
class IsApproveNeededUseCase(
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val stakingErrorResolver: StakingErrorResolver,
|
||||
) {
|
||||
operator fun invoke(cryptoCurrency: CryptoCurrency): Either<StakingError, Boolean> {
|
||||
return Either
|
||||
.catch { stakingRepository.isApproveNeeded(cryptoCurrency) }
|
||||
.mapLeft { stakingErrorResolver.resolve(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -15,9 +15,10 @@ import com.tangem.domain.tokens.model.Network
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface StakingRepository {
|
||||
|
||||
fun isStakingSupported(currencyId: String): Boolean
|
||||
fun isStakingSupported(integrationKey: String): Boolean
|
||||
|
||||
suspend fun fetchEnabledYields(refresh: Boolean)
|
||||
|
||||
|
|
@ -75,4 +76,7 @@ interface StakingRepository {
|
|||
|
||||
/** Returns whether additional staking is possible if there is already active staking */
|
||||
fun isStakeMoreAvailable(networkId: Network.ID): Boolean
|
||||
|
||||
/** Returns whether approve is needed before staking */
|
||||
fun isApproveNeeded(cryptoCurrency: CryptoCurrency): Boolean
|
||||
}
|
||||
|
|
@ -2,11 +2,13 @@ package com.tangem.domain.tokens.repository
|
|||
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.lce.lceFlow
|
||||
import com.tangem.domain.staking.model.*
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata
|
||||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.*
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -237,4 +239,6 @@ class MockStakingRepository : StakingRepository {
|
|||
}
|
||||
|
||||
override fun isStakeMoreAvailable(networkId: Network.ID): Boolean = true
|
||||
|
||||
override fun isApproveNeeded(cryptoCurrency: CryptoCurrency): Boolean = true
|
||||
}
|
||||
|
|
@ -88,6 +88,7 @@ internal sealed class StakingStates {
|
|||
val footerText: String,
|
||||
val transactionDoneState: TransactionDoneState,
|
||||
val pendingActionInProgress: PendingAction? = null,
|
||||
val isApprovalNeeded: Boolean,
|
||||
) : ConfirmationState()
|
||||
|
||||
data class Empty(
|
||||
|
|
|
|||
|
|
@ -86,5 +86,6 @@ internal object ConfirmationStatePreviewData {
|
|||
),
|
||||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingActions = persistentListOf(),
|
||||
isApprovalNeeded = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -38,6 +38,10 @@ object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun openRewardsValidators() {}
|
||||
|
||||
override fun showApprovalBottomSheet() {}
|
||||
|
||||
override fun onApprovalClick() {}
|
||||
|
||||
override fun onExploreClick() {}
|
||||
|
||||
override fun onShareClick() {}
|
||||
|
|
|
|||
|
|
@ -135,23 +135,7 @@ internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
|
|||
}
|
||||
}
|
||||
|
||||
StakingStep.Confirmation -> {
|
||||
if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
resourceReference(R.string.common_close)
|
||||
} else {
|
||||
when (actionType) {
|
||||
StakingActionCommonType.ENTER -> resourceReference(R.string.common_stake)
|
||||
StakingActionCommonType.EXIT -> resourceReference(R.string.common_unstake)
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
StakingActionCommonType.PENDING_REWARDS,
|
||||
-> getPendingActionTitle(confirmationState.pendingActions.firstOrNull()?.type)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resourceReference(R.string.common_close)
|
||||
}
|
||||
}
|
||||
StakingStep.Confirmation -> getConfirmationButtonText()
|
||||
StakingStep.Validators -> resourceReference(R.string.common_continue)
|
||||
StakingStep.Amount,
|
||||
StakingStep.RewardsValidators,
|
||||
|
|
@ -159,6 +143,30 @@ internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
|
|||
}
|
||||
}
|
||||
|
||||
private fun StakingUiState.getConfirmationButtonText(): TextReference {
|
||||
return if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
resourceReference(R.string.common_close)
|
||||
} else {
|
||||
when (actionType) {
|
||||
StakingActionCommonType.ENTER -> {
|
||||
if (confirmationState.isApprovalNeeded) {
|
||||
resourceReference(R.string.give_permission_title)
|
||||
} else {
|
||||
resourceReference(R.string.common_stake)
|
||||
}
|
||||
}
|
||||
StakingActionCommonType.EXIT -> resourceReference(R.string.common_unstake)
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
StakingActionCommonType.PENDING_REWARDS,
|
||||
-> getPendingActionTitle(confirmationState.pendingActions.firstOrNull()?.type)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resourceReference(R.string.common_close)
|
||||
}
|
||||
}
|
||||
|
||||
private fun StakingUiState.onPrimaryClick() {
|
||||
when (currentStep) {
|
||||
StakingStep.InitialInfo -> {
|
||||
|
|
@ -169,18 +177,27 @@ internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
|
|||
StakingStep.Validators,
|
||||
StakingStep.Amount,
|
||||
-> clickIntents.onNextClick()
|
||||
StakingStep.Confirmation -> {
|
||||
if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
clickIntents.onBackClick()
|
||||
} else {
|
||||
clickIntents.onActionClick(confirmationState.pendingActions.firstOrNull())
|
||||
}
|
||||
StakingStep.Confirmation -> onConfirmationClick()
|
||||
StakingStep.RewardsValidators -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun StakingUiState.onConfirmationClick() {
|
||||
if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
clickIntents.onBackClick()
|
||||
} else {
|
||||
val isEnterAction = actionType == StakingActionCommonType.ENTER
|
||||
val isApproveNeeded = confirmationState.isApprovalNeeded
|
||||
|
||||
if (isEnterAction && isApproveNeeded) {
|
||||
clickIntents.showApprovalBottomSheet()
|
||||
} else {
|
||||
clickIntents.onBackClick()
|
||||
clickIntents.onActionClick(confirmationState.pendingActions.firstOrNull())
|
||||
}
|
||||
}
|
||||
StakingStep.RewardsValidators -> Unit
|
||||
} else {
|
||||
clickIntents.onBackClick()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -49,6 +48,7 @@ internal class SetConfirmationStateAssentTransformer(
|
|||
validatorState = validatorState.copySealed(isClickable = true),
|
||||
pendingActions = pendingActionList,
|
||||
isPrimaryButtonEnabled = true,
|
||||
isApprovalNeeded = false,
|
||||
)
|
||||
} else {
|
||||
return this
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
innerState = InnerConfirmationStakingState.ASSENT,
|
||||
feeState = FeeState.Loading,
|
||||
validatorState = ValidatorState.Content(
|
||||
isClickable = false,
|
||||
isClickable = true,
|
||||
chosenValidator = chosenValidator,
|
||||
availableValidators = yield.validators,
|
||||
),
|
||||
|
|
@ -34,6 +34,7 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingActions = persistentListOf(),
|
||||
pendingActionInProgress = null,
|
||||
isApprovalNeeded = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.approval
|
||||
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SetApprovalBottomSheetInProgressTransformer(
|
||||
private val onDismiss: () -> Unit,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val approvalBottomSheetConfig = prevState.bottomSheetConfig?.content as? GiveTxPermissionBottomSheetConfig
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = prevState.bottomSheetConfig?.copy(
|
||||
onDismissRequest = onDismiss,
|
||||
isShow = true,
|
||||
content = approvalBottomSheetConfig?.let { config ->
|
||||
config.copy(
|
||||
data = config.data.copy(
|
||||
approveButton = config.data.approveButton.copy(
|
||||
enabled = false,
|
||||
loading = true,
|
||||
),
|
||||
cancelButton = config.data.cancelButton.copy(
|
||||
enabled = false,
|
||||
),
|
||||
),
|
||||
onCancel = onDismiss,
|
||||
)
|
||||
} as TangemBottomSheetConfigContent,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.approval
|
||||
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.*
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.R
|
||||
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.ValidatorState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class ShowApprovalBottomSheetTransformer(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val onDismiss: () -> Unit,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
|
||||
val cryptoCurrencyValue = cryptoCurrencyStatusProvider().value
|
||||
|
||||
val amountState = prevState.amountState as? AmountState.Data ?: return prevState
|
||||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content ?: return prevState
|
||||
val feeState = confirmationState.feeState as? FeeState.Content ?: return prevState
|
||||
|
||||
val walletAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value.orEmpty()
|
||||
val validatorAddress = validatorState.chosenValidator.address
|
||||
val feeCryptoValue = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = feeState.fee?.amount?.value,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
val feeFiatValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = feeState.fee?.amount?.maxValue,
|
||||
fiatCurrencyCode = appCurrencyProvider().code,
|
||||
fiatCurrencySymbol = appCurrencyProvider().symbol,
|
||||
)
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = GiveTxPermissionBottomSheetConfig(
|
||||
data = GiveTxPermissionState.ReadyForRequest(
|
||||
currency = cryptoCurrency.symbol,
|
||||
amount = amountState.amountTextField.value,
|
||||
approveType = ApproveType.LIMITED,
|
||||
walletAddress = walletAddress,
|
||||
spenderAddress = validatorAddress,
|
||||
fee = resourceReference(
|
||||
R.string.common_crypto_fiat_format,
|
||||
wrappedList(feeCryptoValue, feeFiatValue),
|
||||
),
|
||||
approveButton = ApprovePermissionButton(
|
||||
enabled = true,
|
||||
loading = false,
|
||||
onClick = prevState.clickIntents::onApprovalClick,
|
||||
),
|
||||
cancelButton = CancelPermissionButton(
|
||||
enabled = true,
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.give_permission_staking_subtitle,
|
||||
formatArgs = wrappedList(cryptoCurrency.symbol),
|
||||
),
|
||||
dialogText = resourceReference(R.string.give_permission_staking_footer),
|
||||
),
|
||||
onCancel = onDismiss,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenContent
|
||||
import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButtonsBlock
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
|
|
@ -64,6 +66,7 @@ fun StakingBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
|||
if (bottomSheetConfig == null) return
|
||||
when (bottomSheetConfig.content) {
|
||||
is StakingInfoBottomSheetConfig -> StakingInfoBottomSheet(bottomSheetConfig)
|
||||
is GiveTxPermissionBottomSheetConfig -> GiveTxPermissionBottomSheet(bottomSheetConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
|
||||
fun onActiveStake(activeStake: BalanceState)
|
||||
|
||||
fun showApprovalBottomSheet()
|
||||
|
||||
fun onApprovalClick()
|
||||
|
||||
fun onExploreClick()
|
||||
|
||||
fun onShareClick()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import com.tangem.common.extensions.hexToBytes
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.bundle.unbundle
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
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
|
||||
|
|
@ -36,9 +39,13 @@ import com.tangem.features.staking.impl.presentation.state.transformers.amount.A
|
|||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountCurrencyChangeStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountMaxValueStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountPasteDismissStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalBottomSheetInProgressTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.ShowApprovalBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -64,14 +71,16 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val submitHashUseCase: SubmitHashUseCase,
|
||||
private val isStakeMoreAvailableUseCase: IsStakeMoreAvailableUseCase,
|
||||
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
|
||||
private val isApproveNeededUseCase: IsApproveNeededUseCase,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val vibratorHapticManager: VibratorHapticManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
|
||||
|
||||
val uiState: StateFlow<StakingUiState> = stateController.uiState
|
||||
val value: StakingUiState get() = uiState.value
|
||||
|
||||
var stakingStateRouter: StakingStateRouter by Delegates.notNull()
|
||||
private set
|
||||
private var stakingStateRouter: StakingStateRouter by Delegates.notNull()
|
||||
|
||||
private val cryptoCurrencyId: CryptoCurrency.ID =
|
||||
savedStateHandle.get<Bundle>(AppRoute.Staking.CRYPTO_CURRENCY_ID_KEY)
|
||||
|
|
@ -92,6 +101,10 @@ internal class StakingViewModel @Inject constructor(
|
|||
private var userWallet: UserWallet by Delegates.notNull()
|
||||
private var appCurrency: AppCurrency by Delegates.notNull()
|
||||
|
||||
private var isApprovalNeeded: Boolean = false
|
||||
|
||||
private var approvalJobHolder: JobHolder = JobHolder()
|
||||
|
||||
init {
|
||||
subscribeOnSelectedAppCurrency()
|
||||
subscribeOnBalanceHiding()
|
||||
|
|
@ -254,6 +267,29 @@ internal class StakingViewModel @Inject constructor(
|
|||
onNextClick(actionType, activeStake.pendingActions)
|
||||
}
|
||||
|
||||
override fun showApprovalBottomSheet() {
|
||||
stateController.update(
|
||||
ShowApprovalBottomSheetTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
) {
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onApprovalClick() {
|
||||
viewModelScope.launch {
|
||||
stateController.update(
|
||||
SetApprovalBottomSheetInProgressTransformer {
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
},
|
||||
)
|
||||
|
||||
// todo staking polygon
|
||||
}.saveIn(approvalJobHolder)
|
||||
}
|
||||
|
||||
override fun onExploreClick() {
|
||||
val confirmationDataState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content
|
||||
|
|
@ -265,7 +301,16 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onShareClick() {
|
||||
// TODO add hash to clipboard and send analytics event
|
||||
val confirmationDataState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content
|
||||
val txUrl = transactionDoneState?.txUrl
|
||||
|
||||
if (txUrl != null) {
|
||||
vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click)
|
||||
clipboardManager.setText(text = txUrl)
|
||||
}
|
||||
|
||||
// TODO staking [REDACTED_TASK_KEY]
|
||||
}
|
||||
|
||||
fun setRouter(router: InnerStakingRouter, stateRouter: StakingStateRouter) {
|
||||
|
|
@ -273,6 +318,10 @@ internal class StakingViewModel @Inject constructor(
|
|||
this.stakingStateRouter = stateRouter
|
||||
}
|
||||
|
||||
private fun setupApprovalNeeded() {
|
||||
isApprovalNeeded = isApproveNeededUseCase(cryptoCurrencyStatus.currency).getOrElse { false }
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
viewModelScope.launch {
|
||||
getUserWalletUseCase(userWalletId).fold(
|
||||
|
|
@ -287,6 +336,8 @@ internal class StakingViewModel @Inject constructor(
|
|||
ifRight = {
|
||||
cryptoCurrencyStatus = it
|
||||
|
||||
setupApprovalNeeded()
|
||||
|
||||
val networkId = cryptoCurrencyStatus.currency.network.id
|
||||
val isStakeMoreAvailable = isStakeMoreAvailableUseCase(networkId)
|
||||
stateController.update(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue