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 22f61dc1f1..c852c1ff14 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 @@ -224,4 +224,12 @@ internal object StakingDomainModule { ): CheckAccountInitializedUseCase { return CheckAccountInitializedUseCase(walletManagersFacade) } + + @Provides + @Singleton + fun provideGetActionRequirementAmountUseCase( + stakingRepository: StakingRepository, + ): GetActionRequirementAmountUseCase { + return GetActionRequirementAmountUseCase(stakingRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/DefaultCardSettingsComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/DefaultCardSettingsComponent.kt index bb92d1a9a1..5c31eb375c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/DefaultCardSettingsComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/DefaultCardSettingsComponent.kt @@ -22,7 +22,6 @@ internal class DefaultCardSettingsComponent @AssistedInject constructor( @Composable override fun Content(modifier: Modifier) { val state by model.screenState.collectAsStateWithLifecycle() - CardSettingsScreen(modifier = modifier, state = state) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt index 09a825389e..babdcb09be 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt @@ -74,6 +74,8 @@ internal class CardSettingsModel @Inject constructor( override fun onDestroy() { super.onDestroy() + // Reset card scanned data + cardSettingsInteractor.clear() // Restore the previous value of access code request policy cardSdkConfigRepository.isBiometricsRequestPolicy = previousBiometricsRequestPolicy } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampQuoteResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampQuoteResponse.kt index 46ca99bbb6..ec0d6eb2c7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampQuoteResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampQuoteResponse.kt @@ -33,10 +33,10 @@ data class OnrampQuoteResponse( val providerId: String, @Json(name = "minFromAmount") - val minFromAmount: String, + val minFromAmount: String?, @Json(name = "maxFromAmount") - val maxFromAmount: String, + val maxFromAmount: String?, @Json(name = "minToAmount") val minToAmount: String?, diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 8b279d736b..64fd63a63a 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -52,7 +52,6 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.withContext import org.joda.time.DateTime import timber.log.Timber @@ -303,8 +302,12 @@ internal class DefaultOnrampRepository( OnrampQuote.Data( fromAmount = fromOnrampAmount, toAmount = convertToAmount(response.toAmount, cryptoCurrency), - minFromAmount = convertToAmount(response.minFromAmount, cryptoCurrency), - maxFromAmount = convertToAmount(response.maxFromAmount, cryptoCurrency), + minFromAmount = response.minFromAmount?.let { + convertToAmount(it, cryptoCurrency) + }, + maxFromAmount = response.maxFromAmount?.let { + convertToAmount(it, cryptoCurrency) + }, paymentMethod = paymentMethod, provider = provider, countryCode = response.countryCode, 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 4c145ce44c..b3ab55fbeb 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 @@ -72,6 +72,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import timber.log.Timber +import java.math.BigDecimal import kotlin.time.Duration.Companion.seconds @Suppress("LargeClass", "LongParameterList", "TooManyFunctions") @@ -119,7 +120,7 @@ internal class DefaultStakingRepository( when (val stakingTokensWithYields = stakeKitApi.getEnabledYields(preferredValidatorsOnly = false)) { is ApiResponse.Success -> stakingYieldsStore.store( stakingTokensWithYields.data.data.filter { - it.isAvailable ?: false + it.isAvailable == true }, ) else -> { @@ -650,15 +651,24 @@ internal class DefaultStakingRepository( } override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean { - return withContext(dispatchers.io) { - stakingBalanceStore.getSyncOrNull(userWalletId) - ?.let { - it.isNotEmpty() && - it.any { yieldBalance -> - (yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true - } + return withContext(dispatchers.default) { + val balances = stakingBalanceStoreV2.getAllSyncOrNull(userWalletId) ?: return@withContext false + + val hasDataYieldBalance by lazy { + balances.any { yieldBalance -> + (yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true } - ?: false + } + + balances.isNotEmpty() && hasDataYieldBalance + } + } + + override fun getActionRequirementAmount(integrationId: String, stakingActionType: StakingActionType): BigDecimal? { + return when { + stakingIdFactory.isPolygonIntegrationId(integrationId) && + stakingActionType == StakingActionType.CLAIM_REWARDS -> BigDecimal.ONE + else -> null } } diff --git a/data/staking/src/main/java/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducer.kt b/data/staking/src/main/java/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducer.kt index 887db9af5f..82a820ebae 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducer.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducer.kt @@ -1,5 +1,7 @@ package com.tangem.data.staking.single +import com.tangem.core.analytics.api.AnalyticsExceptionHandler +import com.tangem.core.analytics.models.ExceptionAnalyticsEvent import com.tangem.data.staking.utils.StakingIdFactory import com.tangem.domain.staking.model.StakingID import com.tangem.domain.staking.model.stakekit.YieldBalance @@ -7,6 +9,7 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceProducer import com.tangem.domain.staking.multi.MultiYieldBalanceSupplier import com.tangem.domain.staking.single.SingleYieldBalanceProducer import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.indexOfFirstOrNull import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -14,6 +17,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapNotNull +import timber.log.Timber /** * Default implementation of [SingleYieldBalanceProducer] @@ -29,6 +33,7 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor( @Assisted private val params: SingleYieldBalanceProducer.Params, private val multiYieldBalanceSupplier: MultiYieldBalanceSupplier, private val stakingIdFactory: StakingIdFactory, + private val analyticsExceptionHandler: AnalyticsExceptionHandler, private val dispatchers: CoroutineDispatcherProvider, ) : SingleYieldBalanceProducer { @@ -48,8 +53,34 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor( .mapNotNull { balances -> val currentStakingId = getStakingId() ?: return@mapNotNull YieldBalance.Unsupported - balances.firstOrNull { it.getStakingId() == currentStakingId } - ?: YieldBalance.Unsupported + val currentBalances = balances.filter { it.getStakingId() == currentStakingId } + + if (currentBalances.size > 1) { + analyticsExceptionHandler.sendException( + event = ExceptionAnalyticsEvent( + exception = IllegalStateException("Multiple balances found for staking ID"), + params = mapOf( + "stakingId" to currentStakingId.toString(), + "balances" to currentBalances.joinToString(",") { it.toString() }, + ), + ), + ) + + Timber.w( + "Multiple balances found for staking ID $currentStakingId:\n%s", + currentBalances.joinToString("\n"), + ) + + val dataIndex = currentBalances.indexOfFirstOrNull { it is YieldBalance.Data } + + if (dataIndex != null) { + currentBalances[dataIndex] + } else { + currentBalances.first() + } + } else { + currentBalances.firstOrNull() ?: YieldBalance.Unsupported + } } .distinctUntilChanged() .flowOn(dispatchers.default) diff --git a/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt b/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt index 2f28162955..808ba7a7ae 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt @@ -109,8 +109,15 @@ internal class DefaultYieldsBalancesStore( private suspend fun storeInPersistence(userWalletId: UserWalletId, values: Set) { persistenceStore.updateData { current -> current.toMutableMap().apply { - this[userWalletId.stringValue] = current[userWalletId.stringValue] - ?.addOrReplace(items = values) { old, new -> old.getStakingId() == new.getStakingId() } + this[userWalletId.stringValue] = this[userWalletId.stringValue] + ?.addOrReplace(items = values) { old, new -> + val oldId = old.getStakingId() + val newId = new.getStakingId() + + if (oldId == null || newId == null) return@addOrReplace false + + oldId == newId + } ?: values } } diff --git a/data/staking/src/main/java/com/tangem/data/staking/utils/StakingIdFactory.kt b/data/staking/src/main/java/com/tangem/data/staking/utils/StakingIdFactory.kt index d1b760b601..b27a5c5061 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/utils/StakingIdFactory.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/utils/StakingIdFactory.kt @@ -35,6 +35,8 @@ internal class StakingIdFactory @Inject constructor( return integrationIdMap[integrationKey] } + fun isPolygonIntegrationId(integrationId: String): Boolean = integrationId == ETHEREUM_POLYGON_INTEGRATION_ID + @Suppress("UnusedPrivateMember", "unused") companion object { diff --git a/data/staking/src/test/kotlin/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducerTest.kt b/data/staking/src/test/kotlin/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducerTest.kt index b05ad2e885..c594ea3515 100644 --- a/data/staking/src/test/kotlin/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducerTest.kt +++ b/data/staking/src/test/kotlin/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducerTest.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory import com.tangem.common.test.utils.getEmittedValues +import com.tangem.core.analytics.api.AnalyticsExceptionHandler import com.tangem.data.staking.toDomain import com.tangem.data.staking.utils.StakingIdFactory import com.tangem.domain.staking.model.StakingID @@ -32,12 +33,14 @@ internal class DefaultSingleYieldBalanceProducerTest { private val multiNetworkStatusSupplier = mockk() private val stakingIdFactory = mockk() + private val analyticsExceptionHandler = mockk(relaxUnitFun = true) private val dispatchers = TestingCoroutineDispatcherProvider() private val producer = DefaultSingleYieldBalanceProducer( params = params, stakingIdFactory = stakingIdFactory, multiYieldBalanceSupplier = multiNetworkStatusSupplier, + analyticsExceptionHandler = analyticsExceptionHandler, dispatchers = dispatchers, ) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 4f3a819ef0..4d18c1bfe9 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -97,31 +97,35 @@ internal class DefaultCurrenciesRepository( } } - override suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List) { - withContext(dispatchers.io) { - val savedCurrencies = requireNotNull( - value = getSavedUserTokensResponseSync(key = userWalletId), - lazyMessage = { "Saved tokens empty. Can not perform add currencies action" }, - ) + override suspend fun addCurrencies( + userWalletId: UserWalletId, + currencies: List, + ): List = withContext(dispatchers.io) { + val savedCurrencies = requireNotNull( + value = getSavedUserTokensResponseSync(key = userWalletId), + lazyMessage = { "Saved tokens empty. Can not perform add currencies action" }, + ) - val currenciesToAdd = populateCurrenciesWithMissedCoins( - currencies = currencies, - ).let { - filterAlreadyAddedCurrencies(savedCurrencies.tokens, it) - } - val updatedResponse = savedCurrencies.copy( - tokens = savedCurrencies.tokens + currenciesToAdd.map(userTokensResponseFactory::createResponseToken), - ) - userTokensSaver.storeAndPush( - userWalletId = userWalletId, - response = updatedResponse, - ) + val currenciesToAdd = filterAlreadyAddedCurrencies( + savedCurrencies = savedCurrencies.tokens, + currenciesToAdd = populateCurrenciesWithMissedCoins(currencies = currencies), + ) - fetchExpressAssetsByNetworkIds( - userWallet = userWalletsStore.getSyncStrict(key = userWalletId), - userTokens = updatedResponse, - ) - } + val updatedResponse = savedCurrencies.copy( + tokens = savedCurrencies.tokens + currenciesToAdd.map(userTokensResponseFactory::createResponseToken), + ) + + userTokensSaver.storeAndPush( + userWalletId = userWalletId, + response = updatedResponse, + ) + + fetchExpressAssetsByNetworkIds( + userWallet = userWalletsStore.getSyncStrict(key = userWalletId), + userTokens = updatedResponse, + ) + + currenciesToAdd } private fun filterAlreadyAddedCurrencies( @@ -573,7 +577,7 @@ internal class DefaultCurrenciesRepository( override fun isNetworkFeeZero(userWalletId: UserWalletId, network: Network): Boolean { val blockchain = Blockchain.fromNetworkId(network.backendId) - return blockchain?.isNetworkFeeZero() ?: false + return blockchain?.isNetworkFeeZero() == true } override suspend fun syncTokens(userWalletId: UserWalletId) { diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/GetExtendedPublicKeyForCurrencyUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/GetExtendedPublicKeyForCurrencyUseCase.kt index 7e6414e4ed..3172b275c4 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/GetExtendedPublicKeyForCurrencyUseCase.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/GetExtendedPublicKeyForCurrencyUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.card import arrow.core.Either +import arrow.core.right import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.common.extensions.ByteArrayKey @@ -22,16 +23,17 @@ class GetExtendedPublicKeyForCurrencyUseCase( private val derivationsRepository: DerivationsRepository, private val walletManagersFacade: WalletManagersFacade, ) { + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either { return Either.catch { - val userWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) - ?: error("Wallet not found") + val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) + ?: error("Wallet not found for userWalletId=$userWalletId and network=$network") val blockchain = network.toBlockchain() val isSecp256k1Blockchain = Blockchain.secp256k1Blockchains(network.isTestnet).contains(blockchain) val hdKey = if (isSecp256k1Blockchain) { - userWallet.wallet.publicKey.derivationType?.hdKey ?: error("No derivation found") + walletManager.wallet.publicKey.derivationType?.hdKey ?: error("No derivation found") } else { error("No derivation found") } @@ -50,7 +52,7 @@ class GetExtendedPublicKeyForCurrencyUseCase( val pendingDerivations = getPendingDerivations(childKey, parentKey) val derivedKeys = deriveKeys( userWalletId = userWalletId, - seedKey = userWallet.wallet.publicKey.seedKey, + seedKey = walletManager.wallet.publicKey.seedKey, paths = pendingDerivations, ) @@ -73,15 +75,17 @@ class GetExtendedPublicKeyForCurrencyUseCase( /** * @return true if xpub generation is supported, false otherwise */ - suspend fun isSupported(userWalletId: UserWalletId, network: Network): Boolean { - val userWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) - ?: error("Wallet not found") + suspend fun isSupported(userWalletId: UserWalletId, network: Network): Either = Either.catch { + val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) + ?: error("Wallet not found for user wallet $userWalletId and network ${network.id}") val blockchain = network.toBlockchain() val isSecp256k1Blockchain = Blockchain.secp256k1Blockchains(network.isTestnet).contains(blockchain) - val isHdKey = userWallet.wallet.publicKey.derivationType?.hdKey + val isHdKey = walletManager.wallet.publicKey.derivationType?.hdKey - return isSecp256k1Blockchain && isHdKey != null + val isSupported = isSecp256k1Blockchain && isHdKey != null + + return isSupported.right() } private suspend fun deriveKeys( diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt index 96a60f0cc0..dd0ad48ebc 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt @@ -41,16 +41,15 @@ class SaveMarketTokensUseCase( removedNetworks: Set, ): Either = Either.catch { if (removedNetworks.isNotEmpty()) { - currenciesRepository.removeCurrencies( - userWalletId = userWalletId, - currencies = removedNetworks.mapNotNull { - marketsTokenRepository.createCryptoCurrency( - userWalletId = userWalletId, - token = tokenMarketParams, - network = it, - ) - }, - ) + val removedCurrencies = removedNetworks.mapNotNull { + marketsTokenRepository.createCryptoCurrency( + userWalletId = userWalletId, + token = tokenMarketParams, + network = it, + ) + } + + currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = removedCurrencies) } if (addedNetworks.isNotEmpty()) { @@ -67,13 +66,16 @@ class SaveMarketTokensUseCase( ) } - currenciesRepository.addCurrencies(userWalletId = userWalletId, currencies = addedCurrencies) + val savedCurrencies = currenciesRepository.addCurrencies( + userWalletId = userWalletId, + currencies = addedCurrencies, + ) - refreshUpdatedNetworks(userWalletId, addedCurrencies) + refreshUpdatedNetworks(userWalletId, savedCurrencies) - refreshUpdatedYieldBalances(userWalletId, addedCurrencies) + refreshUpdatedYieldBalances(userWalletId, savedCurrencies) - refreshUpdatedQuotes(addedCurrencies) + refreshUpdatedQuotes(savedCurrencies) } } diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt index dde5f19064..59a0ddefa4 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt @@ -15,8 +15,8 @@ sealed class OnrampQuote { override val fromAmount: OnrampAmount, override val countryCode: String, val toAmount: OnrampAmount, - val minFromAmount: OnrampAmount, - val maxFromAmount: OnrampAmount, + val minFromAmount: OnrampAmount?, + val maxFromAmount: OnrampAmount?, ) : OnrampQuote() data class AmountError( diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetActionRequirementAmountUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetActionRequirementAmountUseCase.kt new file mode 100644 index 0000000000..34d0c03b27 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetActionRequirementAmountUseCase.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.stakekit.action.StakingActionType +import com.tangem.domain.staking.repositories.StakingRepository +import java.math.BigDecimal + +class GetActionRequirementAmountUseCase( + private val stakingRepository: StakingRepository, +) { + + operator fun invoke(integrationId: String, actionType: StakingActionType): Either = + Either.catch { + stakingRepository.getActionRequirementAmount(integrationId, actionType) + } +} \ 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 e399fcd26c..cbb5d358d6 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 @@ -14,11 +14,13 @@ import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.YieldBalanceList 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.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import java.math.BigDecimal @Suppress("TooManyFunctions") interface StakingRepository { @@ -100,4 +102,9 @@ interface StakingRepository { fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean + + /** + * Return action requirement amount + */ + fun getActionRequirementAmount(integrationId: String, stakingActionType: StakingActionType): BigDecimal? } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index 63bb8891bd..d58a0257f9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -50,7 +50,7 @@ interface CurrenciesRepository { * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ - suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List) + suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List): List /** * Removes currency from a specific user wallet. diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt index 475ea789ec..01c6a656e5 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt @@ -48,7 +48,10 @@ internal class MockCurrenciesRepository( override suspend fun saveNewCurrenciesList(userWalletId: UserWalletId, currencies: List) = Unit - override suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List) = Unit + override suspend fun addCurrencies( + userWalletId: UserWalletId, + currencies: List, + ): List = emptyList() override suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) { removeCurrencyResult.onLeft { throw it } 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 303da59cda..19b368f075 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 @@ -188,6 +188,8 @@ class MockStakingRepository : StakingRepository { override fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval = StakingApproval.Empty override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean = false + override fun getActionRequirementAmount(integrationId: String, stakingActionType: StakingActionType): BigDecimal? = + null private companion object { val yield = Yield( diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollections.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollections.kt index 2d42caaf85..3036cc073f 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollections.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollections.kt @@ -1,8 +1,8 @@ package com.tangem.features.nft.collections.ui import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.core.ui.components.appbar.AppBarWithBackButton @@ -17,31 +17,28 @@ import com.tangem.features.nft.impl.R internal fun NFTCollections(state: NFTCollectionsStateUM, modifier: Modifier = Modifier) { BackHandler(onBack = state.onBackClick) - Scaffold( - modifier = modifier, - containerColor = TangemTheme.colors.background.secondary, - topBar = { - AppBarWithBackButton( - modifier = Modifier.statusBarsPadding(), - onBackClick = state.onBackClick, - text = stringResourceSafe(id = R.string.nft_collections_title), - iconRes = R.drawable.ic_back_24, - ) - }, - content = { innerPadding -> - TangemPullToRefreshContainer( - config = state.pullToRefreshConfig, - modifier = Modifier - .padding(innerPadding) - .fillMaxSize(), - ) { - when (val content = state.content) { - is NFTCollectionsUM.Content -> NFTCollectionsContent(content) - is NFTCollectionsUM.Empty -> NFTCollectionsEmpty(content) - is NFTCollectionsUM.Failed -> NFTCollectionsFailed(content) - is NFTCollectionsUM.Loading -> NFTCollectionsLoading(content) - } + Column( + modifier = modifier + .background(TangemTheme.colors.background.secondary), + ) { + AppBarWithBackButton( + modifier = Modifier, + onBackClick = state.onBackClick, + text = stringResourceSafe(id = R.string.nft_collections_title), + iconRes = R.drawable.ic_back_24, + ) + + TangemPullToRefreshContainer( + config = state.pullToRefreshConfig, + modifier = Modifier + .fillMaxSize(), + ) { + when (val content = state.content) { + is NFTCollectionsUM.Content -> NFTCollectionsContent(content) + is NFTCollectionsUM.Empty -> NFTCollectionsEmpty(content) + is NFTCollectionsUM.Failed -> NFTCollectionsFailed(content) + is NFTCollectionsUM.Loading -> NFTCollectionsLoading(content) } - }, - ) + } + } } \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollectionsEmpty.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollectionsEmpty.kt index 7e2c62d33b..167988824f 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollectionsEmpty.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollectionsEmpty.kt @@ -52,14 +52,14 @@ internal fun NFTCollectionsEmpty(state: NFTCollectionsUM.Empty, modifier: Modifi color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, ) - PrimaryButton( - modifier = Modifier - .padding(top = TangemTheme.dimens.spacing48) - .widthIn(min = TangemTheme.dimens.size158), - text = stringResourceSafe(R.string.nft_collections_receive), - onClick = state.onReceiveClick, - ) } + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter), + text = stringResourceSafe(R.string.nft_collections_receive), + onClick = state.onReceiveClick, + ) } } diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/common/ui/NFTContent.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/common/ui/NFTContent.kt index ef907f1e0f..f906d793ee 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/common/ui/NFTContent.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/common/ui/NFTContent.kt @@ -19,8 +19,8 @@ import com.tangem.features.nft.common.NFTRoute @Composable internal fun NFTContent(stackState: ChildStack) { Column( - modifier = Modifier.Companion - .background(color = TangemTheme.colors.background.tertiary) + modifier = Modifier + .background(color = TangemTheme.colors.background.secondary) .fillMaxSize() .imePadding() .systemBarsPadding(), diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/transformer/NFTPriceChangeTransformer.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/transformer/NFTPriceChangeTransformer.kt index 8ab05b6d7b..ff99321696 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/transformer/NFTPriceChangeTransformer.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/transformer/NFTPriceChangeTransformer.kt @@ -1,5 +1,6 @@ package com.tangem.features.nft.details.entity.transformer +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.fiat @@ -8,6 +9,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.nft.models.NFTSalePrice import com.tangem.features.nft.details.entity.NFTAssetUM import com.tangem.features.nft.details.entity.NFTDetailsUM +import com.tangem.features.nft.impl.R import com.tangem.utils.transformer.Transformer internal class NFTPriceChangeTransformer( @@ -18,35 +20,50 @@ internal class NFTPriceChangeTransformer( override fun transform(prevState: NFTDetailsUM): NFTDetailsUM { val topInfo = prevState.nftAsset.topInfo as? NFTAssetUM.TopInfo.Content ?: return prevState - return prevState.copy( - nftAsset = prevState.nftAsset.copy( - topInfo = topInfo.copy( - salePrice = when (nftSalePrice) { - is NFTSalePrice.Empty, - is NFTSalePrice.Error, - -> NFTAssetUM.SalePrice.Empty - is NFTSalePrice.Loading -> NFTAssetUM.SalePrice.Loading - is NFTSalePrice.Value -> NFTAssetUM.SalePrice.Content( - isFlickering = false, - cryptoPrice = stringReference( - nftSalePrice.value.format { - crypto( - symbol = nftSalePrice.symbol, - decimals = nftSalePrice.decimals, - ) - }, - ), - fiatPrice = stringReference( - nftSalePrice.fiatValue.format { - fiat( - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) - }, - ), + val salePrice = when (nftSalePrice) { + is NFTSalePrice.Empty, + is NFTSalePrice.Error, + -> NFTAssetUM.SalePrice.Empty + is NFTSalePrice.Loading -> NFTAssetUM.SalePrice.Loading + is NFTSalePrice.Value -> NFTAssetUM.SalePrice.Content( + isFlickering = false, + cryptoPrice = stringReference( + nftSalePrice.value.format { + crypto( + symbol = nftSalePrice.symbol, + decimals = nftSalePrice.decimals, ) }, ), + fiatPrice = stringReference( + nftSalePrice.fiatValue.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + }, + ), + ) + } + + val hasSalePrice = salePrice !is NFTAssetUM.SalePrice.Empty + + val newTopInfo = if ( + topInfo.rarity is NFTAssetUM.Rarity.Empty && + topInfo.description.isNullOrEmpty() && + !hasSalePrice + ) { + NFTAssetUM.TopInfo.Empty + } else { + topInfo.copy( + title = resourceReference(R.string.nft_details_last_sale_price).takeIf { hasSalePrice }, + salePrice = salePrice, + ) + } + + return prevState.copy( + nftAsset = prevState.nftAsset.copy( + topInfo = newTopInfo, ), ) } diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetails.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetails.kt index 203288ef78..2c3ad2ede5 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetails.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetails.kt @@ -1,13 +1,10 @@ package com.tangem.features.nft.details.ui import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.material3.FabPosition -import androidx.compose.material3.Scaffold +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.appbar.TangemTopAppBar @@ -22,25 +19,25 @@ import com.tangem.features.nft.impl.R internal fun NFTDetails(state: NFTDetailsUM, modifier: Modifier = Modifier) { BackHandler(onBack = state.onBackClick) - Scaffold( - modifier = modifier, - containerColor = TangemTheme.colors.background.secondary, - topBar = { + Box( + modifier = modifier + .background(TangemTheme.colors.background.secondary), + ) { + Column( + modifier = Modifier.fillMaxSize(), + ) { TangemTopAppBar( - modifier = Modifier.statusBarsPadding(), + modifier = Modifier, startButton = TopAppBarButtonUM( iconRes = R.drawable.ic_back_24, onIconClicked = state.onBackClick, ), title = state.nftAsset.name, ) - }, - content = { innerPadding -> + TangemPullToRefreshContainer( config = state.pullToRefreshConfig, - modifier = Modifier - .padding(innerPadding) - .fillMaxSize(), + modifier = Modifier.fillMaxSize(), ) { NFTDetailsAsset( state = state.nftAsset, @@ -49,16 +46,19 @@ internal fun NFTDetails(state: NFTDetailsUM, modifier: Modifier = Modifier) { onExploreClick = state.onExploreClick, ) } - }, - floatingActionButtonPosition = FabPosition.Center, - floatingActionButton = { - PrimaryButton( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - text = stringResourceSafe(id = R.string.common_send), - onClick = state.onSendClick, - ) - }, - ) + } + + PrimaryButton( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing16, + ) + .fillMaxWidth(), + text = stringResourceSafe(id = R.string.common_send), + onClick = state.onSendClick, + ) + } } \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt index df471fec36..049d91b0cd 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt @@ -173,8 +173,7 @@ internal fun NFTDetailsGroupBlock( } else { onBlockClick?.invoke() } - } - .padding(top = TangemTheme.dimens.spacing4), + }, text = value.resolveReference(), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.primary1, diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt index 4dac3a8d5c..21c147c3a9 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt @@ -1,8 +1,8 @@ package com.tangem.features.nft.receive.ui import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheet @@ -22,41 +22,33 @@ import com.tangem.features.nft.receive.entity.NFTReceiveUM internal fun NFTReceive(state: NFTReceiveUM, modifier: Modifier = Modifier) { BackHandler(onBack = state.onBackClick) - Scaffold( - modifier = modifier, - containerColor = TangemTheme.colors.background.secondary, - topBar = { - TangemTopAppBar( - modifier = Modifier.statusBarsPadding(), - startButton = TopAppBarButtonUM( - iconRes = R.drawable.ic_close_24, - onIconClicked = state.onBackClick, - ), - title = stringResourceSafe(id = R.string.nft_receive_title), - subtitle = state.appBarSubtitle.resolveReference(), - ) - }, - content = { innerPadding -> - Column( - modifier = Modifier - .padding(innerPadding) - .fillMaxSize(), - ) { - SearchBar( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing16), - state = state.search, - colors = TangemSearchBarDefaults.secondaryTextFieldColors, - ) + Column( + modifier = modifier + .background(TangemTheme.colors.background.secondary), + ) { + TangemTopAppBar( + modifier = Modifier, + startButton = TopAppBarButtonUM( + iconRes = R.drawable.ic_close_24, + onIconClicked = state.onBackClick, + ), + title = stringResourceSafe(id = R.string.nft_receive_title), + subtitle = state.appBarSubtitle.resolveReference(), + ) - when (val networks = state.networks) { - is NFTReceiveUM.Networks.Content -> NFTReceiveNetworksContent(networks) - is NFTReceiveUM.Networks.Empty -> NFTReceiveNetworksEmpty() - } - } - }, - ) + SearchBar( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16), + state = state.search, + colors = TangemSearchBarDefaults.secondaryTextFieldColors, + ) + + when (val networks = state.networks) { + is NFTReceiveUM.Networks.Content -> NFTReceiveNetworksContent(networks) + is NFTReceiveUM.Networks.Empty -> NFTReceiveNetworksEmpty() + } + } ShowBottomSheet(state.bottomSheetConfig) } diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/traits/ui/NFTAssetTraits.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/traits/ui/NFTAssetTraits.kt index f6e27276f2..9f18df108f 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/traits/ui/NFTAssetTraits.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/traits/ui/NFTAssetTraits.kt @@ -1,9 +1,8 @@ package com.tangem.features.nft.traits.ui import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.material3.Scaffold +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.core.ui.components.appbar.TangemTopAppBar @@ -17,25 +16,21 @@ import com.tangem.features.nft.traits.entity.NFTAssetTraitsUM internal fun NFTAssetTraits(state: NFTAssetTraitsUM, modifier: Modifier = Modifier) { BackHandler(onBack = state.onBackClick) - Scaffold( - modifier = modifier, - containerColor = TangemTheme.colors.background.secondary, - topBar = { - TangemTopAppBar( - modifier = Modifier.statusBarsPadding(), - startButton = TopAppBarButtonUM( - iconRes = R.drawable.ic_back_24, - onIconClicked = state.onBackClick, - ), - title = stringResourceSafe(R.string.nft_traits_title), - ) - }, - content = { innerPadding -> - NFTAssetTraitsContent( - modifier = Modifier - .padding(innerPadding), - state = state, - ) - }, - ) + Column( + modifier = modifier + .background(TangemTheme.colors.background.secondary), + ) { + TangemTopAppBar( + modifier = Modifier, + startButton = TopAppBarButtonUM( + iconRes = R.drawable.ic_back_24, + onIconClicked = state.onBackClick, + ), + title = stringResourceSafe(R.string.nft_traits_title), + ) + + NFTAssetTraitsContent( + state = state, + ) + } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/analytics/CommonSendAnalyticEvents.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/analytics/CommonSendAnalyticEvents.kt index c40b00375d..acaf68a42f 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/analytics/CommonSendAnalyticEvents.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/analytics/CommonSendAnalyticEvents.kt @@ -48,10 +48,14 @@ internal sealed class CommonSendAnalyticEvents( data class TransactionError( val categoryName: String, val token: String, + val blockchain: String, ) : CommonSendAnalyticEvents( category = categoryName, event = "Error - Transaction Rejected", - params = mapOf(TOKEN_PARAM to token), + params = mapOf( + TOKEN_PARAM to token, + BLOCKCHAIN to blockchain, + ), ) /** Close button clicked */ diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index f5bb31ace0..2da8b476da 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -404,6 +404,7 @@ internal class SendConfirmModel @Inject constructor( CommonSendAnalyticEvents.TransactionError( categoryName = analyticsCategoryName, token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, ), ) }, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt index 84d7574034..6b5874101e 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt @@ -312,6 +312,7 @@ internal class NFTSendConfirmModel @Inject constructor( CommonSendAnalyticEvents.TransactionError( categoryName = analyticsCategoryName, token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, ), ) }, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index 2454768e9c..1f75fd46e4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -28,7 +28,6 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType -import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.staking.* import com.tangem.domain.staking.analytics.StakeScreenSource @@ -37,6 +36,7 @@ import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.stakekit.* import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.utils.getValidatorsCount import com.tangem.domain.tokens.* @@ -120,6 +120,7 @@ internal class StakingModel @Inject constructor( private val getActionsUseCase: GetActionsUseCase, private val getYieldUseCase: GetYieldUseCase, private val checkAccountInitializedUseCase: CheckAccountInitializedUseCase, + private val getActionRequirementAmountUseCase: GetActionRequirementAmountUseCase, private val paramsInterceptorHolder: ParamsInterceptorHolder, private val shareManager: ShareManager, @DelayedWork private val coroutineScope: CoroutineScope, @@ -527,9 +528,20 @@ internal class StakingModel @Inject constructor( val rewardPendingActionConstraints = yieldBalance?.reward?.rewardConstraints if (rewardBlockType == RewardBlockType.RewardsRequirementsError) { + val minimumAmount = rewardPendingActionConstraints?.amountArg?.minimum + // Temporary fix, until StakeKit adds minimum requirement amount to balance response + val minimumAmountValue = if (minimumAmount == null && yieldBalance.integrationId != null) { + getActionRequirementAmountUseCase.invoke( + integrationId = yieldBalance.integrationId, + actionType = StakingActionType.CLAIM_REWARDS, + ).getOrNull() + } else { + minimumAmount + } + stakingEventFactory.createStakingRewardsMinimumRequirementsErrorAlert( cryptoCurrencyName = cryptoCurrencyStatus.currency.name, - cryptoAmountValue = rewardPendingActionConstraints?.amountArg?.minimum?.format { + cryptoAmountValue = minimumAmountValue?.format { crypto(cryptoCurrencyStatus.currency) }.orEmpty(), ) @@ -931,11 +943,6 @@ internal class StakingModel @Inject constructor( isSingleWalletWithTokens = false, ) .conflate() - .filter { - val sources = it.getOrNull()?.value?.sources ?: return@filter true - - sources.networkSource == StatusSource.ACTUAL && sources.yieldBalanceSource == StatusSource.ACTUAL - } .distinctUntilChanged() .filter { value.currentStep == StakingStep.InitialInfo } .onEach { maybeStatus -> diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index db102751d3..a4b3ea96fd 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -9,6 +9,7 @@ import java.math.BigDecimal @Immutable internal sealed class InnerYieldBalanceState { data class Data( + val integrationId: String?, val reward: YieldReward, val isActionable: Boolean, val balances: ImmutableList, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index 9d5d52d214..ee906adb33 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -50,6 +50,7 @@ internal class YieldBalancesConverter( ?.firstOrNull { it.type == StakingActionType.CLAIM_REWARDS } InnerYieldBalanceState.Data( + integrationId = yieldBalance?.integrationId, reward = YieldReward( rewardsCrypto = cryptoRewardsValue.format { crypto(cryptoCurrency) }, rewardsFiat = fiatRewardsValue.format { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index e6c32aa82c..7b11e36afb 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -64,7 +64,11 @@ internal class StakingBalanceUpdater @AssistedInject constructor( coroutineScope { listOf( async { - fetchCurrencyStatus() + /* + * It is important to use NonCancellable here to ensure the update is not interrupted midway. + * For example, this can happen if the user enters and immediately leaves the screen. + */ + withContext(NonCancellable) { fetchCurrencyStatus() } }, async { updateStakingActions() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index 1c9a799920..d7af6496e2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -64,6 +64,7 @@ internal object InitialStakingStatePreview { val stateWithYield = defaultState.copy( yieldBalance = InnerYieldBalanceState.Data( + integrationId = null, reward = YieldReward( rewardsFiat = "100 $", rewardsCrypto = "100 SOL", diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt index c6249834ec..da281e339d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt @@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.state.utils import com.tangem.core.ui.R import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.staking.model.stakekit.BalanceType import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.features.staking.impl.presentation.state.BalanceState @@ -44,16 +45,18 @@ internal fun isSingleAction(networkId: String, activeStake: BalanceState): Boole return isSingleAction && !isRestake || isCompositePendingActions } -internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) = if (isStubUnstakeAction(networkId)) { - activeStake.pendingActions.plus( - PendingAction( - type = StakingActionType.UNSTAKE, - passthrough = "", - args = null, - ), - ).toPersistentList() -} else { - activeStake.pendingActions +internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState): ImmutableList { + return if (isStubUnstakeAction(networkId) && activeStake.type != BalanceType.REWARDS) { + activeStake.pendingActions.plus( + PendingAction( + type = StakingActionType.UNSTAKE, + passthrough = "", + args = null, + ), + ).toPersistentList() + } else { + activeStake.pendingActions + } } internal fun isTronStakedBalance(networkId: String, pendingAction: PendingAction?): Boolean { diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt index 5633a2c403..97d35ceba4 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt @@ -6,7 +6,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectList import com.tangem.datasource.local.preferences.utils.getObjectListSync -import com.tangem.datasource.local.preferences.utils.getObjectMapSync +import com.tangem.datasource.local.preferences.utils.getObjectMap import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId @@ -17,9 +17,8 @@ import com.tangem.feature.swap.domain.models.domain.* import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.addOrReplace import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.withContext internal class DefaultSwapTransactionRepository( private val appPreferencesStore: AppPreferencesStore, @@ -82,35 +81,35 @@ internal class DefaultSwapTransactionRepository( } } - override suspend fun getTransactions( + override fun getTransactions( userWallet: UserWallet, cryptoCurrencyId: CryptoCurrency.ID, ): Flow?> { - return withContext(dispatchers.io) { - val txStatuses = appPreferencesStore.getObjectMapSync( - key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, - ) - appPreferencesStore.getObjectList( + return combine( + flow = appPreferencesStore.getObjectList( key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, - ).map { savedTransactions -> - val currencyTxs = savedTransactions - ?.filter { - it.userWalletId == userWallet.walletId.stringValue && - ( - it.toCryptoCurrencyId == cryptoCurrencyId.value || - it.fromCryptoCurrencyId == cryptoCurrencyId.value - ) - } + ), + flow2 = appPreferencesStore.getObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + ), + ) { savedTransactions, txStatuses -> + val currencyTxs = savedTransactions?.filter { + it.userWalletId == userWallet.walletId.stringValue && + ( + it.toCryptoCurrencyId == cryptoCurrencyId.value || + it.fromCryptoCurrencyId == cryptoCurrencyId.value + ) + } - currencyTxs?.mapNotNull { - converter.convertBack( - value = it, - scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] - txStatuses = txStatuses, - ) - } - }.flowOn(dispatchers.io) + currencyTxs?.mapNotNull { + converter.convertBack( + value = it, + scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] + txStatuses = txStatuses, + ) + } } + .flowOn(dispatchers.default) } override suspend fun removeTransaction( diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt index 6948208c03..c52ee66212 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt @@ -17,7 +17,7 @@ interface SwapTransactionRepository { transaction: SavedSwapTransactionModel, ) - suspend fun getTransactions( + fun getTransactions( userWallet: UserWallet, cryptoCurrencyId: CryptoCurrency.ID, ): Flow?> diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index e67344cdce..e1570db46e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -3,6 +3,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.model import androidx.compose.runtime.Stable import androidx.paging.cachedIn import arrow.core.getOrElse +import arrow.core.merge import com.tangem.blockchain.common.address.AddressType import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter @@ -11,7 +12,9 @@ import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.api.AnalyticsExceptionHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.ExceptionAnalyticsEvent import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model @@ -134,6 +137,7 @@ internal class TokenDetailsModel @Inject constructor( private val appRouter: AppRouter, private val router: InnerTokenDetailsRouter, private val tokenDetailsDeepLinkActionListener: TokenDetailsDeepLinkActionListener, + private val analyticsExceptionHandler: AnalyticsExceptionHandler, ) : Model(), TokenDetailsClickIntents { private val params = paramsContainer.require() @@ -205,15 +209,15 @@ internal class TokenDetailsModel @Inject constructor( checkForActionUpdates() } + fun onResume() { + subscribeOnExpressTransactionsUpdates() + } + fun onPause() { expressTxStatusTaskScheduler.cancelTask() expressTxJobHolder.cancel() } - fun onResume() { - subscribeOnExpressTransactionsUpdates() - } - override fun onDestroy() { expressTxStatusTaskScheduler.cancelTask() expressTxJobHolder.cancel() @@ -304,66 +308,61 @@ internal class TokenDetailsModel @Inject constructor( } private fun subscribeOnCurrencyStatusUpdates() { - modelScope.launch(dispatchers.main) { - getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( - userWalletId = userWalletId, - currencyId = cryptoCurrency.id, - isSingleWalletWithTokens = userWallet is UserWallet.Cold && - userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), - ) - .distinctUntilChanged() - .onEach { maybeCurrencyStatus -> - internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus) - maybeCurrencyStatus.onRight { status -> - cryptoCurrencyStatus = status - updateButtons(currencyStatus = status) - updateWarnings(status) - subscribeOnUpdateStakingInfo(status) - } - currencyStatusAnalyticsSender.send(maybeCurrencyStatus) + getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( + userWalletId = userWalletId, + currencyId = cryptoCurrency.id, + isSingleWalletWithTokens = userWallet is UserWallet.Cold && + userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), + ) + .distinctUntilChanged() + .onEach { maybeCurrencyStatus -> + internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus) + maybeCurrencyStatus.onRight { status -> + cryptoCurrencyStatus = status + updateButtons(currencyStatus = status) + updateWarnings(status) + subscribeOnUpdateStakingInfo(status) } - .flowOn(dispatchers.main) - .launchIn(modelScope) - .saveIn(marketPriceJobHolder) - } + currencyStatusAnalyticsSender.send(maybeCurrencyStatus) + } + .flowOn(dispatchers.main) + .launchIn(modelScope) + .saveIn(marketPriceJobHolder) } private fun subscribeOnExpressTransactionsUpdates() { - modelScope.launch(dispatchers.main) { - expressTxStatusTaskScheduler.cancelTask() - expressStatusFactory - .getExpressStatuses() - .distinctUntilChanged() - .onEach { waitForFirstExpressStatusEmmit.value = true } - .onEach { expressTxs -> - internalUiState.value = expressStatusFactory.getStateWithUpdatedExpressTxs( - expressTxs, - ::updateNetworkToSwapBalance, - ) - expressTxStatusTaskScheduler.scheduleTask( - modelScope, - PeriodicTask( - isDelayFirst = false, - delay = EXPRESS_STATUS_UPDATE_DELAY, - task = { - runCatching { - expressStatusFactory.getUpdatedExpressStatuses(internalUiState.value.expressTxs) - } - }, - onSuccess = { updatedTxs -> - internalUiState.value = expressStatusFactory.getStateWithUpdatedExpressTxs( - updatedTxs, - ::updateNetworkToSwapBalance, - ) - }, - onError = { /* no-op */ }, - ), - ) - } - .flowOn(dispatchers.main) - .launchIn(modelScope) - .saveIn(expressTxJobHolder) - } + expressTxStatusTaskScheduler.cancelTask() + expressStatusFactory.getExpressStatuses() + .distinctUntilChanged() + .onEach { waitForFirstExpressStatusEmmit.value = true } + .onEach { expressTxs -> + internalUiState.value = expressStatusFactory.getStateWithUpdatedExpressTxs( + expressTxs = expressTxs, + updateBalance = ::updateNetworkToSwapBalance, + ) + expressTxStatusTaskScheduler.scheduleTask( + scope = modelScope, + task = PeriodicTask( + isDelayFirst = false, + delay = EXPRESS_STATUS_UPDATE_DELAY, + task = { + runCatching { + expressStatusFactory.getUpdatedExpressStatuses(internalUiState.value.expressTxs) + } + }, + onSuccess = { updatedTxs -> + internalUiState.value = expressStatusFactory.getStateWithUpdatedExpressTxs( + updatedTxs, + ::updateNetworkToSwapBalance, + ) + }, + onError = { /* no-op */ }, + ), + ) + } + .flowOn(dispatchers.main) + .launchIn(modelScope) + .saveIn(expressTxJobHolder) } private fun updateNetworkToSwapBalance(toCryptoCurrency: CryptoCurrency) { @@ -449,7 +448,7 @@ internal class TokenDetailsModel @Inject constructor( network = cryptoCurrency.network, ).getOrElse { false } - val isSupported = getExtendedPublicKeyForCurrencyUseCase.isSupported(userWalletId, cryptoCurrency.network) + val isSupported = isXPUBSupported() internalUiState.value = stateFactory.getStateWithUpdatedMenu( cardTypesResolver = userWallet.scanResponse.cardTypesResolver, @@ -459,6 +458,34 @@ internal class TokenDetailsModel @Inject constructor( } } + private suspend fun isXPUBSupported(): Boolean { + return getExtendedPublicKeyForCurrencyUseCase.isSupported( + userWalletId = userWalletId, + network = cryptoCurrency.network, + ) + .mapLeft { + analyticsExceptionHandler.sendException( + event = ExceptionAnalyticsEvent( + exception = it, + params = mapOf( + "blockchainId" to cryptoCurrency.network.id.rawId.value, + "networkId" to cryptoCurrency.network.backendId, + ), + ), + ) + + Timber.e( + /* t = */ it, + /* message = */ "Unable to get wallet manager for user wallet %s and network %s", + /* ...args = */ userWalletId, + cryptoCurrency.network, + ) + + false + } + .merge() + } + private fun createSelectedAppCurrencyFlow(): StateFlow { return getSelectedAppCurrencyUseCase() .map { maybeAppCurrency -> diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt index c12a0e8048..1e567bfc6a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt @@ -52,7 +52,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor( ) } - suspend operator fun invoke(): Flow> { + operator fun invoke(): Flow> { return swapTransactionRepository.getTransactions( userWallet = userWallet, cryptoCurrencyId = cryptoCurrency.id, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt index 4e9ee06a08..2378fa4b52 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt @@ -65,14 +65,12 @@ internal class ExpressStatusFactory @AssistedInject constructor( ) } - suspend fun getExpressStatuses(): Flow> = combine( + fun getExpressStatuses(): Flow> = combine( flow = exchangeStatusFactory(), flow2 = onrampStatusFactory(), ) { maybeExchange, maybeOnramp -> - persistentListOf( - maybeOnramp, - maybeExchange, - ).flatten() + persistentListOf(maybeOnramp, maybeExchange) + .flatten() .sortedByDescending { it.info.timestamp } .toPersistentList() } diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index b6d2f0e7dd..2d629ddca7 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -26,6 +26,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.nft.DisableWalletNFTUseCase import com.tangem.domain.nft.EnableWalletNFTUseCase import com.tangem.domain.nft.GetWalletNFTEnabledUseCase +import com.tangem.domain.notifications.GetApplicationIdUseCase import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.domain.settings.repositories.PermissionRepository import com.tangem.domain.wallets.models.UserWallet @@ -45,6 +46,7 @@ import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnaly import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber @@ -72,6 +74,8 @@ internal class WalletSettingsModel @Inject constructor( private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase, private val settingsManager: SettingsManager, private val permissionsRepository: PermissionRepository, + private val getApplicationIdUseCase: GetApplicationIdUseCase, + private val associateWalletsWithApplicationIdUseCase: AssociateWalletsWithApplicationIdUseCase, ) : Model() { val params: WalletSettingsComponent.Params = paramsContainer.require() @@ -192,10 +196,20 @@ internal class WalletSettingsModel @Inject constructor( if (hasUserWallets) { router.pop() } else { + clearWalletsAssociatedWithApplicationId() router.replaceAll(AppRoute.Home) } } + private fun clearWalletsAssociatedWithApplicationId() = modelScope.launch(NonCancellable) { + getApplicationIdUseCase().onRight { applicationId -> + associateWalletsWithApplicationIdUseCase(applicationId, emptyList()) + .onLeft { + Timber.e("Unable to associate empty wallets with application ID: $it") + } + } + } + private fun onLinkMoreCardsClick(scanResponse: ScanResponse) { analyticsEventHandler.send(Settings.ButtonCreateBackup) analyticsContextProxy.addContext(scanResponse) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 5370e45d02..796767b539 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -58,11 +58,9 @@ internal abstract class BasicTokenListSubscriber( } .distinctUntilChanged() .onEach { maybeTokenList -> - if (!routingFeatureToggle.isDeepLinkNavigationEnabled) { - coroutineScope.launch { - onTokenListReceived(maybeTokenList) - }.saveIn(onTokenListReceivedJobHolder) - } + coroutineScope.launch { + onTokenListReceived(maybeTokenList) + }.saveIn(onTokenListReceivedJobHolder) coroutineScope.launch { startCheck(maybeTokenList) } }, @@ -77,8 +75,7 @@ internal abstract class BasicTokenListSubscriber( ifLoading = { maybeContent -> val isRefreshing = stateHolder.getWalletState(userWallet.walletId) ?.pullToRefreshConfig - ?.isRefreshing - ?: false + ?.isRefreshing == true maybeContent ?.takeIf { !isRefreshing } @@ -118,15 +115,17 @@ internal abstract class BasicTokenListSubscriber( } protected open suspend fun onTokenListReceived(maybeTokenList: Lce) { - /* no-op */ - // Handling sell deeplink requires full content in order to correctly open Send screen - if (maybeTokenList.getOrNull(false) != null) { - deepLinksRegistry.triggerDelayedDeeplink(deepLinkClass = SellCurrencyDeepLink::class.java) - } - // Handling referral deeplink requires only selected wallet to be loaded - // This is temporary solution, will be removed with complete deeplink navigation overhaul - if (maybeTokenList.getOrNull(true) != null) { - deepLinksRegistry.triggerDelayedDeeplink(deepLinkClass = ReferralDeepLink::class.java) + if (!routingFeatureToggle.isDeepLinkNavigationEnabled) { + /* no-op */ + // Handling sell deeplink requires full content in order to correctly open Send screen + if (maybeTokenList.getOrNull(false) != null) { + deepLinksRegistry.triggerDelayedDeeplink(deepLinkClass = SellCurrencyDeepLink::class.java) + } + // Handling referral deeplink requires only selected wallet to be loaded + // This is temporary solution, will be removed with complete deeplink navigation overhaul + if (maybeTokenList.getOrNull(true) != null) { + deepLinksRegistry.triggerDelayedDeeplink(deepLinkClass = ReferralDeepLink::class.java) + } } } diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 65d8993bb1..c1b01e6007 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1104" +tangemBlockchainSdk = "develop-1110" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-489" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^