From df4288e70a365a423280d8e3409c132c8a2fec9b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 11 Dec 2024 13:14:38 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/TransactionDomainModule.kt | 22 +++++++ .../ui/notifications/NotificationsFactory.kt | 11 ++-- .../components/notifications/Notification.kt | 24 +++++--- .../res/drawable/ic_alert_circle_red_20.xml | 9 +++ .../DefaultCustomTokensRepository.kt | 1 + .../DefaultCurrencyChecksRepository.kt | 3 + .../DefaultWalletManagersFacade.kt | 17 +++++- .../walletmanager/WalletManagersFacade.kt | 4 +- .../SdkRequirementsConditionConverter.kt | 11 +++- .../tokens/model/warnings/KaspaWarnings.kt | 16 ++++++ .../domain/tokens/GetCurrencyCheckUseCase.kt | 7 ++- .../tokens/GetCurrencyWarningsUseCase.kt | 37 +++++++++--- .../repository/CurrencyChecksRepository.kt | 2 + .../models/AssetRequirementsCondition.kt | 12 ++++ .../error/IncompleteTransactionError.kt | 5 ++ .../usecase/AssociateAssetUseCase.kt | 2 +- .../DismissIncompleteTransactionUseCase.kt | 34 +++++++++++ .../RetryIncompleteTransactionUseCase.kt | 38 +++++++++++++ ...okenDetailsNotificationsAnalyticsSender.kt | 1 + .../components/TokenDetailsDialogConfig.kt | 21 +++++++ .../components/TokenDetailsNotification.kt | 23 ++++++++ .../TokenDetailsNotificationConverter.kt | 16 ++++++ .../state/factory/TokenDetailsStateFactory.kt | 21 +++++++ .../viewmodels/TokenDetailsClickIntents.kt | 6 ++ .../viewmodels/TokenDetailsViewModel.kt | 57 +++++++++++++++++++ gradle/dependencies.toml | 2 +- .../DefaultBlockchainDataStorage.kt | 10 ++-- 27 files changed, 381 insertions(+), 31 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_alert_circle_red_20.xml create mode 100644 domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/KaspaWarnings.kt create mode 100644 domain/transaction/src/main/java/com/tangem/domain/transaction/error/IncompleteTransactionError.kt create mode 100644 domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/DismissIncompleteTransactionUseCase.kt create mode 100644 domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/RetryIncompleteTransactionUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 73c0236567..507a284a76 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -58,6 +58,28 @@ internal object TransactionDomainModule { ) } + @Provides + @Singleton + fun provideRetryTransactionUseCase( + cardSdkConfigRepository: CardSdkConfigRepository, + walletManagersFacade: WalletManagersFacade, + ): RetryIncompleteTransactionUseCase { + return RetryIncompleteTransactionUseCase( + cardSdkConfigRepository = cardSdkConfigRepository, + walletManagersFacade = walletManagersFacade, + ) + } + + @Provides + @Singleton + fun provideDismissIncompleteTransactionUseCase( + walletManagersFacade: WalletManagersFacade, + ): DismissIncompleteTransactionUseCase { + return DismissIncompleteTransactionUseCase( + walletManagersFacade = walletManagersFacade, + ) + } + @Provides @Singleton fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase { diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index 9841d58e02..e14d6a9c13 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -185,7 +185,7 @@ object NotificationsFactory { if (dustValue == null) return val isExceedsLimit = checkDustLimits( feeAmount = feeValue, - receivedAmount = sendingAmount, + sendingAmount = sendingAmount, dustValue = dustValue, cryptoCurrencyStatus = cryptoCurrencyStatus, feeCurrencyStatus = feeCurrencyStatus, @@ -352,7 +352,7 @@ object NotificationsFactory { private fun checkDustLimits( feeAmount: BigDecimal, - receivedAmount: BigDecimal, + sendingAmount: BigDecimal, dustValue: BigDecimal, cryptoCurrencyStatus: CryptoCurrencyStatus, feeCurrencyStatus: CryptoCurrencyStatus?, @@ -360,7 +360,7 @@ object NotificationsFactory { val change = when (cryptoCurrencyStatus.currency) { is CryptoCurrency.Coin -> { val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - balance - (feeAmount + receivedAmount) + balance - (feeAmount + sendingAmount) } is CryptoCurrency.Token -> { val balance = feeCurrencyStatus?.value?.amount ?: BigDecimal.ZERO @@ -369,6 +369,9 @@ object NotificationsFactory { } val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO - return receivedAmount < dustValue || isChangeLowerThanDust + return when (cryptoCurrencyStatus.currency) { + is CryptoCurrency.Coin -> sendingAmount < dustValue || isChangeLowerThanDust + is CryptoCurrency.Token -> isChangeLowerThanDust + } } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index cd7b4d5852..c18e2c3e71 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -222,13 +222,23 @@ private fun Buttons(state: NotificationButtonsState?, isEnabled: Boolean = true) @Composable private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig, isEnabled: Boolean = true) { - SecondaryButton( - text = config.text.resolveReference(), - onClick = config.onClick, - modifier = Modifier.fillMaxWidth(), - size = TangemButtonSize.WideAction, - enabled = isEnabled, - ) + if (config.iconResId != null) { + SecondaryButtonIconEnd( + text = config.text.resolveReference(), + onClick = config.onClick, + modifier = Modifier.fillMaxWidth(), + iconResId = config.iconResId, + enabled = isEnabled, + ) + } else { + SecondaryButton( + text = config.text.resolveReference(), + onClick = config.onClick, + modifier = Modifier.fillMaxWidth(), + size = TangemButtonSize.WideAction, + enabled = isEnabled, + ) + } } @Composable diff --git a/core/ui/src/main/res/drawable/ic_alert_circle_red_20.xml b/core/ui/src/main/res/drawable/ic_alert_circle_red_20.xml new file mode 100644 index 0000000000..e7528979e2 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_alert_circle_red_20.xml @@ -0,0 +1,9 @@ + + + diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt index ba48dfac71..e146654abd 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt @@ -52,6 +52,7 @@ internal class DefaultCustomTokensRepository( Blockchain.Unknown, Blockchain.Binance, Blockchain.BinanceTestnet, + Blockchain.Kaspa, -> true Blockchain.Cardano -> blockchain.validateContractAddress(contractAddress) else -> blockchain.validateAddress(contractAddress) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index d8a6b44533..f72dea5b6d 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -7,6 +7,7 @@ import com.tangem.blockchain.common.ReserveAmountProvider import com.tangem.blockchain.common.UtxoAmountLimitProvider import com.tangem.data.tokens.converters.UtxoConverter import com.tangem.domain.staking.model.stakekit.YieldBalance +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CurrencyAmount import com.tangem.domain.tokens.model.Network @@ -24,6 +25,7 @@ internal class DefaultCurrencyChecksRepository( private val walletManagersFacade: WalletManagersFacade, private val coroutineDispatchers: CoroutineDispatcherProvider, ) : CurrencyChecksRepository { + override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? { val manager = walletManagersFacade.getOrCreateWalletManager( userWalletId = userWalletId, @@ -108,6 +110,7 @@ internal class DefaultCurrencyChecksRepository( override suspend fun checkUtxoAmountLimit( userWalletId: UserWalletId, network: Network, + currency: CryptoCurrency, amount: BigDecimal, fee: BigDecimal, ): UtxoAmountLimit? { diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index fa05b147e8..440e818d8f 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -594,7 +594,7 @@ class DefaultWalletManagersFacade( } } - override suspend fun associateAsset( + override suspend fun fulfillRequirements( userWalletId: UserWalletId, currency: CryptoCurrency, signer: TransactionSigner, @@ -613,6 +613,21 @@ class DefaultWalletManagersFacade( } } + override suspend fun discardRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): SimpleResult { + return withContext(dispatchers.io) { + val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network) + val currencyType = cryptoCurrencyTypeConverter.convert(currency) + + if (walletManager !is AssetRequirementsManager) { + return@withContext SimpleResult.Failure( + BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"), + ) + } + + walletManager.discardRequirements(currencyType) + } + } + override suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean { val blockchain = Blockchain.fromId(network.id.value) val walletManager = getOrCreateWalletManager( diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index 6031b2300b..7eb56b21c8 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -233,12 +233,14 @@ interface WalletManagersFacade { */ suspend fun getAssetRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): AssetRequirementsCondition? - suspend fun associateAsset( + suspend fun fulfillRequirements( userWalletId: UserWalletId, currency: CryptoCurrency, signer: TransactionSigner, ): SimpleResult + suspend fun discardRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): SimpleResult + /** * Indicates UTXO consolidation availability * diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkRequirementsConditionConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkRequirementsConditionConverter.kt index d7512061fa..92b365f3f3 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkRequirementsConditionConverter.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkRequirementsConditionConverter.kt @@ -7,13 +7,20 @@ import com.tangem.blockchain.common.trustlines.AssetRequirementsCondition as Sdk internal class SdkRequirementsConditionConverter : Converter { override fun convert(value: SdkRequirementsCondition): AssetRequirementsCondition { return when (value) { - SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction + is SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction is SdkRequirementsCondition.PaidTransactionWithFee -> AssetRequirementsCondition.PaidTransactionWithFee( feeAmount = requireNotNull(value.feeAmount.value), feeCurrencySymbol = value.feeAmount.currencySymbol, decimals = value.feeAmount.decimals, ) - is SdkRequirementsCondition.IncompleteTransaction -> TODO() + is SdkRequirementsCondition.IncompleteTransaction -> AssetRequirementsCondition.IncompleteTransaction( + amount = requireNotNull(value.amount.value), + currencySymbol = value.amount.currencySymbol, + currencyDecimals = value.amount.decimals, + feeAmount = requireNotNull(value.feeAmount.value), + feeCurrencySymbol = value.feeAmount.currencySymbol, + feeCurrencyDecimals = value.feeAmount.decimals, + ) } } } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/KaspaWarnings.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/KaspaWarnings.kt new file mode 100644 index 0000000000..00fca5d410 --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/KaspaWarnings.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.tokens.model.warnings + +import com.tangem.domain.tokens.model.CryptoCurrency +import java.math.BigDecimal + +sealed class KaspaWarnings : CryptoCurrencyWarning() { + + abstract val currency: CryptoCurrency + + data class IncompleteTransaction( + override val currency: CryptoCurrency, + val amount: BigDecimal, + val currencySymbol: String, + val currencyDecimals: Int, + ) : KaspaWarnings() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt index 91ae684bf8..41d994c8c0 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.tokens.repository.CurrencyChecksRepository @@ -22,7 +23,8 @@ class GetCurrencyCheckUseCase( recipientAddress: String? = null, ): CryptoCurrencyCheck { return withContext(dispatchers.io) { - val network = currencyStatus.currency.network + val currency = currencyStatus.currency + val network = currency.network val dustValue = currencyChecksRepository.getDustValue(userWalletId, network) val reserveAmount = currencyChecksRepository.getReserveAmount(userWalletId, network) val minimumSendAmount = currencyChecksRepository.getMinimumSendAmount(userWalletId, network) @@ -39,10 +41,11 @@ class GetCurrencyCheckUseCase( recipientAddress, ) } ?: false - val utxoAmountLimit = if (amount != null && fee != null) { + val utxoAmountLimit = if (currency is CryptoCurrency.Coin && amount != null && fee != null) { currencyChecksRepository.checkUtxoAmountLimit( userWalletId = userWalletId, network = network, + currency = currencyStatus.currency, amount = amount, fee = fee, ) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 82dbe90b92..6f2c349a7c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -6,6 +6,7 @@ import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.model.warnings.HederaWarnings +import com.tangem.domain.tokens.model.warnings.KaspaWarnings import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.repository.* import com.tangem.domain.transaction.models.AssetRequirementsCondition @@ -20,7 +21,7 @@ import com.tangem.utils.isNullOrZero import kotlinx.coroutines.flow.* import java.math.BigDecimal -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") class GetCurrencyWarningsUseCase( private val walletManagersFacade: WalletManagersFacade, private val currenciesRepository: CurrenciesRepository, @@ -79,7 +80,17 @@ class GetCurrencyWarningsUseCase( getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency), getMigrationFromMaticToPolWarning(currency), ) - }.flowOn(dispatchers.io) + } + .onEmpty { + setOfNotNull( + getNetworkUnavailableWarning(currencyStatus), + getNetworkNoAccountWarning(currencyStatus), + getBeaconChainShutdownWarning(currency.network.id), + getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency), + getMigrationFromMaticToPolWarning(currency), + ) + } + .flowOn(dispatchers.io) } private suspend fun getSwapPromoNotificationWarning( @@ -298,12 +309,22 @@ class GetCurrencyWarningsUseCase( ): CryptoCurrencyWarning? { return when (val requirements = walletManagersFacade.getAssetRequirements(userWalletId, currency)) { is AssetRequirementsCondition.PaidTransaction -> HederaWarnings.AssociateWarning(currency = currency) - is AssetRequirementsCondition.PaidTransactionWithFee -> HederaWarnings.AssociateWarningWithFee( - currency = currency, - fee = requirements.feeAmount, - feeCurrencySymbol = requirements.feeCurrencySymbol, - feeCurrencyDecimals = requirements.decimals, - ) + is AssetRequirementsCondition.PaidTransactionWithFee -> { + HederaWarnings.AssociateWarningWithFee( + currency = currency, + fee = requirements.feeAmount, + feeCurrencySymbol = requirements.feeCurrencySymbol, + feeCurrencyDecimals = requirements.decimals, + ) + } + is AssetRequirementsCondition.IncompleteTransaction -> + KaspaWarnings.IncompleteTransaction( + currency = currency, + amount = requirements.amount, + currencySymbol = requirements.currencySymbol, + currencyDecimals = requirements.currencyDecimals, + ) + null -> null } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt index e03d3ed0a7..3a03ecb7fc 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.repository +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CurrencyAmount import com.tangem.domain.tokens.model.Network @@ -38,6 +39,7 @@ interface CurrencyChecksRepository { suspend fun checkUtxoAmountLimit( userWalletId: UserWalletId, network: Network, + currency: CryptoCurrency, amount: BigDecimal, fee: BigDecimal, ): UtxoAmountLimit? diff --git a/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/AssetRequirementsCondition.kt b/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/AssetRequirementsCondition.kt index 3503f354df..2bb5c1d8fb 100644 --- a/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/AssetRequirementsCondition.kt +++ b/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/AssetRequirementsCondition.kt @@ -17,4 +17,16 @@ sealed class AssetRequirementsCondition { val feeCurrencySymbol: String, val decimals: Int, ) : AssetRequirementsCondition() + + /** + * The exact value of the fee for this type of condition is stored in `feeAmount`. + */ + data class IncompleteTransaction( + val amount: BigDecimal, + val currencySymbol: String, + val currencyDecimals: Int, + val feeAmount: BigDecimal, + val feeCurrencySymbol: String, + val feeCurrencyDecimals: Int, + ) : AssetRequirementsCondition() } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/IncompleteTransactionError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/IncompleteTransactionError.kt new file mode 100644 index 0000000000..0c80d368c5 --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/IncompleteTransactionError.kt @@ -0,0 +1,5 @@ +package com.tangem.domain.transaction.error + +sealed class IncompleteTransactionError { + data class DataError(val message: String?) : IncompleteTransactionError() +} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt index 72ad169dba..979c1c911d 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt @@ -39,7 +39,7 @@ class AssociateAssetUseCase( catch( block = { - when (val result = walletManagersFacade.associateAsset(userWalletId, currency, signer)) { + when (val result = walletManagersFacade.fulfillRequirements(userWalletId, currency, signer)) { is SimpleResult.Failure -> raise(AssociateAssetError.DataError(result.error.customMessage)) SimpleResult.Success -> Unit } diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/DismissIncompleteTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/DismissIncompleteTransactionUseCase.kt new file mode 100644 index 0000000000..1b11ad2cfd --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/DismissIncompleteTransactionUseCase.kt @@ -0,0 +1,34 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import arrow.core.raise.catch +import arrow.core.raise.either +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.transaction.error.IncompleteTransactionError +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWalletId + +class DismissIncompleteTransactionUseCase( + private val walletManagersFacade: WalletManagersFacade, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): Either { + return either { + catch( + block = { + when (val result = walletManagersFacade.discardRequirements(userWalletId, currency)) { + is SimpleResult.Failure -> raise( + IncompleteTransactionError.DataError(result.error.customMessage), + ) + SimpleResult.Success -> Unit + } + }, + catch = { error -> IncompleteTransactionError.DataError(error.message) }, + ) + } + } +} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/RetryIncompleteTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/RetryIncompleteTransactionUseCase.kt new file mode 100644 index 0000000000..fa8fa53c7e --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/RetryIncompleteTransactionUseCase.kt @@ -0,0 +1,38 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import arrow.core.raise.catch +import arrow.core.raise.either +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.transaction.error.IncompleteTransactionError +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWalletId + +class RetryIncompleteTransactionUseCase( + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val walletManagersFacade: WalletManagersFacade, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): Either { + return either { + val signer = cardSdkConfigRepository.getCommonSigner(cardId = null) + + catch( + block = { + when (val result = walletManagersFacade.fulfillRequirements(userWalletId, currency, signer)) { + is SimpleResult.Failure -> raise( + IncompleteTransactionError.DataError(result.error.customMessage), + ) + SimpleResult.Success -> Unit + } + }, + catch = { error -> IncompleteTransactionError.DataError(error.message) }, + ) + } + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index e46cc3f47c..836894e575 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -48,6 +48,7 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.RentInfo, is TokenDetailsNotification.NetworkShutdown, is TokenDetailsNotification.HederaAssociateWarning, + is TokenDetailsNotification.KaspaIncompleteTransactionWarning, is TokenDetailsNotification.KoinosMana, is TokenDetailsNotification.MigrationMaticToPol, -> null diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt index 47949f59c7..ce5a9f3d49 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt @@ -96,6 +96,27 @@ internal data class TokenDetailsDialogConfig( ) } + data class RemoveIncompleteTransactionConfirmDialogConfig( + val onConfirmClick: () -> Unit, + val onCancelClick: () -> Unit, + ) : DialogContentConfig() { + override val title = null + + override val message: TextReference = TextReference.Res( + id = R.string.warning_kaspa_unfinished_token_transaction_discard_message, + ) + + override val cancelButtonConfig: ButtonConfig = ButtonConfig( + text = TextReference.Res(R.string.common_cancel), + onClick = onCancelClick, + ) + + override val confirmButtonConfig: ButtonConfig = ButtonConfig( + text = TextReference.Res(R.string.common_yes), + onClick = onConfirmClick, + ) + } + data class ErrorDialogConfig( val text: TextReference, val onConfirmClick: () -> Unit, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index 69b5beb389..86db0a1bf3 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -19,12 +19,14 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { subtitle: TextReference, iconResId: Int = R.drawable.img_attention_20, buttonsState: NotificationConfig.ButtonsState? = null, + onCloseClick: (() -> Unit)? = null, ) : TokenDetailsNotification( config = NotificationConfig( title = title, subtitle = subtitle, iconResId = iconResId, buttonsState = buttonsState, + onCloseClick = onCloseClick, ), ) @@ -191,6 +193,27 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { ), ) + data class KaspaIncompleteTransactionWarning( + private val currency: CryptoCurrency, + private val amount: String, + private val currencySymbol: String, + private val onRetryIncompleteTransactionClick: () -> Unit, + private val onDismissIncompleteTransactionClick: () -> Unit, + ) : Warning( + title = resourceReference(R.string.warning_kaspa_unfinished_token_transaction_title), + subtitle = resourceReference( + id = R.string.warning_kaspa_unfinished_token_transaction_message, + formatArgs = wrappedList(amount, currencySymbol), + ), + iconResId = R.drawable.ic_alert_circle_red_20, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.alert_button_try_again), + onClick = onRetryIncompleteTransactionClick, + iconResId = R.drawable.ic_tangem_24, + ), + onCloseClick = onDismissIncompleteTransactionClick, + ) + data class KoinosMana( val manaBalanceAmount: String, val maxManaBalanceAmount: String, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index 86aae8128f..b1435ae301 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -9,6 +9,7 @@ import com.tangem.core.ui.format.bigdecimal.shorted import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.model.warnings.HederaWarnings +import com.tangem.domain.tokens.model.warnings.KaspaWarnings import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification.* @@ -41,6 +42,14 @@ internal class TokenDetailsNotificationConverter( return newNotifications.toImmutableList() } + fun removeKaspaIncompleteTransactionWarning( + currentState: TokenDetailsState, + ): ImmutableList { + val newNotifications = currentState.notifications.toMutableList() + newNotifications.removeBy { it is KaspaIncompleteTransactionWarning } + return newNotifications.toImmutableList() + } + @Suppress("LongMethod", "CyclomaticComplexMethod") private fun mapToNotification(warning: CryptoCurrencyWarning): TokenDetailsNotification { return when (warning) { @@ -109,6 +118,13 @@ internal class TokenDetailsNotificationConverter( feeCurrencySymbol = warning.feeCurrencySymbol, onAssociateClick = clickIntents::onAssociateClick, ) + is KaspaWarnings.IncompleteTransaction -> KaspaIncompleteTransactionWarning( + currency = warning.currency, + amount = warning.amount.format { crypto(symbol = "", decimals = warning.currencyDecimals) }, + currencySymbol = warning.currencySymbol, + onRetryIncompleteTransactionClick = clickIntents::onRetryIncompleteTransactionClick, + onDismissIncompleteTransactionClick = clickIntents::onDismissIncompleteTransactionClick, + ) is CryptoCurrencyWarning.FeeResourceInfo -> KoinosMana( manaBalanceAmount = formatMana(warning.amount), maxManaBalanceAmount = warning.maxAmount?.let { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 649869ebc8..5e15fa4efc 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -199,6 +199,19 @@ internal class TokenDetailsStateFactory( ) } + fun getStateWithDismissIncompleteTransactionConfirmDialog(): TokenDetailsState { + return currentStateProvider().copy( + dialogConfig = TokenDetailsDialogConfig( + isShow = true, + onDismissRequest = clickIntents::onDismissDialog, + content = TokenDetailsDialogConfig.DialogContentConfig.RemoveIncompleteTransactionConfirmDialogConfig( + onConfirmClick = clickIntents::onConfirmDismissIncompleteTransactionClick, + onCancelClick = clickIntents::onDismissDialog, + ), + ), + ) + } + fun getStateWithActionButtonErrorDialog(unavailabilityReason: ScenarioUnavailabilityReason): TokenDetailsState { return currentStateProvider().copy( dialogConfig = TokenDetailsDialogConfig( @@ -300,6 +313,14 @@ internal class TokenDetailsStateFactory( return state.copy(notifications = notificationConverter.removeHederaAssociateWarning(state)) } + fun getStateWithRemovedKaspaIncompleteTransactionNotification(): TokenDetailsState { + val state = currentStateProvider() + return state.copy( + notifications = notificationConverter.removeKaspaIncompleteTransactionWarning(state), + dialogConfig = state.dialogConfig?.copy(isShow = false), + ) + } + fun getStateAndTriggerEvent( state: TokenDetailsState, errorMessage: TextReference, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt index 9f150beb95..e4439079b5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt @@ -59,6 +59,12 @@ interface TokenDetailsClickIntents { fun onAssociateClick() + fun onRetryIncompleteTransactionClick() + + fun onDismissIncompleteTransactionClick() + + fun onConfirmDismissIncompleteTransactionClick() + fun onStakeBannerClick() fun onBalanceSelect(config: TokenBalanceSegmentedButtonConfig) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index f320c748cb..f41620317c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -50,7 +50,10 @@ import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.transaction.error.AssociateAssetError +import com.tangem.domain.transaction.error.IncompleteTransactionError import com.tangem.domain.transaction.usecase.AssociateAssetUseCase +import com.tangem.domain.transaction.usecase.DismissIncompleteTransactionUseCase +import com.tangem.domain.transaction.usecase.RetryIncompleteTransactionUseCase import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase @@ -108,6 +111,8 @@ internal class TokenDetailsViewModel @Inject constructor( private val networkHasDerivationUseCase: NetworkHasDerivationUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val associateAssetUseCase: AssociateAssetUseCase, + private val retryIncompleteTransactionUseCase: RetryIncompleteTransactionUseCase, + private val dismissIncompleteTransactionUseCase: DismissIncompleteTransactionUseCase, private val reduxStateHolder: ReduxStateHolder, private val analyticsEventsHandler: AnalyticsEventHandler, private val vibratorHapticManager: VibratorHapticManager, @@ -861,6 +866,58 @@ internal class TokenDetailsViewModel @Inject constructor( return resourceReference(R.string.wallet_notification_address_copied) } + override fun onRetryIncompleteTransactionClick() { + viewModelScope.launch { + retryIncompleteTransactionUseCase( + userWalletId = userWalletId, + currency = cryptoCurrency, + ).fold( + ifLeft = { e -> + when (e) { + is IncompleteTransactionError.DataError -> { + internalUiState.value = stateFactory.getStateWithErrorDialog( + stringReference(e.message.orEmpty()), + ) + Timber.e(e.message) + } + } + }, + ifRight = { + internalUiState.value = stateFactory.getStateWithRemovedKaspaIncompleteTransactionNotification() + }, + ) + } + } + + override fun onDismissIncompleteTransactionClick() { + viewModelScope.launch { + internalUiState.value = stateFactory.getStateWithDismissIncompleteTransactionConfirmDialog() + } + } + + override fun onConfirmDismissIncompleteTransactionClick() { + viewModelScope.launch { + dismissIncompleteTransactionUseCase( + userWalletId = userWalletId, + currency = cryptoCurrency, + ).fold( + ifLeft = { e -> + when (e) { + is IncompleteTransactionError.DataError -> { + internalUiState.value = stateFactory.getStateWithErrorDialog( + stringReference(e.message.orEmpty()), + ) + Timber.e(e.message) + } + } + }, + ifRight = { + internalUiState.value = stateFactory.getStateWithRemovedKaspaIncompleteTransactionNotification() + }, + ) + } + } + override fun onAssociateClick() { analyticsEventsHandler.send( TokenScreenAnalyticsEvent.Associate( diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index b05595b8f7..4fb9818eec 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.19-881" +tangemBlockchainSdk = "release-app_5.19-882" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.19-414" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/datastorage/DefaultBlockchainDataStorage.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/datastorage/DefaultBlockchainDataStorage.kt index 9122a68be2..6681161851 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/datastorage/DefaultBlockchainDataStorage.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/datastorage/DefaultBlockchainDataStorage.kt @@ -21,13 +21,15 @@ internal class DefaultBlockchainDataStorage( return appPreferencesStore.getSyncOrNull(key = stringPreferencesKey(name = key)) } - override suspend fun remove(key: String) { - TODO("Not yet implemented") - } - override suspend fun store(key: String, value: String) { appPreferencesStore.edit { it[stringPreferencesKey(key)] = value } } + + override suspend fun remove(key: String) { + appPreferencesStore.edit { + it.remove(stringPreferencesKey(key)) + } + } } \ No newline at end of file