diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 3dd86e4e03..ce2402c7de 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -26,6 +26,15 @@ internal object TokensDomainModule { return GetTokenListUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers) } + @Provides + @ViewModelScoped + fun provideRemoveCurrencyUseCase( + currenciesRepository: CurrenciesRepository, + dispatchers: CoroutineDispatcherProvider, + ): RemoveCurrencyUseCase { + return RemoveCurrencyUseCase(currenciesRepository, dispatchers) + } + @Provides @ViewModelScoped fun provideGetCurrencyUseCase( 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 b2ff23a3bc..b431e2b4dd 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 @@ -53,6 +53,22 @@ internal class DefaultCurrenciesRepository( storeAndPushTokens(userWalletId, response) } + override suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) = + withContext(dispatchers.io) { + val savedCurrencies = requireNotNull( + value = userTokensStore.getSyncOrNull(userWalletId), + lazyMessage = { "Saved tokens empty. Can not perform remove currency action" }, + ) + + val token = userTokensResponseFactory.createResponseToken(currency) + storeAndPushTokens( + userWalletId = userWalletId, + response = savedCurrencies.copy( + tokens = savedCurrencies.tokens.filter { it != token }, + ), + ) + } + override suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency { return withContext(dispatchers.io) { val userWallet = getUserWallet(userWalletId) @@ -78,6 +94,21 @@ internal class DefaultCurrenciesRepository( } } + override suspend fun getMultiCurrencyWalletCurrenciesSync( + userWalletId: UserWalletId, + refresh: Boolean, + ): List { + val userWallet = getUserWallet(userWalletId) + ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) + + fetchTokensIfCacheExpired(userWallet, refresh) + val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) + return responseCurrenciesFactory.createCurrencies( + response = storedTokens, + card = userWallet.scanResponse.card, + ) + } + override suspend fun getMultiCurrencyWalletCurrency( userWalletId: UserWalletId, id: CryptoCurrency.ID, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensResponseFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensResponseFactory.kt index 658522a30e..c6cbc7f858 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensResponseFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensResponseFactory.kt @@ -26,7 +26,7 @@ internal class UserTokensResponseFactory { ) } - private fun createResponseToken(currency: CryptoCurrency): UserTokensResponse.Token { + fun createResponseToken(currency: CryptoCurrency): UserTokensResponse.Token { val blockchain = getBlockchain(currency.network.id) return UserTokensResponse.Token( diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/Network.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/Network.kt index 4c60938e9c..aaa1cb39d8 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/Network.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/Network.kt @@ -73,8 +73,6 @@ data class Network( } /** Represents a network that does not adhere to a predefined standard type. */ - class Unspecified(val networkName: String) : StandardType() { - override val name: String = networkName - } + data class Unspecified(override val name: String) : StandardType() } } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/remove/RemoveCurrencyError.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/remove/RemoveCurrencyError.kt new file mode 100644 index 0000000000..906f30f1fd --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/remove/RemoveCurrencyError.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.tokens.models.remove + +import com.tangem.domain.tokens.models.CryptoCurrency + +sealed class RemoveCurrencyError : Throwable() { + data class HasLinkedTokens(val currency: CryptoCurrency) : RemoveCurrencyError() + data class DataError(override val cause: Throwable) : RemoveCurrencyError() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RemoveCurrencyUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RemoveCurrencyUseCase.kt new file mode 100644 index 0000000000..71dc767ed2 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RemoveCurrencyUseCase.kt @@ -0,0 +1,40 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.raise.catch +import arrow.core.raise.either +import arrow.core.raise.ensure +import com.tangem.domain.tokens.models.CryptoCurrency +import com.tangem.domain.tokens.models.remove.RemoveCurrencyError +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider + +class RemoveCurrencyUseCase( + internal val currenciesRepository: CurrenciesRepository, + internal val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): Either { + return either { + ensure( + condition = !currency.hasLinkedTokens(userWalletId), + raise = { RemoveCurrencyError.HasLinkedTokens(currency) }, + ) + catch( + block = { currenciesRepository.removeCurrency(userWalletId, currency) }, + catch = { raise(RemoveCurrencyError.DataError(it)) }, + ) + } + } + + private suspend fun CryptoCurrency.hasLinkedTokens(userWalletId: UserWalletId): Boolean { + val walletCurrencies = currenciesRepository + .getMultiCurrencyWalletCurrenciesSync(userWalletId = userWalletId, refresh = false) + + return this is CryptoCurrency.Coin && walletCurrencies.any { it != this && it.network.id == this.network.id } + } +} \ 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 91a04b5af8..9da5ff82bf 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 @@ -27,6 +27,16 @@ interface CurrenciesRepository { isSortedByBalance: Boolean, ) + /** + * Removes currency from a specific user wallet. + * + * @param userWalletId The unique identifier of the user wallet. + * @param currency The currency which must be removed. + * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If multi-currency user wallet + * ID provided. + */ + suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) + /** * Retrieves the primary cryptocurrency for a specific single-currency user wallet. * @@ -48,6 +58,17 @@ interface CurrenciesRepository { */ fun getMultiCurrencyWalletCurrencies(userWalletId: UserWalletId, refresh: Boolean): Flow> + /** + * Retrieves the list of cryptocurrencies within a multi-currency wallet. + * + * @param userWalletId The unique identifier of the user wallet. + * @param refresh A boolean flag indicating whether the data should be refreshed. + * @return A list of [CryptoCurrency]. + * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * ID provided. + */ + suspend fun getMultiCurrencyWalletCurrenciesSync(userWalletId: UserWalletId, refresh: Boolean): List + /** * Retrieves the cryptocurrency for a specific multi-currency user wallet. * diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ApplyTokenListSortingUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ApplyTokenListSortingUseCaseTest.kt index d5f180d4be..c40e5fc94b 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ApplyTokenListSortingUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ApplyTokenListSortingUseCaseTest.kt @@ -190,8 +190,16 @@ internal class ApplyTokenListSortingUseCaseTest { private fun getTokensRepository( sortTokensResult: Either = Unit.right(), + removeCurrencyResult: Either = Unit.right(), tokens: Flow>> = flowOf(MockTokens.tokens.right()), ): MockCurrenciesRepository { - return MockCurrenciesRepository(sortTokensResult, MockTokens.token1.right(), tokens, emptyFlow(), emptyFlow()) + return MockCurrenciesRepository( + sortTokensResult = sortTokensResult, + removeCurrencyResult = removeCurrencyResult, + token = MockTokens.token1.right(), + tokens = tokens, + isGrouped = emptyFlow(), + isSortedByBalance = emptyFlow(), + ) } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCaseTest.kt index 3d5b25e5eb..b063abbd49 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCaseTest.kt @@ -150,12 +150,14 @@ internal class GetPrimaryCurrencyUseCaseTest { private fun getUseCase( token: Either = MockTokens.token1.right(), + removeCurrencyResult: Either = Unit.right(), quotes: Flow>> = flowOf(MockQuotes.quotes.right()), statuses: Flow>> = flowOf(MockNetworks.verifiedNetworksStatuses.right()), ) = GetPrimaryCurrencyUseCase( dispatchers = dispatchers, currenciesRepository = MockCurrenciesRepository( sortTokensResult = Unit.right(), + removeCurrencyResult = removeCurrencyResult, token = token, tokens = flowOf(), isGrouped = flowOf(), diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt index bea0edb006..124273c991 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt @@ -342,6 +342,7 @@ internal class GetTokenListUseCaseTest { dispatchers = dispatchers, currenciesRepository = MockCurrenciesRepository( sortTokensResult = Unit.right(), + removeCurrencyResult = Unit.right(), token = MockTokens.token1.right(), tokens = tokens, isGrouped = isGrouped, 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 7ac79d2d4c..957095493e 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 @@ -6,10 +6,12 @@ import com.tangem.domain.core.error.DataError import com.tangem.domain.tokens.models.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map internal class MockCurrenciesRepository( private val sortTokensResult: Either, + private val removeCurrencyResult: Either, private val token: Either, private val tokens: Flow>>, private val isGrouped: Flow>, @@ -38,6 +40,17 @@ internal class MockCurrenciesRepository( isTokensSortedByBalanceAfterSortingApply = isSortedByBalance } + override suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) { + removeCurrencyResult.onLeft { throw it } + } + + override suspend fun getMultiCurrencyWalletCurrenciesSync( + userWalletId: UserWalletId, + refresh: Boolean, + ): List { + return tokens.first().getOrElse { e -> throw e } + } + override suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency { return token.getOrElse { e -> throw e } } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 2ebbd8a165..f1315b786c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -80,7 +80,7 @@ okHttp-prettyLogging = "3.1.0" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-330" +tangemBlockchainSdk = "develop-331" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-289" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds