From ea8c9079b7101752c51d71df8b632569b3f7a2b1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Aug 2024 13:54:35 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/StakingDomainModule.kt | 12 +++ .../converters/AmountStateConverter.kt | 2 +- .../GiveTxPermisssionBottomSheet.kt | 11 ++- .../permission/state/GiveTxPermissionState.kt | 2 +- core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-fr/strings.xml | 2 +- core/res/src/main/res/values-ja/strings.xml | 2 +- .../src/main/res/values-uk-rUA/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 2 +- .../data/staking/DefaultStakingRepository.kt | 53 +++++++------ .../domain/staking/IsApproveNeededUseCase.kt | 18 +++++ .../staking/repositories/StakingRepository.kt | 6 +- .../repository/MockStakingRepository.kt | 8 +- .../impl/presentation/state/StakingUiState.kt | 1 + .../ConfirmationStatePreviewData.kt | 1 + .../state/stub/StakingClickIntentsStub.kt | 4 + .../SetButtonsStateTransformer.kt | 69 +++++++++------- .../SetConfirmationStateAssentTransformer.kt | 4 +- .../SetConfirmationStateLoadingTransformer.kt | 3 +- ...pprovalBottomSheetInProgressTransformer.kt | 34 ++++++++ .../ShowApprovalBottomSheetTransformer.kt | 78 +++++++++++++++++++ .../impl/presentation/ui/StakingScreen.kt | 3 + .../viewmodel/StakingClickIntents.kt | 4 + .../viewmodel/StakingViewModel.kt | 57 +++++++++++++- 24 files changed, 311 insertions(+), 69 deletions(-) create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/IsApproveNeededUseCase.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetApprovalBottomSheetInProgressTransformer.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index c280208083..b4f55832a9 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -156,4 +156,16 @@ internal object StakingDomainModule { stakingErrorResolver = stakingErrorResolver, ) } + + @Provides + @Singleton + fun provideIsApproveNeededUseCase( + stakingRepository: StakingRepository, + stakingErrorResolver: StakingErrorResolver, + ): IsApproveNeededUseCase { + return IsApproveNeededUseCase( + stakingRepository = stakingRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt index ad251378f6..410a23b468 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt @@ -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, diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt index ba65ad6d9c..6f61b650b4 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt @@ -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, - 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, diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt index dbdce22ba9..908e42559b 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt @@ -22,7 +22,7 @@ sealed class GiveTxPermissionState { val approveItems: ImmutableList = ApproveType.entries.toImmutableList(), val approveButton: ApprovePermissionButton, val cancelButton: CancelPermissionButton, - val onChangeApproveType: (ApproveType) -> Unit, + val onChangeApproveType: ((ApproveType) -> Unit)? = null, ) : GiveTxPermissionState() } diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 51b2bd8358..1e9ed93914 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -88,6 +88,7 @@ Kopieren Adresse kopieren Erstellen + %1$s (%2$s) Eigen %d tag @@ -639,7 +640,6 @@ Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert %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. Ungültige Adresse - %1$s (%2$s) Transaktion gesendet Bereite das Scannen der Karte vor, die du einrichten möchtest. Entferne diese Wallet diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 9155dc35a7..43f28f5375 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -86,6 +86,7 @@ Copier Copier l\'adresse Créer + %1$s (%2$s) Personnalisé %d jour @@ -567,7 +568,6 @@ 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 %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. Adresse incorrecte - %1$s (%2$s) Transaction envoyée Préparez-vous à numériser la carte que vous souhaitez configurer. Oublier le portefeuille diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 6d695bc564..56465b3e0b 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -87,6 +87,7 @@ コピー アドレスをコピー 作成 + %1$s ( %2$s ) 設定 %d 日 @@ -627,7 +628,6 @@ 取引は正常に署名され、ブロックチェーンノードに送信されました。ウォレットの残高はしばらくして更新されます。 %1$sはTronネットワークのアセットです。手数料を計算して取引を行うには、アカウントにTron(TRX)を入金する必要があります。 無効なアドレス - %1$s ( %2$s ) 取引が送信されました セットアップしたいカードをスキャンするために準備してください。 ウォレット削除 diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index d3c560257c..d352a7be3c 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -90,6 +90,7 @@ Копіювати Скопіювати адресу Створити + %1$s (%2$s) Власна %d день @@ -651,7 +652,6 @@ Транзакція успішно підписана і відправлена до блокчейну. Баланс гаманця буде оновлено через деякий час %1$s — це монета у мережі Tron. Щоб розрахувати комісію та здійснити транзакцію, вам необхідно внести певну кількість Tron (TRX) на свій рахунок. Недійсна адреса - %1$s (%2$s) Трансакцію надіслано Підготуйтеся до сканування картку, яку потрібно налаштувати. Забути гаманець diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 23d562fd61..956b064d05 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -88,6 +88,7 @@ Copy Copy address Create + %1$s (%2$s) Custom %d day @@ -635,7 +636,6 @@ Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while %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. Invalid address - %1$s (%2$s) Transaction sent Prepare to scan card you want to setup. Forget wallet diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index c5748f49a8..8d89311bdd 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -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(), ) - 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, 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, ) } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/IsApproveNeededUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/IsApproveNeededUseCase.kt new file mode 100644 index 0000000000..d183b36a2f --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/IsApproveNeededUseCase.kt @@ -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 { + return Either + .catch { stakingRepository.isApproveNeeded(cryptoCurrency) } + .mapLeft { stakingErrorResolver.resolve(it) } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 5605958156..b80b7b95d2 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -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 } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt index 75e83715bb..b42fe2c3e4 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt @@ -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 } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 98b09f5e78..e9b4c44faa 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -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( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt index 0994dd7cdb..4fc4fa0cde 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt @@ -86,5 +86,6 @@ internal object ConfirmationStatePreviewData { ), transactionDoneState = TransactionDoneState.Empty, pendingActions = persistentListOf(), + isApprovalNeeded = false, ) } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index 3036764d3d..c8e5a5d35a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -38,6 +38,10 @@ object StakingClickIntentsStub : StakingClickIntents { override fun openRewardsValidators() {} + override fun showApprovalBottomSheet() {} + + override fun onApprovalClick() {} + override fun onExploreClick() {} override fun onShareClick() {} diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt index 4332347169..c954aee97d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt @@ -135,23 +135,7 @@ internal class SetButtonsStateTransformer : Transformer { } } - 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 { } } + 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 { 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() } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt index f2dc22698e..33b475fcaf 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt @@ -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 diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt index 88662f7fde..04f8fd9de7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt @@ -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, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetApprovalBottomSheetInProgressTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetApprovalBottomSheetInProgressTransformer.kt new file mode 100644 index 0000000000..085061c2b8 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetApprovalBottomSheetInProgressTransformer.kt @@ -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 { + 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, + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt new file mode 100644 index 0000000000..9eb4e68640 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt @@ -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, + private val cryptoCurrencyStatusProvider: Provider, + private val onDismiss: () -> Unit, +) : Transformer { + 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, + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt index d3c1bda086..fe6c923210 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt @@ -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) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt index 3734e90f19..db14a50094 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt @@ -36,6 +36,10 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun onActiveStake(activeStake: BalanceState) + fun showApprovalBottomSheet() + + fun onApprovalClick() + fun onExploreClick() fun onShareClick() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index a5fd8c4d07..dfac50c551 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -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 = 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(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(